UK Legislation runtime acceptance

The release gate uses the production Explorer build and the generated local okf-uk-legislation/bundle/ publication. It does not substitute a small test fixture for the corpus.

Run from apps/okf-explorer/:

pnpm acceptance:legislation

The runner first creates private, byte-copy snapshots of the complete production build and legislation publication. Files in those snapshots are read-only and are not hard links back to either checkout. The temporary same-origin server serves only the snapshots, with real gzip transfer and the byte ranges used by release datapacks. It executes the same journey in Chrome, Firefox and WebKit:

  1. load the Whole-Law federation and confirm its 36 source classes;
  2. open the declared UK Legislation child and wait for static search to finish initialising;
  3. reload the legislation descriptor in a fresh context and measure compressed startup transfer;
  4. measure a first cold search and a second warm search;
  5. inspect the real facet inventory, coloured distributions and workspace geometry;
  6. verify that the official-effects reconciliation panel exposes agreement, live-addition, superseded and inaccessible states, including explicit zero counts;
  7. open the Consumer Credit Act graph, verify official and derived relationship line styles, and activate an edge with the keyboard;
  8. search for The Air Navigation (Amendment) Order 2026, hydrate its aligned governed v3 accepted model-enrichment shard, verify distinct official, derived and model-assisted relationship lines plus ordered evidence provenance, then hide and restore only the model-assisted class through the URL-backed authority filter;
  9. run WCAG 2.2 axe checks and capture the Chrome renderer JavaScript heap.

The limits are 1 MiB compressed startup transfer, 3 seconds for cold search, 1 second for warm search, and 256 MiB for the nominated Chrome CDP JavaScript heap measure. Firefox and WebKit do not expose that Chromium-only memory metric; the receipt records those measurements as unavailable rather than passing them.

The v0.6.2 release-bound canonical destination is release-assurance/explorer-runtime-acceptance.json. It is intentionally absent until the clean Explorer commit, annotated v0.6.2 tag and exact legislation candidate can be bound by the v2 release gate. The byte-preserved 26 July v1 receipt is retained only as historical evidence under the archive classification; its legacy overall passed field is not current release authority. An unbound local run emits an okf-explorer-runtime-acceptance.v1 diagnostic receipt. Diagnostic evidence is deliberately assigned overall status failed even when all its individual browser checks pass, so it cannot be mistaken for a release gate. A release gate supplies all candidate and Explorer revision arguments plus a fresh external --output path whose basename must be explorer-runtime-acceptance.json; that run emits okf-explorer-runtime-acceptance.v2.

Release evidence is pinned to Explorer v0.6.2. The runner reads the package version, derives the Explorer HEAD, tree and v0.6.2 tag object from Git, and requires an annotated tag that resolves to that HEAD. It independently derives the legislation candidate HEAD, tree and the complete bundle inventory digest. Both checkouts must be clean. The supplied revision arguments are expectations, not evidence: every supplied value must exactly equal the corresponding derived value. See the Git object model for the distinction between commits, trees and tag objects.

The public pnpm acceptance:legislation command acquires the checkout lock and performs the deterministic build before it starts this inner runner. Do not invoke the inner runner or start a separate preliminary build: both would bypass or duplicate the governed build-first sequence. A release-bound run requires the validated on-disk manifest and does not use the local in-memory fallback. The wrapper records a completed-build attestation in its live lock only after the deterministic command succeeds. Before opening the acceptance server, the runner verifies the exact command digest, build-script bytes, canonical manifest bytes and app-build tree. It binds the runner, runtime contract, build-manifest module, wrapper, lock module, deterministic-build script, package metadata and dependency lock before the browser work, then rereads all of them afterwards. It also revalidates the live completed-build lock after all browser contexts and the server have closed. The receipt projects only safe repository-relative paths and digests from that proof; it does not expose the owner token, absolute local paths, URL query strings or credentials. Both live-lock verifications receive the runner's deadline. Lock and build material reads are byte-bounded, check the deadline before allocation and each read, and pass that same deadline into canonical build inspection.

The complete source bundle and build are scanned within bounded entry, file, depth, per-file and aggregate-byte limits while being copied. Directory entries are consumed incrementally and the entry ceiling is checked before a name is retained. Files are size-checked before any result buffer is allocated, opened without following links, read in bounded chunks and checked for truncation, growth, replacement and identity changes. The shared canonical build inspector applies the same finite deadline and ceilings before it reads the manifest or any build material, and checks it again before allocating read buffers and before and after canonical parsing or serialisation. The runner rejects symbolic links, hard links and special files. It computes the bundle digest with the same canonical JSON inventory algorithm as the legislation release tooling. After the browser journeys it rehashes both live sources and both snapshots and requires all four identities to remain exact.

On a successful path the private snapshot directory is removed and its absence verified before the canonical receipt is constructed or published. A cleanup failure therefore cannot leave a passing receipt. Failure paths also attempt bounded cleanup, while the wrapper's process-group timeout remains the final hard stop.

Every invocation stages a self-contained, write-once evidence tree beside the receipt:

Every material has an exact positive byte count and SHA-256 digest. The receipt reports only these safe paths, never a ../ path back into an Explorer or legislation checkout. Its outputs.receipt value is exactly explorer-runtime-acceptance.json, matching the Whole-Law pre-RC controller. The deployed okf-explorer-build-manifest.json is canonical UTF-8 JSON plus a final line feed. It has schema okf-explorer-app-build-manifest.v1, algorithm sha256-canonical-json-materials-v1, a positive file_count, tree_sha256 and materials strictly sorted by raw Unicode code-point path order. Each material is a unique, safe, build-root-relative POSIX path with positive bytes and SHA-256. Directories are not included. The manifest excludes itself to avoid recursive hashing, and the tree digest is SHA-256 over the UTF-8 bytes JSON.stringify(materials) + "\n" with material key order path, bytes, sha256.

The release-bound runner requires that deployed manifest, canonical-parses and re-renders it, scans the complete app build without following links, and rejects missing, extra, duplicate, unsafe, linked or tampered files. It stages the manifest and every described file independently with write-once semantics. inputs.explorer_build exposes root, manifest, the exactly matching index, files, sha256, algorithm and the complete ordered staged materials array so a downstream finalizer can enumerate and independently rehash the evidence tree. The integrity projection also reconstructs the canonical manifest from those rows and requires the staged manifest byte count and digest to match it. A mutable local v1 run may derive the same manifest in memory when a plain local build predates the deployed manifest. Publication is durable and write-once: the runner synchronises a private temporary file, publishes it atomically without replacement, removes the temporary name, synchronises the containing directory and then independently rereads the exact expected byte count through a no-follow file descriptor. It verifies and synchronises the destination inode itself, then verifies the complete bytes and physical parent for both a newly created file and an already-present identical file. It fails rather than replacing a divergent, symbolic or linked destination. The receipt and output/playwright/ captures must be physically contained by the same evidence root. A release evidence root must be a new, absent directory whose parent already exists, and must be disjoint from both source checkouts: it may neither contain a source checkout nor be contained by one. Use a different external directory for each measured attempt. This follows the durability distinction documented for Node file handles and fsync in the Node.js file-system API.

The hard aggregate limits are 19 minutes for the runner, 20,000 bundle entries, 12,000 bundle files, 2 GiB of bundle bytes, 2,000 build entries, 1,000 build files, 128 MiB of build bytes, 128 MiB per file, a 16 MiB canonical build manifest, 50,000 HTTP transfer rows, 8 GiB each of wire and decoded transfer bytes, 16 concurrent requests, 256 MiB of in-flight decoded source bytes, 8 MiB of retained telemetry, 64 KiB per retained string, 10,000 graph edges, two screenshots of at most 20 MiB each, and a 32 MiB receipt. The snapshot cleanup allowance is 15 seconds. At least 10 seconds of the governed runner deadline must remain before receipt publication begins. Receipt and telemetry sizes are measured incrementally before their complete JSON representations are allocated, with deadline checks during measurement and immediately before and after complete serialisation and durable publication. The final post-publication deadline check prevents an earlier duration observation from authorising late evidence. Every admitted HTTP request holds a concurrency reservation until its response finishes or closes; successful, ranged, compressed and not-found responses also reserve their transfer row and byte budgets before material is read or a response is queued. Exceeding any bound puts the same-origin acceptance server into a sticky fatal state or otherwise fails the run; a later request cannot erase that failure, and no bound is reported merely as an advisory observation.

The receipt binds every browser version, exact measurements, resource ceilings, gate decisions and limitations. Live official full-text search is replaced with an empty deterministic Atom response so source availability cannot change the static local-search measurement. The accessibility gate names its assessed standard as WCAG 2.2 AA and uses the axe-core Playwright integration.

The detailed gates and browsers evidence remains authoritative. Stable release-facing projections additionally expose overall runtime counts plus cross_engine, accessibility, performance and integrity statuses. These paths are the contract consumed by the UK Legislation clean-room reproduction gate. The integrity projection fails closed unless both descriptors, the canonical build manifest, its complete staged material set and the exact two-current-Chrome-screenshot set have canonical paths, positive byte counts and valid SHA-256 evidence. A stale, missing, duplicated, extra or internally inconsistent material cannot satisfy a passing receipt.

The projection contract can be tested without building the application or running the real corpus:

pnpm test:acceptance-contract