feat(ci): two conventions become gates (improvise-w8ih, improvise-4ktt)

The stamp gate is scripts/assemble-stamp.sh, taking FILE PATTERN pairs, and
scripts/assemble-stamp-test.sh is its check_the_checker. Deleting the final
`test -z "$missing"` makes four of its eight cases fail; before the extraction
that same deletion was invisible, both misses printing while assemble.sh exited
0. It is a separate script because exercising it inside assemble.sh would need a
fabricated web/pkg, which is a second answer to "what does the site assemble
from" (improvise-vvl3). Wired into the `build` job, first, because it needs
nothing and guards a step in that job.

web/tests/bundle_freedom.test.js refuses a flat web/tests/ file that names the
bundle directory -- the constraint keeping the `js` job at seconds rather than
~4m30s, stated in prose and checked by nothing. It scans its own filename too,
so it cannot become the place a violation hides, and its companion asserts
web/tests/bundle/ still holds a test that DOES use the bundle: a scoping rule is
uninteresting if nothing falls outside it.

Both faults were injected and watched to fail; evidence in
context/test-evidence/tooling.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
Edward Langley
2026-09-20 21:11:08 -07:00
parent 7945221eed
commit 8af37d44a1
7 changed files with 226 additions and 27 deletions
+9 -27
View File
@@ -71,33 +71,15 @@ sedi "s|'improvise_worker_server_bg\.wasm'|'improvise_worker_server_bg.wasm?v=${
# One `grep` over all four files succeeds when ANY one of them matches, so
# three failed substitutions could still produce a "successful" assembly under
# `set -e` (found by Copilot on PR #24). These checks are the only guard
# against the generated glue changing shape, so each stamped reference is
# verified on its own and names itself when it is missing.
#
# `stamped` RECORDS rather than exits, and the gate below is what fails the
# build. Exiting on the first failure named one file and never reached the
# rest, so a shape change that broke two stamps cost two wasm-pack rebuilds to
# diagnose -- the slowest loop in this repo (improvise-x4wv). The gate is the
# whole guard: without it every failure prints and the assembly succeeds, which
# is the check-that-does-not-check this whole file is full of fixes for.
missing=""
stamped() {
if grep -q "$2" "$1"; then
grep -n "$2" "$1"
else
echo "assemble: $1 is missing the cache-bust stamp: $2" >&2
missing="${missing} $1"
fi
}
stamped "$app/app.js" "pkg/improvise_wasm_client.js?v=${token}"
stamped "$app/worker.js" "worker-pkg/improvise_worker_server.js?v=${token}"
stamped "$app/pkg/improvise_wasm_client.js" "improvise_wasm_client_bg.wasm?v=${token}"
stamped "$app/worker-pkg/improvise_worker_server.js" "improvise_worker_server_bg.wasm?v=${token}"
test -z "$missing" || {
echo "assemble: refusing to ship; unstamped:${missing}" >&2
exit 1
}
# `set -e` (found by Copilot on PR #24). Each stamped reference is therefore
# verified on its own, by scripts/assemble-stamp.sh -- which is a separate
# script so its refusals can be exercised without fabricating a web/pkg
# (improvise-w8ih). Its argument is FILE PATTERN, four times.
sh "$root/scripts/assemble-stamp.sh" \
"$app/app.js" "pkg/improvise_wasm_client.js?v=${token}" \
"$app/worker.js" "worker-pkg/improvise_worker_server.js?v=${token}" \
"$app/pkg/improvise_wasm_client.js" "improvise_wasm_client_bg.wasm?v=${token}" \
"$app/worker-pkg/improvise_worker_server.js" "improvise_worker_server_bg.wasm?v=${token}"
# Every relative import the two entry points make must have been copied. The
# cp list above is a second answer to "what ships", and a missing sibling is a
+45
View File
@@ -0,0 +1,45 @@
// The `js` CI job must never need a built wasm bundle. See
// context/code/clients.md; the split is what keeps it seconds, not ~4m30s.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync, readdirSync } from "node:fs";
import { fileURLToPath } from "node:url";
const dir = fileURLToPath(new URL(".", import.meta.url));
/// Built by concatenation so this file carries no literal of the thing it
/// forbids, and is therefore checked by itself rather than exempt from itself.
const FORBIDDEN = "p" + "kg";
/// Comments may name the directory — that is what the header above does. Only
/// what the code reaches for is a dependency, so strip comments first, exactly
/// as render_surface.test.js does.
const code = (name) =>
readFileSync(dir + name, "utf8")
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/(^|[^:"'`])\/\/.*$/gm, "$1");
test("no test in web/tests/ reaches for a built bundle", () => {
const siblings = readdirSync(dir).filter((f) => f.endsWith(".test.js"));
assert.ok(siblings.length > 1, `found ${siblings.length} test files — the glob moved`);
assert.ok(siblings.includes("bundle_freedom.test.js"), "this file must check itself");
const offenders = siblings.filter((f) => code(f).includes(FORBIDDEN));
assert.deepEqual(
offenders,
[],
`these belong in web/tests/bundle/, which the browser job runs: ${offenders}`,
);
});
test("the subdirectory that MAY use a bundle is not the one being checked", () => {
// Reading it proves the exemption is a real directory rather than a
// convention nobody kept, and that this check is scoped to the flat listing.
const nested = readdirSync(dir + "bundle").filter((f) => f.endsWith(".test.js"));
assert.ok(nested.length > 0, "web/tests/bundle/ is empty — the split lost its other half");
assert.ok(
nested.some((f) => readFileSync(dir + "bundle/" + f, "utf8").includes(FORBIDDEN)),
"no bundle test reaches for a bundle, so this check has nothing to be scoped against",
);
});