Repository navigation
refactor(ui): define one design-token owner for static and workspace views - #238
Merged
Merged
Conversation
…y vulnerability govulncheck reports GO-2026-6809 (crypto/x509 distrusts compromised intermediate certificates) as reachable in Go 1.27.2 through the CLI's diagnostic output path, so the release candidate and every source gate fail at go:vulncheck. Go 1.27.3 contains the fix. - go.mod: toolchain go1.27.3; the go directive and every module requirement are unchanged. - release/change-record.v2.json: the toolchain addition now states that release binaries are built with Go 1.27.3 and why; the pinned copy in internal/tools/releasechange/record_test.go is regenerated from it. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree f7cfc5f with npm 12.2.0, CPython 3.15.0 and Go 1.27.3: 561/561 browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so the go:vulncheck step ran govulncheck against a vulnerability database snapshot fetched with curl (modified 2026-10-21T00:27:29Z) and found no vulnerabilities. With the same snapshot, Go 1.27.2 is reported affected by GO-2026-6809.
Add the required job "quality / platform smoke / linux-arm64" on the GitHub-hosted ubuntu-24.04-arm runner. Its first step fails unless uname -m reports aarch64. It then sets up the same pinned Node 26.11.1, verified npm, Go and Python 3.15.0 toolchains as the macOS platform jobs, runs the rootpath, repositorytransaction, processgroup and projectstatus packages natively, runs npm run npm:version and npm run platform:smoke, builds the complete current release wheel set, and runs the CPython 3.11.0 installed-wheel smoke, which selects the manylinux_2_17_aarch64 wheel and the pinned linux/arm64 CPython 3.11.0 image digest. The required aggregate needs the job and requires its success. The workflow oracles pin the job's name, runner, 15-minute timeout and exact ordered steps, the five required leaf jobs and the aggregate shell. They refuse mutants that move the job to another runner, drop, move or weaken its architecture check, drop or narrow its invariant, smoke, wheel-set or CPython 3.11.0 steps, or remove it from the aggregate needs or result checks. The Python runtime oracle counts the job as a pinned Python consumer. README states that required CI executes Linux arm64 on ubuntu-24.04-arm and x64 on ubuntu-24.04, that CI does not execute a host at the manylinux 2.17 floor, and that the CPython 3.11.0 smoke runs on both Linux architectures; its projection test follows. REQ-PROOFKIT-QUALITY-011 now names the Linux arm64 platform job, its architecture check and its CPython 3.11.0 step, and five required leaf jobs instead of four. CONTRIBUTING lists ubuntu-24.04-arm among the source-check targets. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree 5573507 with npm 12.2.0, CPython 3.15.0 and Go 1.27.3: 561/561 browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities. The new arm64 job's success is proven only by hosted CI on this PR.
Add a visual reference gate for the rendered requirement views. npm run browser:visual, which npm run check does not run, compares fourteen views (the source, spec-tree, structured proof, compact proof and coverage static renderings in Cards and Table modes, and the Specifications, Coverage, Diff and Traceability workspace tabs) in Chromium, Firefox and Playwright WebKit, light and dark, at 320 and 1440 CSS pixels, plus one Chromium README scene: 169 references. A reference is a digest, not an image. tests/visual/references.json binds, for each reference ID <engine>/<view>.<scheme>.<width>, the width, the height and the SHA-256 of the canonical pixel encoding that REQ-PROOFKIT-QUALITY-028 specifies. No reference image is stored in the repository. tests/visual/environment.json records the reference environment: the mcr.microsoft.com/playwright:v1.63.0-noble image with its index and platform digests on linux/arm64, and the observed OS identity, machine architecture, image marker, fontconfig, FreeType, HarfBuzz and font package versions, the SHA-256 of the committed tests/visual/fonts.conf, the fc-match results for monospace, sans-serif, serif, system-ui and ui-monospace, the installed engine revisions, the absence of emulation and the launched browser versions. fc-match reads the text after an unescaped hyphen as a point size, so each family is queried by its whole name with every hyphen escaped. fonts.conf includes the system configuration and prefers Liberation Sans for sans-serif and system-ui, Liberation Serif for serif and Liberation Mono for monospace and ui-monospace, which have real bold faces. The system configuration appends sans-serif to every pattern that names no generic family, so the strong sans-serif preference makes each family fontconfig does not know a strong alias of Liberation Sans, which WebKit accepts for the first family that WebKit looks up in fontconfig; without the ui-monospace mapping WebKit drew the product's monospace stacks in Liberation Sans. The gate copies fonts.conf into its run directory and sets the copy as FONTCONFIG_FILE before observing, so fc-match and every browser read the hashed bytes. The engine fixture requires the copy to equal the test snapshot's fonts.conf and requires that no project sets its own browser environment, so every browser inherits that FONTCONFIG_FILE. references.json also binds the SHA-256 of the environment record's canonical JSON. The gate observes the environment, writes it with the CPU vendor, model and features to observed-environment.json in its run directory and prints the CPU before launching any browser. On another environment it records the state unverified with the divergent fields and exits 0; --require-reference-environment makes that state exit 1. Firefox drops color-scheme emulation at a navigation into or out of a document served with Cross-Origin-Opener-Policy: same-origin unless both documents have the same origin and send that header, including the first navigation from about:blank; emulation applied after the last such navigation holds across reload and across navigation within one fixture. The gate therefore applies the color scheme and reduced motion after the first navigation, reloads, applies them again after the reload, the last navigation of every captured view, and asserts both through the matching media queries before each capture. It fits the window to the document height, cut at 2400 CSS pixels, captures each view twice, requires byte-equal PNG captures, decodes them with node:zlib, refusing tRNS outside palette images and a second IHDR, and requires the dimensions and the canonical digest to equal the inventory entry; a failure names the ID and both digests and sizes. Every reference-environment run, passing or failing, keeps its captures, a candidate inventory and the observed environment below its run directory, and a run after which the materialized tests/visual tree gained, lost or changed a file fails. Six controls per engine serve a copied workspace stylesheet: a routed unchanged copy must keep the admitted digest, so routing alone cannot change the pixels, and a one-unit text color change, a one-pixel shift, a font-weight change from 400 to 700, a change of the sans-serif family to serif and a change of the monospace family to sans-serif must each change it. A font-face check per engine reads the font stacks of the workspace and static roots and of the workspace and static monospace elements from their rendered views, draws each beside the named Liberation faces in regular and bold, and requires exact pixel equality with Liberation Sans for the roots and with Liberation Mono for the monospace stacks, which must also be monospaced. The visual Chromium project adds --disable-partial-raster, without which repeated launches rasterized rounded corners differently, and the visual Firefox project adds browser.chrome.site_icons=false; the runtime harness is unchanged. One runtime-harness site with the same Firefox pattern remains: tests/browser/workspace-evidence-layout.spec.mjs emulates the color scheme before its first navigation, so its two dark Firefox rows render light; the runtime harness site is fixed by the next change in this chain, test(browser): assert media emulation in the document under test. browserproofverify selects its input manifest, witness command, record path, admissible states and run-directory retention from a closed table. The runtime manifest, witness command, record path, record format and run cleanup are unchanged. The visual row keeps its run directory, writes artifacts/proofkit/browser-visual-proof.json with the state passed or unverified, recomputes the environment divergence itself, requires each engine to pass exactly its expected comparisons, controls and font-face check with zero retries, and refuses an inventory with a missing, extra, duplicate or malformed entry, dimensions out of bounds or a binding to another environment. Any failure exits non-zero without writing a record. References are admitted by the owner action --admit-references --from <run directory>, which the gate never calls. It admits only a run whose observed environment equals the recorded one and that holds exactly one regular capture file per ID and no unstable directory, recomputes every digest from the PNG captures through the same canonical encoding, refuses a run whose candidate inventory disagrees or whose observed environment claims a divergence, and only then writes references.json. It cannot authenticate the run directory as unmodified gate output; only a passing gate on the admitting tree proves the admitted digests. browsertestserver gains a compact proof selector and a README project selector, which materializes a committed synthetic example.dispatch project, serves it through the same project inspection path as the view command and removes the temporary project on every exit path. A test admits that project through the project plan. CI gains the required job "quality / browser visual references" on ubuntu-24.04-arm in the digest-pinned image with --user 1001 --ipc=host --init. It sets up Node 26.11.1, the verified npm, Go and the locked tooling, runs npm run browser:visual -- --require-reference-environment, uploads the captures, the candidate inventory and the observed environment after every outcome as browser-visual-captures-<sha>-<attempt> for 30 days, and uploads the record only after success. The required aggregate needs the job, and REQ-PROOFKIT-QUALITY-011 now counts six required leaf jobs in the aggregate. REQ-PROOFKIT-QUALITY-025 admits exactly this job container and requires every workflow container image reference to be pinned by a sha256 digest; the workflow oracles pin the job's container, steps, uploads, runner, timeout and aggregate membership and refuse an unpinned image. REQ-PROOFKIT-QUALITY-028 states the reference gate, the canonical pixel encoding, the font-face check and the admission with its bindings, coverage inventory entries, overview line and non-claims: weights 400 and 500, and weights 600 to 900, each render identically because only regular and bold faces are installed; a family name that WebKit looks up and fontconfig does not know, other than ui-monospace, resolves to Liberation Sans in WebKit; pixel identity is claimed only on the calibrated Apple M1 and Arm Neoverse N2 CPU models; admission does not authenticate its run directory; admitted images are reproducible only while the pinned image and the pinned toolchain and dependency downloads remain available. The binding record carries the requirement's source non-claims. The witness plan adds the browser-visual command and policy, the container-linux-arm64-browser environment class and the browser-visual parallel group, and the go-test policy lists the visual records and the digest script its tests read. CONTRIBUTING.md explains how to view an admitted image from a clone in the pinned image with checksum-verified toolchains and how references are re-admitted, and requires, before merge, a passing gate on each CPU model named in the non-claims or a change of those non-claims. The UI-DESIGN-01 backlog row now says that reference screenshot digests are committed and the screenshots are reproducible in the recorded environment, as the owner decided. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree f67bb17 with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities. npm run check does not run the visual gate. Its proof is two fresh-container runs in the pinned arm64 image on Apple M1 (190/190 each, candidates equal to references.json) and three hosted CI runs of this tree on Arm Neoverse N2 (190/190 each, all jobs green).
Pinned Firefox drops color-scheme emulation at a navigation into or out of a document served with Cross-Origin-Opener-Policy: same-origin, unless both documents have the same origin and send that header. That includes the first navigation from about:blank, a navigation to another fixture (each fixture server has its own port) and history traversal across such a boundary; every fixture document sends the header. Emulation applied after the last such navigation holds across reload and across navigation between documents of the same fixture. Chromium and WebKit are unaffected, and reduced-motion and forced-colors emulation survived every such navigation tested in every engine. Playwright's default light scheme is applied before navigation as well, so pinned Firefox drops it; tests that do not call the helper run in Firefox under its own preference. The reflow spec tests/browser/workspace-evidence-layout.spec.mjs, the native reflow witness of REQ-PROOFKIT-SPEC-039 and REQ-PROOFKIT-SPEC-040, emulated before openWorkspace, so its two Firefox dark rows (390x844 and 1280x800) ran in the light scheme. No emulation site asserted that its emulation took effect. - tests/browser/workspace-navigation-harness.mjs: emulateMediaInEffect applies page.emulateMedia and then asserts, in one observation, that the page holds a served http document and that matchMedia matches every given color-scheme, reduced-motion and forced-colors value. It refuses any other feature or value, and the http check refuses a call made in the initial about:blank document, where a match says nothing about the next document. Every pinned engine reflects forced-colors emulation in matchMedia, so that feature is asserted in all three. The assertion proves the media feature, which switches the author forced-colors rules, not an engine forced-color palette, which WebKit does not apply. - tests/browser/workspace-evidence-layout.spec.mjs: each reflow row emulates after openWorkspace through the helper, without a reload. The workspace reads the color scheme and the motion preference only through CSS (media queries and the root color-scheme property), which follows emulation in the loaded document. - tests/browser/workspace.spec.mjs (rendered light and dark contrast), tests/browser/static-view.spec.mjs (static control contrast) and tests/browser/workspace-graph.spec.mjs (dark and forced-colors graph paint) emulate through the helper. They already emulated after navigation. Test titles, requirement statements, bindings and the browser proof input manifest are unchanged. No statement of REQ-PROOFKIT-SPEC-039 or REQ-PROOFKIT-SPEC-040 names a color scheme, the light and dark contrast oracles of REQ-PROOFKIT-QUALITY-022 already ran under the intended scheme, and the runtime proof inputs select tests/browser as a whole. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree 14809a0 with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities.
The Node suites passed CLI input with spawnSync's `input` option. Node then gives the child one end of a Unix-domain socket pair, writes the input, calls shutdown(SHUT_WR) on its end and keeps that end open until the child exits or the timeout kills it. On macOS, a child blocked in read(2) on that socket is sometimes not woken by the shutdown, although the socket already holds end of file, so the call ends only at its timeout. Node 22, 24 and 26 stall this way on darwin/arm64, and so does the same syscall sequence written in C, without Node or libuv. With a pipe, with a close instead of the shutdown or with a regular file, that reproducer did not stall, and neither did Node on Linux. The suites saw the stall as an occasional spawnSync ETIMEDOUT of one CLI call. - scripts/cli-process.mjs adds spawnWithInputFile(file, args, options). It is spawnSync(file, args, options) except that options.input is written to a temporary file that the child gets as its standard input. The file has mode 0600 in a fresh directory of mode 0700, and the directory is removed as soon as the file is open, before the child starts, or at once when writing or opening the file throws. The removal uses path calls only; if it fails, the input descriptor is closed and the call throws. Status, signal, error, stdout, stderr, timeout, maxBuffer and the other passed options keep spawnSync's meaning, except that writing the input can no longer fail with EPIPE. An unsupported option is refused, and so is any args that is not an array, including the two-argument form spawnWithInputFile(file, options), which spawnSync would read as its options while it drops the third argument. The result also reports stdinUnreadBytes, the input bytes past the file offset that the child shares with the parent when it exits: read(2) and lseek(2) on the inherited descriptor move it; pread(2), mmap(2) and, on Linux, reopening /dev/stdin do not. - All 42 spawnSync calls with input in 29 test files under scripts/ use it. - assertCLITransport accepts a non-zero stdinUnreadBytes under the rule it already applies to EPIPE: only from a child that exited normally with a non-zero status and no signal. EPIPE stays accepted for the asynchronous pipe transport of the schema-completeness test. - The early-refusal test of the browser server asserts that stdinUnreadBytes equals the size of its input, so the refusal read none of it, instead of asserting EPIPE. - scripts/cli-process.test.mjs covers the file transport, the unread-input and EPIPE rules through both transports, the kept and refused options, the removal of the input file before the child runs and after a throw, a failed write included, and the closing of the input descriptor after a failed removal. It adds a lexical source check that refuses an `input` member in the arguments of a direct spawnSync, execFileSync or execSync call under scripts/ and tests/. - proofkit/witness-plan.json: the boundary-contract-check policy declares scripts and tests, which that source check reads. No timeout, retry or assertion is relaxed. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree 600c4bf with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3, with no Go test verdict replayed from the test cache: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities.
The TypeScript adapter that json-report-cli-adapter-source generates ran
the CLI with spawnSync(binaryPath, args, {cwd, env, input, maxBuffer}) and
no timeout. On Unix-like systems, Node gives the child one end of a
Unix-domain socket pair for the input option, writes the input, shuts down
writing and keeps its end open until the child exits. On macOS, a child
blocked in read(2) on that socket is sometimes not woken by the shutdown,
so an adapter call that passed input could block forever. With the adapter
generated by the parent commit, Node 26.11.1 and the CLI on 59 captured
spec-proof-bundle-admission inputs (58 of them 514 to 585 KB as stable
JSON), 9 of 3540 calls on darwin/arm64 did not return within 5 seconds.
The one held for 60 seconds stayed blocked, with a Unix socket as its
standard input. The adapter generated by this commit returned in all of
2950 such calls.
- The generated runProofkitCommand writes the stable JSON input to a file
created with O_EXCL and mode 0600 in a fresh mkdtemp directory under
os.tmpdir() of the calling process, opens it read-only and gives that
descriptor to spawnSync as stdio[0]. Except on Windows, it then removes
the file and the directory by path (rmSync of the file, rmdirSync of
the directory) before the CLI starts, so the CLI reads a file that no
longer has a name, and a signal that ends the calling process during
the call leaves no input on disk. When the call returns or throws, the
descriptor is closed and whatever is not yet removed is removed: on
Windows, where an open file can keep its name until it is closed, after
a throw before the early removal, and after a failed early removal. The
removal needs no descriptor of its own, and a failed removal fails the
call. Errors of this transport are thrown through
formatProofkitCliError, as the errors that spawnSync returns already
were.
- A call without input (inputMode "none") gives the child the null device
instead of a shut-down socket. The adapter passes no --input flag for
such a call, so the CLI does not read its standard input.
- cwd, env and maxBuffer are passed as before, and stdout, stderr, status
and the errors that spawnSync returns are handled as before. Argument
errors that spawnSync throws itself, such as a negative maxBuffer or a
NUL byte in binaryPath, cwd or an env value, are now thrown as plain
Errors carrying formatProofkitCliError's redacted, bounded message,
without Node's error class or code. A secret-shaped env value that
Node's message quotes is therefore redacted, where it was shown before.
A child that exits without reading a large input no longer makes the
call throw EPIPE; the call reports the child's status and diagnostic
instead.
- The generated source is 1189 lines with digest
sha256:e782b2cb6e17f1671a43418cf224a566365a4ebe90730a40699adbffbd1b0ac4.
The bundle keeps its record shape, generatorId
proofkit.json-report-cli-adapter-source.typescript.v2 and contractId
proofkit.json-report-cli-adapter-source.output.v1. Neither the CLI
contract nor a bound requirement statement specifies how the adapter
feeds the CLI's standard input, so under the contract-identity rule of
docs/proofkit-contract-map.md this is a defect correction.
- proofkit/cli-contract.v2.json re-pins the native-source digest of the
command's output contract. The generated contract projections and the
public CLI ABI hash follow from that digest.
- scripts/generated-starter-structure.test.mjs declares the byte deltas of
the three adapter bundle rows from the frozen predecessor streams: the
imports, the transport, the source digest and the pretty and compact
line count. The corpus is unchanged.
- A source check requires that the single spawnSync call passes exactly
cwd, env, maxBuffer and a stdin descriptor, with no input member, and
that the input file is removed by path right after it is opened, except
on Windows. The generated-adapter semantics test probes the child's
standard input: a regular file of mode 0600 with no remaining link that
holds exactly the stable JSON, alone in a mode 0700 directory when it is
opened and gone before the child runs, or the null device for a call
without input, although the harness's own standard input is a pipe.
The Windows branch, run by reporting win32 as the platform, keeps the
file under its name until the call returns. Simulated failures of the
read-only open, of either removal call and of the end-of-call removal
make the call throw and leave no descriptor open; only the failed
end-of-call removal leaves the directory. The test also covers an early
refusal, maxBuffer, a negative maxBuffer, a secret-shaped env value, a
missing binary and a missing temporary directory.
- README.md states where a call with input writes it, that the caller
needs a writable temporary directory, when the input is removed on
Windows and elsewhere, and when it can remain on disk.
No timeout is added.
Proof on this tree:
A local npm run check sequence on darwin/arm64 passed (exit 0) on tree
a17845a with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3,
with no Go test verdict replayed from the test cache: 561/561 runtime
browser cases in Chromium, Firefox and WebKit with zero retries. One
recorded deviation: the host firewall blocks Go-built binaries from the
network, so go:vulncheck ran against a vulnerability-database snapshot
fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities.
go:test ran `go test ./...`. Go's test cache replays a package's passing result while the test binary, the files the test process opened and the environment variables it read are unchanged. Many tests here run a child process, such as node, git or a built CLI, and the files that child reads are not part of that key. So a full gate could report a cached pass for a test that fails on the tree under test. On the parent commit's tree, with scripts/source-hygiene.mjs changed to exit 0 before it checks anything, `go test ./scripts -run '^TestSourceHygieneRejectsMalformedUTF8$'` reported `ok (cached)`; the same command with -count=1 failed. Recent local gates served 120 to 143 of the 148 test packages from this cache. The witness plan already declares proofkit.go-test, like each of its commands, with cachePolicy "disabled", which witness-scheduler-plan treats as not cacheable; the command now behaves as declared. - package.json: go:test is `go test -count=1 ./...`. -count=1 is the flag Go documents for disabling the test cache, so every test package runs on the tree under test. The build cache is still used. - proofkit/witness-plan.json: the proofkit.go-test argv is ["go", "test", "-count=1", "./..."]. witness-scheduler-plan admits the plan, and of its node digests only that of proofkit.go-test changes; no tracked file pins it. - proofkit/requirement-bindings.json: the command bound to proofkit.go-test is `go test -count=1 ./...`, as the existing witness-command identity test requires. - scripts/workflow_source_oracles_test.go requires go:test to be that command and the witness argv and the bound command to equal it. It refuses each of the three without -count=1, a narrower package pattern, a repeated count and a masked failure. A second test requires a -count= flag on every Go test invocation it finds in the package scripts, the witness plan argv and the YAML files under .github. It reads shell text lexically and finds an invocation only as adjacent go and test fields: it misses one in a subshell or in backquotes, one through an absolute go path and `go -C . test`, and it accepts -count=1 in a comment and -count=0. The other invocations it finds already carry -count=: go:bench, the legacy Unicode positive witness, and the CI workflow's legacy Unicode, cross-filesystem deletion and platform invariant steps. CI's "Run all Go tests" step and the release workflow's full check run go:test itself. The Go code that starts `go test` itself already passes -count=1: the command oracle behind self:coverage, the schema-completeness mutation witnesses and one coverage-metrics test. Hosted CI time is unchanged: recent hosted quality / source jobs and the release candidate's check served none of the test packages from the cache, even when they restored it, so they already ran every package. A local gate whose test packages the cache used to serve now links and runs all of them, so it takes longer, and on a loaded host it comes closer to Go's per-package test timeout and to the 600-second bound declared for proofkit.go-test. Test results are not made deterministic by this change. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree 18bff04 with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3, with no Go test verdict replayed from the test cache: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities. The gate runner already passes -count=1 to every Go command, so this run does not discriminate the change; the oracle tests and the stale-verdict reproduction above do.
GATE-DIFF-01 asked for a closeout gate that inherits the verdicts of unchanged steps from a passing parent tree. Measured on this repository's recent commits and gates, its rules save a mean of at most 0.2 % of a hosted gate, and the trace check they need on every gate would cost more than that: any overhead above 0.2 % does, and Linux micro-benchmarks of its cheapest suitable mode showed factors of 1.5 and 11. The row is deferred with its measurement, the premises found false and a reopen condition. - GATE-DIFF-01 moves from NEXT to DEFERRED. Its scope keeps the unsoundness statement and adds the measurement: over the 40 first-parent commits from 6d19004 to 834f6ae, the last seven of them not yet on main when measured, the rules would have saved a mean of at most 2.1 s per commit, 0.2 % of a 22.7-minute hosted gate, because 34 of the 40 commits hit a full-gate trigger and the costliest steps cannot be inherited. It lists the premises found false: whole-worktree reads by browser:check, by the ./... steps and by the CLI builds of three node contract steps; same-tree verdict flips of browser:check and boundary-contract:check; Firefox under sandbox-exec on macOS; release:closeout without selectors; an incomplete toolchain list; and go:test verdicts replayed from Go's test cache, which the parent commit fixes. - The scope records that the known causes of the boundary-contract:check flips are fixed: the early-refusal EPIPE race by the shared CLI transport assertion, and the lost macOS Unix-socket wake-up by giving spawned CLI children their input as a file. It also records the browser:check flips: two WebKit navigations that did not reach their target document and six Firefox and Chromium page operations that did not complete, all at the 30-second test timeout, in local full gates whose browser tests took 2.6 to 4.6 times as long as on the passing reruns, with no load sampled during them. - The completion condition starts with the reopen condition. Its first clause asks for 20 consecutive full gates of one unchanged tree that pass browser:check and boundary-contract:check with zero retries inside a load envelope: the 1-minute load average per CPU, sampled at least once a minute during those steps, has a median of at most 6 and a maximum of at most 12, and at least five of the gates have a median of at least 3. A gate outside the envelope is recorded but neither counts nor restarts the count, and no timeout is raised, no retry added and no CSP, console or security-policy check weakened. The other clauses stay: fewer than half of the last 40 first-parent commits on a full-gate trigger, and a trace overhead measured on the real hosted gate below the mean saving then measured. A reopened row first traces every step's reads on its own tree, recomputes the saving and resolves each false premise. - The requirement text stays, except that its toolchain list also names Python, git, shellcheck and pyflakes, and its release:closeout trigger names the witness command that release:closeout must first get. No row is added for browser:check. With fixed 30-second timeouts any test fails under enough contention, so its verdict can be a function of the tree only inside a stated load envelope. None of the recorded failures is known to lie inside one, and the reopen condition above is the only consumer of that claim. AGENTS.md and every product surface are unchanged. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree 99c57d7 with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3, with no Go test verdict replayed from the test cache: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities.
The OSV source scan of the parent tree fails: package-lock.json locks fast-uri 3.1.8, which four advisories affect: GHSA-333x-w4r6-6crp (CVE-2026-110725), GHSA-w74x-v6f9-j3xg (CVE-2026-110306), GHSA-wjr9-j795-jxcr (CVE-2026-110180) and GHSA-xhq7-xmqj-769h (CVE-2026-110095). Each names 3.1.9 as the first fixed 3.x version. - package-lock.json: fast-uri 3.1.9 with its registry integrity. No other entry changes; ajv 8.20.0 still requires fast-uri through ^3.0.1. - BACKLOG.md: a BLOCKED row records the fast-uri copy that the pinned browser-test SDK inlines, which no lock entry can change. fast-uri is a development dependency only. In the lock, ajv is its only dependent, and only the Node schema tests load that ajv. There, ajv passes fast-uri only schema identifiers and references, with default options. In every tracked invocation they come from its bundled meta-schemas and this repository's contract schemas; an operator-set PROOFKIT_*_CONTRACT or PROOFKIT_*_BASELINE variable substitutes that file's schemas. Validated instance data never reaches it. The npm package declares no runtime dependency, and neither it nor the Python wheels contain fast-uri. playwright-core 1.63.0 inlines a separate fast-uri 3.1.3 into lib/utilsBundle.js. This change does not touch that copy, and the OSV scan does not see it. Loading that file runs only the copy's module initializers. Only the Model Context Protocol client and server inlined in the same file call its exported functions; the browser suite never does. The copy stays affected by every fast-uri advisory fixed after 3.1.3, and no Playwright release inlines a fixed fast-uri yet. No other file pins the lock or a digest of it. The release SBOM is built from the Go module graph and the release files, the package gate pins only the direct development dependencies in package.json, there is no Python lock, and the witness plan and the browser proof manifests list package-lock.json only as an input path. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree 90a6ec0 with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3, with no Go test verdict replayed from the test cache: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities. An earlier full gate on a candidate of this change failed once in an unchanged package, internal/tools/releasepreflight, on a timing race in a counterexample test that uncached Go test runs now expose; the next change in this chain fixes that race.
…signal TestReleaseWorkflowExecutesNPMAbsenceBranches failed once in a local full gate on a candidate of the parent change, which did not touch this package: the removed-cleanup-wait counterexample reported "classifier exited without completing admission" and "write |1: broken pipe" instead of "carrier cleanup did not wait for classifier exit". With the macOS /bin/bash 3.2 it failed in 1 of 200 runs at a 1-minute load of 20 to 36 on 8 CPUs, and in 9 of 800 runs with eight added busy loops (400 of them with failure-message diagnostics only). Every failing subtest took 3.02 to 3.05 s. The three other cancellation subtests failed the same way; in 3 of the 10 failures Bash 3.2 instead crashed with SIGSEGV in the same wait. The carrier cancels with one SIGTERM after the shim reports P, its last step before it waits for the classifier. The classifier blocks on a release pipe until the controller releases it after the cleanup has started. Bash 3.2 checks for pending traps when a command starts, and wait does not check again before it blocks. Where that SIGTERM lands decides the outcome: - Before wait sets interrupt_immediately, the trap handler only records the trap, and wait blocks until the awaited child exits, here only at the classifier's own 3-second test timeout. Then the trap runs, the exit oracle reads EOF instead of a live classifier, and the release write fails with EPIPE. - After wait sets interrupt_immediately but while its setjmp of wait_intr_buf still runs, the trap handler longjmps to that incomplete buffer, and Bash faults with SIGSEGV. Because the script traps EXIT, Bash 3.2 only records the fault and lives on until SIGCHLD from the classifier's timeout. Its terminating-signal path then runs the recorded TERM trap, whose exit 124 ends the EXIT trap before cleanup writes W: the trace holds cancelled, and the controller reads C, then EOF. Sending the signal while Bash expands wait's operand reproduces the first outcome in 120 of 120 cancellation subtests. A standalone Bash 3.2 script with one trapped SIGTERM and an EXIT trap reproduces both, the second with the crash-report frames of the measured runs from the signal handler down to wait's setjmp; Bash 5.2 shows neither. The extracted release workflow bytes contain no trap, wait or signal handling; the defect is in the test carrier. - npm_absence_branch_test.go: after P, the controlled shim reads descriptor 7, the read end of a new signal-sent pipe, instead of waiting for the classifier, and then returns 97. The pipe reaches EOF only after the signal was sent, so the read ends at the latest at that EOF and the trap runs before the next command while the classifier is still held. If the trap did not run first, the controller would read W where it requires C, or C without the W that must follow; both fail. The classifier's spawn closes descriptor 7. The uncontrolled shim still waits for the classifier. - A new schedule, "signal pending when the carrier blocks", expands the read's redirection with a command substitution that reports E and then waits for that EOF; the controller signals only after E. The blocked-entrypoint cancellation test runs it, and the removed-cleanup-wait counterexample runs it besides its immediate schedule. With the shim's old wait restored, both fail on every run under Bash 3.2, the counterexample with the gate's message. - npm_absence_carrier_test.go: the control protocol owns the new pipe. cmd.Cancel sends its SIGTERM only through signalCarrier, which calls the signal sender and closes the pipe only after the sender returned. The new subtest "signal precedes its pipe close" gives signalCarrier a sender that reads the pipe without blocking; it requires the pipe open at the signal and at EOF afterwards, so a swapped order fails on every run. The counterexample still requires its cause, the trace view, absent, cancelled, reaped and exit 124. No assertion, timeout or deadline changes, and no retry is added. On this tree 0 of 1000 runs failed at host load under Bash 3.2 and 0 of 200 under Ubuntu 24.04's Bash 5.2; an earlier version of this change, with the same carrier and the same signal-then-close order written inline, passed 4038 of 4038 runs with eight busy loops. This carrier's TERM trap is the only shell signal trap in the repository. Every other signalled process installs a Go, Python or Node handler, not a shell trap. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree 7f6336a with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3, with no Go test verdict replayed from the test cache: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities.
…views The static views, styled by internal/kernel/browserdoc, and the requirement-browser workspace, styled by assets/workspace.css, wrote their colors, sizes and breakpoints as literals. Every literal color and size in the declarations of both style owners is now a token of one stylesheet, internal/kernel/browserdoc/tokens.css, which the binary embeds, and every media and matchMedia length equals a breakpoint token; browserdoc.TokenStylesheet returns its exact bytes. Each token holds the value that its style owner used before, so the change is a substitution: in the pinned engines every computed property value that the views had before, and every pixel, is unchanged; the views now also carry the token custom properties. - Static views: the single inline style element holds the token stylesheet followed by the browserdoc stylesheet, whose declarations read the tokens. The dark color-scheme rules keep their selectors and properties and read separate dark tokens. The static CSP is unchanged. - Workspace: the page links /assets/tokens.css before /assets/workspace.css. The route serves the token bytes for GET and HEAD with the headers of workspace.css, including cache-control: no-store, and answers other methods with 405; the workspace CSP is unchanged. The workspace palette, light and dark, moved verbatim from workspace.css into the token stylesheet, and the system colors of the forced-colors rules became forced-colors tokens. The icon script no longer writes width, height, stroke-width, stroke-linecap and stroke-linejoin on each icon: .workspace-icon, whose size rule already overrode the width and height attributes, now sets the stroke width from a token and the round caps and joins. - tokens.css holds only custom-property declarations in :root at the top level, in @media (prefers-color-scheme: dark) and in @media (forced-colors: active). A literal grammar test in internal/command/requirementbrowser reads the token stylesheet, the static style, workspace.css, every workspace script and the rendered HTML of every static view fixture and of the workspace. It tokenizes CSS by CSS Syntax Level 3 and analyzes declaration values, not selectors. Its literal colors include contrast-color() and vendor-prefixed keywords other than -webkit-box and -apple-system, its literal sizes include a unitless number in the font, line, tab, outline, stroke, SVG geometry, zoom, scale, perspective and transform properties, and a vendor-prefixed property is classified as the property without its prefix. It refuses: a literal color or size or an attr() in either style owner, including var() fallbacks and calc arguments, and any var() other than var(--name); a var() naming no token, no custom property of its owner and none of the three graph geometry properties, an owner custom property that shadows a token, a dark declaration of anything other than a palette token, whose base value holds a literal color, a dark or forced-colors declaration of a base token with a value of another category (a literal color, a literal size or neither; a value that holds both counts as a color), a forced-colors token outside forced-colors rules and an unused token; an owner custom property holding a literal; any token stylesheet content other than the three :root contexts, an empty, important or repeated token, a token that refers to a custom property or an attribute, a breakpoint token that is not one dimension, a system color outside forced-colors; @import, @font-face, url( and image-set(; a media, container, matchMedia or rendered media attribute length that is not textually a breakpoint token's value, and a matchMedia argument that is not one string literal; a listed spelling of a script style write other than the eight graph geometry writes of workspace-graph.js, with attribute and element names in any letter case and setAttributeNS with any namespace, and a listed non-geometry SVG presentation attribute written as an object key or through setAttribute or setAttributeNS, other than fill none, stroke currentColor and the arrowhead marker reference, each admitted only as one whole string literal; a style or presentational attribute in rendered HTML, a static view without exactly one style element and a workspace with one. The rendered-HTML scan ends a comment where the HTML parser does, at -->, at --!> and at once after <!--> or <!--->, and it continues after the content of script, style, textarea and title where the HTML parser ends it as text; after an svg or math start tag, and always for xmp, iframe, noembed, noframes and noscript, it also scans that content as markup on its own, so a comment or quoted value that the content opens does not hide later markup. Each mutant names the violation text it must produce, and every clause, listed style-write spelling, SVG presentation attribute, HTML presentational attribute, size property, color function and size keyword has one; a separate subtest requires the listed exceptions, such as fill none in rendered HTML, a breakpoint length in a media attribute and a percentage in every layout property and in -webkit-transform, to stay admitted. Further tests require every static view to inline the token file's exact bytes, the workspace to link /assets/tokens.css before workspace.css and the route to serve those bytes, and browserdoc to emit one style element that opens with them. Re-pinned expected values, each from test output after isolating its cause: - scripts/proof-lookup-structure.test.mjs declares a second stream delta for the three HTML rows of the frozen lookup corpus: the content of the only style element is the stylesheet with the pinned SHA-256, and replacing it with the declared previous stylesheet, then inverting the card-group delta, reproduces the frozen stream. A moved style element, a second style element and a changed byte outside the style each break conservation. - The coverage relation parity digests are re-captured; with the previous style content put back, all eight previous digests recur, so the style element's content is their only change. - proofkit/cli-contract.v2.json carries the new native source digest of internal/command/requirementbrowser, whose non-test Go files changed, and the generated projections follow. The public CLI ABI hash and the requirement-browser-server input observation of the compact v2 wire corpus change only through that digest: substituting the previous digest and its derived contract hashes reproduces both previous values. No contractId changes, because no wire shape or specified meaning changes. - The workspace asset route table gains the four rows of assets/tokens.css. The visual reference controls follow the moved palette: the one-unit text color control mutates the text color token in a copied token stylesheet, the routed unchanged control serves unchanged copies of both stylesheets, and the other controls still mutate workspace.css. tests/visual/references.json is unchanged. REQ-PROOFKIT-QUALITY-028 states the token owner, its two consumers, the grammar's definitions and rules, and the controls' stylesheets, and gains four non-claims: the font-face check identifies a face through the family name that the recorded font configuration resolves, so it does not detect a configuration that redirects a named Liberation face; the recorded font configuration hash binds tests/visual/fonts.conf, not the system and user configuration that it includes, and the engine fixture checks only FONTCONFIG_FILE in the worker's environment; the grammar test's limits; and that its script checks are lexical, so a write reached through a computed, concatenated or escaped name, reflection such as Reflect.set, an alias or a non-literal key, or through a property, method or attribute that the test does not list, or a listed name or call spelled other than in the form it matches (with whitespace, a line break or a comment between its tokens, as an optional call, with a further argument, a prefixed qualified name or a comma in its namespace argument, or as a shorthand object property), is not claimed to be detected. The binding carries the same non-claims and binds the token owner, route and grammar tests, which the coverage inventory requires; the overview line describes the token owner. CONTRIBUTING.md names the pixel-identity non-claim that the re-admission rule refers to, its CPU models and its four copies, and says that the gate's record identifies an Arm Neoverse N2 runner by implementer and part while the Apple model comes from the host. No Go package enters the browser-check closure, so the witness plan is unchanged. Proof on this tree: A local npm run check sequence on darwin/arm64 passed (exit 0) on tree 80fedbb with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3, with no Go test verdict replayed from the test cache: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, so go:vulncheck ran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities. An earlier full gate on this tree failed two runtime browser tests on 30 s timeouts while the host load average exceeded 180 on 8 CPUs; this rerun at moderate load passed.
iperev
marked this pull request as draft
October 25, 2026 23:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
refactor(ui): define one design-token owner for static and workspace views
Part of the UI-DESIGN-01 chain. Base branch:
chain/rp-npm-absence-race(#237), which sits on #236, #235, #234, #233, #232, #231 and #230. This PR is one commit on that branch's head. Merge after #237. The chain merges after the 0.17.0 release. That release's change record,release/change-record.v2.json(0.16.0 to 0.17.0), stays unchanged here; these non-breaking rendering changes belong to the next release's change record.Why
The static views (styled by
internal/kernel/browserdoc) and therequirement-browser-serverworkspace (styled byinternal/command/requirementbrowser/assets/workspace.css) wrote their colors, sizes and breakpoints as literals: 317 literal colors and sizes in 267 declarations (18 of them custom-property declarations), plus five media andmatchMedialengths and five icon presentation attributes. The UI-DESIGN-01 row asks for one shipped token set used by both style owners and for a test that refuses a literal color or size outside it. This PR builds that mechanism with today's values. In the pinned engines it changes no computed value of any property the views had before and no pixel; the views now also carry the token custom properties; the visual result is the job of a later change in the chain.What changes
One token owner.
internal/kernel/browserdoc/tokens.cssis embedded withgo:embed;browserdoc.TokenStylesheet()returns its exact bytes. It holds only custom-property declarations in:rootat the top level, in@media (prefers-color-scheme: dark)and in@media (forced-colors: active). Each token holds the value that its style owner used before.<style>holds the token stylesheet followed by the browserdoc stylesheet, whose declarations now read the tokens. The dark color-scheme rules keep their selectors and properties and read separate dark tokens; folding them into a switchable palette is left to the redesign. The static CSP is unchanged./assets/tokens.cssbefore/assets/workspace.css. The new exact route serves the token bytes for GET and HEAD with the same headers asworkspace.css(includingcache-control: no-store) and answers other methods with 405. The workspace CSP (style-src 'self') is unchanged.workspace.cssinto the token stylesheet. The system colors of the forced-colors rules became forced-colors tokens.width,height,stroke-width,stroke-linecapandstroke-linejoinon each icon..workspace-icon, whose size rule already overrode the width and height attributes, now sets the stroke width from a token and the round caps and joins.go list -depsof the test server and the verifier is unchanged and equals theinternal/selectors ofproofkit.browser-check), soproofkit/witness-plan.jsonis unchanged.The literal grammar test (
internal/command/requirementbrowser/design_token_*_test.go). It reads the token stylesheet, the static style (the<style>content without the token prefix),workspace.css, everyassets/*.jsand the rendered HTML of the five static view fixtures and of the workspace. It tokenizes CSS with a CSS Syntax Level 3 tokenizer and analyzes declaration values only, so#cafein a selector is not a color. REQ-PROOFKIT-QUALITY-028 states the definitions: a literal color includescontrast-color()and a vendor-prefixed keyword such as-webkit-link(only-webkit-boxand-apple-system, which the owners use as a display value and a font family, are admitted), a literal size includes a unitless number in the font, line, tab, outline, stroke, SVG geometry (cx,cy,r,rx,ry,x,y),zoom,scale,perspectiveandtransformproperties, and a vendor-prefixed property is classified as the property without its prefix, so-webkit-transformcounts astransform. It refuses:attr()in either style owner, includingvar()fallbacks andcalc/min/max/clamparguments, and anyvar()other thanvar(--name);var()naming no token, no custom property of its owner and none of--graph-card-width,--graph-card-height,--graph-edge-stroke; an owner custom property that shadows a token; a dark declaration of anything other than a palette token, whose base value holds a literal color; a dark or forced-colors declaration of a base token with a value of another category (a literal color, a literal size or neither; a value that holds both counts as a color); a forced-colors token used outside forced-colors rules; a token that no owner,matchMediaquery or renderedmediaattribute uses;--navigation-width: 0stays legal);:rootcontexts, an empty, important or repeated token, a token referring to a custom property or an attribute, a breakpoint token that is not one dimension, a system color outside forced-colors, a forced-colors token that is not one system color or is also declared outside forced-colors;@import,@font-face,url(andimage-set(in the three stylesheets;@media,@container,matchMediaor renderedmedia-attribute length that is not textually a--breakpoint-*token's value, and amatchMediaargument that is not one string literal;.style, a bracketed or objectstylekey,attributeStyleMap,styleMap,cssText,insertRule,deleteRule,replaceSync,cssRules,styleSheets,.sheet,adoptedStyleSheets,CSSStyleSheet,.animate(,KeyframeEffect, astyleattribute throughsetAttribute,toggleAttribute,createAttribute,setAttributeNSorcreateAttributeNSwith any namespace,innerHTML,outerHTML,insertAdjacentHTML,setHTML,setHTMLUnsafe,parseHTML,parseHTMLUnsafe,createContextualFragment,DOMParser,srcdoc,document.write,document.writeln, or astyleorlinkelement; attribute and element names in any letter case) other than the eight geometry writes ofworkspace-graph.js, and a listed non-geometry SVG presentation attribute, written as an object key or throughsetAttributeorsetAttributeNS, other thanfill="none",stroke="currentColor"and the arrowhead marker reference (CSS cannot carry that reference, because rule 5 refusesurl(); an SVG width or height is admitted only inside the icon shape table or as the graph's100%; an admitted value counts only when it is one whole string literal, so"currentColor" && "red"is refused;style=or presentational attribute in rendered HTML (the HTML presentational hints, such asbgcolor,border,color,height,sizeandwidth, and the SVG presentation attributes of rule 7 other than its admitted values), a static view without exactly one<style>and a workspace with one. The scan ends a comment where the HTML parser does (at-->, at--!>, and at once after<!-->or<!--->), and it continues after the content ofscript,style,textareaandtitlewhere the HTML parser ends it as text; after ansvgormathstart tag, and always forxmp,iframe,noembed,noframesandnoscript, it also scans that content as markup on its own, so a comment or quoted value that the content opens does not hide later markup.127 mutants each name the violation text they must produce, so each pins the clause that writes it; every clause, listed style-write spelling, SVG presentation attribute, HTML presentational attribute, size property, color function and size keyword has one. A subtest requires the listed exceptions to stay admitted: a percentage in each of the 32 layout properties, and
fr, also in a vendor-prefixed layout property such as-webkit-transform; unitless numbers outside the size properties; the two admitted vendor keywords; a breakpoint length in amediaattribute; markup inside a comment that follows an empty<!---->comment; andfill="none"andstroke="currentColor"in a script or in rendered HTML, and SVG geometry other thanwidthandheightin either; a script may writewidthorheightonly in the icon shape table or as the graph's100%, and rendered HTML carries neither on any element, so a static icon draws its shapes withpath. Two more tests pin the consumers: every static view inlines the token file's exact bytes before the static style, the workspace links exactlytokens.cssthenworkspace.css, the served asset equals the file, and browserdoc emits one<style>that opens with it.Pinned values re-captured, each from test output after its cause was isolated:
scripts/proof-lookup-structure.test.mjs(REQ-PROOFKIT-QUALITY-004): a second declared stream delta for the three HTML rows. The content of the only<style>must have the pinned SHA-256; replacing it with the declared previous stylesheet and then inverting the existing card-group delta must reproduce the frozen stream. Its mutants, a moved style element, a second style element and a changed byte outside the style, each break conservation. The frozen corpus is untouched.relation_parity_test.go: the eight full-output digests are re-captured with a comment. With the previous style content put back, all eight previous digests recur, so the style content is the only change.proofkit/cli-contract.v2.json: the native source digest ofinternal/command/requirementbrowser(commandsrequirement-browser-serverandview), whose non-test Go files changed; the generated projections follow. The public CLI ABI hash and therequirement-browser-server|input|unioncompact v2 wire observation change only through that digest: substituting back the previous digest and its three derived contract hashes reproduces both previous values exactly. Under the contract-identity rule indocs/proofkit-contract-map.md, nocontractIdchanges, because no wire shape or specified meaning changes.workspace_test.go: four route-table rows forassets/tokens.css(GET, HEAD, POST 405, trailing slash 404).Visual controls. The text color token now lives in the token stylesheet, so the one-unit text color control mutates a copied
tokens.css, the routed unchanged control serves unchanged copies of both stylesheets, and the other four controls still mutateworkspace.css. Test titles and the expected test sets are unchanged.tests/visual/references.json,environment.jsonandfonts.confare byte-unchanged; the diff has no PNG.Requirements and docs.
tests/visual/fonts.conf, not the system and user configuration it includes, and the engine fixture checks onlyFONTCONFIG_FILE;matchMediacall, style write or SVG presentation attribute reached through a computed, concatenated or escaped name (for example one that reachesanimateorstyleMap), through reflection such asReflect.set, through an alias or a destructured reference or through a non-literal key such as aMapentry, a write through a property, method or attribute that the test does not list, such as thedisabledproperty of a stylesheet link, and a listed name or call spelled other than in the form it matches (with whitespace, a line break or a comment between its tokens, as an optional call, with a further argument, a prefixed qualified name or a comma in its namespace argument, or as a shorthand object property), are not claimed to be detected.CONTRIBUTING.md: the re-admission rule now names the pixel-identity non-claim of REQ-PROOFKIT-QUALITY-028 and of the visual proof record, Apple M1 and Arm Neoverse N2, and its four copies (the requirement, the binding,visual_record.goand the writer). It says that the gate's record identifies an N2 runner by implementer and part, while the Apple model comes from the host (sysctl -n machdep.cpu.brand_string).Compatibility
Judged against release 0.16.0. Not breaking.
--format htmlof the source, spec-tree, proof and coverage views, and the static browser-server views): the<style>content is the token stylesheet followed by the rewritten stylesheet. The workspace HTML gains one<link>. The browser server answersGET /assets/tokens.css, which returned 404 before. The servedworkspace.cssandworkspace-icons.jschange. ThehtmlByteLengthvalue in browser-server plans grows. In the pinned engines, no computed value of a property that existed before and no rendered pixel changes (Proof); the token custom properties are added.contractIds and exit codes are unchanged. In the shippedproofkit/cli-contract.v2.jsononly the native source digest changes. The shippedrequirement-bindings.json, the supply-chainrequirements.v2.jsonandoverview.mdchange as described above.tests/,scripts/andCONTRIBUTING.mdare not in the npm package.Proof
Proof: A local
npm run checksequence on darwin/arm64 passed (exit 0) on tree80fedbb7ba82with Node 26.11.1, npm 12.2.0, CPython 3.15.0 and Go 1.27.3, with no Go test verdict replayed from the test cache: 561/561 runtime browser cases in Chromium, Firefox and WebKit with zero retries. One recorded deviation: the host firewall blocks Go-built binaries from the network, sogo:vulncheckran against a vulnerability-database snapshot fetched with curl (modified 2026-10-21T00:27:29Z): no vulnerabilities. An earlier full gate on this tree failed two runtime browser tests on 30 s timeouts while the host load average exceeded 180 on 8 CPUs; this rerun at moderate load passed.Narrow evidence on darwin/arm64 (Apple M1) with Node 26.11.1, npm 12.2.0 and Go 1.27.3; the pinned browser-test SDK's engines are Chromium 153.0.8010.12, Firefox 155.0 and WebKit 26.6. The substitution, computed-style and pinned-value evidence below was produced on an earlier candidate of this change whose CSS, scripts, Go product source and references are byte-identical to this tree's; later candidates changed only the grammar test files, REQ-PROOFKIT-QUALITY-028 with its binding and the overview line, so that evidence carries over. The grammar evidence below was rerun on this tree.
var(--token)that the change introduced with the token's value reproduces the previous static stylesheet byte for byte (4092 bytes) and the previousworkspace.cssafter whitespace normalization. Its listed exceptions are moves, each checked: the workspace palette moved verbatim; the forced-colors system colors moved into tokens (substitution restores them, so no textual exception is needed); the icon presentation attributes moved into.workspace-icon. Falsifiers: a token value changed (--static-space-8: 9px) or avar()swapped inworkspace.csseach fail it.::before,::after,::markerand::placeholder, in light and dark. The states were: default at 320 and 1440, hover on one control per view, Table mode, alldetailsopen, root font size 200% at 320 and 1440, forced-colors emulation, keyboard focus on each element reached by Tab, and each workspace panel open at 320 with its::backdropand a pixel capture. That makes 178 states per engine and 534 engine states. Two runs of each side: before against after is identical in both pairs, and each side against its repeat is identical. The only difference is structural: the workspace head's added<link>. Custom properties are compared by the names that existed before; the token custom properties are additions. The comparison is exact, so it also covers font weights 400 and 500, and 600 to 900, which render identically with the recorded Liberation faces. Falsifier: a server built with--text: #202523and--static-space-8: 9pxdiffers in every static state and every light workspace state of every engine and in no dark workspace state, whose text token is separate.tests/visual/references.jsonis byte-unchanged. On a candidate tree that differs from this one only in the two grammar test files, REQ-PROOFKIT-QUALITY-028 and its binding, so that every rendered byte is the same (MATERIALIZED_TREEequal to that candidate's tree),npm run browser:visual -- --require-reference-environmentpassed in a fresh container ofmcr.microsoft.com/playwright@sha256:a0f44989…(linux/arm64,--user 1001 --ipc=host --init): 190 of 190 tests (169 references, 18 controls, 3 font-face checks), recordpassedandclean, a candidate inventory byte-equal toreferences.json, and all 169 capture PNGs byte-identical to the admitted gallery of test(browser): gate rendered views on owner-admitted reference digests #230. An earlier candidate with the same rendering inputs passed the same gate in two more fresh containers, one of them the CONTRIBUTING recipe.contrast-color(), whole-literal SVG values, parser-equal comment ends, markup reading insidesvgandmath, and the reworded unused-token text), applied one at a time to the previous grammar, kept the shipped sources and the listed exceptions admitted and made its own mutants refused.attr(), vendor-prefixed colors, size properties andperspective,contrast-color(), both category directions in the dark context, an attribute in a token, the fourmedia-attribute checks, SVG presentation attributes in any case, namespace or quote, an SVG attribute value that is an expression, presentational attributes in rendered HTML, the four comment and SVG-title scanner cases, markup nested in a raw-text element insidesvg, markup after each of the five elements parsed as text, and 22 script spellings such asstyleMap,deleteRule,.animate(,setAttribute("Style"),setAttributeNS("", "style")andcreateElement("STYLE"). The others lack a listed violation: the 14 added size properties, the 12 added SVG presentation attributes, the forced-colors category directions, or a reworded violation text.contrast-color(), prefix-free property classification (which also turns the listed exceptions red), the three comment ends, the bounded markup reading of raw-text elements aftersvg, the inherited foreign flag, and the unused-token text. Dropping the markup reading for the elements parsed as text turns red thenoscriptmutant and the five mutants that expect its unterminated-comment refusal. Removing each of the five elements parsed as text,perspective, each of the 13 color functions or each of the 12 size keywords turns its mutant red, and removing each of the 32 layout properties turns the listed exceptions red. So do disabling the dimension, percentage, number, unused-token and unscannable-HTML clauses and removing each of the 22 other size properties or either admitted vendor keyword, which an earlier candidate with the same clauses showed.TestSelfHostingRequirementBindingRecordsPreserveSourceAuthority.go test -p 2 -count=1 ./internal/command/requirementbrowser/... ./internal/app/...,npm run command-contract:check,go vet,go tool staticcheckandgofmton the changed package,text-policy,source-hygiene, the binding-reading node suitescripts/admission-records-structure.test.mjsandgit diff --checkpassed. Earlier candidates with the same product source and the same rendered bytes passedgo test -p 2 -count=1 ./...(148 packages ok),browser:static-check(72/72),boundary-contract:check(316/316),compact-,receipt-,spec-tree-andgraph-contract:checkandnpm pack --dry-run(26 files; shipped files changed: the supply-chain spec pair,cli-contract.v2.jsonandrequirement-bindings.json).spawnSynccall with about 430 KB ofinputoccasionally never delivers EOF to the CLI, which then blocks inread(2)on its stdin. Earlier runs of a candidate of this change and of test(browser): assert media emulation in the document under test #231 each lost single tests to it. The test-transport fix (fix(test): give spawned CLI children their input as a file, not a socket #232) is below this change in the chain.Non-claims