Non-blocking points of the second review of PR #47: refusing 65535, and
dropping the end anchor of the pattern, both left the suite green.
- Ports 1 and 65535 must be accepted. Given to both variables at once,
so the run stops on the "must differ" refusal, which comes after the
range check, and nothing listens on a port fixed in advance.
- "80abc" must be refused, on each variable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Non-blocking points of the review of PR #47.
- TOKEN and INGEST_TOKEN are no longer read from the environment, like
the URLs: two inherited values that happened to be equal broke the
compartmentalisation cases. Pinned in the proxy case, which now runs
with one inherited value for both.
- The refusal cases run each bad value (not a number, 0, above 65535)
on each of the two variables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test-curl.sh booted its servers on 3099 and 3098, hardcoded, and
ci.test.js runs it whole on every `npm test`: two runs side by side got
in each other's way, at least one of them failing every time.
- TEST_CURL_PORT and TEST_CURL_NOINGEST_PORT choose the ports, 3099 and
3098 staying the defaults. Names of their own rather than PORT, which
a dev shell often exports already.
- BASE_URL and NOINGEST_URL are derived from the ports and no longer
read from the environment: overridden alone, they sent every curl away
from the servers the script had started.
- A value outside 1-65535 and two identical ports are refused up front,
with the variable named.
- ci.test.js gives every case free ports of its own (freePorts() keeps
its probes open together so they are distinct), and no case listens on
3099 or 3098 any more. New cases: two passes at once, the four
refusals, source pins on the defaults and on the derived URLs, and a
first-try connection check that pins the `wait` of the trap, which the
fixed ports used to pin by accident.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second review of PR #45, non-blocking: two behaviours were right by hand
and held by no test.
- a listener on 3099 that closes at once, then one that accepts and stalls:
the port rule is "anything but connection refused", not "an HTTP answer"
- a server that stays alive and never listens: the wait ends on its
deadline, with a message and exit 1
Also moves the port check above the "Fixtures" comment it had split from
its mkdir lines, and counts four protections in CLAUDE.md, not three.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of PR #45: wait_for did not keep a neighbour out. With a listener
already on 3099, the server started by the script only dies of EADDRINUSE
after the wait loop has reached the neighbour, and the cases then ran
against it.
- require_free checks both ports before anything starts: free means
"connection refused" (curl exit 7), as in scripts/boot-from-copy.sh
- wait_for runs on a clock deadline, and checks the process again after a
successful probe
- the EXIT trap waits for the servers it killed, so a run started right
after finds the two ports free
ci.test.js adds a real neighbour on each port, and bounds the two
failed-start cases to 3 s: a message that does not stop the run is now red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Behind a proxy from the environment, test-curl.sh sent its 18 curl lines to
the proxy instead of the servers it boots: with a dead proxy it exited 7
after 11 s without a line of output.
- export NO_PROXY and no_proxy, as scripts/boot-from-copy.sh does
- unset FORCE_COLOR: node colours a number even into a pipe, and 8 cases
compared "3" against a coloured "3"
- wait_for checks the server process before the port and stops the run with
a message when a server died or never answered
- the EXIT trap guards each kill: under set -e, a failed kill on the first
server ended the trap and left the second one running on 3098
ci.test.js runs the whole script three times: behind a fake proxy that
answers everything, and with a fake node that fails for each port in turn.
Fixes#41
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No runtime file breaks the "require at column 0" pin, so nothing
exercised its detector: on a03e4d4, bringing either regex back to its
form on main, or deleting the require or the import clause, left the 19
cases green, and the two holes this PR closes could reopen unnoticed.
The rule moves into loadsLate(line), which the pin on index.js and
metrics.js now calls, and six rows check it directly: an indented
require and a dynamic import at any column are flagged, each with and
without a space before the parenthesis; a top-level require and a
comment line quoting require are let through.
Measured on the new tree: either regex back to its main form turns 1
row red, removing the require or the import clause turns 2 rows red,
removing the column condition turns 3 red (the two real files and the
top-level row), removing the comment skip turns 1 red.
ci.test.js 19 -> 25 cases, vitest 347 -> 353.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The self-check comment of the continue-on-error detector said that
requiring a colon after the word loses the anchor row only. That holds
when a closing quote may sit before the colon; with the colon right
after the word, the quoted-key row goes too. Measured: 2 rows red with
the colon right after the word, 1 with a closing quote allowed, 3 with
the original anchored regex.
The port check of boot-from-copy.sh named NO_PROXY as what keeps a
proxy out, while curl reads no_proxy first: it now names both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The example of a key that still passes the continue-on-error pin had
lost its u002d escape sequence on the way into ci.test.js and CLAUDE.md:
both read as the plain quoted key, which the pin catches (the "a quoted
key" self-check row holds that very text). CLAUDE.md thus said the
detector was checked on the quoted key, then that the same key passed.
The example now carries the escape, written through a backslash built
at runtime so that no tool decodes it: `grep -c u002d` gives 1 per
file, PyYAML reads the key as continue-on-error, the pin misses it.
The list of known holes of the column-0 require pin now opens with
"among others" ("entre autres" in CLAUDE.md): a line break or a comment
between require and its parenthesis, and require?.(, pass as well, as
the review of 92bfb42 measured.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three findings of the review, each first measured green on the previous
tree (18/18), then red on this one.
The proxy test dropped the inherited no_proxy, so a script exporting
NO_PROXY alone stayed green, although curl reads the lowercase variable
first and an inherited no_proxy would win. The test now sets no_proxy
to a list that leaves the loopback out. Red when the script exports
NO_PROXY alone, and on the script from main.
The column-0 require pin missed a space before the parenthesis:
an indented `require (` and an `import (` both passed. The regexes
now accept whitespace there. Red on each.
The continue-on-error detector required a colon after the word, so a
YAML anchor reused as a key (`&coe continue-on-error`, then
`*coe : true`) passed, while PyYAML reads it as the key. The detector
is now the bare word, with a self-check row for the anchor; red when
the colon is required again, three rows red with the original anchored
regex. An escaped key still passes, since the pin reads text and not
YAML: the comment and CLAUDE.md now claim any literal spelling, no
longer any spelling, and list the indirections the require pin cannot
follow.
ci.test.js 18 -> 19 cases, vitest 346 -> 347.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR #34 review listed two forms that pass the "require at column 0"
heuristic: a function written whole on a column-0 line, and a require(
wrapped onto a column-0 line inside a function. Both stay open: neither
fits the style of the repository, and the second has the very shape of
the legitimate `} = require("node:fs");` that closes index.js's
multi-line import, so only a parser could tell them apart. They are now
written next to the pin and in CLAUDE.md, rather than only in a closed
PR and its follow-up issue.
CLAUDE.md also records the proxy bypass of boot-from-copy.sh, the wider
continue-on-error pin and its self-check, and the new counts (ci.test.js
12 -> 18 cases, vitest 340 -> 346).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pin was anchored at the start of the line, so a step written as a
flow mapping, `- { name: x, run: false, continue-on-error: true }`,
passed it: 12/12 green with that step appended to ci.yml. A quoted key,
`"continue-on-error": true`, escaped it the same way.
The detector is now unanchored and accepts a closing quote before the
colon. It still runs on `ciCode`, so full comment lines may quote the
key; anything else that quotes it (a run: script, a step name, a
trailing comment) now fails the pin, loudly, by design.
The detector is checked on its own: through the same comment stripping
as `ciCode`, it must catch a block key, a list item, a flow mapping and
a quoted key, and ignore a full comment line. Proven by mutation: the
flow-style and quoted-key steps appended to ci.yml each turn the pin
red; the old anchored regex turns the flow-mapping and quoted-key rows
red; dropping the quote class turns the quoted-key row red; disabling
the comment stripping turns the comment row red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the port pre-check of scripts/boot-from-copy.sh carried --noproxy.
The wait loop and the two assertions still honoured http_proxy, so on a
workstation with a proxy configured the script talked to the proxy
instead of the server it had just started, and failed loudly.
Export NO_PROXY='*' and no_proxy='*' at the top of the script, which
covers every curl call, and drop the now-redundant --noproxy.
The new test boots the script for real behind a fake proxy that answers
everything and records what reaches it. It first proves the proxy is
live (a bare curl with the same environment reaches it), then requires
that nothing of the boot check reaches it and that the script passes.
Red on the previous script (the wait loop and the first assertion went
through the proxy), red again when the export is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Forbid `continue-on-error` in ci.yml: that mutation left every test green,
and a failing step would turn the commit status green in silence.
- boot-from-copy.sh refuses a port already held by something else (any curl
exit other than 7). Without it, the wait loop could reach the neighbour and
assert on a server it never started — the new test reproduces exactly that
when the check is removed.
- Pin every require of the COPY'd runtime modules at column 0, with no
dynamic import: the boot check only calls /health, so it only proves the
modules loaded at startup.
Fixes#33
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing verified this repository until now. The workflow runs on every pull
request (no branches filter, so intermediate stack links get a status) and on
pushes to main, in node:22-alpine, the Dockerfile's own base: npm ci, npm test,
the socket-level pass, then scripts/boot-from-copy.sh.
The script stands in for a docker build, which would need the Docker socket in
the instance-scoped runner and so root on the production VPS for every
workflow. It starts the CMD from the COPY sources alone and fails on the
MODULE_NOT_FOUND that #11 had to fix by hand. __tests__/ci.test.js pins the
workflow and what the script assumes of the Dockerfile.
Refs #24
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>