CRUDUI

Execution checklist

Wave 1 — A stylesheet for small screens and a Tailwind version of the stylesheet

ID Task Deliverables Verification State
C1.1 Specify the narrow viewport: every block of the shared fixtures renders without horizontal overflow of the page at a viewport width of 360 CSS pixels and at 1280 pixels, with every control and action inside the viewport — make docs-check [o]
C1.2 Add the browser case of the narrow viewport over the shared fixtures, confirm that it fails where crudui.css overflows, and correct crudui.css — the browser case, make docs-check [o]
C1.3 Specify the Tailwind version: @crudui/generator-core/crudui.tailwind.css holds the rules of crudui.css in the cascade layer components of Tailwind CSS 4, generated from crudui.css and checked against it, so it is imported after tailwindcss and the Tailwind utilities override the CRUDUI rules; the markup does not change, and compiled with the theme and the utilities of the latest stable Tailwind CSS it gives every element of the shared fixtures the same computed style as crudui.css at both widths. Amended on 2026-10-04: the first text required a second stylesheet written with @apply, which two hand-written copies of about 900 rules would let drift apart — make docs-check [o]
C1.4 Generate crudui.tailwind.css with a script that also checks the committed file; add a case that compiles it with the latest stable Tailwind CSS and compares the computed styles with crudui.css at both widths in the three engines; confirm both fail before the file exists — the generation check, the comparison case [o]
C1.4-1 Make the viewport and Tailwind tests and the generation script pass the repository checks: make ci failed npm run lint, because the two tests hand functions with browser globals to a page without being listed as Node and browser files and wrote with console.log, and npm run test:runtimes, because the script that test:forms runs did not print through scripts/test-progress/progress.mjs; list the tests, report through t.diagnostic and print through the progress lines — make ci [o]
C1.5 Package export, feature status, operations document, changelog and the full suite — the full suite, make docs-check [o]

Wave 2 — Test runs that are bounded per test and observable

ID Task Deliverables Verification State
C2.1 State the test rules in AGENTS: during a task only the Red and Green tests that own the change run; the full suite runs exactly once, when every active task is done; a long operation prints step logs in addition to its own timeout; make docs-check runs only when a document or a public API document changed; the Verification column names the owning commands. Cause: AGENTS ran the full suite at each completed feature and let step logs replace a timeout. Red: AGENTS.md and AGENTS.ko.md hold those sentences. Green: they hold the new rules — node scripts/check-documents.mjs, node scripts/run-tests.mjs node --timeout 10 -- tests/docs/changelog.test.mjs tests/docs/repository-writing.test.mjs [o]
C2.1-1 State in AGENTS that a long operation (a build, an install, a toolchain setup, a browser close, a server stop, a whole suite) prints detailed step logs and has no timeout, no inactivity limit included, and is judged from its observed result and errors; each test case keeps its own timeout. Cause: C2.1 gave a long operation step logs in addition to its own timeout, so a normal run that took longer than its limit failed. Red: AGENTS.md and AGENTS.ko.md held the sentence of C2.1. Green: they hold the new rule and not that sentence — node scripts/check-documents.mjs, node scripts/run-tests.mjs node --timeout 10 -- tests/docs/changelog.test.mjs tests/docs/repository-writing.test.mjs [o]
C2.1-2 Enforce the single full run with the guard scripts/full-run.mjs, which make ci starts before any command: it refuses while a task row of this checklist is in progress and lists each active ID with its task, refuses while tracked changes are uncommitted, refuses a second full run of the same tree (git rev-parse HEAD^{tree}) and names the earlier run, removes the conformance evidence of earlier runs, runs each command of CI_COMMANDS with sh -c to its end and writes var/full-run.json (tree, commit, result, each command with its status and times) before and after each command, so a stopped run stays incomplete; make rerun-failed reruns only the commands of the current tree that did not pass, keeps the evidence of those that passed and completes the result when they pass. Cause: AGENTS states that the full suite runs exactly once, when every active task is done, and nothing enforced it: a run with a task in progress, a run of uncommitted changes and a second run of the same tree all started the suite. Red: make -n ci printed the removal of the conformance evidence and the loop over CI_COMMANDS, so the suite started whatever the checklist held, make -n rerun-failed failed with No rule to make target, and tests/build/full-run.test.mjs failed with ERR_MODULE_NOT_FOUND for scripts/full-run.mjs. Green: its 11 cases pass: make -n ci prints only the guard, a fixture checklist with a task in progress, a dirty tree, a second run of one tree and rerun-failed without a record are refused before any stub command runs, rerun-failed runs only the stub commands that failed or did not finish, and a full run removes earlier evidence while a rerun keeps it; tests/build/ci-local.test.mjs passes — node scripts/run-tests.mjs node -- tests/build/full-run.test.mjs tests/build/ci-local.test.mjs [o]
C2.2 Remove the job timeout of every CI job that runs a suite and give each setup step its own short timeout; tests/build/test-commands.test.mjs fails when a suite step or a job with a suite step has timeout-minutes or a setup step of that job has none. test:form-comparison:pipeline builds through scripts/require-current-build.mjs, which bounds the build, and its Go and Cargo tests run through scripts/run-tests.mjs. Cause: the test check required timeout-minutes on every job, so 13 jobs held a limit of 10 to 30 minutes over their tests. Red: the new case listed 13 jobs that run tests with timeout-minutes and their setup steps without one. Green: the same case passes on the changed workflow — node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/form-comparison-ci.test.mjs [o]
C2.2-1 Remove every timeout-minutes of the CI workflow: a step runs tests, whose cases hold their own timeouts in the test runner, or a long operation (checkout, toolchain setup, install, build, lint, type check, upload, deployment), which has no time limit, and no job has a limit over its steps. tests/build/test-commands.test.mjs fails on a job or a step with timeout-minutes and no longer requires one on any step or job. Cause: C2.2 gave the setup, build and upload steps a limit of 5 or 10 minutes and kept job limits, so a slow but normal install or build failed. Red: the new case listed 84 steps and the jobs deploy-docs and conformance with timeout-minutes. Green: the same case passes on the changed workflow — node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/form-comparison-ci.test.mjs [o]
C2.3 Build the Go and Rust benchmark drivers in a separate step with step logs and a build limit, and run the built drivers in tests/build/bench-drivers.test.mjs with timeouts of seconds; no case keeps a 600 or 590-second limit. Cause: the accepted case ran go run and cargo run --release, so it compiled the drivers inside a test with a 600-second timeout and a 590-second deadline, and every rejected Rust case ran Cargo again. Red: the new case of tests/build/test-commands.test.mjs failed on node scripts/run-tests.mjs node --timeout 600 -- tests/build/bench-drivers.test.mjs; that run took 8.1 s for the accepted Rust case and 10.6 s for the rejected Rust cases with a warm Cargo target. Green: the same case passes; npm run test:bench builds the packages, then the Go driver in 1.0 s and the Rust driver in 0.6 s, each with its start, result and elapsed time, and every test of the driver test ends within 1.9 s under the 30-second timeout; tests/build/bounded-commands.test.mjs stops a driver build that never ends at its limit — node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/bounded-commands.test.mjs, npm run test:bench [o]
C2.3-1 Run every command of a long operation to its end without a time limit: scripts/bounded-command.mjs becomes scripts/run-command.mjs, whose command ends at its exit with its status as the result, and which stops the processes the command left in its group when it exits; tools/bench/build-drivers.mjs streams the compiler output with its step lines, and scripts/require-current-build.mjs, scripts/gen-api-docs.mjs, scripts/run-contract-tests.mjs and tools/bench/run.js lose their limits of 600 and 30 seconds and CRUDUI_COMMAND_LIMIT_SECONDS. The driver test cases keep the 30-second timeout of the test runner. Cause: C2.3 gave the driver builds, and the module the other scripts share, a limit, so a slow but normal build failed. Red: tests/build/run-command.test.mjs runs each of the five scripts with a command that ends with status 0 after 1.2 seconds; with the former module and CRUDUI_COMMAND_LIMIT_SECONDS=1 all five cases failed with exceeded its 1s limit. Green: the five cases pass; a child that ignores SIGTERM and holds the output is stopped, observed by the close of the script's output instead of a bound on time; npm run test:bench builds the Go driver in 1.6 s and the Rust driver in 11.6 s with their output and still-running lines, and the nine driver cases pass — node scripts/run-tests.mjs node -- tests/build/run-command.test.mjs tests/build/test-commands.test.mjs tests/build/rust-node-entry-points.test.mjs scripts/gen-api-docs.test.mjs, npm run test:bench [o]
C2.4 Wait for build readiness on changes of the state file (fs.watch) with an injectable clock; the readiness tests change the state with explicit events and a fake clock and compare no wall-clock time. Cause: readyBuild reread the state file on a 1-second timer, which AGENTS forbids when the producer publishes its changes, and its tests rewrote the file on a 20 ms interval and bounded the wall-clock time of the wait. Red: with a scratch module that blocks the event loop of every Node.js process for 350 ms out of every 400 ms, the six readiness cases failed: two stopped as stalled after 1.2 and 1.3 s against their bounds, four failed while the interval writer raced the removal of the state directory. Green: the seven cases (one new over a real file and its directory events) pass without load in 0.1 s and under the same load in each of three runs; the hung step stops at exactly 400 ms of the fake clock — node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs [o]
C2.4-1 Watch the build state file itself in the readiness wait: watchStateFile of examples/form-comparison/verify-tree.mjs watches the file, whose registration is complete when the watch returns (kqueue on macOS, inotify on Linux), and at every replacement watches the new file before the wait reads it. Cause: in the full run of 2026-10-05 after C2.11 the real-file case the build readiness wait reads the state file at each change of its directory ran out of its 30 seconds; a directory watch on macOS starts its event stream after fs.watch returns, so a rename made right after the wait started could be lost and the wait never read the ready state. Red: with the directory watch, 5 of 10 runs of check-verification.test.mjs under a load average of about 145 failed at that case. Green: with the file watch, 20 of 20 runs pass under load averages of 80 to 120, and the seven readiness cases pass — node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs [o]
C2.6 Bound every command other than a test that a CI test step reaches: the Go and Rust validator builds of the cross-check console pretest, the package builds of test:form-comparison:source, the composer reinstall of make test-native-suites and the documentation build of make docs-check have no limit of their own. Cause: C2.2 removed the job limits that bounded them. Closed without a change: these commands are builds and installs, long operations that by the rule of C2.1-1 print their logs and have no time limit, so their missing limit is not a defect — none: no change [o]
C2.7 End every teardown hook by its event without a hook timeout: teardown of scripts/test-progress/teardown.mjs registers a browser close, a server stop or a directory removal as an after hook of a file or of a test with the timeout Infinity, prints its start, a line every five seconds and its end with the elapsed time, and fails the file with its error; the hooks of examples/form-comparison/pipeline.browser.mjs, tests/form-inspector/browser.test.mjs, examples/form-comparison/record-stores.test.mjs, examples/form-comparison/src/browser-job.browser.mjs, tests/form-styles.test.mjs, tests/tailwind-styles.test.mjs, tests/viewport.test.mjs, tests/widget-scripts.test.mjs, tests/widget-script-runs.test.mjs and tests/docs/web-build.test.mjs use it, and the Vitest afterAll hooks of the cross-check console server and the form binding have the timeout Infinity. Cause: in the full run of 2026-10-05 (make ci, 2778 s) every test passed, but the after hook of the pipeline browser test that stops the server processes ran out of its 10-second limit, and the after hook of the form inspector test that closes the browser ran out of the 30-second default that node:test gives a hook from --test-timeout. Red: tests/build/teardown.test.mjs runs files under a test timeout of one second whose teardown resolves or fails after 1.5 seconds; registered as a hook without its own timeout, as the inspector test did, the three cases failed with test timed out after 1000ms. Green: the three cases pass; the inspector test and the documentation server test pass with their teardown lines (browser close 4.8 s on the loaded machine) — node scripts/run-tests.mjs node -- tests/build/teardown.test.mjs tests/form-inspector/browser.test.mjs tests/docs/web-build.test.mjs [o]
C2.7-1 End every setup hook (a browser launch, a server start, a stylesheet compile, the stack build of the pipeline test) by its event without a hook timeout: scripts/test-progress/teardown.mjs becomes scripts/test-progress/hooks.mjs with setup and teardown, each a before or after hook with the timeout Infinity that prints its start, a line every five seconds and its end; the setup hooks of the style, Tailwind, viewport, widget script, form inspector, pipeline and record store tests use it, the browser job test launches its browser once in a setup, connects to it a second time with a protocol timeout of one second for its protocol timeout tests, and opens pages in its tests, and the Vitest beforeAll hooks of the cross-check console server and the form binding have the timeout Infinity. Cause: the setup hooks held limits of 60 or 120 seconds, the browser start limit or the 30-second timeout that node:test gives a hook without its own, and four browser job tests launched a browser inside a test of 10 or 30 seconds, so a slow but normal launch on a loaded machine failed them. Red: the two new cases of tests/build/hooks.test.mjs run a file whose setup resolves or fails after 1.5 seconds under a test timeout of one second; registered without its own timeout, both failed, as did the three teardown cases. Green: the five cases pass; the browser job, form inspector and documentation server tests pass with their setup and teardown lines (browser launch 2.4 s, browser close 9.1 and 14.1 s on the loaded machine) — node scripts/run-tests.mjs node -- tests/build/hooks.test.mjs tests/form-inspector/browser.test.mjs examples/form-comparison/src/browser-job.browser.mjs tests/docs/web-build.test.mjs [o]
C2.7-2 Check the setup contract of the local pipeline stack in examples/form-comparison/check-verification.test.mjs: the pipeline and record store tests import setup and teardown from scripts/test-progress/hooks.mjs, register no hook of their own, and the pipeline test launches and closes its browser in a setup and a teardown. Cause: C2.7-1 removed the browser-start and browser-close limits from pipeline.browser.mjs, and this test still required them; it failed in the full run of 2026-10-05 after C2.11. Red: the local pipeline stack has no summed hook limit and starts its browser with the measured limit failed on the missing browserUnitLimitsMs['browser-start']. Green: the changed case passes with the 16 cases of the file — node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs [o]
C2.8 Shorten the native generator suite (tests/native-generators/run.mjs, run by make crudui-test-native), which runs for tens of minutes: the Go and Rust programs are built once in a build step before any check, with the output of the build tool, a line every five seconds and no time limit (the preparation budgets of 300 and 900 seconds and the 30-second process limit are removed); the PHP programs are checked by a probe within a check budget; every check is its own test with its budget, and the checks of every target run in one pool, os.availableParallelism() at a time, with the signal of each check in its asynchronous context. Cause: in the full run of 2026-10-05 it took 959 s, because the 738 checks of each target ran one after another and the javascript, html, php and php-native targets start an interpreter for every request: measured in the worktree at load averages of 93 to 161, javascript took 254.9 s, html 195.5 s, php 218.3 s and php-native 234.5 s of checks, go 16.7 s of checks after a build of 1.4 s and rust 19.6 s of checks after a build of 21.1 s, so the time went to the checks and not to the builds. Red: that run took 962.5 s, its checks one after another (median 114 ms each). Green: the same 4428 checks pass in 199.8 s at load averages of 93 to 187, 11 at a time (median 241 ms, longest 5.5 s under its budget), after the Go build in 0.7 s and the Rust build in 0.4 s; the inputs are unchanged — node tests/native-generators/run.mjs with the target of the change [o]
C2.9 Replace every assertion that bounds an elapsed time with a check of the cause, and fail on such an assertion: tests/build/test-commands.test.mjs finds assert.ok calls that compare performance.now() or Date.now() minus a start, or an elapsedMs or durationMs, with anything but 0. The hanging test of tests/build/run-tests.test.mjs and the hooks of its hook cases never settle and run through a spawn that does not block the event loop, so only the runner's timeout ends them under the case's own timeout; the readiness, health and stop cases of server-startup.test.mjs, the timeout case of unit-pool.test.mjs and the timeout and stall cases of step-runner.test.mjs end only by the stop under test; the two steps of one stage in step-runner.test.mjs each end only when the other's file exists; the oversized save of record-contract.mjs relies on its 413 and the case's own timeout. Cause: these tests bounded the wall-clock time of a run, a stop, a stage or an answer between 0.5 and 20 seconds, which a loaded machine exceeds while the code is correct. Red: the new case listed 10 assertions in record-contract.mjs, server-startup.test.mjs (3), step-runner.test.mjs (4), unit-pool.test.mjs and run-tests.test.mjs. Green: the case passes and the five changed test files pass — node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/run-tests.test.mjs examples/form-comparison/src/step-runner.test.mjs examples/form-comparison/src/unit-pool.test.mjs examples/form-comparison/src/server-startup.test.mjs [o]
C2.9-1 Meet the two steps of one stage in examples/form-comparison/src/step-runner.test.mjs through a named pipe: one step opens it for reading and the other for writing, and each open blocks until the other end is open, so the stage ends only when both steps run at the same time. Cause: C2.9 made each step wait for the other's file through a directory watch, whose event stream on macOS starts after fs.watch returns, so a file written right after the watch started could be lost and the step waited until the case's timeout. Red: with the directory watch, 4 of 10 runs of the file failed at load averages of 17 to 53. Green: with the pipe, 10 of 10 runs pass — node scripts/run-tests.mjs node -- examples/form-comparison/src/step-runner.test.mjs [o]
C2.10 Replace the comparison of the mounted repository that the form comparison supervisor runs once per second with a published change event, and keep the contract that the service follows uncommitted changes: the host's source watcher examples/form-comparison/source-events.mjs (make deploy-watch) subscribes to the file events of the working tree (fs.watch, recursive: FSEvents on macOS) and for every event runs source-changed.mjs in the container (container exec), which sends SIGUSR2 to the process id the supervisor records in state/supervisor.pid; the supervisor compares the repository at each signal, a signal during a comparison or during the start makes one more comparison after it (src/change-requests.mjs), the 1-second loop and the heartbeat that rewrote the state file every 15 seconds are removed, and the watcher has no time limit and prints its start, every event and every delivery. tests/build/test-commands.test.mjs fails on a program that sleeps or runs an interval that prints no progress line. Cause: supervisor.mjs compared the mount with Git every second (checkInterval), because file events of the macOS host do not reach the Linux container through the VM file share; AGENTS forbids a periodic state read when the producer can publish. Decided on 2026-10-05: keep following uncommitted changes and publish them from the host. Red: with the former supervisor, the guard listed supervisor.mjs:195: sleeps and supervisor.mjs:58: an interval that prints no progress line, and the case the supervisor compares the repository at change signals, not on a timer failed. Green: both pass with the four other cases of src/source-events.test.mjs (coalesced requests, signalled events, a failed delivery, the container command), and npm run test:form-comparison passes 201, 10 and 4 tests. The signal path through the running container is exercised at the next deployment — node scripts/run-tests.mjs node -- examples/form-comparison/src/source-events.test.mjs examples/form-comparison/src/server-layout.test.mjs tests/build/test-commands.test.mjs [o]
C2.10-1 Signal the supervisor from a deployment that reuses the running container, and stop the source watcher cleanly: make deploy in its sync mode runs source-changed.mjs in the container (sourceChangeCommand, now in comparison-deployment.mjs) before it waits for the build, and source-events.mjs closes its watch at SIGINT or SIGTERM and prints stopped after N deliveries. Cause: verified on the real deployment, the C2.10 chain worked (the touch of README.md was delivered in 175 ms, the supervisor logged source change signalled, compared the changed paths and reported cycle 2 ready, and the waiting deployment passed), but before the watcher ran, make deploy of a new commit waited 10 minutes on cycle 1 is ready for <the previous commit> because nothing signalled the reused supervisor; a watcher stopped by SIGINT printed no stop. Red: the two new cases of src/source-events.test.mjs failed (the module exported no sourceChangeCommand to signal from the deployment, and the watcher took no stop). Green: they pass with the 9 other cases of the file and of check-deployment.test.mjs — node scripts/run-tests.mjs node -- examples/form-comparison/src/source-events.test.mjs examples/form-comparison/check-deployment.test.mjs, make deploy, make deploy-watch [o]
C2.10-2 Signal the supervisor from every deployment, not only from one that reuses the container: containerctl also reuses an unchanged container whose supervisor started from an earlier checkout, and since C2.13-3 a new supervisor records its process before its port opens, so the signal reaches it once containerctl returns. Cause: C2.10-1 signalled only in the sync mode, so a deployment that applied the definition with containerctl and got the existing container back would wait on the previous checkout. Red: the changed case of src/source-events.test.mjs failed on the sync-only signal. Green: it passes — node scripts/run-tests.mjs node -- examples/form-comparison/src/source-events.test.mjs, make deploy [o]
C2.11 Make every shared render case of the viewport and Tailwind checks its own short test: tests/viewport.test.mjs and tests/tailwind-styles.test.mjs open the pages of one engine and width in a setup of a suite and run each of the 432 cases as a test with the runner's timeout of 30 seconds, and no test has a timeout of 300 or 600 seconds. Cause: each file ran all 432 cases of one engine and width inside one test with a timeout of 300 or 600 seconds, so a stuck case held the run for up to ten minutes and a failure named no case until the end; the slow part was not a build. Red: the 12 engine and width tests took 1.5 to 11.7 s each under timeouts of 5 and 10 minutes and printed only their start and end. Green: the 5184 cases pass, the longest in 0.4 s, and both files take 57.1 s together as before (47.6 s) — node scripts/run-tests.mjs node -- tests/viewport.test.mjs tests/tailwind-styles.test.mjs [o]
C2.12 Build the packages twice for the reproducible build check in a logged step without a time limit: scripts/repeat-build.mjs runs npm run build twice through scripts/run-command.mjs and records after each build the path and SHA-256 digest of every output file of the published packages (scripts/package-outputs.mjs); tests/build/reproducible-build.test.mjs compares the two records with each other and with the current output under the 30-second timeout of each test, and test:build:repeat has no --timeout. Cause: the test ran both builds inside one test case through a blocking execFileSync under a timeout of 120 seconds, which the runner could not even apply while the builds blocked the event loop. Red: the new case of tests/build/test-commands.test.mjs failed on node scripts/run-tests.mjs node --timeout 120 -- tests/build/reproducible-build.test.mjs; that test case took 28.6 s. Green: the case passes; npm run test:build:repeat builds in 14.7 s and 10.8 s with the build output, records 329 files each time, and its three tests pass in 0.1 s — node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs, npm run test:build:repeat [o]
C2.13 Remove the time limits of the long operations of the form comparison: the build target timeouts of src/build-targets.mjs (60 to 900 seconds), the total and inactivity limits of the steps of src/step-runner.mjs (stepSilenceLimitMs is a no-output deadline) that the supervisor, verify-tree.mjs, comparison-deployment.mjs and the local stack of the pipeline and record store tests use, the step limits the build state publishes to the readiness wait (progress.limitMs), the process start limit of 30 seconds, the deployment step limits and the browser-start and browser-close units of the deployed checks; each operation prints its steps and ends by its event. Cause: the rule of C2.1-1; found while C2.7 changed the teardown of the pipeline test. The change reaches the supervisor, the readiness wait of C2.4 and the deployment; it is split into C2.13-1 and C2.13-2, both done — the tests of C2.13-1 and C2.13-2 [o]
C2.13-1 Remove the limits of the waits of the form comparison: the build readiness wait of verify-tree.mjs has no inactivity or step limit, ends when the state of this source is ready, fails on a failed cycle or a state file that cannot be watched, read or parsed, and prints every new step and a line every 15 seconds; the supervisor publishes progress without limitMs; a process start (waitForChildReadiness, the local stack's startProcess) waits for its readiness event without the 30-second limit and prints a line every 15 seconds; the browser start and close of check.mjs and check-pipeline.mjs run through runOperation of src/unit-pool.mjs without a limit, and the 10-second forced exit after a signal is removed; the units of the browser report keep their limits. Cause: the rule of C2.1-1. Red: with the former sources, 7 cases of check-verification.test.mjs, server-startup.test.mjs and unit-pool.test.mjs failed: the wait stopped as stalled while a step ran for ten hours, progress held limitMs, the browser units included the start and close, a readiness after a heartbeat line was rejected, and runOperation did not exist. Green: they pass, and npm run test:form-comparison passes 194, 10 and 4 tests — node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs examples/form-comparison/src/server-startup.test.mjs examples/form-comparison/src/unit-pool.test.mjs, npm run test:form-comparison [o]
C2.13-2 Remove the limits of the steps of the form comparison: runStep of src/step-runner.mjs runs a step to its end and assertStep rejects a timeoutMs or silenceLimitMs (stepSilenceLimitMs and the progress-line inactivity check are removed); the build targets of src/build-targets.mjs lose their timeouts of 60 to 900 seconds; the verification checks of verify-tree.mjs, the local builds of src/local-servers.mjs, the public build of pipeline.browser.mjs, the host verification step of verification.mjs and the build readiness and deployment steps of comparison-deployment.mjs hold no limit; deploymentStepLimitsMs, deploymentHealth and containerctlHealthLimits are removed and the Compose definition declares no healthcheck, whose budget bounded the first build, so the deployment waits for the build state of the checkout instead; the Git calls of the source comparison and of the OrderedJSON checkout lose their timeouts of 120 and 600 seconds. Units keep their own limits. Rebased onto C3 after it reached main. Cause: the rule of C2.1-1. Red: with the former sources, 7 cases of step-runner.test.mjs, build-targets.test.mjs and check-deployment.test.mjs failed: steps required a limit, a silent step was not run to its end, a build target held a timeout, and the definition declared a healthcheck. Green: they pass, and npm run test:form-comparison passes 203, 10 and 4 tests — node scripts/run-tests.mjs node -- examples/form-comparison/src/step-runner.test.mjs examples/form-comparison/src/build-targets.test.mjs examples/form-comparison/check-deployment.test.mjs examples/form-comparison/check-verification.test.mjs examples/form-comparison/src/source-events.test.mjs, npm run test:form-comparison [o]
C2.13-3 Answer on the service port from the supervisor's start: the supervisor records its process id, then listens on port 8080 and answers every request with 503 and the build state until it hands the port to the public server, closing its listener and connections right before the public server starts. Cause: C2.13-2 removed the Compose healthcheck, so containerctl checked the service port within its fixed one minute right after the container started, before the first build of empty volumes ended and the public server listened; make deploy failed with containerctl-edge did not open a connection to ...:8080 for crudui.test within 1m0s. Red: the new case of src/source-events.test.mjs failed, the supervisor opened no port before its first build. Green: it passes; the next make deploy connects within containerctl's minute — node scripts/run-tests.mjs node -- examples/form-comparison/src/source-events.test.mjs, make deploy [o]
C2.14 Fail the benchmark run when a version command fails: toolVersion of tools/bench/run.js throws with the command, its failure and its output, a command that prints no version fails too, and results.md records the version only of the tools of the backends that ran. Cause: a failed version command was recorded as unavailable and an empty one as unknown, so the report hid the error, and the versions of all four tools were queried even for backends that did not run. Red: the two new cases of tests/build/run-command.test.mjs failed: a run of the JavaScript backend recorded php, go and rust/cargo lines, and a node --version that ends with status 3 still wrote the report and ended with 0. Green: both cases pass with the Rust toolchain case of tests/build/rust-node-entry-points.test.mjs — node scripts/run-tests.mjs node -- tests/build/run-command.test.mjs tests/build/rust-node-entry-points.test.mjs [o]
C2.15 Start the PHP record server on the readiness lines of its processes: servers/php/main.mjs copies the standard error of PHP-FPM and nginx and is ready when PHP-FPM writes NOTICE: ready to handle connections and nginx, at the notice log level, writes start worker processes; a process that exits before its line stops the server with its reason. Cause: the launcher tried a connection every 20 ms and failed after a start limit of 10 seconds, a polling loop with a deadline that a loaded machine could exceed; found while C2.10 looked for interval loops. Red: the new examples/form-comparison/src/php-server.test.mjs runs the launcher with stand-in programs that write the readiness lines and accept no connection; with the former launcher the ready case failed with php-fpm did not accept connections within 10000 ms. Green: both cases pass in 1.4 s — node scripts/run-tests.mjs node -- examples/form-comparison/src/php-server.test.mjs [o]
C2.16 Complete the stop of a process tree whose group holds only exited processes: signal of examples/form-comparison/src/step-runner.mjs treats EPERM of a group signal like ESRCH, as scripts/run-command.mjs does, because macOS answers a signal to a group whose processes have all exited but are not yet reaped with EPERM. Cause: killProcessTree killed the tree, then signalled the group, and failed with kill EPERM; every server stop, teardown and step stop of the form comparison uses it. Found while C2.13-2 rewrote the tree stop case. Red: the new case of step-runner.test.mjs stops ten trees of a child and a detached grandchild that ignore SIGTERM; it failed with kill EPERM in 3 of 3 runs. Green: it passes in 5 of 5 runs; the end of the child's output, which the grandchild holds, shows that both are gone — node scripts/run-tests.mjs node -- examples/form-comparison/src/step-runner.test.mjs [o]
C2.17 Pin the Chromium of the comparison toolchain image to the version Debian trixie serves: chromium and chromium-sandbox 154.0.8037.92-1~deb13u1 in examples/form-comparison/Containerfile and check-toolchain.test.mjs. Cause: the security archive replaced 153.0.8010.47-2~deb13u1, so make deploy, run to verify C2.10 on the real deployment, failed at the toolchain image with Version '153.0.8010.47-2~deb13u1' for 'chromium' was not found; apt-cache policy in node:26-trixie-slim names 154.0.8037.92-1~deb13u1 as the candidate. Red: the changed case of check-toolchain.test.mjs failed on the former pin. Green: it passes, and container build of the image passes — node --test examples/form-comparison/check-toolchain.test.mjs, container build --file examples/form-comparison/Containerfile [o]
C2.18 Declare the route of the comparison service the way containerctl reads it: the Compose service lists x-containerctl: domains: ["crudui.test"], and the deployment check reads the domains and urls that containerctl status --json reports. Cause: containerctl routes only the domains listed under services.<name>.x-containerctl.domains and treats a service without them as internal, so the containerctl.domain label routed nothing; make deploy, run to verify C2.10, built and started the service and then failed with Deployment route domain differs (routed: false, internal: true). Red: the changed case of check-deployment.test.mjs failed on the label. Green: it passes, and make deploy routes https://crudui.test/ — node scripts/run-tests.mjs node -- examples/form-comparison/check-deployment.test.mjs, make deploy [o]
C2.18-1 Reuse a running comparison container only when containerctl routes the declared domain to it: shouldReuseDeployment also requires the service that containerctl status reports to be routed with the domains ["crudui.test"]; otherwise the deployment applies the definition with containerctl. Cause: after C2.18 changed the definition, make deploy reused the running container because its image matched, never applied the new route, and failed with Deployment route is not active. Red: the changed case of check-deployment.test.mjs failed for a running container of the expected image whose route is not applied. Green: it passes; the next make deploy applies the route — node scripts/run-tests.mjs node -- examples/form-comparison/check-deployment.test.mjs, make deploy [o]
C2.19 Stop the servers of a local stack when the test process that started them exits without stopping them: src/local-servers.mjs records the process group of every server startProcess starts and, at the exit of the process, kills the groups not yet stopped. Cause: a run of examples/form-comparison/pipeline.browser.mjs at 05:37 on 2026-10-05, whose server stop hook still ran out of its 10-second limit (C2.7) and whose process was then forced to exit, left its PHP, PHP extension and Go record servers running for more than seven hours (the coordinator stopped the PHP servers; the Go server of crudui-pipeline-h0rCc4 was stopped here). Red: the new src/local-servers.test.mjs starts a stand-in server through startProcess in a child that then exits; the server survived and the case ran out of its 10 seconds. Green: the case passes in 1.1 s in each of 4 runs, observed through the end of a named pipe that the server holds, and no stand-in is left — node scripts/run-tests.mjs node -- examples/form-comparison/src/local-servers.test.mjs [o]
C2.5 Count only PHPUnit tests in the PHPUnit mode of scripts/run-tests.mjs, not class or data provider suites, and print the lines of PHPUnit that are not TeamCity messages. Cause: a testSuiteFinished message carries no location, so the finish of a class or a data provider method was passed as a test, and every line that was not a TeamCity message was dropped. Red: the TeamCity case counted 3 passed tests for 1 and lost the PHPUnit banner and the line Test file "/missing.php" not found; the validator-php run reported 804 passed for PHPUnit's 763. Green: the same case counts 1 passed and 1 failed and prints both lines; the validator-php run reports 763 passed — node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs [o]

Wave 3 — Shared resources of test and deployment runs

ID Task Deliverables Verification State
C3.1 make docs-verify-idempotent writes its two snapshots and their difference to a directory that the run creates with mktemp -d and removes at its exit. Cause: it used the fixed /tmp/crudui-docs-run1, /tmp/crudui-docs-run2 and /tmp/crudui-docs-diff.txt, which a second run removes and rewrites during the first. Red: the new case of tests/build/shared-resources.test.mjs listed the 12 Makefile lines with those paths. Green: the case passes; the recipe with stubbed sub-makes (MAKE=true) over a scratch copy compares the snapshots and leaves no directory behind — node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs [o]
C3.2 Add the holder lock (scripts/holder-lock.mjs): the record (checkout, pid, process start time, acquisition time, command, token) is written to a private file and linked to the lock path, so taking the lock is atomic and complete; a held lock refuses with the holder's record; a lock whose holder process no longer runs, a reused pid included, is reported and kept, and remove-dead removes it explicitly and refuses a running holder; only the holder releases it; hold runs a command while holding the lock. Cause: the comparison deployment, the Playwright image and the package dist directories are single resources, and no run could tell that another run used one. Red: tests/build/holder-lock.test.mjs failed to load without the module. Green: its eight cases pass: the record, a refusal naming the holder of another process, one holder among eight concurrent runs, a dead holder reported and kept until remove-dead, a reused pid, a refused removal of a running holder, a release refused to a replaced record and a lock file without a record — node scripts/run-tests.mjs node -- tests/build/holder-lock.test.mjs [o]
C3.3 The Linux style check keeps the Playwright image: it does not remove the image that its run pulled, and runs its container under the user-wide holder lock of that image; make remove-form-styles-image removes the image under the same lock, so it refuses while a check runs. Cause: the run that pulled the image removed it in its exit trap, also while a run of another checkout used it. Red: with a container CLI stub that reports the image as absent, the new case recorded image rm mcr.microsoft.com/playwright:v1.63.0-noble, and a run while another process held the image lock started its container. Green: both cases pass: a run keeps the image and releases the lock; while another process holds the lock, the check and the removal fail with the holder's pid and process start time and call no container command; after the release the removal calls image rm once — node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs [o]
C3.4 make deploy and make deploy-verify take the user-wide holder lock of the comparison deployment, the single Compose project crudui with its volumes, before any other step. Cause: the deployment of another checkout replaced the container, its Compose project and its volumes while a verification ran. Red: while the test held the lock, comparison-deployment.mjs went on to resolve the container runtime and verification.mjs to read its inputs; neither named a holder. Green: both programs fail with the holder's pid and process start time while the lock is held, and with the lock free they take it, fail at the missing container runtime of the test's PATH and release it — node scripts/run-tests.mjs node -- examples/form-comparison/check-deployment-lock.test.mjs [o]
C3.5 Every package build holds the lock of its package's dist for the whole build, and scripts/package-dist.mjs pack packs a package under the same lock, so no pack reads a dist that a build has emptied; the package install check and other repositories pack through it. Cause: tsup --clean and svelte-package empty dist before writing it, and the package install check and other repositories ran npm pack on dist without knowing of a build. Red: the build scripts held no lock, and while the test held the dist lock of generator-html, npm run build -w @crudui/generator-html rebuilt its dist and scripts/package-dist.mjs did not exist; the package install case expected the pack through it. Green: the cases pass: the build fails with the holder's pid and process start time and leaves dist unchanged, the pack fails and writes no archive, and after the release the pack writes one archive with the dist files and leaves no lock; npm run build takes and releases the seven locks in turn. The toolchain image of the comparison has no ps, so on Linux the start time comes from /proc: the nine holder lock cases pass in node:26.8.1-trixie-slim without ps — node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs tests/build/package-install-pack.test.mjs tests/build/holder-lock.test.mjs, npm run build [o]

Wave 4 — Task state markers that a tool can read

ID Task Deliverables Verification State
C4.1 Allow a task state marker in this checklist only as the state of a task row, at the start of its last cell: AGENTS defines the states; remove the legend, name states in words in the texts, and make scripts/check-documents.mjs fail for every other marker in both languages with its file, line and column, without exceptions; scripts/checklist-markers.test.mjs fails before the change. Red: the test failed with ERR_MODULE_NOT_FOUND; its fixture, which holds markers in a legend, in inline code of a task, in the cause of a bypassed task and in the last cell of a row that is not a task, written as both checklists, passed the former scripts/check-documents.mjs with 53 document pairs: links, examples and status fields passed. Green: the 2 cases pass; the same fixture fails with the 5 locations of each language; with the check and the former texts the document check failed with 14 markers on lines 10 and 35 of each language, and it passes after the change — node scripts/run-tests.mjs node -- scripts/checklist-markers.test.mjs, node scripts/check-documents.mjs [o]
C4.1-1 Treat the task list states of GitHub as state markers: C4.1 checks only the four states of this repository, while a Markdown reader also takes an x or a capital X between brackets as the state of a task list item, so such a marker outside a task state passed the document check; make the check fail for it with its file, line and column, with cases in scripts/checklist-markers.test.mjs that fail before the change. Red: with a task that names a capital X in inline code and a list item in the x form added to the fixture, the case failed because the check reported neither marker. Green: the 2 cases pass, the fixture failing with 7 locations, and the document check passes — node scripts/run-tests.mjs node -- scripts/checklist-markers.test.mjs, node scripts/check-documents.mjs [o]
C4.1-2 Keep only headings and task tables in the checklist: the checklist held a translation link, a paragraph on its content, the How to use rules and a paragraph of dependencies and background under each wave, and no check told such text from tasks. Move the rules to AGENTS and the dependencies and background of the waves to docs/plans/execution-checklist/plan.md, which each wave heading links; make the document check fail for every line that is not blank, a heading, a task row, a table header row or its separator row, with its file, line and column; a case in scripts/checklist-markers.test.mjs that fails before the change. Red: the test failed because the module had no textErrors, and its fixture with a translation link, a paragraph, a list item under How to use, a dependency line, a row that is not a task and a table without a separator row, written as both checklists, passed the former document check. Green: the 3 cases pass, the fixture failing with 6 locations; with the check and the former checklists the document check failed with 9 lines of each language, and it passes after the move — node scripts/run-tests.mjs node -- scripts/checklist-markers.test.mjs, node scripts/check-documents.mjs [o]

Wave 5 — CI runs that collect every failure and explain it

ID Task Deliverables Verification State
C5.1 Run every checking step of CI and every native suite after an earlier failure: each workflow step that runs a checking command has if: ${{ !cancelled() }}, and make crudui-test-native runs test-php-extension and test-native-suites to their ends and fails when one failed. Cause: CI skipped Lint, Type-check, the dependency check and the benchmark drivers after one failed step, and make crudui-test-native stopped at its failed prerequisite test-php-extension, so tests/native-generators/run.mjs never ran and wrote neither its report nor its conformance evidence. No Makefile target takes a target that runs tests as a prerequisite, so make docs-check also runs both arms; nativeTestNodeEntrypoints of tests/build/dependency-health.test.mjs read only the recipe lines of crudui-test-native, which had none, and checked an empty list, so it follows prerequisites and $(MAKE) targets and fails for an empty list. Red: tests/build/ci-local.test.mjs listed 26 checking steps without the condition, tests/build/test-commands.test.mjs listed docs-check: docs-check-documents, crudui-test-native: test-php-extension and crudui-test-native: test-native-suites, and with a failing PHP extension test command make crudui-test-native of tests/build/make-tool-path.test.mjs never started tests/native-generators/run.mjs; the former nativeTestNodeEntrypoints returned []. Green: the 21 cases of the three files and the 2 entry point cases of tests/build/dependency-health.test.mjs pass — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/test-commands.test.mjs tests/build/make-tool-path.test.mjs, node scripts/run-tests.mjs node -- --test-name-pattern="native test root imports" --test-name-pattern="Node.js programs of a target" tests/build/dependency-health.test.mjs [o]
C5.2 Make the dist lock test of tests/build/shared-resources.test.mjs hermetic: it builds and packs a fixture package in a temporary directory instead of reading packages/generator-html/dist. Cause: CI runs npm run test:runtimes before npm run build, and the test failed with ENOENT: no such file or directory, scandir '.../packages/generator-html/dist'; a local run read the dist of an earlier build. Red: in a worktree without build output the test failed with ENOENT: no such file or directory, scandir '.../packages/generator-html/dist' at line 111. Green: in the same worktree, still without dist, the 6 cases pass; the test copies scripts/package-dist.mjs and scripts/holder-lock.mjs into a temporary checkout, builds its fixture package, checks that the build and the pack are refused under the lock with dist unchanged and packs dist/index.js after the release — node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs [o]
C5.2-1 Read the npm pack report of the dist lock test in both forms of npm: npm 11 prints an array of archive reports and npm 12 an object keyed by package name, so with npm 12, which CI installs with npm i -g npm@latest, the test failed in CI with TypeError: object is not iterable at tests/build/shared-resources.test.mjs:154, a message that does not say what was expected. The test reads the report through the reader of scripts/package-install-pack.mjs, which accepts both forms and fails with the expected archive count and package name. Red: with npm 12.2.0 first on PATH the test failed locally with the same TypeError: object is not iterable at line 154. Green: the 9 cases of both files pass with npm 12.2.0 and with npm 11.19.0; packReport reads both forms and names @crudui/package in its failures — node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs tests/build/package-install-pack.test.mjs [o]
C5.2-2 Use one exact npm release locally, in CI and in the container images: packageManager of package.json names it, scripts/install-npm.mjs installs exactly that release, CI and the containers install it, and tests/build/runtime-version-policy.test.mjs fails when the running npm, a workflow step or a container definition selects another release. Cause: CI ran npm i -g npm@latest, so its npm changed with the registry; npm 12.2.0 in CI broke the dist lock test that passed with npm 11.19.0 locally (C5.2-1). packageManager records npm 12.2.0, the latest stable release, and the toolchain image check of the comparison accepts the npm installation as a toolchain step. Red: the new cases failed because package.json recorded no packageManager, and with it the local check failed with npm 11.19.0 runs here and package.json records npm 12.2.0; fix: node scripts/install-npm.mjs. Green: node scripts/install-npm.mjs installed 12.2.0 on the development machine, the 34 cases of the four files and the 16 of the comparison image checks pass, npm ci with npm 12.2.0 leaves package-lock.json unchanged, and npm run test:runtimes passes with 123 cases and tests/build/dependency-health.test.mjs with 13 — node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs tests/build/ci-local.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/test-commands.test.mjs examples/form-comparison/check-toolchain.test.mjs, node scripts/install-npm.mjs [o]
C5.3 Write the bytes of a long C string of the engine fixtures as character constants: cString of packages/php-ext/tests/engine.test.mjs wrote a value longer than 4000 bytes as integers, so a UTF-8 byte above 127 initialized a char, and GCC failed with overflow in conversion from 'int' to 'char' changes value from '236' to '-20' [-Werror=overflow]; Apple clang accepts the same source. Each byte of the character array is a character constant with an octal escape, '\354', whose value fits char on both signednesses. Red: with the former cString the case a long C string initializes each char with a character constant of its byte failed with an integer above 127 initializes a char and the elements 195, 172, 195, 35 cases passing. Green: the 36 cases of the file pass, the render fixtures compiling with Apple clang; GCC is not installed on the development machine, so CI shows the GCC compile — node scripts/run-tests.mjs node -- packages/php-ext/tests/engine.test.mjs [o]
C5.4 Compile every C program of the engine tests through one function that links the math library: the template and value tests wrote their own compiler commands, and the template test, without -lm, failed on Linux with undefined reference to 'log10'; on macOS the math functions are part of the system library. compileAndRunEngineProgram compiles a program file with named extension sources and -lm and runs it; the fixture, template and value tests call it. Red: the case every C program of the engine tests compiles through one function that links the math library failed with compiler commands on lines 282, 2224, 2252, 36 cases passing. Green: the 37 cases of the file pass — node scripts/run-tests.mjs node -- packages/php-ext/tests/engine.test.mjs [o]
C5.5 Name the suite behind missing conformance evidence: every evidence writer records the start and the end of its suite, and scripts/check-conformance.mjs reports for each missing group whether the suite that records it did not run, did not finish or ended with a failure. Cause: the check reported 185 missing for each runtime when tests/native-generators/run.mjs did not run, and did not say why. scripts/run-tests.mjs and tests/native-generators/run.mjs write a run record through tests/conformance/runs.mjs, null at the start and the exit status at the exit, and evidenceSuites of the check declares each suite with its command and runtimes. Red: the test failed because scripts/check-conformance.mjs had no export evidenceSuites; over a directory without evidence that holds a passed validator PHP run and a failed PHP extension run, the former check printed compileForm · tests/fixtures/compose/cases.json · php: 20 missing (…) without a suite. Green: the 8 cases pass, and the check prints … · php: 20 missing (…) — suite validator PHP passed; suite native generators did not run and … · php-native: 20 missing (…) — suite PHP extension ended with failure (exit 1); suite native generators did not run — node scripts/run-tests.mjs node -- tests/build/conformance-evidence.test.mjs [o]
C5.6 Report the comma locale build of the engine tests by its result: localedef -c exits with status 1 when it writes a locale with warnings, and the step printed comma locale: compiling: failed (1) before its test passed. The step reports status 1 with a written LC_NUMERIC as wrote the locale with warnings (exit 1), and another status other than 0 fails the build of the locale. Red: with localedef replaced by a stub that writes the locale, prints a warning and exits with 1, the case a comma locale that localedef wrote with warnings is reported as written failed with the line comma locale: compiling: failed (1), 37 cases passing. Green: the 38 cases of the file pass, the stub run printing comma locale: compiling: wrote the locale with warnings (exit 1) and a stub that writes nothing still failing with localedef wrote no locale — node scripts/run-tests.mjs node -- packages/php-ext/tests/engine.test.mjs [o]
C5.7 Run make ci from a fresh clone of the committed tree, so a local full run starts from the state of a CI checkout. Cause: make ci ran in the working tree, where ignored build output of earlier runs exists, so a test that needs a build passed locally and failed in CI. scripts/full-run.mjs clones the checkout into var/full-run/clone, checks out the committed commit and runs make install there before the first command, and runs every command of CI_COMMANDS with that clone as its directory; a rerun of the same commit reuses the clone when its installs completed (a marker in the Git directory of the clone), and a failed make install stops the run before any command with the clone and the commit. Red: the new case of tests/build/full-run.test.mjs failed on the previous guard, whose commands ran in the working tree with its ignored var/dist/stale.js and its untracked file. Green: the 15 cases pass: the commands run in the clone at the committed commit with exactly the committed files, make install runs once, a rerun reuses the installed clone, and a failed install names the clone — node scripts/run-tests.mjs node -- tests/build/full-run.test.mjs [o]
C5.8 Judge the dependencies by the state known at their review instead of the live registries: the test tracked npm lock files have no moderate or higher vulnerability of tests/build/dependency-health.test.mjs ran npm audit --package-lock-only against the registry in test:dependencies of make ci and CI, so the same tree passed or failed with the advisories published on the day of the run, and nothing checked the latest stable releases that docs/operations/package-build.md requires. The spec section "Dependency review" of docs/operations/package-build.md states the rule first: the registry dependencies are the dependencies and devDependencies of the root and the 8 workspace manifests and the require and require-dev of the 2 Composer packages; peer, URL and local dependencies and Composer platform requirements are outside the review by definition. scripts/check-dependencies.mjs, a case of test:dependencies, reads the manifests, the locks, the exception policy config/dependency-policy.json and the review record config/dependency-review.json and reports every finding with its rule and fix; composer validate --strict runs with COMPOSER_DISABLE_NETWORK=1. make dependency-review (scripts/dependency-review.mjs) asks the registries for the latest stable release that its publisher has not deprecated and for the advisories of npm audit and composer audit; RECORD=1 writes the record and UPDATE=1 updates first; the scheduled workflow .github/workflows/dependency-review.yml runs it every day. The policy excepts react and react-dom of the root and of generator-react at 19.2.8 with the reproduced failure of C5.8-2-1. The TypeScript 6.0.3 of C5.8-3 is a peer resolution of the root for typescript-eslint, typedoc, svelte-check and @sveltejs/package, not a dependency of a manifest, so the review does not read it and the policy holds no exception for it; the seven workspaces declare typescript ^7.0.2, the latest release. The first review, run by make dependency-review UPDATE=1, raised typescript-eslint from 8.71.0 to 8.71.1 and recorded 86 registry dependencies and 3 locks without an advisory. Red: on the previous tree, node scripts/check-dependencies.mjs with the new record reported the package-lock.json changed after its review and typescript-eslint 8.71.0 against the recorded 8.71.1; the cases of tests/build/dependency-review.test.mjs fail for a changed lock, a missing review entry, an older release without an exception, a stale or incomplete exception, a recorded advisory, an unrecorded manifest, a local package at another version and a Composer lock that is not current. Green: npm run test:dependencies passes with 21 cases and queries no registry, which a case asserts for its files — node scripts/run-tests.mjs node --timeout 60 -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs [o]
C5.8-1 Review the advisories of the six tracked Cargo locks like those of npm and Composer: crudui ships Rust packages, and no check read their advisories. The review runs cargo-audit 0.22.2, the release that cargoAudit of config/toolchain.json records and make install installs into .tools/cargo-audit (scripts/install-cargo-audit.mjs, never into the machine), against the RustSec database cloned into .tools/rustsec-advisory-db, and records each Cargo lock of the checkout (scripts/tracked-files.mjs) with its sha256 and its vulnerabilities and unmaintained, unsound or yanked crates in the record of C5.8; UPDATE=1 runs cargo update -p for an affected crate; the scheduled workflow installs the Rust toolchain and cargo-audit. The spec section "Dependency review" names the Cargo locks first. Red: with the extended check, node scripts/check-dependencies.mjs reported the 6 Cargo locks without an entry in the review record. Green: make dependency-review RECORD=1 recorded 86 registry dependencies and 9 locks without an advisory, the check passes, and npm run test:dependencies passes with 21 cases, whose fixtures fail for a changed or unreviewed Cargo lock, ignore a Cargo lock under an ignored directory and fail for a recorded RustSec advisory — node scripts/run-tests.mjs node --timeout 60 -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs [o]
C5.8-2 Raise every npm and Composer dependency to the latest stable release of its locked major: on 2026-10-05 the registries report newer releases in the same major for 19 npm dependencies of the root and workspace manifests (puppeteer 25.12.0, eslint 10.12.0, react and react-dom 19.3.0, vite 8.3.2, @types/node 25.9.9 and others) and PHPUnit 10.5.66 for both Composer packages. Each manifest keeps its range operator, an exact pin stays exact, and peer ranges stay unchanged; the build, lint, type checks and the tests of the packages run on the result. @testing-library/jest-dom 6.10.0 is deprecated by its publisher as a minor release with breaking changes, so the 6.x line stays at 6.9.1 and moves with its major update. The overrides of react and react-dom follow their direct ranges, and the install script approval of puppeteer names 25.12.0. Green: npm install with npm 12.2.0 updated the lock, npm ls --all passes, no dependency of a manifest has a newer release in its major except the deprecated one, the build, lint and type checks pass, the workspace suites pass after the Chrome 154.0.8037.57 of puppeteer 25.12.0 was installed (the form binding browser test could not find it before), npm run test:dependencies passes with 13 cases, npm run test:runtimes with 123, npm run test:packages with 7 install checks, and the PHPUnit suites with 763 and 311 cases — npm run build, npm run lint, npm run typecheck, npm test, npm run test:dependencies, npm run test:packages, composer --working-dir=packages/validator-php test, composer --working-dir=packages/generator-php test [o]
C5.8-2-1 Keep React and React DOM at 19.2.8: with React 19.3.0, raised by C5.8-2, the React cases of tests/widget-script-runs.test.mjs fail in Chromium, Firefox and WebKit with the browser error Encountered a script tag while rendering React component. Scripts inside React components are never executed when rendering on the client. Consider using template tag instead, because the React renderer renders widget scripts as <script> elements. C5.8-2 did not run that test, which npm run test:forms runs. The root ranges, the overrides and the development ranges of @crudui/generator-react pin 19.2.8; the update to React 19.3 is retried when the React renderer runs widget scripts without <script> elements in client rendering. Red: on the committed tree with React 19.3.0 the test failed with 6 of 24 cases. Green: with React 19.2.8 the 24 cases pass, the React suite passes with 1088 cases, type checks pass, npm run test:packages passes with 7 install checks and npm run test:dependencies with 13 — node scripts/run-tests.mjs node --timeout 120 -- tests/widget-script-runs.test.mjs, npm test -w @crudui/generator-react, npm run typecheck, npm run test:packages [o]
C5.8-3 Raise TypeScript of the seven TypeScript workspaces from 6.0.3 to 7.0.2, the latest stable release. typescript-eslint 8.71.0 declares the peer range >=4.8.4 <6.1.0, typedoc 6.0.x and below, svelte-check ^5.0.0 || ^6.0.0 and @sveltejs/package ^6.0.0, so these tools keep TypeScript 6.0.3, which npm installs at the root as their peer, while the tsc of each workspace build and type check is 7.0.2. Green: npx tsc --version in packages/validator-ts prints Version 7.0.2, the build, type checks and lint pass, the workspace suites pass, npm run test:packages passes with 7 install checks, npm run test:build with 36 cases, npm run test:dependencies with 13 and npm run test:runtimes with 123 — npm run build, npm run typecheck, npm run lint, npm test, npm run test:packages, npm run test:build [o]
C5.8-4 Raise Vitest of the root and the eight workspaces from 4.1.11 to 5.0.3, the latest stable release. Vitest 5 prints a failed hook with the Error: prefix of its stack, so the runner case of tests/build/run-tests.test.mjs for a timed-out Vitest hook accepts that prefix before Hook timed out in 300ms. Red: with Vitest 5 that case failed because its expectation required the message at the start of the line. Green: npx vitest --version prints vitest/5.0.3, the workspace suites pass with the same case counts as with Vitest 4 (40, 103, 293, 621, 1088, 817, 14, 824 and 690), type checks and lint pass, npm run test:runtimes passes with 123 cases, npm run test:packages with 7 install checks and npm run test:dependencies with 13 — npm test, npm run typecheck, npm run lint, npm run test:runtimes, npm run test:packages [o]
C5.8-4-1 The runner case of tests/build/run-tests.test.mjs for a failed Vitest hook asserts structured values instead of the text of Vitest: the Vitest reporter prints <hook name> hook of <entity> started and failed for a hook that started (onHookStart) and did not end before its file or suite failed, and the case asserts the hook name, the failed entities and the summary counts. Cause: the case asserted (?:Error: )?Hook timed out in 300ms, the human-readable message of Vitest, whose wording changed from Vitest 4 to 5 (C5.8-4). The elapsed time and the failure kind are not asserted: with Vitest 5.0.3 a probe reporter received onHookStart and no onHookEnd for a timed-out and for a throwing afterAll or beforeAll, so a timeout and a throw send the same events, and the events arrive batched from the worker, which measured 212 ms for a hook with a 300 ms timeout Red: with the structured assertions, the case failed on the previous reporter, whose failure lines held only the Vitest error. Green: the reporter keeps the started and unended hooks of each file and suite and prints them before the Vitest errors of a failed one; the 17 cases of tests/build/run-tests.test.mjs pass, and the generator-svelte suites pass with 817 and 14 cases through the reporter — node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs [o]
C5.8-5 Raise jsdom of the root (exact) and of generator-react from 29.1.1 to 30.1.2, the release that form-binding already uses, so one major of jsdom runs in the tree. Red: npm ls jsdom listed jsdom 29.1.1 for the root, generator-react and Vitest and 30.1.2 for form-binding Green: npm ls jsdom lists jsdom 30.1.2 alone, npm install regenerated the lock with 12 packages fewer, and npm run test:forms passes with every workspace suite and the 5264 and 24 node cases, npm run test:inspector with 27, npm run test:dependencies with 13, and the type checks and lint pass — npm run test:forms, npm run test:inspector, npm run typecheck, npm run lint, npm run test:dependencies [o]
C5.8-6 Raise js-yaml of the root and of the cli from 4.2.0 to 5.4.2 and read YAML with its named load export. js-yaml 5 has no default export, and its default loader schema is the YAML 1.2 core schema instead of the schema of js-yaml 4 with timestamps and << merge keys: a date-like scalar stays a string and << is a plain key, which aligns the cli and the cross-check console with scripts/check-schema.mjs, whose yaml package reads the core schema; no tracked YAML uses <<. Cases of the cli (packages/cli/tests/yaml-schema.test.ts) and of the console (coerceSpec) pin the alignment. Red: with js-yaml 5 and the default imports, the cli and console suites failed with Cannot read properties of undefined (reading 'load'); js-yaml 4.2.0 reads 2026-01-01 as a Date and merges <<: {a: 1}, which the new cases refuse. Green: the cli suite passes with 41 cases, the cross-check console suite with 1226, and the form checks, the dependency checks, type checks and lint pass — npm test -w @crudui/cli, npm test --prefix examples/cross-check-console/server, npm run test:forms, npm run typecheck, npm run lint, npm run test:dependencies [o]
C5.8-7 Raise @types/node of form-binding, generator-core, generator-html, generator-react, generator-vue and validator-ts from ^25.9.9 to ^26.6.4, the major of the recorded Node.js 26.8.1 that runs the code, and require it: the new case of tests/build/runtime-version-policy.test.mjs fails for an @types/node range of another major than .node-version. Red: the case listed the 6 manifests with ^25.9.9. Green: the tree holds @types/node 26.6.4 alone, the 17 runtime cases pass, and the type checks, the build, lint, the dependency checks and the form checks pass — node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs, npm run typecheck, npm run build, npm run lint, npm run test:dependencies, npm run test:forms [o]
C5.8-8 Raise @sveltejs/package of generator-svelte from 2.5.8 to 3.0.0, the latest major known at its review. Its peer typescript ^6.0.0 is the TypeScript 6.0.3 that the root keeps (C5.8-3), which 2.5.8 already loaded for the declarations, so the TypeScript of the package output does not change. No case fails before the update; the evidence is the output: diff -r of the dist of generator-svelte built with 2.5.8 and with 3.0.0 found the 57 files identical. Green: the build, the generator-svelte suites with 817 and 14 cases, the type checks, lint, the dependency checks with 13 cases, the public package checks with 38 and the form checks pass — npm run build, npm test -w @crudui/generator-svelte, npm run typecheck, npm run lint, npm run test:dependencies, npm run test:build, npm run test:forms [o]
C5.8-9 Raise @testing-library/jest-dom of generator-react from 6.9.1 to 7.0.1, the major that carries the changes for which its publisher deprecated 6.10.0 (C5.8-2). Its peers are met by Vitest 5.0.3 and @testing-library/dom 10.4.1, and the suite imports @testing-library/jest-dom/vitest as before. No case fails before the update; the evidence is the same suite with the same 1088 cases passing on 7.0.1. Green: the generator-react suite passes with 1088 cases, and the type checks, lint, the dependency checks with 13 cases and the form checks pass — npm test -w @crudui/generator-react, npm run typecheck, npm run lint, npm run test:dependencies, npm run test:forms [o]
C5.8-10 Raise PHPUnit of validator-php and generator-php from 10.5.66 to 13.4.1, the latest major known at its review, and declare the 18 data providers with the attribute #[DataProvider(...)] instead of the @dataProvider annotation, which PHPUnit 12 removed. Red: with PHPUnit 13 and the annotations, the 12 data provider tests of validator-php and the 6 of generator-php failed without their arguments. Green: validator-php passes with 763 cases and generator-php with 311, the counts of PHPUnit 10.5 on main, so the TeamCity reader of scripts/run-tests.mjs reads PHPUnit 13 unchanged — composer --working-dir=packages/validator-php test, node scripts/run-tests.mjs phpunit --cwd packages/generator-php, node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs, npm run test:dependencies [o]
C5.8-11 Install one version of each npm package that one version satisfies: make dependency-review UPDATE=1 raised a workspace dependency with npm install --workspace, which keeps the release that the root installs and adds the raised release beside it, so one package was installed at two versions, as svelte 5.57.1 and 5.57.2; the case installed packages form one valid dependency graph ran only npm ls --all, which accepts two versions that both satisfy their ranges. updatePlan of scripts/dependency-review.mjs runs npm dedupe after its npm updates, and npmDuplicates of scripts/dependency-state.mjs reads package-lock.json, finds the copy that each package loads in the nearest node_modules upward and reports a package installed at two versions when one of them satisfies every range of the packages loading either copy; npmSatisfies evaluates the npm range forms of the lock. The lock installs magic-string 1.4.3 for @sveltejs/vite-plugin-svelte (^1.0.0) and the hoisted undici-types 8.9.0 for @types/jsdom (^8.9.0), and make dependency-review RECORD=1 recorded the lock without an advisory or a newer release without an exception. Red: with an npmDuplicates that reported no package, the case of the fixture lock with svelte 5.57.1 at the root and 5.57.2 in a workspace failed, as did the range case; the complete check reported magic-string 1.3.1 and 1.4.3 and undici-types 8.9.0 and 8.11.2 in the committed lock. Green: npm run test:dependencies passes with 26 cases — node scripts/run-tests.mjs node --timeout 60 -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs [o]
C5.9 Run every registered test of a node:test file under scripts/run-tests.mjs, or fail the file: the runner starts node --test with --test-force-exit, which ends the file's process when its known tests end (lib/internal/test_runner/test.js of Node.js 26.8.1: "It is theoretically possible that a ref'ed handle could asynchronously create more tests, but the user opted into this behavior"), and packages/php-ext/tests/engine.test.mjs registered tests after top-level awaits, so a later test ran only while an earlier one still ran. A synchronous case before the first await ran alone with 1 passed for the file, and with --test-name-pattern the file passed while no case ran; full runs, locally and in CI, ran every registered case. The runner preloads scripts/test-progress/load-check.mjs, which fails a file whose process ends before its module finished evaluating; the reporter fails a file that reports no case; the lint rule crudui/no-await-after-test-registration rejects a top-level await after the first registration, and engine.test reads its fixtures synchronously. Red: a file that registers a test after a 300 ms wait passed through the runner with 1 passed, a pattern that selects no case passed with 1 passed, and the lint rule reported the 9 waits of engine.test at lines 367, 448, 525, 917, 1492, 1672, 1795, 1904 and 2275. Green: the 12 cases of tests/build/run-tests.test.mjs, the 13 of tests/build/test-commands.test.mjs and the 39 of engine.test pass, npm run lint passes, engine.test with --test-name-pattern runs the selected case and fails with the file ran no test case for a pattern without a case, a test that leaves a handle open still ends its file, and npm run test:runtimes passes with 111 cases — node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs tests/build/test-commands.test.mjs packages/php-ext/tests/engine.test.mjs, npx eslint --max-warnings 0 packages/php-ext/tests/engine.test.mjs [o]
C5.10 Run every check of a Makefile target after an earlier check failed: make stops at the first recipe line that fails, so make docs-check-documents skipped the document checks, the documentation tests and the documentation build after a failing npm run manifest:check, and make format-check stopped at the first Rust crate that differs from rustfmt and skipped the other crates and gofmt. A recipe line that runs a check is the last line of its target and runs each check with || status=1, ending with exit $$status; preparation lines before it still stop the target, and a chain joined by && whose later steps read the result of the earlier ones, as in make docs-verify-idempotent, is one check. tests/build/test-commands.test.mjs fails for every other form. Red: the new case listed 5 lines of docs-check-documents that run only when npm run manifest:check passed, the gofmt and summary lines of format-check after its Rust loop, and format-check: a failing check ends the recipe with || exit. Green: the 19 cases of both files pass, and make format-check passes with [make] format-check: Rust crates and Go files are formatted — node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/make-tool-path.test.mjs [o]
C5.11 Compute the styles of both stylesheets of tests/tailwind-styles.test.mjs in one visible page per engine and width: the test opened a page with crudui.css and then a page with the Tailwind version in the same browser, so the first page was hidden in Chromium and Firefox, and under memory pressure a call on the hidden Chromium page waited on page-ins of its renderer past the 30 s timeout of a case. Measured on 2026-10-05 with 30 GB of 31.7 GB swap in use: with two pages, 19 of 4320 calls took over 1 s, up to 44.2 s with 98,227 page-ins during the call, all on the hidden page, whose cases otherwise take 4 to 70 ms; with one visible page, none of 8640 calls took over 1 s and the longest took 140 ms at a load average of 64 to 87. The page now holds both stylesheets and applies one with its media; each call fails unless the page is visible and only the requested stylesheet applies. Red: with the visibility check and two pages, the 432 cases of Chromium and Firefox at each width failed with the page is hidden, not visible. Green: the 2599 cases pass, and with both stylesheets applied the check fails with the stylesheets plain, layered apply instead of plain — node scripts/run-tests.mjs node -- tests/tailwind-styles.test.mjs [o]

Wave 6 — Pushes without a task in progress

ID Task Deliverables Verification State
C6.1 Refuse a push while a task of the checklist is in progress: the tracked pre-push hook .githooks/pre-push runs node scripts/push-gate.mjs hook, which reads the checklist of every pushed commit and of the working tree with activeItems of scripts/full-run.mjs and refuses the push with each task in progress, its ID and title, the reason and the remedy; a pushed commit without the checklist, a Git error and a parser error also refuse it. Every make run sets core.hooksPath to .githooks; make hooks installs and checks it and make hooks-check fails when it is not installed, and the guard of make ci refuses while it is not installed. The workflow .github/workflows/push-gate.yml runs node scripts/push-gate.mjs commit <sha> on every pushed commit and pull request, so a push past the hook still fails the job push-gate with the same message. Red: in a temporary checkout without the push check, git push of a commit whose checklist has a task in progress reached a bare remote with status 0; the 7 cases of tests/build/push-gate.test.mjs failed because scripts/push-gate.mjs did not exist. Green: the 7 cases pass: a push of a commit with a task in progress is refused with its ID, title, reason and remedy and the bare remote keeps no ref (R1); a clean push reaches it (G1); a task in progress only in the working tree refuses the push (R2); hooks-check fails while core.hooksPath is unset or the hook is not executable, and make -n help and make hooks install it (R3); a pushed commit without the checklist is refused (R4); commit <sha> exits 1 with ::error:: lines for a task in progress and for a hook that is missing or not mode 100755, and 0 for a clean commit. The 12 cases of tests/build/full-run.test.mjs pass, with the refusal of a checkout without the hook — node scripts/run-tests.mjs node -- tests/build/push-gate.test.mjs tests/build/full-run.test.mjs [o]
C6.2 Publish every change through a pull request and the merge queue. The ruleset main moves into .github/repository.json and make github-settings: it requires a pull request without approval (methods merge, squash and rebase), the merge queue with the method REBASE, a linear history and the checks of push-gate and of every job of .github/workflows/ci.yml, one per matrix entry, of integration 15368, and refuses a force-push and a deletion without a bypass actor; the settings add allow_auto_merge and delete_branch_on_merge. ci.yml runs on pull requests and merge groups and cancels only pull request runs, push-gate.yml also runs on merge groups and ignores the branches of the queue, and .github/workflows/pages.yml builds and deploys the documentation web of main. Red: with the previous declaration, script and workflows, 9 of the 15 cases of tests/build/github-repository.test.mjs and tests/build/ci-local.test.mjs failed: no ruleset was planned, the declaration held no merge queue, CI ran on a push to main, cancelled every run, deployed from ci.yml, and no Pages workflow existed. Green: the 15 cases pass, among them the required checks equal to the jobs of the push check and the CI workflow, an order of rules, checks and merge methods that is no difference, and a differing ruleset replaced by its id while another ruleset is kept; the CI, push check, runtime, command, owner and document unit tests pass — node scripts/run-tests.mjs node -- tests/build/github-repository.test.mjs tests/build/ci-local.test.mjs [o]
C6.2-1 Declare the triggers of each workflow exactly: the on: block of .github/workflows/ci.yml is pull_request, merge_group and workflow_dispatch, of .github/workflows/push-gate.yml push with branches-ignore: ['gh-readonly-queue/**'], pull_request and merge_group, of .github/workflows/pages.yml push to main and workflow_dispatch, and of .github/workflows/dependency-review.yml its schedule and workflow_dispatch; no other workflow exists. The ruleset main of .github/repository.json requires push-gate and every job and matrix entry of ci.yml. The operations documents state the triggers. Red: the new case of tests/build/ci-local.test.mjs failed for an added workflow. Green: the cases of tests/build/ci-local.test.mjs and tests/build/github-repository.test.mjs pass — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/github-repository.test.mjs [o]

Wave 7 — Runs that give the same result for the same tree

ID Task Deliverables Verification State
C7.1 Every test that asserts the output of make -n runs make --no-print-directory -n through one helper whose environment sets MAKEFLAGS=w and removes MAKELEVEL, GNUMAKEFLAGS and MAKEFILES, so GNU Make 4 under a sub-make prints the same commands as GNU Make 3.81; a static case fails for a dry run of make outside the helper. Cause: tests/build/full-run.test.mjs asserted the first line and the line count of make -n ci, and GNU Make 4 under a sub-make adds Entering directory lines Red: with GNU Make 4.4.1 first on PATH and MAKELEVEL=1, the case make ci and make rerun-failed start the guard before any step failed with make[1]: Entering directory as the first line of make -n ci, and the new static case would list tests/build/full-run.test.mjs:33 and tests/build/push-gate.test.mjs:111. Green: tests/build/make-dry-run.mjs runs every dry run, the 22 cases of the three files pass with macOS make 3.81 and with GNU Make 4.4.1 under MAKELEVEL=1, and npm run test:runtimes lists the new file — node scripts/run-tests.mjs node -- tests/build/full-run.test.mjs tests/build/push-gate.test.mjs tests/build/make-dry-run.test.mjs [o]
C7.2 Record exact toolchain versions and require the running tools to equal them: the specification replaces the release channels by exact versions; .node-version 26.8.1, .go-version 1.27.0 with a toolchain line in every go.mod and GOTOOLCHAIN=local, rust-toolchain.toml 1.98.1 with its components, RUSTUP_AUTO_INSTALL=0 exported by the Makefile and set in CI, make install with rustup toolchain install --no-self-update, exact PHP and Composer releases with a check of the running releases, container stages by digest, actions by commit SHA, ubuntu-24.04 runners, the Linux style check with an exact Node.js archive and its checksum; scripts/run-contract-tests.mjs runs sh -c instead of a login shell. Cause: the specification selected release channels, so the toolchain of a run depended on its date The PHP stages of the containers and the running PHP move with C7.3 and C7.2-1. Red: the new cases of tests/build/runtime-version-policy.test.mjs, run over the previous tree with the new records copied in, failed 10 of 15: .node-version held 26, no go.mod had a toolchain line, the Makefile exported neither RUSTUP_AUTO_INSTALL nor GOTOOLCHAIN and had no install, the workflows ran on ubuntu-latest with actions by major tag and dtolnay/rust-toolchain@stable, no job checked its tools, the container stages had no digest and read the live Debian archive, the Linux style check fetched latest-v26.x, firefox@stable and the Chrome of the runner, and the contract runner ran sh -lc. Green: the 15 cases pass, with node 26.8.1, npm 12.2.0, go 1.27.0, rustc 1.98.1 and Composer 2.10.3 running at their records, a stub version run failing with the record, the expected and running version and the fix, and make -n install printing rustup toolchain install --no-self-update; the 65 cases of the runtime, CI, browser, contract and command files pass, all six Go modules list under GOTOOLCHAIN=local, node scripts/install-browsers.mjs chrome firefox installed Chrome 154.0.8037.57 and Firefox stable_156.0.1, and the 2598 cases of tests/viewport.test.mjs pass in the pinned Chrome and Firefox and in WebKit — node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs [o]
C7.2-1 Require the running PHP to be the release that config/toolchain.json records, as the other tools: add php to the tools of the case every tool runs at the version that the checkout records. Cause of the bypass: the machine runs Homebrew PHP 8.5.10, which every repository of the machine shares, while CI's setup-php and the PHP releases of the registries deliver 8.5.11. Retry when php -r 'echo PHP_VERSION;' prints the recorded 8.5 release on the machine Performed by C7.2-3: the patch pin is gone, PHP is pinned by its minor, and the running PHP check covers PHP. — node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs [o]
C7.2-2 The toolchain image test of the form comparison accepts the stages of the recorded releases by digest. Cause: C7.2 named the stages of examples/form-comparison/Containerfile by exact tag and digest, and examples/form-comparison/check-toolchain.test.mjs, which owns that file, still required golang:1.27-trixie, rust:1-slim-trixie and node:26-trixie-slim; C7.2 was committed without running it. Red: builds one toolchain image without repository source failed on the three FROM lines. Green: the case requires the three stages as exact releases with a digest, and the 16 cases of check-toolchain.test.mjs and check-deployment.test.mjs pass — node scripts/run-tests.mjs node -- examples/form-comparison/check-toolchain.test.mjs examples/form-comparison/check-deployment.test.mjs [o]
C7.2-3 Pin PHP by its minor release, as every polyspec repository does: config/toolchain.json records the tested minors 8.4 and 8.5, node scripts/check-toolchain.mjs compares the major and minor of the running PHP with them and names the expected and the actual release, and the patch that a run ran on is evidence: the check prints the running releases and var/full-run.json records them. The container images keep their exact tags with digests, which reproduce by digest. Cause: setup-php and Homebrew cannot install the same patch, so an exact patch record failed on one of them (C7.2-1) Red: on the previous code, the new full-run case found no toolchain in the record, and the runtime file failed to load without toolchainVersions, with config/toolchain.json recording 8.4.26 and 8.5.11. Green: the 46 cases of the runtime, full-run, push and npm files pass: the running PHP 8.5.10 of the machine passes as minor 8.5, another patch of a recorded minor passes, 8.3.30 fails with records 8.4 or 8.5, the running releases are reported, the run record and each rerun hold them, and the container stages 8.5.11-cli-trixie and 8.5.11-fpm-trixie with their digests are exact releases of minor 8.5; node scripts/check-toolchain.mjs node npm go rust php composer prints php 8.5.10 and passes — node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs tests/build/full-run.test.mjs [o]
C7.2-4 Install the Chrome sandbox helper with one install command: sudo install -o root -g root -m 4755 <chrome_sandbox> /usr/local/sbin/chrome-devel-sandbox, and the same without sudo as root. Cause: CI failed in every browser job at the step that installs the browsers that Puppeteer pins, with install: target '/usr/local/sbin/chrome-devel-sandbox': No such file or directory: scripts/install-browsers.mjs of C7.2 put install twice into the command (sudo install install -o root ...), so install read three operands and took the helper path as a directory; Chrome then aborted with The SUID sandbox helper binary is missing Whether the set-user-ID sandbox of that Chrome passes the preflight of the runner shows only in the next CI run. Red: the 2 new cases of tests/build/install-browsers.test.mjs failed: the command was sudo install install -o root ..., and a sudo stand-in that runs install with the operands of a correct command failed with install: 4755: No such file or directory, as CI failed. Green: the 3 cases pass: the browsers are the pinned builds, the helper command is sudo install -o root -g root -m 4755 and install -o root -g root -m 4755 as root, and the stand-in wrote the helper file with mode 4755 — node scripts/run-tests.mjs node -- tests/build/install-browsers.test.mjs [o]
C7.3 A run that executes no test case or no command fails: close() of scripts/test-progress/progress.mjs fails a go, cargo, phpunit or vitest run with ran no test case, the PHP container check reads the php8.N- package names and fails on an empty list, the containers install PHP 8.5, and scripts/run-contract-tests.mjs fails when it runs no command. Cause: those runs and the container check passed while they checked nothing Debian has no package of PHP 8.5, and an apt pin of another archive keeps only its latest build, so both images take PHP from the php image of the recorded 8.5.11 by digest (cli for the native check, fpm for the comparison, whose php-fpm is in /usr/local/sbin), Composer from the composer 2.10.3 image and Node.js from its stage; PHP_EXTENSION_PHP_CONFIG and the comparison builds use /usr/local/bin/php-config, and the php-fpm link is gone. Red: the new cases of tests/build/run-tests.test.mjs failed on the previous code: close of a run without a test case returned ok, and run-tests.mjs go over a module without tests passed; the contract runner case expected status 1 and got 0 for a selection without a command; the rewritten container case listed the 9 php8.4-* packages of both definitions and their Debian Composer, while the previous case passed over an empty list of FROM php: stages. Green: the 14 cases of run-tests.test.mjs, the 8 of run-command.test.mjs, the 16 of runtime-version-policy.test.mjs, the 16 of the comparison image and deployment tests, the build target tests and the 38 of packages/php-ext/tests/engine.test.mjs pass — node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/contract-manifest.test.mjs [o]
C7.4 Run every independent check of a package script and of the contract runner after an earlier one failed, and keep a chain whose later step reads the output of the earlier one as one check; the rule test of C5.10 covers the scripts of package.json and scripts/run-contract-tests.mjs. Cause: test:forms, test:form-comparison, test:form-comparison:pipeline, test:form-comparison:source, test:bench, the test script of generator-svelte and the CI step test:build && test:build:repeat chain independent suites with &&, and the contract runner exits at its first failed command test:bench (require-current-build, the driver build, the driver test) and test:build:repeat (two builds, then the test that reads their records) are build-then-test chains with one check. Red: the new case of tests/build/test-commands.test.mjs listed 25 violations: the chained checks of test:forms, test:form-comparison, test:form-comparison:build, test:form-comparison:pipeline and docs:check:all, the test of generator-svelte, the typecheck of form-binding and the CI step npm run test:build && npm run test:build:repeat; the new case of run-command.test.mjs failed because the runner stopped after a/fails and the second command never ran. Green: the scripts run each check with || status=1 and exit with the collected status after require-current-build || exit 1 where they build, test:form-comparison:build is removed and each form comparison suite builds what it reads, CI and make ci run test:build and test:build:repeat as two commands, and the runner runs every command and fails after the last; the 80 cases of the command, contract, CI, comparison, runtime and guard files pass, the four changed root scripts parse with sh -n, the form-binding type check passes and the generator-svelte suites pass with 817 and 14 cases — node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/contract-manifest.test.mjs [o]
C7.5 Every check reads only state that it or its declared preparation creates: make test-php-extension reinstalls the validator copy of generator-php, test:build runs require-current-build, reproducible-build.test builds its own record, and scripts/run-rust-command.mjs refuses a CARGO_TARGET_DIR outside the checkout. Cause: those checks read output that another command left behind Red: the new case of tests/build/php-extensions.test.mjs named test-php-extension, whose api.test.mjs loads packages/generator-php/vendor without a reinstall; the new case of test-commands.test.mjs failed because test:build ran public-packages.test.mjs, which imports every package through its exports, without a build; the previous reproducible-build.test.mjs read node_modules/.cache/crudui/repeat-build/*.json, which only repeat-build.mjs wrote, and on the previous tree the new reproducible build file failed to load without buildDifferences; the new Rust case failed because runRustCommand had no refusal and ran cargo with a CARGO_TARGET_DIR in another temporary directory. Green: test-php-extension reinstalls crudui/validator before its tests, test:build runs require-current-build first, repeat-build.mjs compares its own two builds and fails with each differing, missing or added file and its digests, its test checks the package list and the comparison without a build, and run-rust-command.mjs refuses a target outside the Git working tree of its directory (or outside the directory itself outside Git) before any tool runs; the 29 cases of the four files pass and node scripts/repeat-build.mjs built twice and found the 329 output files equal — node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/reproducible-build.test.mjs tests/build/rust-node-entry-points.test.mjs [o]
C7.6 Publish shared outputs atomically: the PHP extension builder links a per-process name and renames it to crudui.so, the ordered-json source checkout goes to a temporary directory that is renamed into place, the build stamp of require-current-build and the record of repeat-build are written to a temporary file and renamed, and readers of a package dist take its lock. Cause: a reader could see a partly written module, checkout, stamp, record or dist repeat-build.mjs keeps no record since C7.5, so it publishes nothing. Red: the new builder case failed on the previous builder, which linked -o modules/sample.so in place after removing .build and modules as generated paths; both OrderedJSON cases failed on the previous checkout, which ran Git in directory after removing it; the previous package builds ran tsup --clean and svelte-package into dist, so a reader saw an emptied dist for the whole build, and the new fixture build had no CRUDUI_DIST under the previous package-dist.mjs. Green: the builder compiles into .build/run-<pid>, links <module>.so.<pid>, loads and checks it and renames it, and its manifest is renamed into place; the OrderedJSON checkout is made in <directory>.next-<pid> and renamed, and a failed checkout leaves the previous one; every package build writes into dist.next and package-dist.mjs replaces dist with it under the lock, keeping dist after a failed build; the stamp of require-current-build is written and renamed. The 8 builder cases, 2 OrderedJSON cases, 7 shared resource cases, 2 atomic publication cases and the related command, lock, pack, lint and Vite cases pass, node scripts/build-crudui-php-extension.mjs built and loaded crudui.so and left only build.json in .build, npm run build left no staging directory and no dist.next path in the output, and the 10 public package cases pass on that build — node scripts/run-tests.mjs node -- tests/native-generators/php-extension-builder.test.mjs tests/build/atomic-publish.test.mjs [o]
C7.7 Install npm into the checkout and never into the machine: scripts/install-npm.mjs runs npm install --prefix .tools/npm npm@<recorded> into a temporary directory that it renames to the ignored .tools/npm, keeps an installed recorded release, checks the installed release, and never runs npm install --global; the Makefile exports PATH with .tools/npm/node_modules/.bin first, every script that starts npm puts it first with useCheckoutNpm of scripts/checkout-npm.mjs, and every CI job adds it to GITHUB_PATH before its first npm command. Cause: scripts/install-npm.mjs ran npm install --global npm@12.2.0, which replaced the npm of every repository of the machine and broke the npm ci of another repository GNU Make 3.81 looks up the program of a recipe line without shell syntax on the PATH that make started with, so every recipe starts npm as $(NPM), the path in the checkout. Red: the previous scripts/install-npm.mjs ran ['install', '--global', ...] at line 40 and exported no installNpm, so the new file failed to load; the new cases listed the six scripts that start npm without useCheckoutNpm, the first PATH entry of make was ~/.local/bin, and a probe recipe npm --version under make 3.81 printed the machine npm 11.19.0 while the exported PATH held 12.2.0. Green: the 9 cases of tests/build/checkout-npm.test.mjs pass: a stub npm installs into .tools/npm through a temporary directory that is renamed, an installed release is kept, a wrong release fails with the expected and the installed one and leaves nothing, no script, make target or CI step installs npm globally, make puts .tools/npm/node_modules/.bin first on PATH, no recipe starts a bare npm, a recipe $(NPM) --version under make 3.81 prints 12.2.0, and every CI job adds the path to GITHUB_PATH before its first npm command; node scripts/install-npm.mjs installed 12.2.0 into .tools/npm while the machine npm stayed 11.19.0; the 46 cases of the runtime, CI, command and dry-run files and the 54 of the changed scripts' tests pass with the checkout npm first on PATH — node scripts/run-tests.mjs node -- tests/build/checkout-npm.test.mjs tests/build/runtime-version-policy.test.mjs [o]
C7.7-1 The tests that run a copy of a script that starts npm copy scripts/checkout-npm.mjs with it. Cause: C7.7 made check-doc-coverage.mjs, gen-api-docs.mjs and require-current-build.mjs import scripts/checkout-npm.mjs, and tests/build/rust-node-entry-points.test.mjs and tests/build/run-command.test.mjs copied those scripts into temporary checkouts without it. Red: 1 case of rust-node-entry-points and 2 of run-command failed with ERR_MODULE_NOT_FOUND for scripts/checkout-npm.mjs. Green: the 13 cases of both files pass — node scripts/run-tests.mjs node -- tests/build/run-command.test.mjs tests/build/rust-node-entry-points.test.mjs [o]
C7.8 Every failure names what failed and why: a timeout prints the command, its limit and the elapsed time, run-tests.mjs reports a tool that cannot start with its path and the install command, Rust file reads name the path, and a Go package without tests is not counted as passed. Cause: those failures printed no command, no path or a pass The other limits name their command or check, the limit and the elapsed time as well: the native generator budget, the form comparison units, the deployment requests, the validator processes of the cross-check console, the PHP mode checks and the commands of the package install check. A read of standard input in the native Rust program maps a non-UTF-8 request to the protocol error, which is its contract, so the rule covers file reads (fs::read_to_string). Red: the three new cases of tests/build/run-tests.test.mjs failed: a Go package that ran no test printed ✔ empty, the timeout line named neither the command nor the elapsed time, and a missing vendor/bin/phpunit crashed the runner on an unhandled error event; the new case of tests/build/failure-messages.test.mjs lists the 7 file reads of the previous tree that unwrap, expect a fixed text or drop the error, in 6 files. Green: a package without a started test prints ○ <package>: ran no test case and is not counted, the timeout line reads ⏱ <test> ran <elapsed> and exceeded its <limit> timeout; stopping followed by <command>, a tool that cannot start prints cannot start <path>: <error>; fix: <install command>, and every Rust file read panics or logs with read <path>: <error>; the 17 run-tests cases, the Rust read case, the 7 unit pool cases and the 1225 cases of the cross-check console suite pass, and cargo check --tests passes for the four changed crates — node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs tests/build/failure-messages.test.mjs examples/form-comparison/src/unit-pool.test.mjs [o]
C7.9 Add the owner check: scripts/owner-check.mjs selects the checks of scripts/owner-checks.json whose declared inputs match the changed paths, fails for a changed path without an owner, skips a removed path that no rule owns, and AGENTS names it as the check before a commit. Cause: the owning tests of a change were chosen by hand, so a change could be committed without the checks that read it The checks are make targets, root npm scripts, the test scripts of workspaces and package directories, and node test files; inputs names the paths that make crudui-test-validators, make crudui-test-native, npm run test:forms, npm run test:form-comparison, npm run test:bench, the cross-check console and make docs-check read. Building the declaration found tests/ordered-json/test_check.py run by no project command, which now runs as make test-ordered-json (its Python release is C7.12), and make install did not install phpDocumentor, which make docs-check reads; it now runs scripts/install-phpdocumentor.sh. Red: tests/build/owner-check.test.mjs failed to load without scripts/owner-check.mjs. Green: the 9 cases pass: a path runs exactly the checks of its rules, every check runs after one failed, a tracked or new path without an owner fails before any check, a removed path selects nothing and a removed test is not run, a glob without a path, a full-suite target, an unknown script and a missing test fail with their names, an input that no rule of its path selects fails and a prerequisite target counts, and the declaration of the repository validates; make owner-check over this change ran make docs-check, npm run test:dependencies and 10 test files, and passed after phpDocumentor was installed — node scripts/run-tests.mjs node -- tests/build/owner-check.test.mjs [o]
C7.10 killProcessTree of the form comparison step runner waits until every descendant has ended. Cause: it sent SIGKILL to the descendants and returned before they ended, so a later step could meet a process of the stopped one The event of the end is the close of the step's output: a pipe closes when every process that holds it has ended, and a killed descendant outside the tree has no other event that this process can receive; when the output stays open for the grace after SIGKILL, the stop fails and names the process that still holds it. The local server case read a FIFO whose open blocks a thread of the runner until a writer opens it, so a stand-in that never started held the file beyond its timeout; it now waits for the end of a Unix socket connection that the stand-in opens. Red: the new case of examples/form-comparison/src/step-runner.test.mjs failed in four of four runs with run 0: the output of the tree had not ended when the stop resolved, because the stop returned right after it sent SIGKILL to a grandchild that left the group and ignored SIGTERM. Green: killProcessTree sends SIGKILL to the recorded tree and the group, also for a process that already exited with open output, and resolves on close; the 9 step runner cases pass in three runs, with ten stopped trees each whose output ended and whose grandchild runs no more, and the 3 local server cases and the 34 cases of the verification, response and startup tests pass — node scripts/run-tests.mjs node -- examples/form-comparison/src/step-runner.test.mjs [o]
C7.11 Remove the races of shared resources: the local servers of the form comparison bind port 0 and announce their address on their ready line, composer reinstall and scripts/full-run.mjs take the checkout lock, and the partial conformance record has a per-process name. Cause: a free port could be taken between its probe and its use, and two runs could reinstall or record at once The partial conformance record of tests/conformance/runs.mjs already had a name of its process (<pid>-<12 hexadecimal digits>.json.partial), so it is unchanged. Every server program (Go, Rust, the JavaScript record server, the public server and the PHP launcher) binds its address, port 0 included, and prints CRUDUI_READY {server} {host}:{port} with the address it took; the PHP launcher binds the address itself and passes the socket to nginx as file descriptor 3 named by NGINX; startProcess reaches a server on the address of its line, and the local stacks start every server on 127.0.0.1:0 and the public server with the announced ports; the supervisor expects the exact address of its fixed ports. make test-php-extension, make test-native-suites, the local record servers and the comparison build target reinstall the generator-php vendor under the checkout lock composer-generator-php; the guard of make ci holds the checkout lock full-run while it reads, decides and writes its record, and the lock lines of scripts/holder-lock.mjs print through the progress lines. Red: on the previous code the new full-run case ran the target while another holder held the lock, the launcher case failed binding 127.0.0.1:1, the static case listed the unlocked reinstalls of the Makefile, local-servers.mjs and build-targets.mjs, and freePort probed a port, closed it and gave it to a later process. Green: the 20 guard and push cases, the 6 PHP extension and build target cases, the 4 local server cases, the 2 launcher cases with the inherited socket, the server layout, response and startup cases, the shared resource and lock cases pass; record-stores.test.mjs passed with 130 cases, where nginx logged using inherited sockets from "3;" and every server was reached on its announced port, and pipeline.browser.mjs passed with 47 cases — node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs tests/build/full-run.test.mjs examples/form-comparison/src/local-servers.test.mjs [o]
C7.12 Record the Python release of the processor checks of tests/ordered-json and require the running Python to be it, as for the other tools, and run make test-ordered-json in CI. Cause: the owner declaration of C7.9 found tests/ordered-json/test_check.py run by no project command; C7.9 gave it the target make test-ordered-json, which runs the Python of the machine (3.9.6 here) at no recorded release. Python is pinned by its minor version, as in ssr and ordered-json: config/toolchain.json records "python": "3.9", node scripts/check-toolchain.mjs python compares the major and minor of python3 --version and prints the patch, the build-lint job installs it with actions/setup-python by commit SHA with python-version: '3.9', checks it and runs make test-ordered-json, which make ci runs after npm run typecheck. Red: on the previous tree the runtime case failed because config/toolchain.json recorded no python, and no workflow step set up Python or ran the ordered-json tests. Green: the 50 cases of the runtime, CI, command and full-run files pass, with the machine's Python 3.9.6 accepted as minor 3.9, another patch accepted, 3.1.4 refused with the record, and the workflow step checked against the record; make test-ordered-json ran 5 tests — node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs [o]
C7.13 Find and remove the cause of the stall in which the stub programs of tests/build/checkout-npm.test.mjs and tests/build/install-browsers.test.mjs did not finish for 30 s. Evidence: in one make owner-check of C7.2-4 on 2026-10-06, the two install-npm cases each timed out after 30000 ms while their npm stub ran, and the helper case of crudui-install-browsers, which ran at the same time, ended after 30.1 s, just after the first timeout; four runs of the same files afterwards passed in 3 to 7 s. Hypothesis: macOS checks a newly written executable on its first execution and can take long for it, as template found for new build outputs (T19.6-1); the stubs are new shell scripts in a temporary directory. Result: the stall did not recur. The first executions that C7.13-1 logs took 5 to 81 ms in the 12 CI runs on Linux from C7.13-1 to C7.18 and 338 to 979 ms in 3 local runs on macOS on 2026-10-06, all 12 cases passing in 2.0 to 2.5 s: macOS spends up to about a second on the first execution of a new shell script, which supports the hypothesis, and no logged first execution approached the 30 s of the stall, so no cause of a 30 s stall is observed to remove. The logging of C7.13-1 stays, so a recurrence names its elapsed time and gets a new task. Green: the 12 cases of both files pass with the logged times above — node scripts/run-tests.mjs node -- tests/build/checkout-npm.test.mjs tests/build/install-browsers.test.mjs [o]
C7.13-1 Log the elapsed time of the first execution of each stub of tests/build/checkout-npm.test.mjs and tests/build/install-browsers.test.mjs as [stub] the first execution of <path> ended in <ms> ms on standard error, so a stall of C7.13 explains itself. Green: the 12 cases pass and print 1209 ms and 842 ms for the two npm stubs and 905 ms for the sudo stand-in, a cost of the first execution of a new shell script that supports the hypothesis of C7.13 — node scripts/run-tests.mjs node -- tests/build/checkout-npm.test.mjs tests/build/install-browsers.test.mjs [o]
C7.14 Every check that walks the tree reads the files of the checkout, tracked or new and not ignored by Git (scripts/tracked-files.mjs), and ESLint ignores every path that Git ignores. Cause: npm run lint linted a copy of a dist that C5.8-8 had put under the ignored var/ and failed, because the ESLint configuration ignored a hand-kept list of output directories; the contract manifest check, the runtime, public package, Vite and PHP extension tests walked the tree with their own lists of skipped directory names, so a stray file under an ignored directory changed their results scripts/tracked-files.mjs lists the files of a checkout with git ls-files --cached --others --exclude-standard and the ignored paths with --ignored --directory; eslint.config.mjs ignores exactly the ignored paths instead of its hand-kept list, and the contract manifest check, the container and Composer manifest lookups of the runtime test, the package command, Vite and PHP extension tests and the source digests of the native report read the tracked files; the dist digests of the native report and the documentation build read build output by design. Red: the new lint case found .tools/, var/ and a stray var/lint-coverage-stray/Actions.svelte.d.ts not ignored by ESLint, and npm run lint failed on a copy of a dist under var/ (C5.8-8); the previous tree held six walkers with lists of skipped names. Green: the 3 lint coverage cases, the 2 tracked-files cases and the 49 cases of the runtime, package, Vite, PHP extension, manifest and lint files pass, npm run manifest:check passes, and npm run lint passes with a copy of the generator-svelte dist under var/ — node scripts/run-tests.mjs node -- tests/build/lint-coverage.test.mjs tests/build/tracked-files.test.mjs [o]
C7.15 Development runs unit tests only, and CI runs every other check after the push: AGENTS required make owner-check before every commit and the full suite once before a push, and docs/operations/testing.md ran make owner-check before a commit and the Linux style check before a push, so a change waited for local end-to-end checks that CI runs on every push. AGENTS and docs/operations/testing.md state that development runs the unit tests that own the change (its Red and Green cases), that end-to-end checks (browsers, containers, the form comparison, the native and cross-check suites, full builds), make owner-check and make ci run in CI after the push, and that no rule requires a local check before a push or a commit; the pre-push hook stays the check that refuses a push while a task is in progress, and make ci and make owner-check remain available on request. Red: AGENTS named make owner-check before every commit in its development and idempotency rules, and the testing procedure named it before a commit. Green: no rule of AGENTS or the testing procedure requires a local check before a commit or a push, and the document checks pass — node scripts/check-documents.mjs, node scripts/run-tests.mjs node -- scripts/checklist-markers.test.mjs scripts/documentation-links.test.mjs [o]
C7.15-1 Describe the pre-push hook and the documentation job of C7.15 and C7.16 without the word that tests/docs/repository-writing.test.mjs refuses as an acceptance-check metaphor: the rows named the hook and the documentation job with it, and the documentation job of CI failed on C7.16; the Red and Green of those rows ran scripts/check-documents.mjs and the checklist tests, not npm run test:docs, which reads the checklist too. The rows name them a check. Red: the case named lines 130 and 131 of both checklists. Green: the 3 cases of tests/docs/repository-writing.test.mjs pass — node scripts/run-tests.mjs node -- tests/docs/repository-writing.test.mjs [o]
C7.16 Run the checks offline, and download only in the install targets: the recipes ran cargo, go, npm and Composer with the network, so a missing crate, module or package was downloaded by whichever check needed it first, and the comparison pipeline fetched the OrderedJSON checkout from GitHub when it was missing. The Makefile exports CARGO_NET_OFFLINE=true, GOPROXY=off, npm_config_offline=true and COMPOSER_DISABLE_NETWORK=1, and only install, crudui-install-crates, crudui-install-ordered-json, install-cargo-audit and dependency-review lift them with $(ONLINE). make crudui-install-crates downloads the crates of every Cargo.lock after the OrderedJSON checkout (examples/form-comparison/install-ordered-json.mjs), which the lock of the Rust record server reads; make install runs it, and the CI jobs of the documentation checks and the native packages run it before their make targets, the pipeline job runs make crudui-install-ordered-json. Every target that runs cargo depends on cargo-downloads-check (scripts/check-cargo-downloads.mjs), which fails with each lock, the first error line of cargo and run make install, which downloads them; the pipeline fails for a missing checkout with run make crudui-install-ordered-json and downloads nothing; cargo-audit moves from make install to make install-cargo-audit, a prerequisite of make dependency-review. Audit: the Go modules require only modules of the checkout through replace; make ci and make rerun-failed start the guard before any step, and their commands that run cargo through make depend on the check. Red: tests/build/offline-checks.test.mjs found no offline export and named 17 targets that run cargo without the check (bench, bench-go, bench-js, bench-php, bench-rust, ci, conformance, docs-api, docs-check-documents, docs-check-libs, docs-dev, docs-preview, docs-web, format-check before fmt was told apart, owner-check, test-native-suites, crudui-test-validators); with an empty CARGO_HOME, node scripts/check-cargo-downloads.mjs named the 6 locks and run make install. Green: the 3 cases of tests/build/offline-checks.test.mjs pass, and the CI, make, owner, full-run and form-comparison unit tests pass — node scripts/run-tests.mjs node -- tests/build/offline-checks.test.mjs tests/build/ci-local.test.mjs tests/build/full-run.test.mjs tests/build/make-dry-run.test.mjs [o]
C7.16-1 Install the packages of the install project of npm run test:packages offline from the releases of the root lock: with the offline settings of C7.16, npm install of the install project failed in CI on C7.18 with ERESOLVE and react@undefined, because it resolved the ranges of the packed packages and the framework versions against the registry, which also let the installed releases change from day to day. scripts/install-lock.mjs derives the install lock from the root package-lock.json: the packed packages as file: tarballs with their integrity, and the entry of the root lock that npm resolves for each registry dependency, from the root for a direct dependency and from the workspace directory for a dependency of a packed package, whose nested entries move under the package; scripts/check-packages.mjs writes it and installs with npm ci --offline from the npm cache that make install fills. Red: the CI job named npm error code ERESOLVE and Found: react@undefined for npm install; the lock generator did not exist. Green: the 3 cases of tests/build/install-lock.test.mjs pass, the generator derives 128 entries from the root lock without a missing dependency, and CI runs make crudui-test-packages on the pushed commit — node scripts/run-tests.mjs node -- tests/build/install-lock.test.mjs [o]
C7.17 Run every CI step through a make target: the steps of .github/workflows/ci.yml, dependency-review.yml and push-gate.yml started node, npm, composer, rustup and sh directly (node scripts/install-npm.mjs with GITHUB_PATH, npm ci, npm run test:runtimes, npm run build, npm run lint, npm run typecheck, node scripts/check-toolchain.mjs, rustup toolchain install and the others), so the offline settings, $(NPM) and cargo-downloads-check of the Makefile (C7.16) did not apply to them. The Makefile declares the install targets (install-npm, install-node-modules, crudui-install-composer, install-rust, crudui-install-phpdocumentor, crudui-install-browsers with BROWSERS), toolchain-check with TOOLS, check-ci-browser, push-gate-check and one target for each checking command (crudui-test-runtimes, crudui-test-dependencies, build, lint, typecheck, crudui-test-validator-js, crudui-test-validator-php, crudui-test-validator-go, crudui-test-validator-rust, crudui-test-cross-check, manifest-test, require-build, crudui-test-cli, manifest-check, manifest-docs-check, crudui-test-forms, crudui-test-form-comparison, crudui-test-form-comparison-pipeline, crudui-test-packages, crudui-test-build, crudui-test-build-repeat, crudui-test-inspector, crudui-test-bench, check-conformance); make install runs the install targets; CI_COMMANDS names the targets, and the jobs that run cargo run make crudui-install-crates first. A case of tests/build/ci-local.test.mjs fails for a step that starts node, npm, npx, cargo, go, php, composer, rustup, python3 or sh without make, also after sudo, env or xvfb-run and in a step of several lines; the checks of the workflow tests read the make forms. Red: on the previous workflows the case named 90 commands in 83 steps. Green: the case passes for the three workflows, and the CI, runtime, offline, command, full-run, form-comparison and npm unit tests pass — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/checkout-npm.test.mjs [o]
C7.18 Leave the reason of every failure of CI: no job kept the output of a check beyond the job log, and only the conformance evidence and the native report were uploaded. make ci-targets TARGETS="..." (scripts/ci-targets.mjs, scripts/target-report.mjs) runs each target as make -k <target> to its end, writes its output to var/report/ci-targets/targets/<target>.log and summary.md with each target, its result and time and the first failure lines of each failed target, also to the job summary of GitHub Actions; every job of ci.yml, dependency-review.yml and push-gate.yml runs its checks in one such step and uploads the report as report-<job> under if: ${{ !cancelled() }} with if-no-files-found: error. The PHP extension configuration of the cross-check and native jobs moves to the job environment. A case of tests/build/ci-local.test.mjs fails on a job that runs a check outside make ci-targets or uploads no report, and tests/build/target-report.test.mjs runs a failing and a passing probe target and finds both logs, the failure line in the summary and the job summary. Red: on the previous workflows the case named 30 findings in 16 jobs. Green: the case passes for the three workflows, the 2 report cases pass, and the CI, command, runtime and owner unit tests pass — node scripts/run-tests.mjs node -- tests/build/target-report.test.mjs tests/build/ci-local.test.mjs [o]
C7.19 A new push to a ref stops the CI run of its previous push, because the runners are few: .github/workflows/ci.yml declares concurrency with the group ${{ github.workflow }}-${{ github.ref }} and cancel-in-progress: true, and the push check of .github/workflows/push-gate.yml declares none, so it checks every pushed commit. The workflow named its group with a ci- prefix and no test held the setting. Red: the new case of tests/build/ci-local.test.mjs failed with the group ci-${{ github.workflow }}-${{ github.ref }}. Green: the 7 cases pass — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs [o]
C7.20 Remove the comparison deployment and every dependence on containerctl, a container tool of a development machine: make deploy, make deploy-verify and make deploy-watch, the Compose definition of examples/form-comparison/comparison-deployment.mjs, the supervisor, the toolchain image examples/form-comparison/Containerfile, the source watcher, the tree verification (verification.mjs, verify-tree.mjs and the checks that only it ran: check.mjs, check-interaction.mjs, check-generation.mjs, check-servers.mjs, check-pipeline.mjs, check-browser-reports.mjs, test-php-modes.mjs), the container runtime resolution, the build targets, scripts/install-ordered-json-js.mjs, their tests and docs/operations/verification.md. The comparison tests keep their standard: make crudui-test-form-comparison and make crudui-test-form-comparison-pipeline start every record server as a local process on a free port of 127.0.0.1, compare the servers and stop them; src/server-layout.mjs keeps the request routing of the public server, src/source-tree.mjs the source identity, docs/operations/form-comparison.md and docs/operations/examples.md the record resource, the canonical page, the canonical flow check and the validator response contract, and docs/operations/testing.md the description of the form checks. A case of tests/docs/repository-writing.test.mjs fails for a tracked file outside the checklists and the change logs that names a container tool of a development machine. Cause: the deployment ran only on a machine with containerctl, and no CI job ran it. Red: the new case named 57 lines in 10 files. Green: the case passes, and the form-comparison source suite passes 114 tests — node scripts/run-tests.mjs node -- tests/docs/repository-writing.test.mjs examples/form-comparison/src/local-servers.test.mjs examples/form-comparison/src/server-layout.test.mjs examples/form-comparison/src/source-tree.test.mjs examples/form-comparison/src/browser-job.test.mjs [o]
C7.20-1 Run the checks that C7.20 removed with the deployment against a local stack in CI: examples/form-comparison/local-verification.mjs builds the record server programs and the public directory, starts the four native record servers and the public server as local processes on ports of 127.0.0.1 that the system assigns (startLocalStack of src/local-servers.mjs, which pipeline.browser.mjs uses too), runs test-php-modes.mjs, check-generation.mjs, check-servers.mjs (with --origin, --data and --report instead of a fixed port and fixed directories), check-pipeline.mjs, the four check.mjs browser checks with check-interaction.mjs, check-browser-reports.mjs and check-typing.mjs, which had no runner, checks the evidence of all reports, the typing report included (verification-evidence.mjs), and stops the servers. make crudui-test-form-comparison-checks (npm run test:form-comparison:checks) runs it, in CI the job form-comparison-checks, which ci-passed needs; CI_COMMANDS and scripts/owner-checks.json name it. The unit tests of the checks return (browser-report.test.mjs, check-generation.test.mjs, check-interaction.test.mjs, check-servers.test.mjs, and check-verification.test.mjs with the stages of the local run and the typing evidence); the image, the domain route, the supervisor and the deployment lock stay removed. The feature rows server-template-browser, native-generation-integration, form-typing and form-focus name these checks as their evidence. The scenario check serverInvalid of src/frame.mjs expected only the store names in hidden with the store names hidden by the specification; since C8 the validator also reports the detail of a store whose enabled is off, so the expectation takes those paths from the validation of the same record. Cause: C7.20 removed checks that CI did not run instead of running them in CI. Red: the first local run failed serverInvalid in all 64 scenario reports (16 per server) with a hidden required field skips its rules. Green: the next run passed every stage, PHP modes, 450 generation results with 899 requests, 120 persistence results, 40 canonical flow combinations, 7,008 browser checks of four servers and 24 typing results, and its evidence check passed; the 7 cases of check-verification.test.mjs pass — node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs examples/form-comparison/browser-report.test.mjs examples/form-comparison/check-generation.test.mjs examples/form-comparison/check-interaction.test.mjs examples/form-comparison/check-servers.test.mjs, make crudui-test-form-comparison-checks [o]
C7.20-2 Run the Linux stylesheet layout checks only on the CI Linux runner and invoke no container tool of a development machine: scripts/test-form-styles-linux.sh, which ran them in the Playwright image through the container CLI of macOS when present and another container CLI otherwise, and its targets make test-form-styles-linux and make remove-form-styles-image are removed with their cases in tests/build/shared-resources.test.mjs and tests/build/runtime-version-policy.test.mjs and the image row of the shared resources; the CI job form-runtime runs make crudui-test-forms, tests/form-styles.test.mjs included, on ubuntu-24.04 with the Chromium and Firefox of Puppeteer and the WebKit of Playwright. A case of tests/docs/repository-writing.test.mjs fails for a tracked tool, a Makefile line or a workflow that invokes the container CLI of macOS or another container CLI. Cause: the script picked a container tool of the machine that ran it. Red: on the previous tree the new case named 7 lines in Makefile, scripts/test-form-styles-linux.sh and tests/build/shared-resources.test.mjs. Green: the 5 cases of tests/docs/repository-writing.test.mjs pass, and the shared-resource, runtime-version and holder-lock tests pass — node scripts/run-tests.mjs node -- tests/docs/repository-writing.test.mjs tests/build/shared-resources.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/holder-lock.test.mjs [o]
C7.20-3 Track no container definition and instruct no container tool of a development machine, because Linux runs on the CI runners: tests/containers/native.Containerfile, which ran make crudui-test-native through the container CLI of macOS, .dockerignore, the section Apple container of docs/operations/native-generators.md and the container-build rules of docs/operations/package-build.md are removed; the CI jobs php-engine, native-generators and php-api run the native suites on ubuntu-24.04. The cases of packages/php-ext/tests/engine.test.mjs that read the definition and .dockerignore and the container cases of tests/build/runtime-version-policy.test.mjs give way to a case that requires no tracked container definition, the PHP range case keeps its check of the newest tested minor, and scripts/owner-checks.json drops the rows of the removed files. A case of tests/docs/repository-writing.test.mjs fails for a code block or inline code of a document, other than the checklists and the change logs, that runs the container CLI of macOS or another container CLI. Cause: the native suites had a definition and a procedure for a container tool of a development machine. Red: the new document case named 4 lines of docs/operations/native-generators.md and docs/operations/native-generators.ko.md, and the new runtime case named tests/containers/native.Containerfile. Green: both cases pass, and the runtime-version, dependency-health, checkout-npm, owner and lint-coverage tests and the builder cases of the engine test pass — node scripts/run-tests.mjs node -- tests/docs/repository-writing.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/dependency-health.test.mjs tests/build/checkout-npm.test.mjs tests/build/owner-check.test.mjs tests/build/lint-coverage.test.mjs [o]

Wave 8 — Display switching

ID Task Deliverables Verification State
C8.1 Update the display in the browser binding: after every change it sets the hidden attribute of each node from design.show with hiddenPaths(spec, data), a new export of @crudui/validator, removes the errors of a node that becomes hidden and of the nodes inside it, and changes no value — make crudui-test-form-binding, make crudui-test-validator-js [o]
C8.1-1 Check the public export hiddenPaths of @crudui/validator in the public build test: C8.1 added it to the public entry and to contracts/features.json, but tests/build/public-packages.test.mjs kept the list of the exports before C8.1, so make crudui-test-build failed on C8.5; the test now expects the exports of the contract and calls hiddenPaths through the built ESM and CommonJS entries — make crudui-test-build [o]
C8.2 Resolve a relative path that moves up from a field of a repeated group with the row as one level: ..x in a row reads the field beside the collection, whatever the row key is, in the five validators and in the renderers; amend the path resolution of the expression contract and add shared cases — make crudui-test-validators, make test-php-extension, npm test -w @crudui/generator-core [o]
C8.3 Return the data paths of the hidden fields with the validation result in the five validators, so a server can leave the values of a hidden branch unstored — make crudui-test-validators, make test-php-extension [o]
C8.3-1 Document the interface ValidationResult of @crudui/validator: C8.3 placed ListValidationResult between the comment of ValidationResult and the interface, so the comment described ListValidationResult twice, ValidationResult had none, and the documentation coverage check of make docs-check failed on C8.5 with ValidationResult (Interface) ... does not have any documentation — make docs-check [o]
C8.3-2 Compile the public types of the validation results in the public build test: C8.3 gave validateList and validateDetail the type ListValidationResult and validate the member hidden, but tests/build/public-types.mts and .cts assigned the list and detail results to ValidationResult, so make crudui-test-build failed on C8.5 with TS2741; both files now use ListValidationResult and read hidden of ValidationResult — make crudui-test-build [o]
C8.3-3 Answer a record save of the form comparison with the validator's result, hidden included, in every record server: after C8.3 the PHP servers sent hidden, the JavaScript server sent { valid: true, errors: [] } on success, and the Go and Rust servers rebuilt { valid, errors } and dropped hidden, so make crudui-test-form-comparison and make crudui-test-form-comparison-pipeline failed on C8.5; amend the save contract of the form comparison, expect hidden in the record contract, the Rust server test, the interaction check and the PHP generation check — make crudui-test-form-comparison, make crudui-test-form-comparison-pipeline [o]
C8.3-4 Keep the response { valid, errors } of the validator processes of the cross-check console in the PHP process and in the input text cases: after C8.3 the PHP process wrote the whole result of Validator::validate, hidden included, while the Go process left it out as validators/README.md states, and the input text cases expected the expect of the shared fixture, which now holds hidden, so make crudui-test-cross-check failed on C8.5 with invalid php validation response — make crudui-test-cross-check [o]
C8.3-5 Describe hidden wherever a document states the form validation result: after C8.3 the READMEs of the TypeScript, Go and Rust validators, the PHP extension specification, the fixture specification and the README of the shared validation cases still stated { valid, errors } — make docs-check [o]
C8.3-6 Export a form validation case of the cross-check console in the shape of tests/fixtures/validate/cases.json: the validator processes answered { valid, errors } for a form, so the fixture export of the form tab wrote expected without hidden, while every case of cases.json states expected as { valid, errors, hidden } since C8.3. The JavaScript, PHP, Go and Rust processes answer a form with { valid, errors, hidden }, as validate of each validator returns it, and a list or detail with { valid, errors }; the gateway requires hidden of a form result, keeps it in the result and compares it in order across the languages; client/fixture-export.js writes expected as { valid, errors, hidden } in the order of the validator and expectFailure as { code, message, at }. Red: server/fixture-export.test.mjs failed for its three result cases, whose exported expected had no hidden. Green: its 5 cases pass, and make crudui-test-cross-check passes with 1,267 tests — make crudui-test-cross-check [o]
C8.4 Resolve a design.show string in the Rust validator only when it is a condition expression, as the other runtimes do; add the shared case show: "enabled" — make crudui-test-validator-rust [o]
C8.4-1 Record the form fixture inventory of the native generator suite as 188 cases: C8.2 added design-show-row-parent-on and design-show-row-parent-off and C8.4 added design-show-literal-identifier-visible to tests/fixtures/form-render/cases.json, but tests/native-generators/run.mjs kept 185, so make crudui-test-native stopped before any target on C8.5 and the conformance evidence had no native result; each of the three cases runs on every native target — make crudui-test-native [o]
C8.5 Add shared validation cases of a required field that a parent value switches on and off: a group subtree, two nested groups, a switch inside a repeated row and a switch that reads the parent of the row — make crudui-test-validators, make test-php-extension [o]

Wave 9 — Package names of the polyspec repositories

ID Task Deliverables Verification State
C9.1 Name the packages as template and hyper do: the npm packages @polyspec/crudui-cli, @polyspec/crudui-form-binding, @polyspec/crudui-generator-core, @polyspec/crudui-generator-html, @polyspec/crudui-generator-react, @polyspec/crudui-generator-svelte, @polyspec/crudui-generator-vue and @polyspec/crudui-validator, the workspace root @polyspec/crudui-workspace, the Composer packages polyspec/crudui-generator and polyspec/crudui-validator with the PHP namespace Polyspec\Crudui\ (the classes Generator, Validator, Form and FormError, the internal namespaces Generator\ and Validator\) in the PHP packages and the classes that the PHP extension registers, and the crates polyspec-crudui-generator and polyspec-crudui-validator with the libraries polyspec_crudui_generator and polyspec_crudui_validator; the Go modules already were github.com/polyspec/crudui/.... The code, tests, fixtures, examples and documents name the packages by these names, the locks of npm, Composer and Cargo are written again by their tools, and crudui_arginfo.h is generated again from the stub; the changelog, the checklist and the wave descriptions keep their recorded names. Red: the 2 cases of the new tests/build/package-names.test.mjs failed: the packages had the earlier names, and 2199 places in maintained files named a package by an earlier form. Green: the 2 cases pass; the unit tests of the validator and generator workspaces, the CLI and the form binding (3731 cases), the PHP validator (781) and generator (314), the PHP extension (50, the address sanitizer case runs on Linux only), the Rust validator (126) and generator (37), cargo check of the four Rust programs that use the crates, the contract manifest and its documents, the document tests, and the build and example tests that name the packages with the owner and command tests (143 cases) pass — node scripts/run-tests.mjs node -- tests/build/package-names.test.mjs [o]
C9.1-1 Require the PHP API pages of the namespace Polyspec\Crudui\ in the API documentation: after C9.1 phpDocumentor writes classes/Polyspec-Crudui-Generator.html and the other class pages, while scripts/gen-api-docs.mjs required classes/CRUDUI-Generator.html, so make docs-check-documents failed with Documentation output is missing; the package name check also refuses the page form CRUDUI- of the earlier namespace. Red: tests/build/package-names.test.mjs failed on scripts/gen-api-docs.mjs:119. Green: its 2 cases pass and make docs-check-documents passes — node scripts/run-tests.mjs node -- tests/build/package-names.test.mjs, make docs-check-documents [o]
C9.2 Name the programs of examples/, tests/ and tools/ by the same convention: the npm package @polyspec/crudui-cross-check-console, the crates polyspec-crudui-cross-check-validator, polyspec-crudui-form-comparison, polyspec-crudui-native-generator and polyspec-crudui-bench, and the Go module github.com/polyspec/crudui/tools/bench/go; tests/build/package-names.test.mjs requires the convention of every tracked package.json, composer.json, Cargo.toml and go.mod. Red: the case failed for seven manifest names — node scripts/run-tests.mjs node -- tests/build/package-names.test.mjs [o]

Wave 10 — OrderedJSON from a tag of polyspec/ordered-json

ID Task Deliverables Verification State
C10.1 Take OrderedJSON from the branch main of polyspec/ordered-json: installOrderedJson fetches the head of main and checks the five package manifests in it, make crudui-install-ordered-json installs it on every run and make install-node-modules runs it first, the root package.json names @polyspec/ordered-json as file:.form-comparison/sources/ordered-json/js, the build tree copies the js/ package to node_modules/@polyspec/ordered-json, tests/ordered-json/check.py requires a clean repository checkout and reports its commit, and the record servers use the package names of that branch: the PHP namespace Polyspec\OrderedJson (which parses with the ordered_json extension exactly when it is loaded), the Go module github.com/polyspec/ordered-json/go and the crate polyspec-ordered-json. Red: the new installer test failed against the previous module, which exported no orderedJsonBranch. Green: the installer, build target, server layout, verification and offline check tests pass (38), the Rust record server's OrderedJSON case passes, go vet passes for the Go server, and the PHP adapters parse — node scripts/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs examples/form-comparison/src/build-targets.test.mjs tests/build/offline-checks.test.mjs [o]
C10.2 Take OrderedJSON from the tag v0.0.1 of polyspec/ordered-json: orderedJsonTag of examples/form-comparison/src/ordered-json-source.mjs declares the tag from orderedJsonVersion, installOrderedJson fetches refs/tags/v0.0.1 with depth 1, checks out the commit of the tag and requires the HEAD of the checkout to be that commit, keeps a checkout at that commit without tracked changes and replaces any other one, and orderedJsonAtTag accepts a checkout only when its HEAD is the commit of its tag ref and it has no tracked changes, so the local pipeline names the tag when it refuses a checkout; the operations, comparison and package build documents name the tag. Until version 0.1, a polyspec repository depends on another polyspec repository through a GitHub tag of it. Red: the new installer test failed against the module of C10.1, which exported no orderedJsonAtTag. Green: its 7 cases pass, among them a checkout at another commit than the tag that is replaced and a Git checkout at another commit that is not accepted; make crudui-install-ordered-json replaces a checkout at another commit with the commit of the tag and keeps it on the next run — node scripts/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs, make crudui-install-ordered-json [o]
C10.3 Build, encode and review against the OrderedJSON tag v0.0.1: scripts/build-ordered-json-php-extension.mjs compiles the C sources that PHP_NEW_EXTENSION([ordered_json], ...) of the config.m4 of the checkout declares (ordered_json.c and hydrate.c in v0.0.1), and the load check php -n -d extension=... --ri ordered_json passes; runCommand of scripts/tool-resolution.mjs reports the standard output of a failed command when its standard error is empty, where PHP writes a startup failure. A constructed OrderedJSON string writes every code unit outside printable ASCII as a \u escape, so examples/form-comparison/src/json.test.mjs expects \uc11c\uc6b8 for 서울 from a UTF-8 and from an escaped source. The dependency check reads @polyspec/ordered-json as a polyspec package taken from a GitHub tag: the root package.json links it to the tag checkout, the checkout has the version of the tag and package-lock.json records it. No npm manifest declares a URL or Git dependency, every registry package of the lock records its SHA-512 integrity and .npmrc sets allow-remote=none and allow-git=none. make dependency-review UPDATE=1 RECORD=1 raises js-yaml to 5.4.3, vite to 8.3.3 and svelte to 5.57.2 and records the review of every lock. Red: the module load check exited 1, the encoded text held \uc11c\uc6b8 where the case expected 서울, the URL dependency case failed with root package must declare a URL dependency, and the review check reported the file: link of @polyspec/ordered-json against version 0.0.1 and nine locks changed after their review. Green: the module builds and loads with PHP 8.5, the 130 cases of examples/form-comparison/record-stores.test.mjs pass for the php, php-ext, go and rust stores, the 215 cases of the form comparison source tests, the 22 cases of the dependency tests and the 9 cases of the PHP extension builder test pass — node scripts/run-tests.mjs node -- examples/form-comparison/src/json.test.mjs examples/form-comparison/record-stores.test.mjs tests/native-generators/php-extension-builder.test.mjs, node scripts/run-tests.mjs node --timeout 60 -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs [o]

Wave 11 — Releases from tags of main

ID Task Deliverables Verification State
C11.1 Release from tags of commits of main: the change logs keep the changes of the coming release under ## Unreleased (C11.1-1), the ruleset main requires the checks push-gate and ci-passed, the last job of .github/workflows/ci.yml, which needs every other job (C11.1-2), and .github/workflows/release.yml checks a pushed tag vX.Y.Z or <directory>/vX.Y.Z and creates its GitHub Release with the package archives (C11.1-3), with the Go module tags at any depth, the four steps release-verify, release-versions, release-assets and release-publish, and npm tarballs and Composer zips only (C11.1-4) — node scripts/run-tests.mjs node -- tests/docs/changelog.test.mjs tests/build/ci-passed.test.mjs tests/build/ci-local.test.mjs tests/build/github-repository.test.mjs tests/build/release.test.mjs [o]
C11.1-1 Keep the changes of the coming release under ## Unreleased: CHANGELOG.md and CHANGELOG.ko.md start with the section ## Unreleased, which holds the 386 entries, each a heading ### <date> — <title> (<task ID>) with its text, and a release adds a section ## X.Y.Z below it. tests/docs/changelog.test.mjs requires the title, ## Unreleased as the first section, every other section ## X.Y.Z and older than the one above it, every entry heading ### <date> — <title> inside a section and no other heading, the same sections and entries in both files and entries newest first; AGENTS states where an entry goes. Red: the new case named the 386 headings ## <date> — <title> of CHANGELOG.md. Green: the 4 cases pass — node scripts/run-tests.mjs node -- tests/docs/changelog.test.mjs [o]
C11.1-2 Require one completion check of the CI workflow: the last job ci-passed of .github/workflows/ci.yml needs the 14 other jobs, runs under if: ${{ always() }} on ubuntu-24.04 and runs make ci-passed RESULTS='${{ toJSON(needs) }}'; scripts/ci-passed.mjs exits 0 only when every needed job has the result success, so a failed, cancelled or skipped job fails it, and RESULTS that is missing, unreadable or names no job fails it too. The ruleset main of .github/repository.json requires exactly the checks push-gate and ci-passed; the checking commands of make ci leave out the completion job. Red: make ci-passed had no target, the new case of tests/build/ci-local.test.mjs found no last job ci-passed, and the case of tests/build/github-repository.test.mjs found the 17 checks of the push check and of every CI job and matrix entry. Green: the 3 cases of tests/build/ci-passed.test.mjs, the 10 cases of tests/build/ci-local.test.mjs and the 7 cases of tests/build/github-repository.test.mjs pass — node scripts/run-tests.mjs node -- tests/build/ci-passed.test.mjs tests/build/ci-local.test.mjs tests/build/github-repository.test.mjs [o]
C11.1-3 Publish a GitHub Release from a pushed tag: .github/workflows/release.yml runs on a pushed tag v* or */v* with the permission contents: write and runs make release-check, then make install-node-modules and make build, make release-assets and make release-publish (scripts/release.mjs). The check requires the commit on origin/main, the check runs push-gate and ci-passed of the commit completed with conclusion success, the version of the tag in every package.json, composer.json with a version, Cargo.toml, VERSION and pyproject.toml that the tag covers and the section ## X.Y.Z of CHANGELOG.md, and names each missing or failed check and each file with both versions; the archives are npm pack of the 7 npm packages that are not private, git archive zips of the 2 Composer packages and cargo package --no-verify --exclude-lockfile of the 2 crates of packages/, named <package>-<version>.<extension>, and a tag <directory>/vX.Y.Z of a Go module writes none; gh release create <tag> --verify-tag --title <tag> --notes-file takes the section ## X.Y.Z. cargo package without --exclude-lockfile fails for polyspec-crudui-generator, whose dependency polyspec-crudui-validator is not in the crates.io index. AGENTS and docs/operations/repository.md state the release procedure; the 6 go.mod module paths are github.com/polyspec/crudui/<directory> of their directories, as tests/build/package-names.test.mjs requires. Red: tests/build/release.test.mjs failed with ERR_MODULE_NOT_FOUND for scripts/release.mjs, and 3 cases of tests/build/ci-local.test.mjs failed without release.yml. Green: the 10 cases of tests/build/release.test.mjs and the 11 cases of tests/build/ci-local.test.mjs pass, and make release-assets for v0.0.1 wrote the 11 archives — node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/build/ci-local.test.mjs [o]
C11.1-4 Align the release steps, the Go module tags and the archives: in a tag filter * does not match /, so the trigger tags: ['v*', '*/v*'] missed the Go module tags packages/<directory>/vX.Y.Z; the job ran make release-check, make build, make release-assets and make release-publish with the tag of GITHUB_REF_NAME; and make release-assets packed the 2 crates with cargo package --exclude-lockfile, which rewrites git dependencies into crates.io requirements that do not resolve. .github/workflows/release.yml runs on tags: ['v*', '**/v*'], sets TAG: ${{ github.ref_name }}, and after the setup steps make install-npm, make toolchain-check TOOLS="node npm" and make install-node-modules ends with make release-verify, make release-versions, make release-assets and make release-publish, whose recipes run node scripts/release.mjs <step> "$$TAG". make release-assets runs make build and writes the 7 npm tarballs and the 2 Composer zips; a crate is not released as an archive; it is consumed by git tag, and a Go module tag builds and attaches nothing. Red: with the trigger */v* the 2 trigger cases of tests/build/ci-local.test.mjs failed, and tests/build/release.test.mjs failed on the missing export releaseManifests. Green: the 10 cases of tests/build/release.test.mjs and the 12 cases of tests/build/ci-local.test.mjs pass — node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/build/ci-local.test.mjs [o]
C11.2 Release 0.0.1: every package.json, Cargo.toml and lock entry of a package of the repository has the version 0.0.1, and ## Unreleased of CHANGELOG.md and CHANGELOG.ko.md becomes ## 0.0.1 with its 396 entries below a new empty ## Unreleased. Red: versionProblems of scripts/release.mjs, the check of make release-versions, named for v0.0.1, packages/generator-go/v0.0.1 and packages/validator-go/v0.0.1 the missing section ## 0.0.1 of CHANGELOG.md. Green: it names no problem for the three tags, and the cases of tests/build/release.test.mjs and tests/docs/changelog.test.mjs pass — node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/docs/changelog.test.mjs [o]
C11.3 Keep the release notes within the body limit of GitHub: GitHub refuses a release body over 125000 characters, and the section ## 0.0.1 of CHANGELOG.md has 360629. releaseNotes of scripts/release.mjs returns the section when it has at most NOTES_LIMIT = 125000 characters, counted as code points, and otherwise the line The changes of X.Y.Z are listed in [CHANGELOG.md](https://github.com/polyspec/crudui/blob/<tag>/CHANGELOG.md#<version without dots>). with each path segment of the tag URL-encoded; make release-publish writes it as the notes. AGENTS and docs/operations/repository.md state the rule. Red: tests/build/release.test.mjs failed on the missing export NOTES_LIMIT. Green: the 12 cases of tests/build/release.test.mjs pass, with a section of exactly 125000 characters kept whole and a longer one written as the line — node scripts/run-tests.mjs node -- tests/build/release.test.mjs [o]
C11.4 Release 0.0.2: every package.json, Cargo.toml and lock entry of a package of the repository, the Composer path version and lock entry of polyspec/crudui-validator, the version of contracts/features.json with the checks that read it, the module version of packages/php-ext and the package versions stated in the documents are 0.0.2, and ## Unreleased of CHANGELOG.md and CHANGELOG.ko.md becomes ## 0.0.2 above ## 0.0.1 below a new empty ## Unreleased. Red: versionProblems of scripts/release.mjs, the check of make release-versions, named for v0.0.2 the version 0.0.1 of 16 package files and the missing section ## 0.0.2, and for packages/generator-go/v0.0.2 and packages/validator-go/v0.0.2 the missing section. Green: it names no problem for the three tags, and the cases of tests/build/release.test.mjs, tests/docs/changelog.test.mjs and tests/build/contract-manifest.test.mjs pass — node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/docs/changelog.test.mjs tests/build/contract-manifest.test.mjs [o]
C11.4-1 Record the dependency review of the 0.0.2 locks: config/dependency-review.json holds the review of the 9 locks with the 0.0.2 entries of the packages of the repository, written by make dependency-review RECORD=1, which found no newer stable release without an exception and no advisory. Red: make crudui-test-dependencies failed in the case "the dependencies match their recorded review, without a registry query" of tests/build/dependency-health.test.mjs, whose recorded locks differed from package-lock.json, packages/generator-php/composer.lock and the Cargo locks. Green: the 25 cases of make crudui-test-dependencies pass — node scripts/run-tests.mjs node -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs [o]
C11.4-2 Take OrderedJSON from its released tag v0.0.2: orderedJsonVersion of examples/form-comparison/src/ordered-json-source.mjs is 0.0.2, so orderedJsonTag is v0.0.2 and make crudui-install-ordered-json checks out that tag; the polyspec-ordered-json dependency of the Rust record server, its Cargo.lock entry, the package-lock.json entry of the linked @polyspec/ordered-json, VERSION of tests/ordered-json/check.py, the operations document of OrderedJSON, the package build specification and the wave plan name 0.0.2, and config/dependency-review.json records the review of the changed locks, written by make dependency-review RECORD=1. The tag v0.0.1 of polyspec/ordered-json has no GitHub Release; between the two tags OrderedJSON changed its release scripts, documents and package versions and no source of a package. Red: the case of examples/form-comparison/src/ordered-json-source.test.mjs that requires the tag v0.0.2 failed with v0.0.1. Green: its 7 cases, the cases of examples/form-comparison/src/json.test.mjs and tests/native-generators/php-extension-builder.test.mjs, the 130 cases of examples/form-comparison/record-stores.test.mjs for the php, php-ext, go and rust stores and the 25 cases of make crudui-test-dependencies pass — node scripts/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs examples/form-comparison/src/json.test.mjs examples/form-comparison/record-stores.test.mjs tests/native-generators/php-extension-builder.test.mjs, make crudui-test-dependencies [o]
C11.5 Publish the package manifests unchanged and install the release archives outside the repository: the published manifests are the package.json and composer.json of packages/, packed unchanged, and name every dependency of the scope @polyspec and the vendor polyspec by its exact version; both PHP manifests declare their version and no repositories. Development resolution is in the root composer.json (polyspec/crudui-workspace, never published) with a path repository of packages/validator-php, the generator sources by autoload, and composer.lock and vendor/ at the root; the PHP package locks are removed, make crudui-install-composer installs the root, PHPUnit runs the root vendor/bin/phpunit with the bootstrap scripts/php-package-autoload.php, which loads the tested package from its source directory, and the comparison, the native generator tests, the PHP extension tests, the benchmarks and scripts/php-doc-coverage.php load the root vendor/. config/dependency-policy.json reviews the root manifest and lock, and make crudui-test-dependencies validates each published composer.json without a lock. make release-assets fails with each packed manifest that differs from its package manifest or names such a dependency by a URL, a path, a git source, a range or a development version, or is a composer.json with repositories or without version. make release-install-check, a step of the release workflow, installs the archives of var/release/assets from the committed consumer fixtures of tests/release-install outside the repository with npm ci (empty cache, the scope @polyspec on an unreachable registry) and composer install (an artifact repository, empty COMPOSER_HOME and COMPOSER_CACHE_DIR); make release-install-lock regenerates the fixture locks from the archives. Red: the check named 3 problems in the v0.0.2 release archives, the path repository of polyspec/crudui-generator and the missing version of both Composer zips, and the install test packed the local packages and ran npm install --offline, which a warm cache satisfied. Green: the cases of the verification files pass, and make release-install-check installs the archives of make release-assets TAG=v0.0.2 RELEASE_COMMIT=HEAD from the fixtures with empty caches — node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs tests/build/php-extensions.test.mjs tests/build/run-tests.test.mjs scripts/php-doc-coverage.test.mjs, make release-install-check [o]
C11.6 Release 0.0.3: every package.json, composer.json of packages/, Cargo.toml and lock entry of a package of the repository, the root composer.json requirement and composer.lock entry of polyspec/crudui-validator, contracts/features.json with the checks that read it, the module version of packages/php-ext, the package versions stated in the documents and the consumer fixtures of tests/release-install, written by make release-install-lock from the archives of make release-assets TAG=v0.0.3 RELEASE_COMMIT=HEAD, have the version 0.0.3, and ## Unreleased of CHANGELOG.md and CHANGELOG.ko.md is ## 0.0.3 below a new empty ## Unreleased, so that make release-versions passes for v0.0.3 and the Go module tags packages/generator-go/v0.0.3 and packages/validator-go/v0.0.3 — node scripts/run-tests.mjs node -- tests/build/release.test.mjs, make release-install-check [o]
C11.6-1 Install the release archives in CI: a broken archive was found only after its tag. The CI job build-lint runs make release-install-head after make test-ordered-json, which writes the archives of HEAD at the version of package.json with RELEASE_COMMIT=HEAD node scripts/release.mjs assets and installs them from the consumer fixtures of tests/release-install with scripts/release-install.mjs check; CI_COMMANDS lists it, it lifts the offline settings for the install, and the release workflow keeps make release-install-check. Red: no CI job installed the archives; only the release workflow, after the tag, did. Green: the cases of the verification files pass, and make release-install-head installs the 0.0.3 archives with empty caches — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs tests/build/owner-check.test.mjs, make release-install-head [o]
C11.6-2 Take OrderedJSON from its released tag v0.0.3: orderedJsonVersion of examples/form-comparison/src/ordered-json-source.mjs is 0.0.3, so orderedJsonTag is v0.0.3 and make crudui-install-ordered-json checks it out; the version requirement of the Rust record server and its Cargo.lock, the package-lock.json entry of @polyspec/ordered-json, VERSION of tests/ordered-json/check.py and the documents name 0.0.3, and make dependency-review RECORD=1 records the review of the changed locks. The fixture locks of tests/release-install name no OrderedJSON package. Green: make test-ordered-json, make crudui-test-dependencies, the 7 cases of ordered-json-source.test.mjs, the 130 cases of record-stores.test.mjs, the 18 tests of the Rust record server and make release-install-head pass — make test-ordered-json, make crudui-test-dependencies, node scripts/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs examples/form-comparison/record-stores.test.mjs, node scripts/run-tests.mjs cargo -- --locked --manifest-path examples/form-comparison/servers/rust/Cargo.toml, make release-install-head [o]
C11.6-3 Run the browser checks of each form server in a CI job of its own: the CI job form-comparison-checks ran the four browser checks of PHP, the PHP extension, Go and Rust at the same time on one runner, where the eight initialization reports of each check took about 75 s and one exceeded its limit. The CI job form-comparison-browser has a matrix of one job per server (php, php-ext, go, rust, equal to formServers); each runs make crudui-test-form-comparison-browser, which runs local-verification.mjs with --servers of FORM_SERVERS against a local stack of its own and uploads the report of its server. The CI job form-comparison-checks needs those four jobs, downloads their reports and runs make crudui-test-form-comparison-summary, which runs local-verification.mjs with --browser-reports: the PHP modes, generation, persistence, the canonical flow, the browser summary of the four reports, typing and the evidence of all reports, each once. Every public server takes the address FORM_ADDRESS (--address), so the reports name the origin that the browser summary requires. The checks of the single run and of the two parts are equal: php-modes, generation, persistence, pipeline, browser-php, browser-php-ext, browser-go, browser-rust, browser-summary, typing and evidence; no check, report, timeout or limit changes. ci-passed needs both jobs, and CI_COMMANDS lists both targets. Red: CI run 37617382051 failed browser-php with Browser report php/createForm/react/initialization exceeded its 100000 ms limit after 100007 ms while the other three checks passed all 1752 checks. Green: the CI run of this change passes the four form-comparison-browser jobs and form-comparison-checks; the cases of check-verification.test.mjs, form-comparison-ci.test.mjs and ci-local.test.mjs pass — node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/ci-local.test.mjs, the CI jobs form-comparison-browser and form-comparison-checks [o]
C11.6-4 Release a Go module tag without the archive install check failing: make release-install-check read the archives of the version of package.json whatever the tag was, while make release-assets writes no archive for a Go module tag <directory>/vX.Y.Z, so the release runs of packages/generator-go/v0.0.3 and packages/validator-go/v0.0.3 failed with var/release/assets/polyspec-crudui-generator-html-0.0.3.tgz is missing and created no GitHub Release. make release-install-check requires TAG and passes it to scripts/release-install.mjs check, which for a Go module tag writes release-install: <tag> is a Go module tag, which releases no archive to install and installs nothing, and for a tag vX.Y.Z installs the archives as before; make release-install-head keeps the check of HEAD without a tag. Red: the new case of tests/build/release.test.mjs failed on the check before this change, which read the empty assets directory. Green: the case passes, tests/build/release.test.mjs, tests/build/ci-local.test.mjs and tests/build/offline-checks.test.mjs pass (34), TAG=packages/generator-go/v0.0.3 make release-install-check names the tag, and make release-install-head and TAG=v0.0.3 make release-install-check install the 0.0.3 archives — node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs, make release-install-head [o]
C11.6-5 Start the servers of the form comparison runs on ports of the system: every public server of make crudui-test-form-comparison-browser and make crudui-test-form-comparison-summary took the fixed address FORM_ADDRESS (--address, 127.0.0.1:47100), because the browser summary required every report to name its own origin, so two runs at the same time collided on that port. The Makefile, local-verification.mjs and startLocalStack of src/local-servers.mjs take no address, and every server takes a port of the system and announces it. summarizeBrowserReports of check-browser-reports.mjs requires the scheme and host of its origin in every report, without the port, and the source identity of the same tree as before. Red: the public servers of two runs on 127.0.0.1:47100 at the same time failed, the second exiting before readiness; the new case of browser-report.test.mjs failed with php verification: report origin for reports of other ports, and the case of form-comparison-ci.test.mjs failed on the argument --address. Green: the public servers of two local stacks start at the same time on ports of their own, the summary passes reports of other ports and rejects another scheme, host or source identity, and the 30 cases of the four files pass — node scripts/run-tests.mjs node -- examples/form-comparison/browser-report.test.mjs examples/form-comparison/src/local-servers.test.mjs examples/form-comparison/check-verification.test.mjs tests/build/form-comparison-ci.test.mjs, the CI jobs form-comparison-browser and form-comparison-checks [o]

Wave 12 — CI jobs of a shorter wall time

ID Task Deliverables Verification State
C12.1 Shorten the wall time of the CI workflow: the native suites run in three jobs, the C engine of the PHP extension and the generators of the JavaScript, HTML, Go and Rust targets once and the PHP API for each PHP release, with the conformance evidence of each job in a directory of its own (C12.1-1), and every job that installs the npm packages caches ~/.npm by package-lock.json (C12.1-2) — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/conformance-evidence.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/make-tool-path.test.mjs tests/build/offline-checks.test.mjs tests/build/release.test.mjs [o]
C12.1-1 Run the native suites in three CI jobs: the job Native generation and PHP API ran make crudui-test-native and make crudui-test-bench for PHP 8.4 and for PHP 8.5, in 489 s and 454 s, and 335 s of each run needed no PHP. php-engine runs make crudui-test-php-engine (packages/php-ext/tests/engine.test.mjs, 171 s in CI) once with Node.js and the C compiler; native-generators runs make crudui-test-native-generators (the Go and Rust generator tests, the protocol and widget tests and tests/native-generators/run.mjs --target javascript,html,go,rust) and make crudui-test-bench once; php-api runs make crudui-test-php-api (the build of crudui.so, the builder and API tests, the PHP generator tests and run.mjs --target php,php-native) for PHP 8.4 and 8.5. make crudui-test-native and make conformance run the three targets, CI_COMMANDS lists them in the order of the jobs, and PHP_NATIVE_REPORT names the report of the PHP targets. Each job preserves its evidence under an artifact name of its own, the job conformance needs the three jobs and downloads each artifact into a directory of its own, and scripts/check-conformance.mjs reads the evidence files and the run records of the directory and its subdirectories: merge-multiple extracted every artifact into one directory, where a file php-<pid>.jsonl of one job replaces the file of the same name from another. The cargo case of tests/build/offline-checks.test.mjs counts a run of run.mjs as a cargo command only when its targets include rust. Red: 5 cases of tests/build/ci-local.test.mjs, tests/build/form-comparison-ci.test.mjs and tests/build/make-tool-path.test.mjs failed against the previous workflow and Makefile, tests/build/conformance-evidence.test.mjs did not load without the export readEvidence, and the cargo case named crudui-test-php-api. Green: the 37 cases of the five files pass — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/conformance-evidence.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/make-tool-path.test.mjs tests/build/offline-checks.test.mjs [o]
C12.1-2 Cache the npm packages by package-lock.json: the cache npm of setup-node saved the npm directory of the job that finished first, also of a job that installs no npm package, and every job restored 700 bytes. Every job of .github/workflows/ci.yml, pages.yml and release.yml that runs make install-node-modules restores and saves ~/.npm with actions/cache v6.1.0 under the key npm-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('package-lock.json') }} before make install-npm, and no setup-node step caches; a run reads the caches of its ref and of main, where pages.yml saves this key. The Cargo caches keep their keys of the job id, the Rust toolchain and the Cargo locks of their workspaces; native-generators is the only job of its key, which the two PHP entries of the matrix both saved before, and one of them failed with Unable to reserve cache. Red: the new case of tests/build/ci-local.test.mjs named the 18 setup-node steps with cache: npm and the 14 jobs that install the npm packages without a cache of ~/.npm. Green: the 15 cases of tests/build/ci-local.test.mjs and the 291 cases of tests/build/*.test.mjs pass — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/release.test.mjs [o]

Wave 13 — A Python implementation of the validator and the generator

ID Task Deliverables Verification State
C13.1 Add a Python implementation of the validator and the generator with the standing of the other language implementations: packages/validator-python publishes polyspec-crudui-validator with the namespace package polyspec.crudui.validator (C13.1-1, C13.1-2) and packages/generator-python publishes polyspec-crudui-generator with polyspec.crudui.generator (C13.1-3, C13.1-4), every shared fixture case of the two products passes (C13.1-5), the language lists and checks of the repository name python (C13.1-6) and the CI workflow runs the Python tests on 3.11 and on the release of .python-version (C13.1-7) Scope: tests/fixtures/form-outline is out of scope of C13.1 and no Python package reads it. The reference products of C13.1 (packages/validator-ts, packages/validator-php, packages/generator-php, packages/generator-go) do not read it; the HTML, React, Vue and Svelte generators read it, and they are not reference products of C13.1. — the verification commands of the sub-items [o]
C13.1-1 Publish the Python validator packages/validator-python: pyproject.toml (PEP 621, setuptools pinned, requires-python >= 3.11, py.typed), the namespace package polyspec.crudui.validator without an __init__.py in polyspec/, and the modules errors (ComposeLoadError, FormInputError), compose ($ref/$patch expansion, the memory loader), forbidden (the forbidden meta-key scan), text (the input text and value limit checks), unicode_data (the generated Unicode 16.0.0 ranges of contracts/unicode-properties.json), pattern (the pattern language recognizer, the linear-time matcher and the code-point sets), parser (the condition lexer and parser with the 64-level depth limit), resolver (path resolution and condition evaluation), values (canonical text, whitespace, empty values, length, membership and numbers, with the ECMAScript number text), rules (the 24 registered rules) and validator (the field traversal, the validate-slot evaluation and the declared parameter checks). The public surface is validate, hiddenPaths, validateList, validateDetail, Validator, ComposeLoadError, FormInputError. Red: no Python package existed, and python3 -m unittest discover -s packages/validator-python/tests found no directory. Green: the module imports and its unit tests pass (C13.1-2) Fact (process): no commit of this checklist adds a in-progress marker line for C13.1-1 through C13.1-4; these rows first appear as done marker. Fact (committer and body): commit the C13.1-1 fix commit, a fix of this task, has the committer max <max@blue.tools>, and its body describes the names before the fix (validate_list, validate_detail, hidden_paths). — python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' [o]
C13.1-2 Verify the Python validator against the shared validation fixtures: the unit tests read tests/fixtures/validate/cases.json (309 cases: 178 results reproduced exactly and 131 failures through their code, message and at), tests/fixtures/expr/cases.json (54: 47 token, tree and evaluation cases and 7 rejected expressions with the first line of the message), tests/fixtures/compose/cases.json (20: 13 composed maps and 7 load codes), tests/fixtures/spec-validity/cases.json (34: 20 clean and 14 forbidden-key failures with the dotted trace), tests/fixtures/list-validity/cases.json (20: 16 and 4), tests/fixtures/detail-validity/cases.json (13: 6 and 7) and the validate (21), validateList (6) and validateDetail (5) files of tests/fixtures/text-validity with its value-graphs.json (12 graphs built with shared and self-containing containers). Red: before the package existed the discovery command found no test; the first run of the 309 validate cases failed 19 of them (the pattern matcher indexed its atoms wrongly, unique read the keys of a keyed collection instead of its values, and composed maps kept insertion order instead of specification member order), the number text wrote 1.0e+21 for 1e+21, and the list and detail results carried a hidden member. Green: the 23 tests of the five files pass in 3 s Fact: the test directory packages/validator-python/tests was added by this commit. The tree of the C13.1-1 package commit contains no such directory, so the discover command of that tree failed with ImportError: Start directory is not importable. — python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' [o]
C13.1-3 Publish the Python generator packages/generator-python: pyproject.toml as the validator package, the namespace package polyspec.crudui.generator, and the modules for template compilation, field binding, buttons, the form instance with its row operations, the interface messages, list and detail models, the widget, style and rendering internals and the value helpers. The public surface is Generator (compileForm, bindForm, bindButtons, formButtonsHtml, createForm, renderForm, renderList, buildList, buildDetail, renderDetail, sequenceRowKey, createRowKey), Form, FormError and the validator failures of polyspec.crudui.validator Fact (committer): commit the C13.1-3 package commit has the committer max <max@blue.tools>; the spec requires min-0-max <max@blue.tools> for commits of this work. — python3 -m unittest discover -s packages/generator-python/tests -p 'test_*.py' [o]
C13.1-4 Verify the Python generator against the shared generation fixtures: the unit tests read tests/fixtures/form-render/cases.json (188), tests/fixtures/list-render/cases.json (157), tests/fixtures/detail-render/cases.json (62), tests/fixtures/form-complete/cases.json (28) and the compileForm, bindForm, createForm, buildList and buildDetail files of tests/fixtures/text-validity, and compare every result with the recorded expectation, the rendered HTML through a parsed normalized tree and every failure through its code and message Fact: the test directory packages/generator-python/tests was added by this commit. The tree of the C13.1-3 package commit contains no such directory, so the discover command of that tree failed with ImportError: Start directory is not importable. Fact (committer): commit the C13.1-4 test commit has the committer max <max@blue.tools>; the spec requires min-0-max <max@blue.tools> for commits of this work. — python3 -m unittest discover -s packages/generator-python/tests -p 'test_*.py' [o]
C13.1-5 Run the Python implementations in the cross-language checks: the console of examples/cross-check-console gains the validator process validators/python/validate.py and python in validatorProcesses, so every request of the console runs the Python validator beside the JavaScript, PHP, Go and Rust processes with the same responses, and the conformance evidence of the Python tests joins the evidence the conformance job compares — node scripts/run-tests.mjs vitest --cwd examples/cross-check-console/server --root . --dir . (the server test suite of the console, 1267 tests including validate-runner.test.mjs and validator-processes.test.mjs) [o]
C13.1-6 Name python in every language list of the repository: contracts/features.json gains the python support of each feature and the packages entries, docs/features.md and docs/features.ko.md state the Python support at the same depth, the README pairs name the Python packages, .python-version holds the newest stable release (3.14), config/toolchain.json moves python to 3.14 and tests/ordered-json/check.py passes with it, and scripts/owner-checks.json maps the Python paths to their checks — node scripts/run-tests.mjs node -- tests/build/contract-manifest.test.mjs, make records-check [o]
C13.1-7 Run the Python tests in CI: the job python of .github/workflows/ci.yml has a matrix of 3.11 and the release of .python-version, runs the validator and the generator unit tests of the two packages through make targets that CI_COMMANDS lists, and preserves its conformance evidence; ci-passed needs it. The generator depends on polyspec-crudui-validator by the tag URL of v0.0.4 (not yet published, C13.1-9) and declares no dependency on polyspec-ordered-json, which no generator module imports; the unit tests run against the validator package of the repository checkout until that tag exists, and the manifest keeps the tag URL — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs, the CI job python [o]
C13.1-7-1 Record the conformance evidence of every shared case that the Python unit tests run: tests/conformance/evidence.py and runs.py write one record per case of tests/fixtures/{validate,expr,compose,spec-validity,list-validity,detail-validity,form-render,form-complete,list-render,detail-render} and of the text-validity files the Python packages prove, for each feature that proves it, and the unit test modules wrap each case in Proving; a failing case records passed: false — PYTHONPATH=packages/validator-python/src CRUDUI_CONFORMANCE_EVIDENCE=<directory> python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' and the generator suite with the same variable: 1637 records, none with passed: false [o]
C13.1-8 Correct the Python generator defects found by the audit of the committed history: Generator.renderForm over a createForm instance renders the placeholder row of missing data under the key __0000000000000__ as empty-collections states (Form._normalize_fields in form.py), the form-render unit test compares the public createForm and renderForm path (test_form_render.py), the text-validity unit test reads FormError.path as at (test_text_validity.py), and conformance.py normalizes each child node without a dead branch. Red: multiple-leaf-empty-placeholder failed with the key __0a592ccb0f808__ where the recorded expectation is __0000000000000__. — PYTHONPATH=src:../validator-python/src python3 -m unittest discover -s tests -p 'test_*.py' in packages/generator-python [o]
C13.1-9 Remove the unused polyspec-ordered-json dependency of packages/generator-python and name the validator dependency by its tag URL. No module of the generator imports polyspec.ordered_json, so pyproject.toml declares no such dependency. The generator imports polyspec.crudui.validator at run time, so its dependency is polyspec-crudui-validator @ git+https://github.com/polyspec/crudui@v0.0.4#subdirectory=packages/validator-python, which resolves once the maintainer creates the tag v0.0.4 — PYTHONPATH=src:../validator-python/src python3 -m unittest discover -s tests -p 'test_*.py' in packages/generator-python, and a tomllib read of the declared dependencies [o]
C13.1-10 Record the cause of the ImportError: Start directory is not importable of the committed trees in the rows C13.1-2 and C13.1-4: the test directory of each package was added after the commit that created the package, so the verification command of that commit named a missing directory — git ls-tree -r --name-only <commit> -- <test directory> for the package and test commits of C13.1-1 to C13.1-4 [o]
C13.1-11 Record three facts of the committed history in the rows C13.1-1 to C13.1-4: no commit adds a the in-progress marker line for these rows before their the done marker (process), the commits the C13.1-3 package commit and the C13.1-4 test commit have the committer max <max@blue.tools>, not the identity the spec requires (committer), and the body of the C13.1-1 fix commit describes the names before its fix (body) — git log -G on docs/plans/execution-checklist/checklist.md for the the in-progress marker lines of C13.1-1 to C13.1-4, and git log --format='%cn <%ce>' on the commits [o]
C13.1-12 Record the fix of C13.1-1 with its own sub-item and CHANGELOG entry: the public names of the Python validator became validateList, validateDetail and hiddenPaths, the names of the shared surface, and the fix was made after C13.1-1 was closed — PYTHONPATH=packages/validator-python/src python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' from the repository root [o]
C13.1-13 Correct the file count of the row C13.1-2: the unit tests run by discover with the pattern test_*.py are five files (the conformance.py file is not in that pattern), so the row says five files, not six — PYTHONPATH=packages/validator-python/src python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' reports 23 tests [o]
C13.1-14 State in the wave plan that the .python-version file does not exist yet: docs/plans/execution-checklist/plan.md names it as the newest stable release, but C13.1-6 creates it and is not done — grep -n 'python-version' docs/plans/execution-checklist/plan.md shows the file as not yet present [o]
C13.1-15 Declare the scope of the form-outline fixture of C13.1: tests/fixtures/form-outline is out of scope. The reference products of C13.1 (packages/validator-ts, packages/validator-php, packages/generator-php, packages/generator-go) do not read it; the HTML, React, Vue and Svelte generators read it, and they are not reference products of C13.1 — grep -rl form-outline over the reference product directories returns no file [o]
C13.1-16 Build the PHP extension for the architecture of its PHP executable: the builder passes no target architecture to cc and reads none from the PHP executable, so the compiled objects and the linked module take the architecture of the processes that start the build. Under /usr/local/bin/timeout, an Intel binary, cc produces x86_64 objects and the module does not load in the arm64 PHP. The builder reads the architecture of the PHP executable with lipo -archs, passes it with -arch to the compile and link, and fails when the linked module has another architecture — node scripts/run-tests.mjs node -- tests/build/php-extensions.test.mjs (the architecture unit tests), then make crudui-build-php-extension run without timeout, then node scripts/run-tests.mjs node -- examples/cross-check-console/server/validator-processes.test.mjs for the php-native process [o]
C13.1-17 Give every public definition of packages/validator-python/src and packages/generator-python/src parameter and return type hints. The public definitions without annotations are 82 in 10 files of the validator and 98 in 19 files of the generator, counted by the AST of each file; each annotation takes its type from the body of its function — the AST count of public definitions without annotations at zero, and mypy --strict over both src trees, run in a scratch virtual environment outside the repository [o]
C13.1-18 Remove the commit identifiers from the records of the rows C13.1-1, C13.1-2, C13.1-3, C13.1-4, C13.1-10 and C13.1-11: each commit is named by the checklist id of the task it implements (for example, the commit that adds the validator package is named C13.1-1), as the record guide requires — grep -nE '[0-9a-f]{7,8}' docs/plans/execution-checklist/checklist.md lists no commit id in the rows of C13.1 [o]
C13.1-19 Reject data that is not an object in Form.setData with the message that the shared contract names: setData of a non-object value fails with INVALID_FORM_INPUT and Form data must be an object, the message of the shared action case action-failure-keeps-state (the native generator checks), where createForm keeps Expected an object — node tests/native-generators/run.mjs --target python runs request:action-failure-keeps-state with the Python program and reports it passed [o]
C13.1-7-2 Run the native generator checks for the Python program: tests/native-generators/python.py answers the operation protocol of run.mjs through the public API of the two packages, the python target is added to run.mjs, and make crudui-test-native-generators runs the Python target with the JavaScript, HTML, Go and Rust targets — node tests/native-generators/run.mjs --target python reports 741 passed and exit 0 [o]
C13.1-20 Replace the state markers written in the text of the rows C13.1-10 and C13.1-11 with words, so that a marker appears only as the state of a task row, as the checklist rule requires — node scripts/check-documents.mjs passes the document pairs, and no row of C13.1 holds a marker outside its status cell [o]
C13.1-6-1 Record the Python support of each feature from the recorded evidence: a feature is pass for Python only when every case of each JSON fixture it lists has a passing Python record, partial where the cases are not all recorded (createForm), unsupported where the Python packages do not implement it (buildOutline, runAction, viewState, formHistory), and not applicable where the feature is client-side (connectForm, patchContent, updateView, connectOutline); the feature documents and the README pairs name the Python packages — node scripts/generate-feature-contract-docs.mjs, node scripts/check-documents.mjs, and a count of the Python records against each JSON fixture of every pass feature (0 mismatches) [o]
C13.1-7-3 Keep the CI mapping test of the native generator command in step with its target list: the make crudui-test-native-generators line runs run.mjs --target javascript,html,go,rust,python, and the test that pins that line matches the python target too — node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs passes 15 tests, exit 0 [o]
C13.1-21 Commit the generator behavior left uncommitted by an earlier session, separately from the type annotations: the markup of the root, header and body of binding, the language option, the list model in lists (build_public, the display of cells and render_cell), object_value in value, the widget attributes in widget, the Generator methods in __init__, the civil-date arithmetic of dates for instants outside the datetime range and the weekday check, and the bracket matching of style — cd packages/generator-python && PYTHONPATH=src:../validator-python/src python3 -m unittest discover -s tests -p 'test_*.py' (11 tests) and node tests/native-generators/run.mjs --target python (741 passed, exit 0) at this state [o]
C13.1-17-1 Give every function of packages/validator-python/src parameter and return types: the JSON values use the JsonValue alias, the composition loader is the DocumentLoader protocol, the rule context and the expression nodes are dict aliases, and the entry points take Mapping options. The code of the package is unchanged in behavior — MYPYPATH not needed: mypy --strict packages/validator-python/src exit 0 in the scratch virtual environment, and PYTHONPATH=packages/validator-python/src python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' (23 tests) [o]
C13.1-17-2 Give the foundation modules of packages/generator-python/src parameter and return types, with no strict error in them: errors, messages, input_text, numbers, dates and style. The modules keep their behavior — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src reports no error in these six modules, and the generator unit tests pass (11 tests) [o]
C13.1-17-3 Give the modules value, design and template of packages/generator-python/src parameter and return types, with no strict error in them: the absent marker is a class instance, the form path values are the Value alias, the specification objects are dictionaries, and the evaluator results are object in design. The behavior is unchanged — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/{value,design,template}.py reports no error in the three modules, and the generator unit tests pass (11 tests) [o]
C13.1-17-4 Give the modules buttons and choice_list of packages/generator-python/src parameter and return types, with no strict error in them, and their imports for those types. The behavior is unchanged — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/{buttons,choice_list}.py reports no error in the two modules, and the generator unit tests pass (11 tests) [o]
C13.1-17-5 Give the modules rendering and display_declaration of packages/generator-python/src parameter and return types, with no strict error in them: the view models are mappings, the escaping helpers take and return str, and the declaration checks raise FormError through functions that never return. The behavior is unchanged — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/{rendering,display_declaration}.py reports no error in the two modules, and the generator unit tests pass (11 tests) [o]
C13.1-17-6 Give the module widget of packages/generator-python/src parameter and return types, with no strict error in it. A date or date-time value that is not a string is no longer parsed: the widget keeps it as the value of the control, where the module previously failed on it (parse_utc takes a string) — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/widget.py reports no error in the module, and the generator unit tests pass (11 tests) [o]
C13.1-17-7 Give the module lists of packages/generator-python/src parameter and return types, with no strict error in it: the list specification and rows are dynamic mappings and sequences, the composed members are typed as dynamic, and the absent-value checks read the checked value once. The behavior is unchanged — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/lists.py reports no error in the module, the generator unit tests pass (11 tests), and node tests/native-generators/run.mjs --target python reports 741 passed, exit 0 [o]
C13.1-17-8 Give the modules details and form_render of packages/generator-python/src parameter and return types, with no strict error in them. The render options are external input checked by the module, and the record of a detail is typed as a JSON value. The behavior is unchanged — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/{details,form_render}.py reports no error in the two modules, the generator unit tests pass (11 tests), and node tests/native-generators/run.mjs --target python reports exit 0 [o]
C13.1-17-9 Give the module binding of packages/generator-python/src parameter and return types, with no strict error in it. The model objects are built through one typed helper, and the widget and the composed specification are passed as dictionaries where the widget module reads them. The behavior is unchanged — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/binding.py reports no error in the module, the generator unit tests pass (11 tests), and node tests/native-generators/run.mjs --target python reports exit 0 [o]
C13.1-17-10 Give the module form of packages/generator-python/src parameter and return types, with no strict error in it. The record of a form instance is a dynamic mapping, and the copies of record values go through one helper. The behavior is unchanged: the repeated-row entries are narrowed by the check that already rejects other values — MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/form.py reports no error in the module, the generator unit tests pass (11 tests), and node tests/native-generators/run.mjs --target python reports exit 0 [o]
C13.1-17-11 Give the package initializer of packages/generator-python/src (the Generator operations and their helpers) parameter and return types, and check that every module of both Python packages passes mypy --strict with exit 0. The operations take JSON specifications, data and options as values the modules check — mypy --strict packages/validator-python/src and MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src, each exit 0 in the scratch virtual environment; the validator suite (23 tests) and the generator suite (11 tests) pass [o]
C13.1-22 Restore the done marker of the status cell of C13.1-11: a replacement of the words written for the markers in its text also replaced its status cell, which then reads a word instead of the marker — the status cell of C13.1-11 reads the done marker in both checklists, and node scripts/check-documents.mjs passes [o]
C13.1-23 Add the stale-pin check to the version-bump pull request: scripts/release.mjs (replaced by make release-pins, scripts/release-pins.mjs) checks only the version field of each pyproject.toml, so a dependency polyspec-crudui-validator @ git+https://github.com/polyspec/crudui@vX.Y.Z of a repository package that names another version passes. The check names each such pin with its file, the pin and the tag, with a unit test in tests/build/python-git-pins.test.mjs that fails on a pin of the previous version. It is not added to the branch feat/python-C13.1: the pin v0.0.4 of packages/generator-python names a tag that is created only after the version-bump pull request merges (C13.1-24), so the check would reject the branch. Added with the version-bump row C13.1-34 on the branch chore/release-0.0.4 — node --test tests/build/python-git-pins.test.mjs: the test a Python manifest pins this repository at the version of the tag, and another repository at any version passes, and a Python manifest pin polyspec/crudui@vX.Y.Z that is not the release version fails make release-versions [o]
C13.1-24 Document the order of the release steps for Python in the Releases section of AGENTS.md: the maintainer creates the tag vX.Y.Z of a repository package only after the version-bump pull request merges, because the dependency pin of packages/generator-python names that tag — node scripts/check-documents.mjs passes, and the Releases section of AGENTS.md and AGENTS.ko.md states the order [o]
C13.1-25 Run the release unit tests in the CI job build, lint and types: the job runs make ci-targets TARGETS="crudui-test-runtimes crudui-test-dependencies build lint typecheck test-ordered-json kit-test", crudui-test-runtimes runs tests/build/python-git-pins.test.mjs (the release check of the Python pins, make release-pins), and ci-passed keeps needing every job — node scripts/kit/run-tests.mjs node -- tests/build/python-git-pins.test.mjs, and node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs [o]
C13.1-26 Open the pull request of feat/python-C13.1 against main, whose body names the order of the release steps of C13.1-24 — gh pr list -R polyspec/crudui --head feat/python-C13.1 --state open lists one pull request [o]
C13.1-27 Define the names that the annotations of packages/validator-python/src/polyspec/crudui/validator/resolver.py use before their first use, so that Python 3.11 imports the module: the alias Node is imported from parser (one definition) and the alias Context is defined above the annotations that name it. The CI job python failed on 3.11 with NameError: name 'Node' is not defined. Red: the validator suite on Python 3.11 fails at the import of resolver.py. Green: the validator and generator suites pass on Python 3.11 — PYTHONPATH=src python3 -m unittest discover -s tests -p 'test_*.py' in packages/validator-python and in packages/generator-python, on Python 3.11 [o]
C13.1-28 Pin the Python target in the native generator command of the test make-tool-path: make crudui-test-native runs tests/native-generators/run.mjs --target javascript,html,go,rust,python (C13.1-7-3), and the test expected the list without python, so it failed in the CI job build, lint and types. Red: the test fails on the crudui-test-native case. Green: the test passes — node scripts/run-tests.mjs node -- tests/build/make-tool-path.test.mjs [o]
C13.1-29 Name the Python modules by their dotted names in the docstrings of packages/generator-python/scripts/generate_interface_messages.py and packages/validator-python/scripts/generate_unicode_data.py: the docstrings named the src/polyspec/crudui/... paths, which the package-name rule reads as the Composer package form, so the test package-names failed in the CI job build, lint and types. Red: the test names the two lines. Green: the test passes — node scripts/run-tests.mjs node -- tests/build/package-names.test.mjs [o]
C13.1-30 Set up the Node.js release of .node-version in the CI job python and check node python with make toolchain-check: the job runs make ci-targets, which needs Node.js, and the toolchain policy test requires every job to set up and check node. The policy test accepts, beside the recorded Python minor, the lowest minor of requires-python as a matrix leg, because the matrix runs 3.11 (C13.1-7). Red: the test every CI job sets up the recorded toolchains and checks the tools it set up fails with ci.yml python: setup-python must install the recorded minor 3.14 and checks python, sets up node python in the CI job build, lint and types. Green: the test passes — node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs (the test every CI job sets up the recorded toolchains and checks the tools it set up) [o]
C13.1-31 Declare the Python conformance suites: scripts/check-conformance.mjs declares the suites validator Python (packages/validator-python/tests) and generator Python (packages/generator-python/tests), whose run records match the program tests/conformance/runner.py (C13.1-33), and both prove the runtime python (C13.1-6-1). Before this row, no declared suite proved python, so the test every runtime that a feature supports is proven by a declared suite failed in the CI job build, lint and types — node scripts/run-tests.mjs node -- tests/build/conformance-evidence.test.mjs [o]
C13.1-32 Measure a partial runtime by the cases it records: contracts/features.json declares createForm with python: partial (the Python Form has no undo and redo history, which the shared fixtures do not cover), and the native checks of the Python runtime record evidence for the form scenarios of tests/native-generators/cases.mjs. scripts/check-conformance.mjs rejected that evidence as evidence without a declaration (runtime declared partial), so the CI job conformance evidence against the standard failed. The check accepts the recorded cases of a partial runtime: each recorded case must pass, and no case is required (docs/operations/conformance.md, "Evidence"). Red: the test a partial runtime is measured by the cases it records, and each recorded case must pass fails. Green: the test passes — node scripts/run-tests.mjs node -- tests/build/conformance-evidence.test.mjs [o]
C13.1-33 Leave the run record of each Python suite with its program, tool, directory and exit status: tests/conformance/runs.py recorded the program as the name of sys.executable (python3.11 or python3.14 by matrix leg, python3 on a local machine), so no declaration can name it, and finish_suite_run had no caller, so every record kept status null and read did not finish. The runner tests/conformance/runner.py <test directory> discovers the test_*.py files of the directory, records the run with the program tests/conformance/runner.py, the directory and its arguments, and writes the exit status when the suite ends. The make targets crudui-test-validator-python and crudui-test-generator-python run the runner, and tests/conformance/evidence.py no longer records a run at import. Red: the test the Python runner leaves a run record with the exit status of its suite fails. Green: the test passes — node scripts/run-tests.mjs node -- tests/build/conformance-evidence.test.mjs [o]
C13.1-34 Set the version 0.0.4 in the package files of the repository, as the release 0.0.3 (0137999d) did: the package.json, Cargo.toml and pyproject.toml files with their lock entries, the Composer manifests with the requirement of polyspec/crudui-validator, contracts/features.json and the checks that read its version, the module version of packages/php-ext, and the package versions stated in the documents. Rename ## Unreleased of CHANGELOG.md and CHANGELOG.ko.md to ## 0.0.4 below a new empty ## Unreleased — node --test tests/build/release.test.mjs passes after C13.1-35 [o]
C13.1-35 Write the consumer fixtures of tests/release-install for 0.0.4 and regenerate their locks from the archives of HEAD: make release-assets TAG=v0.0.4 RELEASE_COMMIT=HEAD writes the 9 archives to var/release/assets, and make release-install-lock writes the package.json, package-lock.json, composer.json and composer.lock of the fixtures. No tag is created — node --test tests/build/release.test.mjs passes, including the test the consumer fixtures of tests/release-install name the archives of the version of package.json [o]
C13.1-36 Record the dependency review of the 0.0.4 locks: make dependency-review RECORD=1 writes config/dependency-review.json with the sha256 of the 8 locks changed by the version bump and their advisories, none. The review reports 4 newer stable releases without an exception, puppeteer 25.13.0 and playwright 1.64.0 in package.json, and vite 8.3.4 in package.json and packages/generator-svelte/package.json; they are raised by make dependency-review UPDATE=1 in a separate change — node --test tests/build/dependency-review.test.mjs and node --test tests/build/release.test.mjs pass [o]
C13.1-37 Make the npm lock record the repository packages at the release version: the 0.0.4 bump changed the versions of the packages and their manifests, but the dependency entries of package-lock.json still named 0.0.3 (14 entries of root and workspace packages). Red: tests/build/dependency-health.test.mjs "the dependencies match their recorded review" lists the lock as changed after the review. Green: the entries name 0.0.4, and the review record is written again by C13.1-38. package-lock.json, config/dependency-review.json node --test tests/build/dependency-health.test.mjs [o]
C13.1-38 Update the dependencies to their latest stable release and approve their install scripts by exact version: the review found puppeteer 25.13.0, playwright 1.64.0 and vite 8.3.4 newer than the manifests. make dependency-review UPDATE=1 RECORD=1 updates the manifests and the locks and writes the record; npm install-scripts approve names the install scripts of puppeteer@25.13.0 and esbuild (0.27.7 and 0.28.2). Red: the test "tracked npm dependency graphs approve every install script by exact version" fails with ESTRICTALLOWSCRIPTS on puppeteer@25.13.0. Green: the approvals. package.json, packages/*/package.json, config/dependency-review.json, package-lock.json node --test tests/build/dependency-health.test.mjs [o]

Wave 14 — Tools shared through polyspec/kit

ID Task Deliverables Verification State
C14.1 Take the tools that every polyspec repository shares from polyspec/kit v0.0.4 (C14.1-1 to C14.1-6): the repository keeps vendored copies of scripts/kit and tests/kit, removes its own copies of the tools that kit provides, and differs from the other repositories only in config/*.json. The 0.x workflow has no pull request, merge queue or ruleset — make kit-check, make kit-test, make docs-check, make dependency-policy-check, make owner-validate [o]
C14.1-1 Vendor polyspec/kit v0.0.4: kit.json, .kit/kit.lock.json, scripts/kit and tests/kit are the files of the tag, and the Makefile includes scripts/kit/kit.mk. make kit-sync KIT_TAG=v0.0.4 writes the lock once and prints unchanged on the second run — make kit-sync KIT_TAG=v0.0.4, make kit-test [o]
C14.1-2 Replace the toolchain, dependency and test runner tools with those of kit: config/toolchain.json follows the kit schema (Go from packages/validator-go/go.mod, Composer as an object) and make install-tools installs npm, Go and cargo-audit into var/tools; config/dependency-policy.json follows the kit shape (composerPlatforms with PHP 8.4.1 in the root manifest and lock, taggedNpmPackages for the OrderedJSON tag, the four React exceptions) and make dependency-review RECORD=1 records the review of the npm, Composer and Cargo locks and the six Go modules; scripts/kit/run-tests.mjs replaces scripts/run-tests.mjs and scripts/test-progress, and tests/conformance/run-suite.mjs leaves the run record of the suites that record conformance evidence. Removed: install-npm, checkout-npm, check-toolchain, check-cargo-downloads, install-cargo-audit, dependency-state, dependency-review, check-dependencies, run-tests, test-progress, tracked-files and their tests — make dependency-policy-check, make kit-check, `node scripts/kit/run-tests.mjs node -- tests/build/tool-path.test.mjs tests/build/offline-checks.test.mjs tests/build/dependency-health.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/conformance-evidence.test.mjs tests/build/lint-coverage.test.mjs tests/build/repository-files.test.mjs [o]
C14.1-3 Replace the push check, the guard of the full run, the holder lock and the CI report tools with those of kit: config/checklist.json declares the checklist and the hook pre-push, .githooks/pre-push is the hook that make hooks writes, make ci passes conformance-reset and CI_TARGETS (make targets) to scripts/kit/full-run.mjs, and scripts/package-dist.mjs and the comparison servers take their locks from scripts/kit/holder-lock.mjs. Removed: push-gate, full-run, holder-lock, ci-targets, ci-passed, target-report and their tests — make hooks-check, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/shared-resources.test.mjs tests/build/make-dry-run.test.mjs tests/build/offline-checks.test.mjs tests/build/test-commands.test.mjs [o]
C14.1-4 Replace the document check with that of kit: config/documents.json selects every Markdown document except the vendored tests of kit and two files without a Korean twin, the status table of docs/features.md and CHANGELOG.md; every English document and its Korean twin hold the same doc-id, and the Korean twin holds the source-sha256 of the English file; config/commits.json declares the commit message rules; the generator of the feature contract pages writes both markers. Removed: scripts/check-documents.mjs, scripts/checklist-markers.mjs and its test. Four documents are corrected: the cross-check console README (four renderers, one code block), the Korean form markup link, the Korean release tag command and the links of tests/build/README — make docs-check, node scripts/kit/run-tests.mjs node -- tests/build/contract-manifest.test.mjs tests/docs/repository-writing.test.mjs tests/docs/changelog.test.mjs [o]
C14.1-5 Replace the release tool and the GitHub settings tool with kit and the 0.x workflow: config/release.json lists the 9 archives (7 npm packages named <package>-npm-<version>.tgz, 2 Composer packages named <package>-php-<version>.zip), the manifests of each mode, the two Go modules packages/generator-go and packages/validator-go with their tags <directory>/vX.Y.Z, the consumer projects of tests/release-install and the git-tag proof of the two crates and two Python packages; make release-assets builds first; release.yml runs make release-consumer; ci.yml runs on a push to main, push-gate.yml on every push; the ruleset, the merge queue, .github/repository.json, scripts/github-repository.mjs, scripts/release.mjs and scripts/release-install.mjs are removed; tests/build/python-git-pins.test.mjs keeps the check of the git pins of pyproject.toml — make release-coverage, make release-versions TAG=v0.0.4, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs tests/build/python-git-pins.test.mjs [o]
C14.1-6 Replace the owner check with that of kit and state the rules of the 0.x workflow: config/owner-checks.json (checked by make owner-validate) replaces scripts/owner-checks.json, with owners for the vendored copy, the configuration files and scripts/repository-files.mjs; AGENTS.md and AGENTS.ko.md state that main is pushed once when every row is done, that a tag follows the CI check ci-passed, that the shared tools are changed only in polyspec/kit and that this repository differs only in config/*.json, and drop the pull request, merge queue and ruleset rules. Removed: scripts/owner-check.mjs, scripts/owner-checks.json and the test of the owner check — make owner-validate, make docs-check, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/test-commands.test.mjs [o]
C14.1-7 Vendor polyspec/kit v0.0.8, which fixes the mutation check of the dependency policy, the root manifests of the policy schema and the selection of tools by make install-tools TOOLS, and adds make documents-stamp. make kit-sync KIT_TAG=v0.0.8 writes the lock and prints unchanged on the second run — make kit-sync KIT_TAG=v0.0.8, make kit-check [o]
C14.1-8 Use the fixes of kit v0.0.8: composerPlatforms names composer.json and pythonManifests names the manifests of packages/validator-python and packages/generator-python (their setuptools==84.0.0 pin joins the review, 87 registry dependencies), make dependency-policy-mutation-check passes, and every workflow job installs only the tools it uses (make install-tools TOOLS="npm"; npm cargoAudit after make install-rust in the dependency review job), so no job installs Go or cargo-audit it does not use — make dependency-policy-check, make dependency-policy-mutation-check, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/form-comparison-ci.test.mjs [o]
C14.1-9 Start push-gate.yml on branch pushes only (push: branches: ['**']): the push of a tag started it and left a push-gate run that had not completed on the tagged commit, which make release-verify rejects. Red: the test only the release workflow starts on a tag push of tests/build/ci-local.test.mjs fails for push-gate.yml. Green: it passes, and release.yml is the only workflow that starts on a tag — node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs [o]
C14.1-10 Name the hook module of kit in the test of the local pipeline stack: examples/form-comparison/src/local-servers.test.mjs required pipeline.browser.mjs and record-stores.test.mjs to import setup and teardown from scripts/test-progress/hooks.mjs, which C14.1-2 replaced by scripts/kit/test-hooks.mjs in the files but not in the pattern of the test, so CI job Form comparison runner regressions failed with ../pipeline.browser.mjs uses setup and teardown. Red: the test fails for the module of kit. Green: npm run test:form-comparison:source and :library pass — node scripts/kit/run-tests.mjs node -- examples/form-comparison/src/local-servers.test.mjs, npm run test:form-comparison:source [o]
C14.2 Release 0.0.5: set the version 0.0.5 in every manifest that config/release.json lists with its locks (package-lock.json, composer.lock, the six Cargo.lock files), in the git pin of packages/generator-python, in the version of contracts/features.json, in the module version of packages/php-ext and in the checks that read them; rename ## Unreleased of CHANGELOG.md and CHANGELOG.ko.md to ## 0.0.5 below a new empty ## Unreleased; record the dependency review; write the consumer projects of tests/release-install for the archives of 0.0.5 with make release-assets and make release-consumer-lock on local tags that are deleted afterwards. No tag is pushed — make release-versions TAG=v0.0.5, make release-consumer TAG=v0.0.5, make release-go-tags TAG=v0.0.5, make dependency-policy-check [o]
C13.1-23-1 Give scripts/release-pins.mjs an owner in config/owner-checks.json: the rule of the check added with C13.1-23 left the script without an owner, so make owner-validate failed. The rule names the target release-pins and the test tests/build/python-git-pins.test.mjs. Red: make owner-validate exits 2 with scripts/release-pins.mjs: the path matches no owner. Green: it exits 0 — make owner-validate [o]
C14.3 Take the shared tools as the package @polyspec/kit and apply the consumer migration of kit (kit K79): the vendored scripts/kit, tests/kit, kit.json and .kit/kit.lock.json are removed; the Makefile includes the copy scripts/kit/kit-bootstrap.mk, every recipe runs a kit tool as $(KIT)/<tool>.mjs, make check runs CHECK_TARGETS through the guard of kit, and every workflow job runs make install-tools TOOLS="npm" and make install-packages first; code imports @polyspec/kit/<module> and resolves its own root; the copies of kit.copies, config/layout.yml and the declared tools of config/toolchain.json are added; the floors are Node.js 26.11.1, PHP >=8.5 with the platform 8.5.0, Python >=3.14, Go 1.27.2 without toolchain lines, Rust 1.99.0 and TypeScript 7.0.2, with the exception of TypeScript 6.0.3 in packages/generator-svelte; the Go modules resolve each other through go.work instead of replace; .npmrc sets strict-allow-scripts; the tests of kit behavior move to kit. Red: the new cases of tests/build/offline-checks.test.mjs, tests/build/repository-files.test.mjs and tests/build/test-commands.test.mjs fail on the vendored tree. Green: they pass with the package installed — make kit-check, make owner-validate, make docs-check, node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/offline-checks.test.mjs tests/build/repository-files.test.mjs tests/build/test-commands.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/ci-local.test.mjs [o]
C14.3-1 Run the Vitest suites of generator-core, form-binding and cli from the root (found with C14.8): their package scripts ran the run-tests of kit with --cwd packages/<name> from the package folder, where kit takes its root from the working directory, so npm test -w failed with spawn node ENOENT. The package scripts are removed; make crudui-test-cli, make crudui-test-form-binding, test:forms and the verification commands of contracts/features.json run run-tests.mjs vitest --cwd packages/<name> from the root. — node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/test-commands.test.mjs, make manifest-check [o]
C14.4 Lint the TypeScript and JavaScript with make lint-ts of kit instead of ESLint: make lint runs lint-ts (oxlint with the copied .oxlintrc.json, knip and API Extractor) and the Svelte lint of packages/generator-svelte, which keeps ESLint with eslint-plugin-svelte and typescript-eslint for its components only; the root eslint.config.mjs, the ESLint packages of the root and of the other workspaces, their lint scripts and the rule crudui/no-await-after-test-registration are removed (kit runs node:test without --test-force-exit, the cause of that rule). Red: tests/build/lint-coverage.test.mjs fails, since make lint runs npm run lint. Green: make lint runs both lints, and each fails on a planted violation — node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/lint-coverage.test.mjs tests/build/test-commands.test.mjs, make owner-validate [o]
C14.5 Check and generate the TypeScript API with API Extractor and API Documenter instead of TypeDoc: each of the seven TypeScript packages has an api-extractor.json that extends the copied config/api-extractor.kit.json (ae-undocumented and ae-forgotten-export are errors) and commits its report etc/<package>.api.md; make lint-ts runs it, so scripts/check-doc-coverage.mjs checks Go, Rust and PHP only; scripts/gen-api-docs.mjs writes the doc model to var/api-model/<package> and the pages with API Documenter; typedoc, typedoc-plugin-markdown and their configurations are removed. With TypeDoc and typescript-eslint gone, the root declares TypeScript 7.0.2, and npm nests TypeScript 6.0.3, svelte-check, @sveltejs/package, svelte2tsx and typescript-eslint in packages/generator-svelte. Red: scripts/check-doc-coverage.test.mjs and scripts/gen-api-docs.test.mjs fail without the configurations. Green: they pass, and API Extractor fails on a planted undocumented export — node node_modules/@polyspec/kit/dist/run-tests.mjs node -- scripts/check-doc-coverage.test.mjs scripts/gen-api-docs.test.mjs, make owner-validate [o]
C14.6 Rewrite config/dependency-review.json with the review of kit (red recorded by K79 on kit 0.0.38): make kit-check fails with config/dependency-review.json: / must have required property 'vcs', and make dependency-review RECORD=1 stops at examples/cross-check-console/validators/go/go.mod requires modules and examples/cross-check-console/validators/go/go.sum is missing, a Go module that resolves its siblings through go.work, which kit reads from 0.0.39 (kit K129). — make dependency-review RECORD=1, make kit-check [o]
C14.7 Make the folders follow S5, S7 and S9 of kit's layout (red recorded by K79 on kit 0.0.38): make layout-check reports 161 findings, the first schema: error: layout-root-folders: S5: the root holds only packages/, contracts/, docs/, scripts/, tests/, examples/, benchmarks/ and config/. — make layout-check [o]
C14.8 Take the stable releases that the dependency review names (found by kit K131): make dependency-policy-check reports 20 findings, the first packages/generator-vue/package.json vitest 5.0.3 < 5.0.4: a newer stable release exists. — make dependency-review UPDATE=1, make dependency-policy-check [o]
C14.9 Move to kit 0.0.40 and its mise toolchain (kit K131): the pin with the integrity of the published file; mise.toml declares Go, Python with uv, ruff, vulture, alint, golangci-lint, deadcode, cargo-audit and Composer at the pins of kit, mise.lock locks them, and config/toolchain.json keeps the release of mise, PHP, Node.js and the TypeScript exception; .go-version and .python-version are removed; CI installs those tools with make install-tools instead of setup-go, setup-python and the Composer of setup-php, and the Go module cache moves to actions/cache; the sprintf-js advisory that kit brings is accepted as kit accepts it. — make kit-check, make toolchain-check, make docs-check, make owner-validate, node --test tests/build/runtime-version-policy.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs [o]
C14.10 Make the tool tests of tests/build pass on a built checkout (found by kit K131): beside the packages that a checkout has not built (dist, vendor, the bench programs), they fail on the TypeScript JavaScript API that TypeScript 7 lacks (Cannot read properties of undefined (reading 'ReactJSX') and 14 more), on the vendored paths of the old kit (node scripts/kit/full-run.mjs rerun-failed, scripts/kit/run-tests.mjs cargo), on a Composer reinstall outside the checkout lock in examples/form-comparison/src/local-servers.mjs, and on the crate name crudui-validator in scripts/gen-api-docs.mjs:58. — node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build [o]
C14.10-1 Read the value exports of the contract manifest from the built ES module file of each package.json entry with es-module-lexer, following each export * from with Node's own resolution, instead of typescript/unstable/sync, its virtual file system and the .svelte declaration stub; npm run manifest:check builds the packages first (C14.10). — node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/contract-manifest.test.mts, make manifest-check [o]
C14.11 Move to kit 0.0.42: the pin with the integrity of the published file; make toolchain-lock locks the tools of mise.toml for linux-arm64 and macos-x64 too (kit K134), and the copied bootstrap writes var/tools/go.mod, so ./... of a Go module of the checkout does not walk the Go that mise installs (kit K135). — make kit-check, make toolchain-check, make owner-validate, node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs [o]
C14.12 Make make lint-ts pass (found with C14.10): oxlint reports about 700 findings, 516 of them typescript(no-floating-promises) on the calls of node:test, knip about 230, and API Extractor fails in all seven packages, mostly on 187 ae-missing-release-tag warnings. The node:test and release-tag findings are gaps of kit, which kit 0.0.43 closes; the rest is fixed in the code, with real dead code removed. — make lint-ts [o]
C14.13 Move to kit 0.0.44 (kit K138, K139, K142–K147, K149–K151, through 0.0.43): the pin with the integrity of the published file; make kit-sync copies the bootstrap, .oxlintrc.json that allows the registrations of node:test, knip.json with the entries of the layout, config/api-extractor.kit.json without ae-missing-release-tag, ruff.toml with the cache in var/ruff and config/layout.kit.yml with the additive root files, so config/layout.yml names only .npmrc, composer.json and composer.lock in vars.root_files; make install-tools installs the Rust toolchain of rust-toolchain.toml. The review of the new lock takes the latest stable releases: puppeteer 25.14.0, playwright 1.64.1 and tsup 9.0.0. Red: the CI run 38571337025 of the pushes failed toolchain-check with toolchain '1.99.0-x86_64-unknown-linux-gnu' is not installed, and Pages run 38571336972 failed API Extractor on ae-missing-release-tag. — make kit-check, make toolchain-check, make layout-check, make dependency-policy-check, make build, make crudui-test-build, make crudui-test-packages [o]
C14.14 The Pages workflow builds the documentation web: scripts/docs-web/build.mts takes the API pages of API Documenter as they are, whose title is their first heading of any level, and keeps the rule of exactly one level-one heading for the written pages; the generated markdown is not rewritten. Red: Pages run 38594695043 failed docs-web with Documentation requires exactly one level-one heading: docs/api/crudui-form-binding/crudui-form-binding.bindform.md, red since C14.5 replaced the API pages with API Documenter. — make docs-web [o]
C14.15 The rule parameters of equalTo, notEqual, enddate and unique are checked when the specification loads, in every language, with a spec sentence and shared vectors; the TypeScript rules then read the checked string instead of String(ruleParam). Red: oxlint no-base-to-string in equalTo.ts and enddate.ts; a non-string parameter reached the rule. — the shared vectors in every language, make lint-ts [o]
C14.16 The defects that the oxlint findings of C14.12 showed are fixed, each with a test that failed before: the TypeScript ==, != and in of an array or object, the values that connectForm writes into the controls, the dispose of the Svelte adapters of the form comparison and the stop of its PHP server. — the tests of each defect [o]
C14.17 The checks that the CI jobs reach once they run past toolchain-check (C14.13) pass: test:forms runs the Vitest suite of form-binding from the root, whose package script C14.3-1 removed; the PHP engine test names the build entry point scripts/build-crudui-php-extension.mts and the local-servers test the import @polyspec/kit/test-hooks of the TypeScript port (C14.7). Red: CI run 38597830858: npm error Missing script: "test" of @polyspec/crudui-form-binding, The input did not match the regular expression /node scripts\/build-crudui-php-extension\.mjs/, ../pipeline.browser.mjs uses setup and teardown. — make crudui-test-forms, make crudui-test-php-engine, make crudui-test-form-comparison [o]
C14.18 The Go modules of crudui resolve without the ignored clone of ordered-json: go.work uses only the modules of the checkout; each workspace module requires another at the pseudo-version of a pushed commit of main (go mod edit -require with the version that go list -m resolves), since C14.7 moved them to packages/{validator,generator}/go, where no tag exists yet; the form-comparison Go server requires github.com/polyspec/ordered-json/go v0.0.3; make install downloads the Go modules with go mod download, and the checks stay offline. The next crudui release replaces the pseudo-versions with the tags packages/{validator,generator}/go/vX. Red: with kit 0.0.45, make dependency-policy-mutation-check failed on the use of .form-comparison/sources/ordered-json/go, and on 0.0.44 with benchmarks/validators/go/go.mod requires modules and benchmarks/validators/go/go.sum is missing. — make dependency-policy-mutation-check, the Go builds and tests [o]
C14.19 Move to kit 0.0.45 (kit K152–K154): the pin with the integrity of the published file; make kit-sync copies knip.json, which ignores the binaries of the pinned runtimes, and config/knip-entries.json declares the files that make, a test, a page or a tool runs by path, and the workspaces packages/svelte, packages/svelte/tools, packages/form-binding and the cross-check server with their entries and ignored dependencies; the dependencies that knip found unused are removed and the unlisted ones declared; make dependency-review RECORD=1 records the lock. — make kit-check, make dependency-policy-check, make dependency-policy-mutation-check [o]
C14.20 The form comparison resolves its aliases through the standard subpath imports of the root package.json, which knip, Node, esbuild and Vite read: #adapter and #stage are conditional imports whose condition each frame and stage build passes (resolve.conditions), #html, #react/*, #vue/* and #svelte/* map to the sources, and the Vite aliases are gone; the benchmark console imports the sources and build.mjs bundles it, and record-view.mjs lives in public/, so every import resolves in the source tree and when served. Red: knip reported 41 unresolved imports in examples/form-comparison. — make lint-ts (knip), the form-comparison build and its tests [o]
C14.21 The benchmark console stops a capture and a comparison while the pointer is over a comparison frame, as docs/operations/form-comparison.md requires: no page had called pointerOverFrame since d5fd5ca3. Red: the Chromium test of the guard failed with requirePointerOutside is not a function, and the wiring test of check-interaction failed. — src/browser-job.browser.mjs, check-interaction.test.mjs [o]
C14.22 The authoring types of docs/spec/schema.md are public: the entry of @polyspec/crudui-validator exports FieldSpec, RangeFieldSpec, DesignSlot, Items, Multiple, Lang, ConditionMap and every type that they name, each with TSDoc, and the API report etc/crudui-validator.api.md lists them; ae-undocumented stays an error. Cause: docs/spec/schema.md:66 and :84-110 make schema.ts the authoring types, and the entry exported none of them. — API Extractor of packages/validator/ts, the type tests of types.roundtrip.test.ts [o]
C14.23 docs/operations/form-comparison.md describes the served modules as C14.20 builds them: the modules of public/ are served as they are with browser-resolvable imports, and the build bundles the benchmark console with the source modules it imports (browser-job.mjs, frame-readiness.mjs, frame-pointer.mjs, runtime-paths.mjs, the snapshot module), so no source module is copied to the public directory; the Chromium case of src/browser-job.browser.mjs loads the modules of public/ instead of frame-readiness.mjs of src/. Cause: the spec (lines 305-307) and that case still described the modules copied to the public directory, which C14.20 replaced with the bundle. — src/browser-job.browser.mjs, make docs-check [o]
C14.24 The type assertions of the TypeScript validator run: its Vitest configuration enables the typecheck mode of Vitest (test.typecheck), which checks the expectTypeOf assertions of src/*.test-d.ts with tsc and the tsconfig.json of the package, and the assertions of types.roundtrip.test.ts move to src/types.test-d.ts. Cause: a runtime Vitest run does not evaluate expectTypeOf, and the package tsconfig.json excludes the test files, so nothing checked them. Red: one wrong assertion fails the Vitest run. — the Vitest suite of packages/validator/ts on src/types.test-d.ts and src/types.roundtrip.test.ts [o]
C14.25 ==, != and in compare two arrays or two objects alike in every port: docs/spec/expressions.md defines JSON-value structural equality (the same type; for arrays the same elements in order, for objects the same key set with equal values; numbers by value; an array never equals an object) and the text of a boolean, true or false, in a string comparison; shared vectors of tests/fixtures/expr check both, and TypeScript, PHP, Go, Rust, Python and the C engine of the PHP extension pass them. Cause: two arrays or two objects compared by identity in TypeScript and Python, by structure in PHP, Rust and C and never equal in Go, and the C engine wrote a boolean as 1 or the empty text. — the expression conformance of TypeScript, PHP, Go, Rust and Python, the C expression fixture test of packages/php-ext/tests/engine.test.mjs [o]
C14.26 Remove make install-rust, which duplicates make install-tools of kit since K151 (kit installs the Rust toolchain of rust-toolchain.toml with rustup): the target, its place in make install, the steps of .github/workflows/ci.yml, dependency-review.yml and pages.yml, whose Cargo cache steps follow make install-tools so the toolchain exists for their key, the documents and the tests that name it; tests/build/runtime-version-policy.test.mts counts the Rust toolchain among the tools of make install-tools and fails on a step that runs make install-rust. — node --test tests/build/runtime-version-policy.test.mts tests/build/form-comparison-ci.test.mts, make docs-check [o]
C14.27 Every kit lint target of CHECK_TARGETS passes: make lint-python (ruff check, ruff format, vulture), make lint-php (phpcs, PHPStan), make lint-rust (cargo clippy with -D warnings) and make lint-go (golangci-lint, deadcode) report no finding after make install-tools and the Composer install; each finding is fixed in the code, with no disable and no per-file ignore, and an #[expect(..., reason)] only where the lint is wrong, with its concrete reason. The build-lint job of .github/workflows/ci.yml runs every lint target of CHECK_TARGETS that no CI job ran. The cast of JSON.rawJSON in tests/fixtures/expr/generate.ts stays only if no standard lib entry of TypeScript 7 declares it, with its reason. make lint-ts stays red only on kit K159 and K161; make lint-php only on the PHPStan step that asks for a vendor/autoload.php in each package, which kit K163 (0.0.46) replaces with the autoloader of the nearest ancestor, and on the PSR-1 findings and the spl_autoload_register of the PHP programs outside the packages, which C14.30 restructures; make lint-python only on the TID251 and E402 of the sys.path changes, which C14.29 removes; make lint-go only on staticcheck ST1005, since the error strings of crudui are the messages of the specification that every port returns as they are, which kit K164 (0.0.46) turns off. Red: lint-python, lint-php, lint-rust and lint-go each failed. — make lint-python, make lint-php, make lint-rust, make lint-go [o]
C14.28 No compiled program is tracked and the Go builds write into var/: the three Mach-O executables named go that 0ca0ca2f (C14.3) committed in examples/cross-check-console/validators/go/, examples/form-comparison/servers/go/ and tests/native-generators/programs/go/ go, which a bare go build in each module wrote and no command of the repository writes; build:go of the cross-check console builds its validator to var/build/cross-check-console/go-validate and benchmarks/validators/drivers.mjs the Go benchmark driver to var/build/benchmarks/polyspec-crudui-bench, so the .gitignore lines of the two old paths go. tests/build/tracked-files.test.mts fails on a tracked file that starts with the magic number of a Mach-O, ELF or PE executable. Red: the case named the three files. — node --test tests/build/tracked-files.test.mts, node benchmarks/validators/build-drivers.mjs, npm run build:go of the cross-check console [o]
C14.29 The Python suites run against the installed packages (S3): the root pyproject.toml is a uv workspace of packages/validator/python, packages/generator/python and tests/conformance/python, whose [tool.uv.sources] takes the validator of the workspace for the git pin of the generator, with its uv.lock; make crudui-install-python makes var/python with make python-venv of kit and syncs the workspace into it; the suites run as the package tests with relative imports under run-tests python --venv through tests/conformance/run-suite.mts, the evidence helpers are the package polyspec.crudui.conformance of tests/conformance/python, and tests/conformance/runner.py, runs.py and evidence.py go; the native generator program and the Python program of the cross-check console import their modules without sys.path. tests/ordered-json and make test-ordered-json go: they drove the registry and adapters of an OrderedJSON checkout, which the parity check and tests of OrderedJSON own (R7); the consumer tests of the record servers (json.test.mjs, test-json.php, the Rust JSON tests and the new json_test.go of the Go server) cover crudui's use of the packages. Red: ruff check reported 20 TID251 and one E402, and each suite failed under the new command. — make lint-python, make crudui-test-validator-python, make crudui-test-generator-python, go test of the Go record server [o]
C14.30 The PHP programs outside the packages follow S2 and S3: the classes of examples/form-comparison (form-shape.php, generation.php, json.php, records.php, repository.php, request-body.php) are PSR-4 classes in a namespace under Polyspec\Crudui\, one class to a file, and its test-*.php scripts and packages/php-ext/tests/api.php become PHPUnit tests; every other PHP entry script (api.php, the two generate-*.php generators, scripts/php-doc-coverage.php, benchmarks/validators/bench-php.php, the PHP programs of the cross-check console and the native generators) declares no symbol beside its side effects; scripts/php-package-autoload.php registers no autoloader (PHPStan, S2). The PHP packages link through Composer path repositories with the default symlink, OrderedJSON PHP is the release zip of config/release-assets.json from an artifact repository, scripts/php-package-autoload.php goes, and in php-ext mode every class under Polyspec\Crudui\ but FormComparison must be internal (ReflectionClass::isInternal()). The php-ext legs of the PHPUnit suites of the form comparison and of packages/php-ext wait on the repeatable --php-extension of kit K165, and make lint-php on kit K163; phpcs and PHPStan with the root autoloader report nothing. Red: phpcs reported 23 PSR-1 findings (6 MissingNamespace, 17 SideEffects) and PHPStan the spl_autoload_register of scripts/php-package-autoload.php. — make lint-php (with kit K163), the PHPUnit suites of the PHP packages and of the form comparison, make crudui-test-php-api [o]
C14.31 The ten TypeScript fixture generators of tests/fixtures (compose, detail-render, expr, form-outline, form-render, list-render and the four modules of validate) are type-checked scripts under S9, with no cast, no any and no @ts-expect-error. Those of detail-render, list-render, form-outline and validate are .mts files on the public package entries, which the include of the root tsconfig.json checks in make typecheck, run by CI; they run with node. Only package code imports an internal entry (contracts/features.json), so the generators that need package internals belong to the packages that own them (R7): those of compose and expr are packages/validator/ts/scripts/generate-{compose,expr}-cases.mts on the validator's internal entry, which exports Lexer, Parser, ParseError and MAX_EXPRESSION_DEPTH, and tsconfig.scripts.json checks them with the root options in the validator's typecheck; the scenarios of form-render join packages/react/scripts/generate-form-render-cases.mts, the one script that writes tests/fixtures/form-render/cases.json and keeps the order of its cases. tests/fixtures/json-raw.d.mts declares JSON.rawJSON for both callers. No generator imports a package source by path, so the root options do not apply to package sources. Each regenerated cases.json is byte-identical. Red: no tsc checked the generators, and tsc over them under the root options reported 555 errors, 42 in the generators and the rest in the package sources they imported. — make typecheck, make manifest-check, each generator run with git diff --exit-code of its cases.json [o]
C14.31-1 The case order of tests/fixtures/form-render/cases.json comes from the sources of packages/react/scripts/generate-form-render-cases.mts alone: the scenarios of scenario-form-render-cases.ts in their order, then the widget cases and the written cases in their order, a widget or written case taking the place of the scenario of the same name; the generator does not read cases.json. The reordered cases.json is committed, a second run writes it byte-identical, and the suites that read it pass (C14.31). Red: the generator kept the order of the committed cases.json, so its output depended on the file's history. — a second generator run with git diff --exit-code, the form-render suites of React, Vue, Svelte, HTML, Python and Rust and tests/form-markup/naming.test.mts [o]
C14.32 Move to kit 0.0.46 (kit K155, K157–K168): the pin with the integrity of the published file; make kit-sync copies .oxlintrc.json, .golangci.yml, the bootstrap and config/layout.kit.yml, which allows the root composer.json and composer.lock of a Composer monorepo, so vars.root_files of config/layout.yml drops them (K167); make dependency-review RECORD=1 records the lock. — make kit-check, make layout-check, make dependency-policy-check, make dependency-policy-mutation-check [o]
C14.33 The php-ext leg of the PHPUnit suite of the form comparison runs (named pending by C14.30): the local verification runs the suite of servers/php a second time, in the php-ext mode, with --php-extension of the run-tests of kit for ordered_json.so and crudui.so (kit K165) and without the group php, which constructs the PHP library. The child process helper PhpProcess of tests/php, which that PHPUnit process loads, moves from Polyspec\Crudui\TestSupport\ to Polyspec\CruduiTestSupport\, outside the namespace whose classes a php-ext run requires internal. The suite of packages/php-ext already loads the module in child processes through PHP_EXTENSION and needs no leg. Red: examples/form-comparison/check-verification.test.mjs failed on the missing stage php-ext-mode; in the php-ext mode the suite failed 8 of 21 tests: the 6 tests of the group php and 2 constructions with A CRUDUI class loaded from PHP source in the PHP extension process: Polyspec\Crudui\TestSupport\PhpProcess. — node node_modules/@polyspec/kit/dist/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs, the suite of servers/php in the php and php-ext modes, the PHPUnit suite of packages/php-ext, make lint-php [o]
C14.34 The form comparison takes OrderedJSON 0.0.9, the published release, in every language instead of 0.0.3: the Composer package polyspec/ordered-json 0.0.9 from the release zip polyspec-ordered-json-php-0.0.9.zip that config/release-assets.json pins with its sha256 (the archive names the language since 0.0.6); the Go module github.com/polyspec/ordered-json/packages/go v0.0.9 (go get; the module path names its folder since T1.42 of OrderedJSON); the Rust crate of the Git tag v0.0.9 (cargo update); and the checkout of the tag v0.0.9, whose packages lie in packages/, for the npm link packages/js and the extension source packages/php-ext/src. make dependency-review RECORD=1 records the locks and reports no newer stable release. Red: make dependency-review reviewed OrderedJSON 0.0.3 while 0.0.9 was published, and tests/build/dependency-health.test.mts failed once the checkout moved while taggedNpmPackages named v0.0.3. — node node_modules/@polyspec/kit/dist/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs examples/form-comparison/check-verification.test.mjs examples/form-comparison/src/local-servers.test.mjs tests/build/dependency-health.test.mts, the PHPUnit suite of servers/php in the php and php-ext modes, go test of the Go server, cargo test --locked of the Rust server, make lint-go, make lint-php, make dependency-policy-check [o]
C14.35 Move to kit 0.0.47 (kit K156, K170): the pin with the integrity of the published file; make kit-sync copies .oxlintrc.json, which fails a non-null assertion and applies the type-aware rules to TypeScript files only; make dependency-review RECORD=1 records the lock and reports @sveltejs/package 3.0.0 < 3.0.1 of packages/svelte/tools, which make dependency-review UPDATE=1 takes. — make kit-check, make dependency-policy-check, make dependency-policy-mutation-check, the build of packages/svelte [o]
C14.36 Move to kit 0.0.48 (kit K169, K171): the pin with the integrity of the published file; the Node.js release moves from .node-version to node = "26.12.0" of mise.toml, which make toolchain-lock locks, .node-version and node of config/toolchain.json are removed, engines.node of every package.json is >=26.12.0, each recipe of the Makefile starts a tool of the checkout by the variables of kit.mk or an absolute path, CI's setup-node reads mise.toml, and the actions of the workflows are pinned by full SHA; make kit-sync copies the bootstrap and release.yml. — make kit-check, make toolchain-check, make layout-check, make lint-ts, ci-passed of main [o]
C14.37 Publish a decoder of the JSON of a compiled form template from @polyspec/crudui-generator-core, so that a client such as the GUI of fsm reads the template of its page without a cast: decodeFormTemplate(value) checks the parsed value against the new contracts/schema/form-template.schema.json with ajv's compile<FormTemplate> and throws a FormTemplateError (INVALID_FORM_INPUT) with the JSON Pointer of the first invalid member; TSDoc, the API report, the contract manifest and docs/spec/form-runtime.md name it. — packages/generator/ts/src/decode-template.test.ts, API Extractor of the package, npm run manifest:check [o]
C14.38 decodeFormTemplate validates with ajv's standalone validation code of contracts/schema/form-template.schema.json, which scripts/generate-form-template-validator.mts writes to packages/generator/ts/src/form-template-validator.mjs (make form-template-validator) and make build checks (make form-template-validator-check), as template T25.6-2 does: @polyspec/crudui-generator-core imports no ajv and compiles no code at run time, so a Content-Security-Policy without unsafe-eval allows it, and ajv is a development dependency of the package; the pointer and the errors are those of C14.37. — packages/generator/ts/src/decode-template.test.ts, make form-template-validator-check [o]
C14.39 packages/cli has a strict tsconfig (the options of the root tsconfig.json with noUnusedLocals, noUnusedParameters and noImplicitReturns) over its sources and tests, which its typecheck script and so make typecheck run; the code passes it without casts. — make typecheck, the vitest of packages/cli [o]
C14.40 The test of packages/generator/ts/src/form.test.ts imports the shared scenario tests/fixtures/form-session/scenario.mts without @ts-expect-error: the tsconfig of the package, which checks the sources and tests, reaches the repository root and allows the .mts extension, as the tsconfig of packages/react does, and tsconfig.build.json keeps src as the root of the declarations. — make typecheck, the vitest and the build of packages/generator/ts [o]
C14.41 Move to kit 0.0.51 (kit K172–K192, through 0.0.49 and 0.0.50): the pin with the integrity of the published file; make kit-sync copies kit-bootstrap.mk, which defines $(NODE) before the package is installed (K185), so crudui-install-ordered-json starts node by $(NODE) again, and knip.json without ignoreBinaries, so config/knip-entries.json declares the binaries go, php and make that the scripts and tests run (K176); the generated form template validator leaves its oxlint-disable header for an ignore entry of config/knip-entries.json, which keeps it out of knip and oxlint (K172); make dependency-review UPDATE=1 takes es-module-lexer 3.0.4 and make dependency-review RECORD=1 records the lock. — make kit-check, make toolchain-check, make layout-check, make lint-ts, make dependency-policy-check, ci-passed of main [o]
C14.42 crudui describe reads the token table of docs/spec/expressions.md without dropping or cutting a row: it matched each line with exec(line) ?? [] and skipped a row of another shape, and its greedy pattern cell cut the LPAREN RPAREN row at the unescaped || of its example, which GFM splits into cells too, so the pattern read ( ) | (.a |. Every row after the header and the separator is now a token row whose cells escape |, or describe throws naming the row, and the doc escapes that ||; the precedence list keeps its filter of the numbered items, now explicit, and a missing section throws instead of reading as ''. The toolchain test names a tool that mise.toml does not declare (recorded.node ?? '') instead of matching ''. Red: describe gave LPAREN RPAREN the pattern ( ) | (.a |; green: ( ) and 17 tokens — make crudui-test-cli, make docs-check, tests/build/runtime-version-policy.test.mts [o]
C14.43 Release 0.0.6: set the version 0.0.6 in every manifest that config/release.json lists with its locks (package-lock.json, the composer.lock files, the six Cargo.lock files), in the git pin of packages/generator/python, in the version of contracts/features.json, in the module version of packages/php-ext and in the checks and documents that read them; rename ## Unreleased of CHANGELOG.md and CHANGELOG.ko.md to ## 0.0.6 below a new empty ## Unreleased; record the dependency review; write the consumer projects of tests/release-install for the archives of 0.0.6 with make release-assets and make release-consumer-lock; create the local tags v0.0.6, packages/validator/go/v0.0.6 and packages/generator/go/v0.0.6 at the release commit, which the maintainer pushes. The Go workspace modules keep their pseudo-version requirements in this release — make release-versions TAG=v0.0.6, make release-consumer TAG=v0.0.6, make release-go-tags TAG=v0.0.6, make dependency-policy-check [o]
C14.43-1 v0.0.6 has no published release: its release proof (run 38699087245) failed on the check of kit that compared the release URL of the sibling wheel in the Requires-Dist of the generator with the URL that the draft lists, which GitHub gives as untagged-… until the release is published; kit K195 fixes the check. The draft release v0.0.6 is deleted and its tag stays; 0.0.7 supersedes it. — gh release view v0.0.6 reports no release [o]
C14.44 Release both Python packages as wheels (kit K29), which removed the Python git-tag kind of proof.gitTag that config/release.json still named, so make release-versions failed: polyspec-crudui-validator and polyspec-crudui-generator are wheel packages whose pyproject.toml is an archive manifest, consumers.wheel imports each, pythonBackend of config/toolchain.json pins setuptools==84.0.0 by the sha256 of its wheel (K166), and the generator requires polyspec-crudui-validator==<version>, which [tool.uv.sources] resolves to the validator of the workspace; scripts/release-pins.mts, make release-pins and tests/build/python-git-pins.test.mts are removed. — make release-coverage, make kit-check, make release-versions TAG=v0.0.5 reads the configuration, make dependency-policy-check [o]
C14.45 The PHP of every workflow is the minor of .php-version: the new file holds the PHP minor of crudui (8.5, the minor of config/toolchain.json that the workflows pinned inline), every setup-php step of the workflows reads php-version-file: .php-version, so the PHP API and PHP validator jobs have no PHP matrix, and kit's release.yml sets up that PHP for the release proof instead of keeping the PHP 8.3 of the runner (template v0.0.7). — tests/build/runtime-version-policy.test.mts, tests/build/ci-local.test.mts, make toolchain-check [o]
C14.46 The cross-check console suite does not hang in the Vite SSR boot: locally the first four-renderer SSR case of each test file timed out at 120 s in 2 of 3 runs of npm test --prefix examples/cross-check-console/server, and a boot of getEngine alone failed in the dependency scan of rolldown. The cause is found and fixed, with a test that fails before; no longer timeout and no retry. — npm test --prefix examples/cross-check-console/server [o]
C14.47 tests/widget-script-runs.test.mts has no navigation race: a run failed with "Execution context was destroyed, most likely because of a navigation" (chromium vue client) and "window.scriptRunsTest is undefined" (firefox html client), and a rerun passed. The cause is found and fixed, with a test that fails before; no longer timeout and no retry. — tests/widget-script-runs.test.mts [o]
C14.48 Move to kit 0.0.53 (kit K191, K193, K195, through 0.0.52): the pin with the integrity of the published file; make kit-sync copies the release workflow, which runs make kit-check before any release step, and kit-check requires the .php-version that crudui declares since C14.45; the release proof checks that the release lists each sibling wheel (K195); make dependency-review RECORD=1 records the lock. — make kit-check, make toolchain-check, make layout-check, make lint-ts, make dependency-policy-check, ci-passed of main [o]
C14.49 Release 0.0.7, which supersedes the unpublished v0.0.6 (C14.43-1): set the version 0.0.7 in every manifest that config/release.json lists with its locks (package-lock.json, composer.lock, the six Cargo.lock files, uv.lock), in the sibling URL of the validator wheel that the Python generator requires, in the version of contracts/features.json, in the module version of packages/php-ext and in the checks and documents that read them, and in the npm consumer project; rename ## Unreleased of CHANGELOG.md and CHANGELOG.ko.md to ## 0.0.7 below a new empty ## Unreleased; record the dependency review; write the consumer projects of tests/release-install for the archives of 0.0.7 with make release-assets and make release-consumer-lock; create the local annotated tags v0.0.7, packages/validator/go/v0.0.7 and packages/generator/go/v0.0.7 at the head commit whose CI ended ci-passed success, which the maintainer pushes. — make release-versions TAG=v0.0.7, make release-consumer TAG=v0.0.7, make release-go-tags TAG=v0.0.7, make release-verify TAG=v0.0.7, make dependency-policy-check [o]
C14.50 Publish a decoder of the render options from @polyspec/crudui-generator-core, so that a client such as the GUI of fsm reads the render member of its page state without a cast: decodeFormRenderOptions(value) runs the same check that formRenderModel runs on the options before it binds them to nodes, with no second copy, returns the value as FormRenderOptions, and throws a FormRenderOptionsError (INVALID_FORM_INPUT, the messages of form-runtime.md) whose pointer is the JSON Pointer of the invalid member; TSDoc, the API report, the contract manifest and docs/spec/form-runtime.md name it. — packages/generator/ts/src/form-render.test.ts, API Extractor of the package, npm run manifest:check [o]
C14.51 Point to the local links of 0.0.x (kit K198, docs/plans/local-links.md of kit): AGENTS.md and AGENTS.ko.md state in one sentence that while polyspec versions are 0.0.x, development checkouts link installed polyspec packages to their source as that document describes and that links are never committed — make docs-check [o]

Wave 15 — Standard tools of each ecosystem used as they are

ID Task Deliverables Verification State
C15.1 Add the rule that the repository does not rebuild what an existing outside tool or library already does: AGENTS.md and AGENTS.ko.md name the standard tool of each ecosystem used as it is (uv for Python interpreters, environments and locks, Composer for PHP dependencies), accept the symbolic links that such a tool creates, and limit the path and symbolic link rule to what this repository creates — make docs-check [o]