Skip to content

ci: gate the browser-bridge node suite in the flake (460 tests, counted) - #280

Merged
ZacxDev merged 1 commit into
mainfrom
ci/node-suite-flake-gate
Aug 2, 2026
Merged

ci: gate the browser-bridge node suite in the flake (460 tests, counted)#280
ZacxDev merged 1 commit into
mainfrom
ci/node-suite-flake-gate

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ci: gate the browser-bridge node suite in the flake (460 tests, counted)

The 460-test .mjs suite gated NOTHING. flake.nix had zero references to
node/mjs; checks.pytests runs scripts/run-tests.sh, which invokes
python -m pytest only. Every "460 pass" claim in recent PRs was a MANUAL
run, and a .mjs regression could merge freely.

Adds checks.x86_64-linux.nodetests + scripts/run-node-tests.sh.

Separate check, not folded into pytests: distinct toolchain (so the python
gate's closure doesn't grow node, and a node bump can't invalidate its
cache), distinct failure signal from nix flake check, and they run in
parallel so the wall-clock cost is ~0.

Two structural guards in the runner, because a green exit code lies here:

  1. Never pass a directory. node --test <dir> silently collapses to
    # tests 1 / # fail 1 (MODULE_NOT_FOUND). The runner globs the files
    into an array and refuses any element that is not a regular file
    ending in .test.mjs, so it cannot degrade to the dir form.
  2. COUNT the tests. Parses node's TAP summary and fails below MIN_TESTS
    (450; 460 today) and MIN_FILES (14). Without this the gate can go
    green while testing nothing -- exactly how the pytest gate silently
    skipped 41 test_server.py tests for want of curl on PATH.

Hermeticity audited AND measured: the suite has no createServer/.listen(),
no spawn/execFile, every fetch is a stub on globalThis.fetch, and its only
I/O is repo-relative reads plus tmpdir() writes. All 460 run in the
sandbox -- nothing excluded, nothing skipped.

nodejs is pinned from this flake's nixpkgs: the sandbox runs v24.18.0 while
the host PATH node is v26.5.0, and the runner echoes node --version each
run so the toolchain is never a guess.

run-tests.sh's header claimed to be the source of truth for the flake gate;
it now states it covers the PYTHON suites only and points at the node
runner + check.

Verification:

  • negative control: mutating one assertion in browser_tool.test.mjs made
    nix build .#checks.x86_64-linux.nodetests exit 1 with
    "files=14 tests=460 pass=459 fail=1 / RESULT: FAIL". Reverted -> exit 0.
  • runner guards proven reachable individually: MIN_TESTS=99999 -> exit 1
    with the vacuous-green error; MIN_FILES=999 -> exit 2; no node on PATH
    -> exit 2.
  • nix flake check -> "running 2 flake checks ... all checks passed!"
  • pytests unchanged and still green.

Timing: nodetests ~8s. nix flake check 3m30s vs 3m28s for pytests alone.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

🤖 Generated with Claude Code

The 460-test `.mjs` suite gated NOTHING. flake.nix had zero references to
node/mjs; `checks.pytests` runs scripts/run-tests.sh, which invokes
`python -m pytest` only. Every "460 pass" claim in recent PRs was a MANUAL
run, and a .mjs regression could merge freely.

Adds `checks.x86_64-linux.nodetests` + scripts/run-node-tests.sh.

Separate check, not folded into pytests: distinct toolchain (so the python
gate's closure doesn't grow node, and a node bump can't invalidate its
cache), distinct failure signal from `nix flake check`, and they run in
parallel so the wall-clock cost is ~0.

Two structural guards in the runner, because a green exit code lies here:

  1. Never pass a directory. `node --test <dir>` silently collapses to
     `# tests 1 / # fail 1` (MODULE_NOT_FOUND). The runner globs the files
     into an array and refuses any element that is not a regular file
     ending in .test.mjs, so it cannot degrade to the dir form.
  2. COUNT the tests. Parses node's TAP summary and fails below MIN_TESTS
     (450; 460 today) and MIN_FILES (14). Without this the gate can go
     green while testing nothing -- exactly how the pytest gate silently
     skipped 41 test_server.py tests for want of curl on PATH.

Hermeticity audited AND measured: the suite has no createServer/.listen(),
no spawn/execFile, every fetch is a stub on globalThis.fetch, and its only
I/O is repo-relative reads plus tmpdir() writes. All 460 run in the
sandbox -- nothing excluded, nothing skipped.

nodejs is pinned from this flake's nixpkgs: the sandbox runs v24.18.0 while
the host PATH node is v26.5.0, and the runner echoes node --version each
run so the toolchain is never a guess.

run-tests.sh's header claimed to be the source of truth for the flake gate;
it now states it covers the PYTHON suites only and points at the node
runner + check.

Verification:
  * negative control: mutating one assertion in browser_tool.test.mjs made
    `nix build .#checks.x86_64-linux.nodetests` exit 1 with
    "files=14 tests=460 pass=459 fail=1 / RESULT: FAIL". Reverted -> exit 0.
  * runner guards proven reachable individually: MIN_TESTS=99999 -> exit 1
    with the vacuous-green error; MIN_FILES=999 -> exit 2; no node on PATH
    -> exit 2.
  * `nix flake check` -> "running 2 flake checks ... all checks passed!"
  * pytests unchanged and still green.

Timing: nodetests ~8s. `nix flake check` 3m30s vs 3m28s for pytests alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ZacxDev
ZacxDev merged commit cd347f1 into main Aug 2, 2026
@ZacxDev
ZacxDev deleted the ci/node-suite-flake-gate branch August 2, 2026 19:15
ZacxDev added a commit that referenced this pull request Aug 2, 2026
…ollapses (#284)

The flake's pytest gate read an exit code and counted nothing about skips,
while carrying 125 skipped tests. A skipped test reports safety without
providing it — and that exact failure already shipped once (41 test_server.py
tests skipping for want of `curl`, fixed in #251 with nothing asserting the
fix kept working).

Mirrors what #280 did for the node suite. scripts/run-tests.sh now parses
pytest's summary instead of its exit code and enforces four guards:

1. REQUIRED_TOOLS — the 10 binaries the suites `skipif` on (curl, node, rg,
   git, awk, jq, grep, setsid, bash, python3) must be on PATH; absence is a
   named FATAL, not 41 silent skips.
2. EXPECTED_SKIPS — a PINNED (directory, reason-regex) set, not a numeric
   ceiling. Every skip must match an entry AND the skip total must equal the
   entry count, so a new skip, a relocated skip, a swapped skip and a pinned
   skip that stops firing all fail. Suites run with -rs so reasons print.
3. Collected-test floors — global MIN_TESTS=2850 (2920 today) plus a >=1
   per-directory floor, so one suite collapsing cannot hide in the total.
4. Parse guard — an unparseable summary is a FAILURE, not a pass.

flake.nix: adds pkgs.nodejs to checks.pytests. MEASURED — the initiatives
suite reported `660 passed, 123 skipped` in the sandbox vs `783 passed,
0 skipped` on a host with node; all 123 are recovered. Sandbox skips go
125 -> 2, both pinned and both genuinely unavoidable there.

Verified — each guard broken individually and shown to fail with ITS OWN
message: tool precondition (drop pkgs.curl -> "FATAL — required tool(s)
missing from PATH: curl"); skip pin (drop curl AND bypass guard 1 -> "76
UNPINNED skip group(s)" + "111 test(s) skipped, but 2 are pinned"); global
floor (trim to one dir -> "only 331 tests were collected, floor is 2850",
alone); per-dir floor (a dir with no tests, MIN_TESTS=1 -> "nix (collected 0
tests)", alone); parse guard (a conftest ImportError -> "unparseable
summary", alone). Green after each revert.

`nix flake check` green end to end in 3:36 — pytests 2920 collected /
2918 passed / 2 skipped, nodetests 468/468. Added test time is ~5s
(initiatives 15.1s -> 20.5s from the recovered node tests).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ZacxDev added a commit that referenced this pull request Aug 2, 2026
… clawgate)

RULES.md — three additions, all from measured ground cases:

* Verify the CONSUMER is running your artifact, not that the deploy
  reported success. `ship.sh` reported "VERIFIED — on branch main at
  origin/main + switched" while the browser-bridge unit was crash-looping
  on `OSError: [Errno 98] Address already in use`; an orphaned process
  from the previous day (Aug 1 16:18, in NO systemd cgroup) held
  127.0.0.1:8788 and served the OLD server.py. The converge check
  verified branch + switch and structurally could not see that the
  service never started. Recipe (`ss -lptn` -> `/proc/<pid>/cgroup` ->
  `systemctl show -p MainPID`) was executed live before being written.

* A count of DECLARATIONS is not a count of INSTANCES. A grep of
  `skipif` decorators found "2 node-related skips"; the two decorators
  gated 123 tests (initiatives: 660 passed/123 skipped sandboxed vs
  783 passed/0 skipped with node). A 60x sizing error.

* Sharpen the harness bullet from a MANIFESTATION to a CLASS: when you
  parse a tool's OUTPUT, its format is an unpinned dependency, and "no
  matches" means "possibly the wrong pattern". The file already named
  `diff`'s unified default; that rule was READ this session and the trap
  was hit anyway in three new shapes (a false CLEAN over a 1,445-byte
  difference that only `cmp` caught; node 24's reporter change emptying
  a `^# (tests|pass|fail)` grep; `rc=$?` reading `echo`'s status).

CLAUDE.md — correct stale facts in the browser-bridge bullet:

* Drop the hand-pinned "281 B free today". test_skill_size.py owns
  MAX_BYTES/MIN_HEADROOM_BYTES (the floor was raised to 250 in #275);
  point at it instead of re-pinning a figure that rots.
* Record that CI now gates BOTH suites — nodetests (#280) and the
  pytest gate's silent-coverage-collapse guards (#284) — and the
  headline outcome, skips 125 -> 2.
* Extension 0.7.1; `nav`/`open` accept `--wake[=MS]`.

reference/tabs-instances.md — record the #273 measurement: a forced
discard assigns a NEW tabId and releases ownership (484065264 ->
484065273, ownedTabId None), so the stale-documentEmulation hazard is
not reachable on this Chromium. Scope stated honestly (one build, one
profile, one mechanism; auto-discard not exercised; onReplaced inferred,
not observed) and the load-bearing caveat kept: the safety is a property
of the BROWSER, not of the bridge. Placed in reference/, not the
byte-capped core — SKILL.md is unchanged at 11,845 B.

clawgate/SKILL.md — live version 0.7.82 (embedded kubeclaw chart 0.7.1,
so the pending re-sync note is resolved); "derive from the LIVE pin"
stays primary. Adds the `make check-chart` hazard: it depends on
`sync-chart`, which rsyncs from ~/workspace/kubeclaw — that clone sat at
0.3.14 against a vendored 0.7.1, so running it would have clobbered the
deployed chart and reported a false failure. Fetch + `merge --ff-only`
that clone first.

Docs-only. No source file touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ZacxDev added a commit that referenced this pull request Aug 2, 2026
…ations-vs-instances + output-format class, CLAUDE.md CI/byte facts, #273 tab discard, clawgate check-chart) (#287)

* docs: record what 2026-08-02 measured (RULES, CLAUDE.md, tab discard, clawgate)

RULES.md — three additions, all from measured ground cases:

* Verify the CONSUMER is running your artifact, not that the deploy
  reported success. `ship.sh` reported "VERIFIED — on branch main at
  origin/main + switched" while the browser-bridge unit was crash-looping
  on `OSError: [Errno 98] Address already in use`; an orphaned process
  from the previous day (Aug 1 16:18, in NO systemd cgroup) held
  127.0.0.1:8788 and served the OLD server.py. The converge check
  verified branch + switch and structurally could not see that the
  service never started. Recipe (`ss -lptn` -> `/proc/<pid>/cgroup` ->
  `systemctl show -p MainPID`) was executed live before being written.

* A count of DECLARATIONS is not a count of INSTANCES. A grep of
  `skipif` decorators found "2 node-related skips"; the two decorators
  gated 123 tests (initiatives: 660 passed/123 skipped sandboxed vs
  783 passed/0 skipped with node). A 60x sizing error.

* Sharpen the harness bullet from a MANIFESTATION to a CLASS: when you
  parse a tool's OUTPUT, its format is an unpinned dependency, and "no
  matches" means "possibly the wrong pattern". The file already named
  `diff`'s unified default; that rule was READ this session and the trap
  was hit anyway in three new shapes (a false CLEAN over a 1,445-byte
  difference that only `cmp` caught; node 24's reporter change emptying
  a `^# (tests|pass|fail)` grep; `rc=$?` reading `echo`'s status).

CLAUDE.md — correct stale facts in the browser-bridge bullet:

* Drop the hand-pinned "281 B free today". test_skill_size.py owns
  MAX_BYTES/MIN_HEADROOM_BYTES (the floor was raised to 250 in #275);
  point at it instead of re-pinning a figure that rots.
* Record that CI now gates BOTH suites — nodetests (#280) and the
  pytest gate's silent-coverage-collapse guards (#284) — and the
  headline outcome, skips 125 -> 2.
* Extension 0.7.1; `nav`/`open` accept `--wake[=MS]`.

reference/tabs-instances.md — record the #273 measurement: a forced
discard assigns a NEW tabId and releases ownership (484065264 ->
484065273, ownedTabId None), so the stale-documentEmulation hazard is
not reachable on this Chromium. Scope stated honestly (one build, one
profile, one mechanism; auto-discard not exercised; onReplaced inferred,
not observed) and the load-bearing caveat kept: the safety is a property
of the BROWSER, not of the bridge. Placed in reference/, not the
byte-capped core — SKILL.md is unchanged at 11,845 B.

clawgate/SKILL.md — live version 0.7.82 (embedded kubeclaw chart 0.7.1,
so the pending re-sync note is resolved); "derive from the LIVE pin"
stays primary. Adds the `make check-chart` hazard: it depends on
`sync-chart`, which rsyncs from ~/workspace/kubeclaw — that clone sat at
0.3.14 against a vendored 0.7.1, so running it would have clobbered the
deployed chart and reported a false failure. Fetch + `merge --ff-only`
that clone first.

Docs-only. No source file touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(rules): two-tier suites must be green in BOTH; three declarations-vs-instances instances

Both measured while unbreaking the pytest gate (#289).

TWO TIERS. 'Gate on the merged tree' extended to environments. The same
suite runs in the nix sandbox and on a dev host, and each environment
silently decides which tests execute — so a defect can be permanently
unobservable in the tier you happen to read. #276 shipped THREE
regressions that masked each other:

  (1) a FILE in run-tests.sh's target list rejected by `[ ! -d ]`
      -> gate red, 913 tests never ran
  (2) SECRET_PATTERNS moved to guard_core.py -> the drift test parses []
      and FAILS on a host with the hook deployed, SKIPS in the sandbox
  (3) 10 nix-instantiate tests pytest.fail() without the binary
      -> FAIL in the sandbox, pass on every dev host

(2) and (3) are exact complements; both hid behind (1)'s red.

DECLARATIONS VS INSTANCES gets its third instance in one session:
2 skipif decorators -> 123 tests; 1 list entry -> 913 tests;
1 nix_eval() helper -> 10 parametrized tests. Three is a pattern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant