Appearance
Changelog
Unreleased
- S-44-23: AGENTS.md points to kit
docs/plans/local-links.md: while polyspec versions are 0.0.x, development checkouts link installed polyspec packages to their source, and links are never committed. - S-44-22: the shared tools are kit 0.0.53, whose release proof checks that a release lists the asset of each sibling wheel.
- S-44-21: the shared tools are kit 0.0.52, whose kit-check requires the
.php-versionof fsm and whose release workflow runs kit-check before any release step. - S-44-20:
.php-versiondeclares PHP 8.5, the minor ofconfig/toolchain.json, and every setup-php step of the workflows reads it. - S-44-19: the shared tools are kit 0.0.51:
make toolchain-lockstarts with the node of the machine, andconfig/knip-entries.jsondeclares the go, make and php that the scripts and tests run. - S-44-15:
make lint-tspasses andmake checkruns it:config/knip-entries.jsondeclares packages/db, packages/gui, packages/js and docs as knip workspaces. - S-44-17: the shared tools are kit 0.0.45, whose knip ignores the binaries of the pinned runtimes and reads the nested npm projects that
config/knip-entries.jsondeclares as workspaces. - S-44-15: orm is v0.0.15 in every kind (npm and fsm-db tarballs, go module, python wheel with rapidyaml and ruamel.yaml, cargo tag, Composer zip with purrphp/yaml), whose generated TypeScript models import only what they use and whose generated Rust models carry no allow attribute; the Go imports read
github.com/polyspec/orm/packages/go/..., andconfig/knip-entries.jsondeclares the generated models.ts, whose MANIFEST_TEXT and MANIFEST_HASH orm exports by design. - S-44-15:
config/knip-entries.jsondeclares the files that make or a test runs or reads by path outside kit's layout entries (the fsm-gen and fsm-gui commands, the GUI client entry, the golden npm files, the generated declarations and their consumer, the configuration of the documentation site) and the two GUI programs run by hand, so knip reports no unused file;isGeneratedof write-generated.mjs is no export. - S-44-8-1:
config/release-assets.jsonpins the release zips of orm and ordered-json, andmake release-assets-fetchof kit downloads them into var/composer-artifacts, the Composer artifact repository of packages/php and packages/php-ext; thecomposer-artifactsrecipe andconfig/composer-artifacts.txtare removed. - S-44-16: the shared tools are kit 0.0.44:
make install-toolsinstalls the Rust toolchain ofrust-toolchain.toml, so CI runs no rustup step of its own, ruff keeps its cache in var/ruff, and knip takes the entries ofconfig/knip-entries.json. - S-44-14: the PHP of fsm passes phpcs: the generated ConformanceTest.php runs one method
testConformanceover the data providercases(), one data set per case named<feature>__<id>, which parity runs by its symbol (kit K137), and the generated throws take one line per argument. - S-44-13: rustls is 0.23.46, which fixes RUSTSEC-2026-0346 to 0349, and playwright is 1.64.1.
- S-44-12: the PHP files of fsm put a blank line after
<?php(PSR-12), so the fsm-gen header of a generated PHP file is its third line. - S-44-11: the generated rust of a machine without edges passes clippy:
targetreturns the error alone instead of a match of one wildcard arm. - S-44-9: every test of a kind is the test of a parity binding: the persist tests are four feature units bound in all six kinds (two transitions of one row at the same time, and the deadlock retry), the npm type check is the root tool test tests/npm-types, and the rust conformance target is tests/conformance/main.rs with its module vectors.rs.
- S-44-10: the shared tools are kit 0.0.43: parity counts the module files of a cargo target
tests/<name>/main.rsas files of its binding, the oxlint configuration allows the registrations of node:test, knip takes the scripts, benchmarks and tests of the layout as entries, and API Extractor no longer asks every export for a release tag. - S-44-5: the folders follow kit's layout: the packages are packages/compiler, db, gui, go, js (the npm kind), php, php-ext, python and rust; the fsm project of the repository (fsm.config.json, fsm.lock.json, machines/, schema/ and conformance/vectors.json) is contracts/, and the tables of the db store are packages/db/schema/fsm.dbs; the root scripts and tool tests are TypeScript (.mts). The Go module path is github.com/polyspec/fsm/packages/go (it was github.com/polyspec/fsm-go).
- S-44-8: the dependencies are at their latest releases: orm v0.0.14 and ordered-json v0.0.8 in every kind, whose generators read the set files
dbspec-set.json; Composer takes the release zips of orm and ordered-json from anartifactrepository pinned by sha256 inconfig/composer-artifacts.txt, and the Composerpackagecopies of their metadata are gone; python takes the release wheels; hyper v0.0.13, mysql2 3.24.6, mermaid 12.2.0 and PHPUnit 13.4.2. The npm installs pass--allow-remote=root, the package tests run kit's run-tests from the checkout root, and CI sets up no Go of its own. - S-44-7: the shared tools are kit 0.0.42: mise.lock pins the tools for linux-arm64 and macos-x64 too, and the bootstrap writes
var/tools/go.mod, so the Go of mise is a module of its own. - S-44-6: the shared tools are kit 0.0.40, whose mise installs the toolchain of
mise.tomlat the digests ofmise.lock; the python package runs the uv and the Python of the shims of mise. The test of the Python of the checkout parses again; its quoting broke it in S-44-4. - S-44-4: the shared tools are the npm package
@polyspec/kit0.0.38, pinned by its release asset and integrity; the Makefile includes the copiedscripts/kit/kit-bootstrap.mk, the vendoredscripts/kit/,tests/kit/,.kit/kit.lock.jsonandkit.jsonare gone, and the stale TypeScript exception ofpackages/fsm-dbis removed.make layout-checkis recorded red as S-44-5. - S-44-3: config/parity.json follows kit's parity schema: no
PYTHONPATH,supportfiles for the generated go vectors and php-ext stub and header, andtoolsroots for the compiler, DB, GUI and root tool tests. - S-44-2: the toolchains are at kit's pins: Node.js 26.11.1, PHP 8.5, Python 3.14, Go 1.27.2 (no toolchain line), Rust 1.99.0 and TypeScript 7.0.2, with orm's TypeScript 6.0.3 in packages/fsm-db declared as an exception; uv and alint are declared in config/toolchain.json and installed into var/tools, and the CI workflow installs no uv of its own.
- S-44-1: the compiler imports no file of kit:
src/io/files.mjsandsrc/io/git.mjswrite files atomically and run git on node built-ins, and the import test refuses a path intoscripts/kit(plan C-5). - S-43: AGENTS.md(.ko) Rules: an existing outside tool is used as it is, its own symbolic links accepted; the repository creates no symbolic link and no relative path, and a path is named in a tracked file or found by a stated discovery rule.
- S-34-4: the python lock is checked by uv itself (
uv lock --checkand the export of uv.lock against pylock.toml); fsm's own lock script is removed. - S-42-2: fsm's own vector parity script (
scripts/check-parity.mjs,vector-parity-check,parity-<kind>) is removed; kit's parity check does the work, and each kind's owner check runs its package tests. - S-34-3: the python lock holds registry packages and release archives only: orm and ordered-json come from the archives of their v0.0.7 tags with their sha256, so both uv and pip install the lock with hashes, and the lock check refuses a git source.
- S-42-1: the vector parity runs on kit's parity check (K33): config/parity.json, one vector file conformance/vectors.json with a case name per case, per-case PHPUnit methods, and scripts/check-parity.mjs keeps only the record, time-limit and feature-coverage rules.
- S-21: fsm is public at https://github.com/polyspec/fsm. The CI workflow (
.github/workflows/ci.yml) runsmake installandmake checkwith PostgreSQL and MySQL services on every push to main and passed the full suite (run 38121153369); the Pages workflow publishes the documentation site at https://polyspec.github.io/fsm/. The local repository directory is namedfsm. - S-21-1: CI and Pages run on ubuntu-26.04, because orm's PHP client requires SQLite 3.46 and the PHP of ubuntu-24.04 links SQLite 3.45.1; CI run 38121153369 passed the full suite there, 47 of 47 targets (tree 4963dbe).
- S-41-1: the python package venv is made by the kit tool (
make python-venv DIR=packages/fsm-python/.venv, kit K44 in kit v0.0.24), not bypython -m venvin fsm's Makefile. On Linux CPython's venv makes alib64 -> liblink that run-tests --venv refuses, which failed the ubuntu CI of S-41 (run 38118092530); the kit tool removes that link. A test of tests/python-tools asserts thatmake -n installcalls the kit tool (RED with-m venv).make installremoves the venv before it makes a new one, as before. - S-35-3:
make checkrunsvector-parity-checkagain; a rebase had put the kit nameparity-checkback into CHECK_TARGETS, so the suite ran kit K33's checker instead of fsm's vector parity, and a make-install test now asserts the target. - S-42: one parity tool runs in
make check.CHECK_TARGETSnamed kit'sparity-check(S-35-2 renamed the fsm target but not the list), so the vector records were never checked. It namesvector-parity-checknow, and a test of tests/check-parity reads the Makefile to require that. Kit K33 is not adopted: it runs each binding itself and reads no record, so it cannot state the record,kinds, feature, tool-test and time rules (plan C-4 lists each one). - S-34-1: the kit dependency gate covers every transitive python package of the python lock: its latest-release and advisory review reads
packages/fsm-python/pylock.tomlthroughpythonLocks(kit K36). RED: kit's python-lock fixture tests (K36) pass in kit-test; in fsm,dependency-policy-checkfails withpythonLocksempty and passes with the lock listed and reviewed. - S-34-2: the python lock is
packages/fsm-python/pylock.toml, the PEP 751 export ofuv.lock(uv export --format pylock.toml --frozen --no-emit-project, kit K36).config/dependency-policy.jsonnames it inpythonLocks, andmake dependency-review RECORD=1records its packages and the two git packages (polyspec-orm and polyspec-ordered-json atv0.0.7, by commit) in the review.make python-lock-checkfails when the committed body differs from a fresh export (RED with the rule absent). The package venv is filled from this file (S-41). - S-41: python comes from
make install-tools(pythonBuild3.14.8 in config/toolchain.json, sha256 per platform), not from uv.install-pythonandtests/install-pythonare replaced bytests/python-tools(make python-tools-test). The package venv is a copy of that interpreter (python3 -m venv --copies --without-pip packages/fsm-python/.venv) filled withuv pip install --link-mode copyfrompackages/fsm-python/pylock.toml, which uv exports from uv.lock;uv venvanduv syncwrite links that the kit runner refuses. The python tests runrun-tests.mjs python --venv, not a PATH entry, which fixesmake test-python("No module named 'polyspec'" on main). RED: python-tools-test fails on the uv-synced venv of the checkout. GREEN: test-python 290 cases, python-persist-test-postgres and python-persist-test-mysql on the running servers, parity-python, toolchain-check, make-install-test and workflows-test pass. - S-35-2: the vendored kit is v0.0.23 (
make kit-sync KIT_TAG=v0.0.23, kit K36 and K37).config/dependency-policy.jsongets the required fieldpythonLocks([]until S-34-2 names the lock): the kit gate and review read the PEP 751pylock.tomlfiles it lists. The review recordconfig/dependency-review.jsongets the requiredvcslist ([]) frommake dependency-review RECORD=1. The fsm targetparity-checkis renamedvector-parity-check(kit v0.0.23 definesparity-check, and the repository's target replaced it with a make warning); a test in tests/make-install fails on any make warning ofmake -n check. Kit'sparity-checkis not adopted yet: S-42 evaluates it (plan C-4). The review record (config/dependency-review.json) gets the requiredvcslist ([]) frommake dependency-review RECORD=1, the v0.0.23 schema.run-tests.mjs python --venvnow runs the venv interpreter only when itspyvenv.cfgnames the install-tools Python. Known: the python package tests (make test-python, the python persist targets) stay broken until S-41, because kit 0.0.18 onward runs the python tool only asvar/tools/bin/python3and ignores PATH, while the fsm-python tests put the venv on PATH ("No module named 'polyspec'", CI run 38115497717). RED: kit-check against the v0.0.23 files with the lock at v0.0.20 gave 29 findings. GREEN: kit-check, owner-validate, documents-check, toolchain-check, dependency-policy-check, hooks-check, name-check, commits-check and kit-test pass. - S-35-1: the vendored kit is v0.0.20 (
make kit-sync KIT_TAG=v0.0.20). Itskit-testruns the vendored tests throughscripts/kit/run-tests.mjs(kit K32, released in 0.0.19), so they report per-case progress and timeouts like every other test; 0.0.20 is taken because the vendored python-real test of 0.0.18 and 0.0.19 read the consumer's toolchain and failed here, which kit K34-1 moved out of the vendored tests. - S-29-2: the composer vendor of
packages/fsm-phpandpackages/fsm-php-extis keyed oncomposer.jsonas well ascomposer.lock, and its stamp is touched after the install, because the autoload ofcomposer.jsonis not in the lock: an existing checkout kept the autoload of before S-25 and the persist cases failed with an undefined function. - S-37-2: the fsm-db CLI and command tests run on PostgreSQL and MySQL (audit 23, 24). Each case gets an empty database: a new SQLite file, or on the servers a new database that
createEmptyDatabase(packages/fsm-db/tests/dialect.mjs) creates through the admin DSNFSM_TEST_PG_ADMIN_DSNorFSM_TEST_MYSQL_ADMIN_DSN, and the file drops again. Thedb-test-postgresanddb-test-mysqltargets runcli.test.mjsandcommand.test.mjstoo, and require the admin DSNs. The CLI tests check the quoted table name of the missing-table message, because the PostgreSQL server answers in Korean. RED: the tests on the PostgreSQL target ran on SQLite (silent pass), and then failed on the server messagerelation "machine" does not exist(the server wrote it in Korean). GREEN: 36 cases pass on each server. Plan C-6 has the decision and the CI service lines; the fsm-db devDependenciespgandmysql2are pinned and reviewed. - S-37-1: the persist cases of go, rust, php, php-ext and python run on PostgreSQL and MySQL too (audit 23). Each kind has
make <kind>-persist-test-postgresandmake <kind>-persist-test-mysql, inTOOL_TESTSandCHECK_TARGETS. The test readsFSM_TEST_DIALECT, takes its DSN fromFSM_TEST_PG_DSNorFSM_TEST_MYSQL_DSN, checks that the connection has that driver, clears the persist table and runs the cases one after another. A server target with an empty DSN stops with exit 2 (require_dsn), as the db and npm server targets now do. RED: before the test read the dialect, the PostgreSQL target passed with the SQLite cases (go failed withdriver "postgres" is not registereduntil its example importedorm/pg). GREEN on PostgreSQL 17 and MySQL 8.4 for every kind; the concurrency proof stays in npm (plan C-4). - S-26-2: the README facts that S-26-1 found follow the real output. The compiler README gives
--go-moduleforvectors --kind go. The python README names the package that its command writes,./app/order_fsm(run for real; the example imports it withapp/on the module path), instead ofsrc/app_fsm, which the checkout does not have. The go README says that the persistence excerpt imports the test bed's model package,examples/model, and that a consumer imports its own; the excerpts were checked against the files. RED: the compiler command failed without--go-module, and the python note named a path the command does not write. GREEN: each documented command ran, and the Korean fenced blocks match the English ones. - S-26-1-1: the go vectors file imports only the machine packages of its module (
<module>/src/<machine>), so a consumer'svectors --kind gofile vets in the consumer's module. The generateddecidereturns the unnamed func type thatexamples.Decidenames, so the file no longer imports the test bed'sexamples(which needs the test bed's orm model); the persistence example stays the test bed's own. RED:go veton a consumer module failed withcannot find module providing package github.com/polyspec/fsm-go/examples; GREEN: the consumer test ofpackages/fsm-compiler/tests/go-backend.test.mjspasses, the golden vectors file andpackages/fsm-go/tests/vectors_test.goare regenerated, andmake test-go,make parity-goandmake gen-checkpass. Plan C-2 and C-5 record the decision. - S-22: the GUI manual check of the plan ran in the Chromium of the pinned Playwright with the json, mermaid and db stores on commit bc9f1b1 and is recorded with its steps, outcome and recordings in
docs/verification/gui.md: the diagram names every state, an added row holds the suggested code 6, a saved state appears in the table and the diagram, a removed state leaves and the next code is 7, a rename keeps its code, andfsm-gen checkorfsm-db checkpasses on each store. The driver ispackages/fsm-gui/scripts/manual-check.mjs. Its first runs found S-22-1, S-22-2, S-22-3 and S-38. - S-20-2: the docs install prints no npm warning. npm 11 warned for the install scripts of esbuild and fsevents, which
docs/package.jsondid not decide. The esbuild script is approved for its pinned version (allowScripts), and the fsevents script is denied, because the site runs no watcher and the package ships its prebuilt binary; plan C-8 has the trace and the evidence.tests/docs-buildruns a freshnpm ciof the lock and fails on any npm warning. - S-29-1:
gui-browsersdownloads the Chromium once per pinned Playwright version. The browser is the filevar/deps/playwright-<version>, keyed on the version inpackages/fsm-gui/package-lock.json, andgui-browser-testdepends on that file, so a later run with the stamp present prints no download and needs no network (make -n gui-browser-testchecked intests/make-install).gui-browsersstays as a phony alias of the stamp. - S-34: the python dependencies are locked (audit 16).
packages/fsm-python/uv.lock(uv lock) holds every package of the tree with its exact version, the sha256 of each artifact, and the commit of each git package (orm and ordered-json atv0.0.7).make -C packages/fsm-python installrunsuv sync --lockedfrom it, so uv verifies each artifact against its hash. The python stamp ofmake installis keyed onuv.lockinstead ofpyproject.toml. The kit dependency gate reads no Python lock, soscripts/check-python-lock.mjs(make python-lock-check) checks the pin, commit, tag, current and closure rules of the lock (plan C-1). The review of the latest releases and advisories of the transitive packages is filed as S-34-1. - S-37: the persistence and the DB store are proven on SQLite, PostgreSQL and MySQL (audit 23, 24).
packages/fsm-npm/tests/persist-dialects.test.mjsruns two concurrent transitions of one row on two connections: the same (expected, event) gives one Changed and one Unchanged, two events from one expected state give one Changed and one E_CONFLICT; on SQLite the two run in sequence, because the SQLite driver is synchronous. WithoutforUpdate()both transitions report Changed on PostgreSQL and MySQL, which is the RED.make db-test-postgresandmake db-test-mysqlrun the DB store tests and the store contract on the two servers throughpackages/fsm-db/tests/dialect.mjs; a missingFSM_TEST_PG_DSNorFSM_TEST_MYSQL_DSNfails the target, and no case is skipped (RED: the run without the variable failed). The documents name the tested dialects (spec B-2, B-4; fsm-db README; root README). Plan C-6 has the CI service lines for S-21. - S-31-1: the layout contract allows the rust retry test (audit of S-31).
contracts/layout.jsondeclaredtests/persist_retry.rsas a difference, but check-layout also requires the file in the allowed globs of rust; the S-31 commit missed it because its layout-check was not run.make layout-checkandmake layout-testpass. - S-31: the rust persistence example retries a deadlock (spec B-4).
examples/persist.rsusedtransaction_once, which runs its callback once, while B-4 says that orm runs it again on a deadlock. It uses orm v0.0.7transactionnow (three retries by default), and the callback is aFn. A machine error is returned as a value, because the callback oftransactionfails only with an orm error.persist_runs_again_on_a_deadlock(RED: one attempt, expected two) andpersist_does_not_run_again_on_another_errorare inpackages/fsm-rust/tests/persist_retry.rs(make rust-persist-test). Spec B-4 records why the transaction takes the lock withforUpdate()rather than one conditional update: orm v0.0.7 has no public update with an affected-row count, and the rule to revisit it. - S-39: a machine key that a go test file cannot take is reserved (spec B-5).
examples,vectors,machines,expectationandlookuppassfsm-gen checkbut breakgo test ./...ofpackages/fsm-go(an import redeclared, or a package name already declared through an import), so they failE_KEY_RESERVEDnow.testsbuilds in the measurement, so it is not reserved; the S-39 row named it, and the measurement corrects that. - S-33:
fromCoderefuses a value that is not an int in every caller (audit 13, 14, 22). In a caller without strict_types, php and php-ext converted"2"and2.0to the state of 2 andtrueto the state of 1; they now throwE_UNKNOWN_CODE, as python and npm do (spec B-6). The GUI edit page shows a refused next code (E_CODE_RANGE) before a save, and the core and the forms return no null. The spec B-6 lists the four error constants that the generated code has. - S-40: the command test of fsm-gui builds its project from one machine.
packages/fsm-gui/tests/command.test.mjscopied the whole rootfsm.lock.jsoninto the project of the order machine, so a machine added to the root lock changed the test project. The test now writes the project lock with theorderentry of the root lock only ({ order: rootLock.order }) and asserts that the project lock namesorderalone. RED: the project lock held the five root machines (in_review,limits,order,standby,ticket). GREEN:make gui-testpasses, and the command test passes among its 233 cases. - S-26-1: the language READMEs follow S-25 and S-26 (plan C-9). The go README gives
fsm-gen vectors --kind gowith--go-moduleand the real output, describes the key tables and adapters oftests/vectors_test.go, and copies theDecideofexamples/persist.goagain from the file (with the adapter oforderMachine.decide). The php README copies the realautoloadblock ofcomposer.json(the loadersrc/functions.phpinautoload.files, which requires every machine) and its files table names the loader. Every language's Errors section states the field rules of spec B-6 (which of expected, actual and event each code sets) with the field names of that language. RED: the go command failed without--go-module, the composer example loaded only the Order functions, and no Errors section stated B-6. GREEN: each excerpt is verbatim in its file, the Korean code blocks match, and the snippets and commands were run in temporary consumer projects. The go vectors file imports the test bed's examples, not the consumer's, which checklist item S-26-1-1 records. - S-29: a fresh clone can run
make check(audit 8).make installinstalls the toolchains ofinstall-toolsand every package dependency once: the npm locks offsm-npm,fsm-db,fsm-guianddocs, the composer locks offsm-phpandfsm-php-ext, the Python 3.14 venv offsm-python, and the go modules and cargo crates (stamps invar/deps/). Each is a file target keyed on its lock, and each target that uses a package names it as a prerequisite (gen-check,npm-types,test-<kind>,db-test,gui-test,docs-build). Before, a checkout without installed packages failed ingen-check,npm-types,packages-testandparity-check.make make-install-test(tests/make-install/) checks the prerequisites on a copy of the Makefile. - S-26: every vector asserts the whole result (audit 4, 15). An error case names the four fields of
fsm.Error(code,expected,actual,event), null where a field does not apply (spec B-6, plan C-4), and every language harness asserts all four.from_codehas three more inputs: a negative code, 2^31 and the 64-bit maximum, written as decimal strings so that the file stays exact. An invalid state value (0 and the unknown code) is given tocode,key,is_finalandapplyin go, npm and python, the kinds whose state type holds any integer; rust, php and php-ext have no such value, so their native modules do not hold those cases. Thekindslist of a case names the kinds that run it, andcheck-paritychecks it. The contractcontracts/features.jsonis unchanged. - S-25: the conformance machines cover the definition (audit 3).
machines/gainsstandby(no events),in_review(underscore keys at every level),ticket(reserved codes 2 and 4: its states were removed after a first lock, and the second lock keeps the codes reserved) andlimits(the code 2147483647), with their lock entries andconformance/vectors. The go harness is generic:tests/vectors_test.goholds the key tables and one adapter per machine, generated by the go backend (vectors --kind gotakes--go-module);tests/conformance_test.goruns every case through the adapter of its machine, andexamples/persist.gotakes the machine as aDecide. The php package loads the functions of every machine through the loadersrc/functions.php(spec B-6, plan C-2). The compiler tests that compare with the order golden files read a project of the order machine alone (tests/order-project.mjs). Every kind passes the 209 cases of the five machines. The package machine without events of S-23 isstandby. - S-24: the php-ext backend derives every C name from
--php-namespaceand--php-ext-name. The machine files includedphp_polyspec_fsm_test.handpolyspec_fsm_test_arginfo.h, and the Error class symbol wasPolyspec_FsmTest_Fsm_Errorwhatever the options; now they arephp_<ext>.h,<ext>_arginfo.hand<namespace with _>_Fsm_Error(spec B-6). A backend test builds the output with a second pair,Acme\Toolandacme_tool, with phpize, ./configure and make and loads it in php. - S-23: the rust backend compiles a machine without events. A zero-variant enum takes no representation (rustc E0084), so
Eventof such a machine ispub enum Event {}without#[repr(i32)](spec B-6). A backend test builds the generated files of that machine with cargo. - S-22-3: the browser test waits for the connected form, not for
networkidle. The client setsdata-fsm-connected="true"on the form host#fsm-formonceconnectFormand the subscription are in place, and removes it on disconnect; the layout keeps the row buttons from taking a click until the marker is set. The first-run failure was the first navigation of a newly downloaded Chromium, 2.5 to 12.4 s against the 5 s case limit (four fresh downloads measured, plan C-7), not a click before the bind; the setup now opens the edit page once with its own limit. - S-30-1: the READMEs state the lock baseline of S-30 (spec B-3). The root README gives the rule with the real output of
fsm-gen lockfor a swapped, deleted lock (E_LOCK_REGRESSED), the git HEAD baseline of json and mermaid, the outside-git limit and the db store, which has no commit soE_LOCK_REGRESSEDdoes not arise there. The fsm-db README tables and lock section and the fsm-gui save, rename and delete rules say the same. - S-38: the Mermaid diagram names every state (spec B-1 and B-5). With the pinned mermaid 12.1.0
draft : 1drew only the code, so the GUI and any Mermaid viewer showed 1..n and no key. Each state is now a label linestate "draft" as draftfollowed bydraft : 1: the diagram shows the key as the box title and the code as the description. The reader refuses a label that differs from its key withE_MERMAID_UNSUPPORTED. Four other forms were checked and rejected in B-1 (a repeated description, the code inside the label, the key and the code in one description, the former form). The reserved words of B-5 were measured again with the label line; the list is unchanged.packages/fsm-gui/tests/browser/diagram.test.mjsasserts that every state key and code is a drawn label (it failed before). The mermaid store, its canonical texts, the GUI fixtures and every Mermaid example in the README and the spec follow. - S-20-1: the documentation site builds without a warning. The build printed
Circular chunk: framework -> theme -> frameworkbecause the alias ofvuenamedvue/index.mjs, a CommonJS wrapper; the alias now namesdist/vue.runtime.esm-bundler.js, theimportentry of vue. The vite override and vitepress were ruled out with evidence, recorded in the plan (C-8).docs-build-testfails on any warning line of the build output. - S-35: the plan and the contracts match the repository (audit 17, 18, 19, 21). The plan D table no longer holds states and points to the checklist; the C-1 tree lists
src/store-lock, the name checker, the docs-build and install-python tests and the docs site files.contracts/layout.jsondeclares npm'stests/types.test.mjsandtests/types/**indifferenceswith their reasons, and check-layout refuses a test file of a kind that no declaration covers (RED tests intests/check-layout/). S-11-3 has a repeatable test,make install-python-test, which runs the target in a temporary checkout with a stub uv and shows that a recipe without--no-binwrites the link.kit-testis inCHECK_TARGETS. - S-32: the compiler imports are stated and tested (plan C-5).
packages/fsm-compiler/srcimports node built-ins by theirnode:specifier, its own files and the vendored kit of this clone (scripts/kit/), and no npm package. The plan said zero dependencies, which the kit imports contradicted.tests/imports.test.mjsscans every import specifier ofsrc(static, dynamic and relative) and fails on anything outside that set;src/coreimports nonode:module (audit 5). - S-27:
name-checkjudges each occurrence of the old name, not the line: an allowed phrase is removed from its line and the rest is scanned again. The CHANGELOG entry allows only the bullets up to the rename (S-01 to S-18), and the model entry allows only the helper and column names that orm-gen writes for the status column. The entries that allowed a whole line now allow exact phrases, and the audit probe lines are findings (RED tests intests/check-name/). The rename leftovers are fixed: the Makefile comment, the helper comments of the compiler and gen-check tests, andstatus_offor the status column of the python and rust examples (audit 6, 12). - S-19: the user documentation (R11, C-9 of the plan). The root README and every package README (English and Korean) explain the library to its users: what fsm is and is not, the model with the order example in JSON, Mermaid and the database, codes and the lock, transitions with
applyand whyexpectedmakes retries idempotent, every validation code, the project files, the requirements and setup, everyfsm-gencommand and flag with its real output and exit codes, the generated names and the checks.packages/fsm-compiler/README.mdis new (architecture, the store contract, commands, backend options, vectors). The database and GUI READMEs gained their required sections (the GUI keeps its Tests section). Each language README gives the generation command, the generated files, the API with an apply example, the errors, the persistence pattern of itsexamples/persist.*and the tests.scripts/check-readmes.mjs(make readme-check, tests intests/check-readmes/) requires the sections of each README and a Korean twin with the same headings; it reported 54 findings before the documents were written.make docs-buildbuilds the site with the new pages. - S-30: a committed code never changes meaning, even when the lock is lost (R1; spec B-3 and B-5).
load()of every store returns the committed lock asbaseline(json and mermaid:fsm.lock.jsonat git HEAD; db: the stored rows).check,gen,vectors,lock,saveandcopyrefuse withE_LOCK_REGRESSEDa lock that loses a committed code, gives a committed reserved code to a state, or moves a committed key to another code. A rename is accepted before its commit. The audit (swap two codes, then emptyfsm.lock.json, thenlock) printedlock: okand is refused now. Outside git and for an untracked lock nothing is committed, so only the stored lock is judged. A row changed in SQL on the db store is not detected.lockrestores a deleted lock only when the committed codes are kept. - S-22-2: the GUI forms are usable.
make gui-buildcopies the crudui stylesheet@polyspec/crudui-generator-core/styles/crudui.csstopublic/assets/crudui.cssand passes it tohyper-build-assets --static, and the layout links/assets/crudui.css, so the row buttons show their icon and size. The new field of a state row is labeledNew key(the stored key staysKey), so no two fields of a row share a label.public/favicon.ico(an ICO written byscripts/make-favicon.mjs) answers/favicon.icowith 200 instead of 404, and the layout links it. The browser test checks all three. - S-22-1: the GUI forms re-render on a row change. The edit form (
packages/fsm-gui/client/main.ts) subscribes to its crudui instance and re-renders it withpatchContent(host, renderForm(instance, render)), then callsconnection.sync(), the contract ofconnectForm. Before the change a click on a row's add or remove button changed the instance but not the page.renderholds the page's crudui render options (the action, the_csrftoken and the errors), embedded in the page state, and the form sits in a host element#fsm-form.packages/fsm-gui/tests/browser/browser.test.mjs(make gui-browser-test) drives the form in a headless Chromium of the pinned Playwright 1.64.0 (exact devDependency;make gui-browsersdownloads the browser) and covers add, save, remove and the suggested codes.make gui-testruns the top-level tests only. Before the fix the first browser assertion failed with5 !== 6. - S-36: the fsm-db CLI is tested as a process for every command the README documents.
packages/fsm-db/tests/cli.test.mjsasserts the output, the exit code and the rows after:lock --rename order.draft=createdprints[fsm-db] lock: ok, keeps the code of the renamed state and writes the new key to the rows; an unknown rename prints the README'sE_UNKNOWN_STATE_REFline and writes no row;copy --to mermaid --root <dir>writes the canonical.fsm.mmdfile and the lock;copy --from mermaid --root <dir>fills the empty database, and a second copy prints the README'sE_STORE_NOT_EMPTYline and writes nothing. RED: the process cases were absent, and a mutated expected output fails the rename case. The README matched the commands, so no README text was wrong; it gained thecopy --to mermaidexample (en and ko). - S-28: the store contract of spec B-2 is proven on the three stores.
packages/fsm-db/tests/store-contract-all.test.mjs(the fsm-db package owns it, since it depends on the compiler;make db-test) round-trips every valid fixture ofpackages/fsm-compiler/tests/fixtures/valid, each and all together, json → db → mermaid → json, and compares the files as bytes. It also givesrungen of every kind, with its required options, andvectors(with the vectors file of every kind) byte-identical output on the json, mermaid and db stores. RED: a db store that dropsfinalon save fails the four equality cases, because the core refuses the round trip withE_DEAD_ENDor the bytes differ; the detection case proves the same. The real stores pass, so no store defect was found. Spec B-2 (en and ko) names the file. - S-20: the documentation site (R11, C-8 of the plan).
docs/.vitepress/config.mtsbuilds the repository documents in place with VitePress 1.6.4 (make docs-build, its packages fromdocs/package-lock.jsonbydocs-deps): the root and package READMEs in English and Korean, the specification, the plan, the verification pages and the changelogs. A README.md is the index page of its directory, a link to a repository file that is not a site page points to the public repository, and a dead link fails the build.make docs-build-test(tests/docs-build) checks the failure of a dead link and the real build.docs/package.jsonoverrides vite to 6.4.4, because vitepress 1.6.4 pins the 5.4.x line, which has moderate and high advisories. The plan writes its placeholders<major>in code spans, which the site compiles as Vue markup.config/documents.jsonsetssiteLinks. - S-18-1: the commands
fsm-gen,fsm-dbandfsm-guiexist. They are thebinentries ofpackages/fsm-compiler/package.json(new, without dependencies),packages/fsm-db/package.jsonandpackages/fsm-gui/package.json; each file starts with a node shebang and is executable, andnpm install -g ./packages/<package>from a clone links the command onto the PATH. S-18 had renamed them in the documents only. A test in each package installs it offline into a temporary npm prefix and runs the command; the GUI usage line namesfsm-gui. - S-15-1:
fsm-db install [--project <dir>]installs the schema ofschema/fsm.dbsinto the database thatfsm.config.jsonnames, through ormutils().schema().install, so a database gets its tables without code. On a new databasefsm-db checkfails withno such table: machine(exit 1),installprints[fsm-db] install: ok(exit 0), andcheckthen passes. A secondinstallchanges nothing and exits 0; on a database that has only some of the tables orm rejects the install withCONFIG, which is printed with its stack (exit 1), with no fallback (observed with orm v0.0.7).packages/fsm-db/README.mdandREADME.ko.mdare the user guide of the DB store. The spec (B-2) and the plan (C-6) list the commands; the spec'scopyline is corrected to the flags the CLI has (--toor--fromwith--root; the DSN comes only from the variable thatdsnEnvnames). The install is tested as processes on SQLite inpackages/fsm-db/tests/cli.test.mjs. - S-18: the library is renamed from
statustofsmeverywhere (R12). The packages arepackages/fsm-*, the commands arefsm-gen,fsm-dbandfsm-gui, the files arefsm.config.json,fsm.lock.json,machines/*.fsm.json,*.fsm.mmd,schema/fsm.schema.jsonandschema/fsm.dbs(dbspecfsm), the documents aredocs/spec/fsm.mdanddocs/plans/fsm-v1.md, the generated shared module isfsm(fsm.Error,<ns>\Fsm\Error, the modulefsmof each backend), and the reserved machine key isfsm;statusis an ordinary key again. The manifests carry the namespolyspec_fsm,polyspec-fsm,@polyspec/fsm-*,github.com/polyspec/fsm-go, the namespacePolyspec\FsmTestand the extensionpolyspec_fsm_test. The consumer's status column and the process and test statuses keepstatus.scripts/check-name.mjs(make name-check) fails on a tracked file or path that names the librarystatusoutside its allowlist, and the goldens and generated files were regenerated byfsm-gen. AGENTS.md and the plan listname-checkamong the tools that enforce the rules, the scope is R1–R12, and a document names the old name only as "old namestatus". - S-11-2: the python package drops its color workaround.
packages/status-python/Makefileno longer setsPYTHON_COLORS=0inmake test, because kit v0.0.15 (K22) makes run-tests python setNO_COLOR=1andPYTHON_COLORS=0itself. Without the kit setting, unittest colors its verbose lines underFORCE_COLOR=3and the reporter does not read them: the v0.0.14 runner failed 10 of 10 runs with the line removed, and the v0.0.15 runner passed 10 of 10.make test-pythonpasses 89 of 89 cases withFORCE_COLOR=3and without it. - S-16-1: the GUI test hang is fixed at its cause. The kit v0.0.15 sync (K23) makes the stdout pipe of a test child blocking (
scripts/kit/test-load-check.mjs). On the asynchronous pipe, theprocess.exitof the test child dropped the results still queued in it, so a file reported fewer results than it ran, and the runner could print "the test did not finish". With 600 cases in one file, v0.0.14 lost results in 12 of 30 runs, v0.0.15 in 0 of 30, and v0.0.15 with only the blocking lines reverted in 10 of 30. The sync also brings K21 (the node file group under--cwd) and K22 (run-tests python setsNO_COLOR=1andPYTHON_COLORS=0). Before the test change,make gui-testpassed 20 of 20 runs, 4 at a time (210 results each), andmake compiler-testpassed 20 of 20 (302 results each); the hang did not reproduce in this checkout. The per-call 20-second deadline inpackages/status-gui/tests/mermaid-parse.test.mjsonly hid the hang, so it is removed; the runner already gives each case its own timeout. The flat tests (one per reserved word and position, and one per ordinary key) are kept, because each case has its own name and result. After the change,make gui-testpassed 20 of 20 runs. - S-11-3:
make install-pythonrunsuv python install --no-bin 3.14, so uv no longer writes apython3.14executable into a user bin directory (~/.local/binby default). The interpreter stays uv's managed install, and onlyvar/tools/bin/python3links to it. Before the change, withUV_PYTHON_BIN_DIRset to a scratch directory, the target wrotepython3.14there; after it, the directory stays empty and~/.local/binis unchanged. - S-14-3:
genandvectors --kindskip the git-ignored files of their output directory (packages/status-compiler/src/cli/write-generated.mjs). An ignored file is a build output, not a hand-written file: it neither blocks the generator withE_NOT_GENERATEDnor is removed as stale, somake geninpackages/status-php-extruns aftermake testwithoutmake clean. A tracked file or a file that Git does not ignore still givesE_NOT_GENERATED, and outside a git work tree nothing counts as ignored. The ignored files come fromgit ls-files --others --ignored --exclude-standardin the output directory.make cleanremains the removal of the phpize output. The tests are inpackages/status-compiler/tests/write-generated.test.mjs. - S-14-1: the php-ext package
packages/status-php-extis the kindphp-extof the canonical layout (docs/plans/status-v1.md, C-2).make genwrites itssrc/andtests/Vectors.phpwith status-gen (--php-namespace 'Polyspec\StatusTest' --php-ext-name polyspec_status_test), copies the php package'stests/ConformanceTest.phpandexamples/persist.php(gen-check then proves they are the same bytes), and writesexamples/model/with orm-gen from the rootschema/persist.dbs.make testbuilds the extension with phpize,./configureandmakeinsrc/and runs the 89 vector cases with--php-extension, andmake parity-php-extchecks them.composer.lockpins the same 30 packages as the php package (polyspec/orm 0.0.7 and PHPUnit 12.5.38), andconfig/dependency-policy.jsonandconfig/dependency-review.jsonrecord the php-ext manifest and lock.contracts/layout.jsondeclares the kind with its generated globs. The toolchain is the php and composer declaration ofconfig/toolchain.json, which is not duplicated. - S-14-2: the php-ext backend's C output is corrected after its first build. The object parameters are
zval *, becauseZ_PARAM_OBJECT_OF_CLASSwrites azval *; the Error method table follows its constructor; the format ofZEND_LONG_FMTcarries no extra%; the class type of theStepproperty isZEND_TYPE_INIT_CLASSwith a persistent class-name string, since a literal name does not resolve for a property; and theCodeenum values are persistent interned strings, so they are not freed at shutdown..gitignorelists the phpize output ofpackages/status-php-ext/src/with exact anchored patterns;config.m4and the generated sources stay tracked. The golden files are regenerated. - S-14: the php-ext backend (
packages/status-compiler/src/backends/php-ext.mjs) generates thesrc/files of a php-ext package for every machine:<machine>.cregisters the enumsStateandEventwithzend_register_internal_enum, the final classStepand the namespace functions of B-6 withZEND_NS_NAMED_FE, andapplyandtargetthrow the B-4 codes throughstatus_throw;status.cholds the enumStatus\Code, the exceptionStatus\Errorand the module entry, which initializes every machine in definition order;php_<ext>.h,<ext>_arginfo.h(the arginfo is written directly, without gen_stub),<ext>.stub.php(the declarations for editors) andconfig.m4complete the set.status-gen gen --kind php-extrequires--php-namespaceand--php-ext-name;vectors --kind php-extwrites the test module, which is byte-identical to the php kind'sVectors.php. The golden files underpackages/status-compiler/tests/golden/php-ext/hold the order example; they were reviewed against B-6 before they were written.write-generated.mjsaccepts thednlheader ofconfig.m4. - S-17-1: the edit page carries the next code of its machine. The embedded form state holds core
nextCode(machine, definitions, lock)asnextCode(src/forms.mjs,nextCodeOfandeditState), andclient/main.tsgives each state row added in the browser the next code, then the codes after it (client/new-codes.ts,codesOfAddedRows). The save still checks every code, and a blank code is still filled bynextCode.tests/edit-next-code.test.mjscovers the embedded code on the json, mermaid and db stores, including the code after a delete, andtests/delete-state.test.mjsshows that deleting a state in the edit form moves its code toreservedand out ofcodeson the three stores.make gui-testruns 210 tests. - S-17: the GUI edits the machines (
packages/status-gui)./machines/{machine}/editis the crudui form ofcrud/machine.yml: the states and the events are repeated rows, and the key and the code of a stored state are read-only. A save parses theform[...]fields (src/form-data.mjs; a malformed body is 400), gives each new state the code that corenextCodesuggests and calls corerun savewith the full definition set. A refused save writes nothing and answers 422 with every finding; a finding that names a field is shown on that field./machines/{machine}/renamecallsrun lockwith the renames (the code is kept),/newcreates a machine with one initial and final state, and the detail page deletes a machine by a save without it, so its codes move to the reserved list. Every save, rename and delete needs the_csrftoken of its page (403 without it).client/main.tsbinds the rows of the edit form with cruduiconnectFormafter every htmx settle.make gui-testruns 194 tests, on the json, mermaid and db stores. - S-11-1: the python package
packages/status-pythonis the kindpythonof the canonical layout (docs/plans/status-v1.md, C-2). Itspyproject.tomlpins the orm client to its git tagv0.0.7and the build backend tosetuptools==84.0.0.make installcreates a Python 3.14 venv (.venv, gitignored) and installs them, andmake testruns offline with that venv first onPATH.make genwritessrc/polyspec_status/andtests/vectors.pywith status-gen andexamples/model/with orm-gen.tests/test_conformance.pyruns each of the 89 vector cases astest_<feature>__<id>, andexamples/persist.pyruns the B-4 transaction on SQLite with polyspec orm, which the persist cases assert after each run.config/toolchain.jsondeclares python 3.14,config/dependency-policy.jsonlists the pyproject, andcontracts/layout.jsondeclares the kind without a lock file, as C-2 allows for python.make test-pythonandmake parity-pythonrun the package. - S-11: the python backend (
packages/status-compiler/src/backends/python.mjs) generates thesrc/polyspec_status/files of the python package for every machine:<machine>.pywithStateandEventasIntEnumclasses whose members are upper case (B-6), a frozenStepdataclass withtoandchanged, and the eight functions, which raisestatus.Error; the sharedstatus.py(Code, aStrEnumof theE_codes, andError) and__init__.py.status-gen gen --kind python --out <dir>writes the set throughwriteAtomicwith the python header# Code generated by status-gen from <machine>; DO NOT EDIT., removes the stale generated files and refuses a file without the header (E_NOT_GENERATED);vectors --kind python --out <file>writes the native moduletests/vectors.pyas a Python literal. The header check of the writer accepts the#comment syntax as well as//. The golden files underpackages/status-compiler/tests/golden/python/hold the order example; they were reviewed against B-6 before they were written, and the tests compare them byte for byte. - S-09-2:
make npm-types(packages/status-npm/tests/types.test.mjs) compilestests/types/consumer.tsin strict mode without emitting, against the generatedsrc/*.d.ts, with the pinnedtypescript7.0.2 devDependency ofpackages/status-npm. The consumer uses every public name of B-6 and holds three@ts-expect-errorlines that must fail to compile, so a too-loose declaration leaves a directive unused and fails the test. The test runs in its own record,.results/npm-types.json;make test-npmnames the conformance file only, so the npm record holds vector cases alone forparity-npm.contracts/layout.jsonallows the two test paths. - S-13-1: the rust package
packages/status-rustis the kindrustof the canonical layout (docs/plans/status-v1.md, C-2).make genwrites itssrc/andtests/vectors.rswith status-gen;build.rsgenerates the orm models of the rootschema/persist.dbsintoOUT_DIR, so the package has noexamples/model/(the declared difference of rust incontracts/layout.json).tests/conformance.rsholds the check functions, and the 89 vector cases run astest_<feature>__<id>;examples/persist.rsruns the B-4 transaction on SQLite with polyspec orm (v0.0.7, pinned by tag), and the persist cases assert the stored code after each run.rust-toolchain.tomldeclares Rust 1.98.0 at the root, andconfig/toolchain.jsondeclarescargoAudit0.22.2 for the dependency review ofCargo.lock.make test-rustandmake parity-rustrun the package, andmake packages-testruns it with the other kinds. - S-13: the rust backend (
packages/status-compiler/src/backends/rust.mjs) generates thesrc/files of a rust package for every machine:<machine>.rswith the B-6 notation (#[repr(i32)]StateandEvent, theStepstruct, the eight functions,from_codetakingi64,applyreturningResult<Step, Error>), the sharedstatus.rs(CodeandError) andlib.rs.status-gen vectors --kind rust --out <file>writestests/vectors.rs, one#[test]per case (#[tokio::test]for a persist case) that calls the check functions oftests/conformance.rs. The golden files underpackages/status-compiler/tests/golden/rust/hold the order example; they were reviewed against B-6 before they were written, and tests compare them byte for byte. - S-12-1: the php package
packages/status-phpis the kindphpof the canonical layout (docs/plans/status-v1.md, C-2).make genwrites itssrc/andtests/Vectors.phpwith status-gen (--php-namespace 'Polyspec\StatusTest') andexamples/model/with orm-gen from the rootschema/persist.dbs.tests/ConformanceTest.phpruns each of the 89 vector cases as the PHPUnit data settest_<feature>__<id>, whichcheck-parityreads as one case name per id; it uses the public API of the generated namespace only. The persist cases run the B-4 transaction on SQLite with polyspec orm v0.0.7 (the release zip, pinned by its sha1 incomposer.json, with ordered-json v0.0.7) and assert the stored state after each run.composer.jsonpins PHPUnit 12.5.38 (the latest stable release at the review) andconfig.platform.php8.4.0;config/toolchain.jsondeclares PHP 8.5 and Composer 2.10.3, andconfig/dependency-review.jsonrecords the review.make test-phpandmake parity-phprun the package. - S-12: the php backend (
packages/status-compiler/src/backends/php.mjs) generates thesrc/files of a php package for every machine:<Pascal>/State.phpandEvent.php(backed enums with the B-6 codes and indexes),Step.php, andfunctions.phpwith the functions of B-6 as namespace functions, and the sharedStatus/Code.php(enum of the error codes) andStatus/Error.php(an\ExceptionwhosegetCode()is the code string).status-gen gen --kind php --php-namespace <ns> --out <dir>requires the namespace (Polyspec\StatusTestfor the test bench), so a missing or malformed one is a usage error.vectors --kind php --php-namespace <ns> --out <file>writesTests/Vectors.php, whosecases()keys every case bytest_<feature>__<id>. A php file has<?phpon its first line, so the header is read on the second line, and a file without it is still refused (E_NOT_GENERATED). The golden files underpackages/status-compiler/tests/golden/php/hold the order example (89 cases); they were reviewed against B-6 before they were written, andphp -laccepts every generated file. - S-10-1: the go package
packages/status-gois the kindgoof the canonical layout (docs/plans/status-v1.md, C-2), modulegithub.com/polyspec/status-go.make genwritessrc/(order/order.go,status/status.go) andtests/vectors_test.gowith status-gen, andexamples/model/with orm-gen (go tool orm-gen, atooldirective of go.mod) from the rootschema/persist.dbs; the models are generated inside the output directory, so gen-check runs them in a module copy.tests/conformance_test.goruns every vector case as the subtesttest_<feature>__<id>against the generated functions, andexamples/persist.goruns the B-4 transaction on SQLite with polyspec orm v0.0.7, which the persist cases assert after each run.contracts/layout.jsondeclares the kind with itsgeneratedglobs. Its language-forced differences are two:gen --kind gotakes--go-module(the generated machines import the shared package by the module path), and the toolchain is thetoolchainline ofpackages/status-go/go.mod(go1.27.2, which fixes the stdlib advisory GO-2026-6607 that go1.27.0 reports), read byconfig/toolchain.jsongo.config/toolchain.jsonalso pins govulncheck 1.8.0, which the dependency review runs.make test-go(withGOPROXY=off),make parity-goandmake packages-testrun the package; the lock isgo.sum, reviewed inconfig/dependency-review.json. - S-10: the go backend (
packages/status-compiler/src/backends/go.mjs) generates thesrc/files of a go package:<machine>/<machine>.goper machine with the B-6 notation (type State int32with the membersStateDraft,type Event int32withEventNext(positions from 1), the structStep{To, Changed}, and the functionsInitial,Code,Key,FromCode,IsFinal,EventsFrom,TargetandApply, which return(value, error)with a*status.Errorand check their arguments in the order of npm), and the sharedstatus/status.go(theErrortype and theCode*constants).status-gen gen --kind go --go-module <module path> --out <dir>writes that set; the module path is required because the machines import the shared package by it.status-gen vectors --kind go --out <file>writes the nativetests/vectors_test.go: one literal per case, with the keys of the oracle, which the conformance test maps to the constants. The golden files underpackages/status-compiler/tests/golden/go/hold the order example; they were reviewed against B-6 before they were written, and tests compare them byte for byte. - S-16: the GUI skeleton (
packages/status-gui) is a hyper v0.0.11 application./lists the machines of the project with the crudui list ofcrud/machines.yml, and/machines/{machine}shows the states, the events with their edges and the canonical Mermaid text in<pre class="mermaid">, which the client renders with mermaid 12.1.0 on the first load and after every htmx swap.node src/main.mjs --project <dir>selects the store thatstatus.config.jsonnames (json, mermaid or db) and calls only that store and corerun; every finding of the project is shown as an error, and an unknown machine answers 404.make gui-testbuilds the assets and runs 133 tests, which include the parse of the order example with the pinned mermaid package and the parse of every reserved Mermaid state key of B-5 at five positions. The pins are hyper v0.0.11, crudui v0.0.5, template v0.0.5, and exact versions of mermaid, htmx.org and yaml, reviewed inconfig/dependency-review.json. The criterion of the Mermaid parse test is recorded indocs/spec/status.md, B-5. - S-09-1: the npm package
packages/status-npmis the kindnpmof the canonical layout (docs/plans/status-v1.md, C-2).make genwrites itssrc/andtests/vectors.mjswith status-gen andexamples/model/with orm-gen from the rootschema/persist.dbs(created here and shared by every kind).tests/conformance.test.mjsruns every vector case astest_<feature>__<id>against the generated functions, andexamples/persist.mjsruns the B-4 transaction on SQLite with polyspec orm, which the persist cases assert after each run.contracts/layout.jsondeclares the kind with itsgeneratedglobs:check-layoutrequires them, andgen-checkcompares exactly the files they match.make test-npm,make parity-npmandmake packages-testrun the package, and the lock is inconfig/dependency-policy.jsonwith its review record. - S-09: the npm backend (
packages/status-compiler/src/backends/npm.mjs) generates thesrc/files of an npm package for every machine:<machine>.jsand<machine>.d.tswith the B-6 notation (frozenStateandEvent, the eight functions,applyreturning{to, changed}or throwingstatus.Error), the sharedstatus.jsandstatus.d.ts(Errorand theCodeconstants), andindex.jsandindex.d.ts.status-gen gen --kind npm --out <dir>writes the set throughwriteAtomic, removes the stale files that carry the generated header and refuses a file without it (E_NOT_GENERATED, nothing written).status-gen vectors --kind npm --out <file>writes the native moduletests/vectors.mjs. The golden files underpackages/status-compiler/tests/golden/npm/hold the order example; they were reviewed against B-6 before they were written, and tests compare them byte for byte. - S-15: the DB store (
packages/status-db/src/db-store.mjs) keeps the definitions and the lock in the six tables ofschema/status.dbsthrough the orm client (v0.0.7).dbStore(db)reads and writes through the models that orm-gen generates intosrc/models/models.ts, andsavereplaces every row inside one transaction, so a failed write leaves the rows that were there. Every key column isvarchar(64), the bound of S-05-3, and the edge table has the two indexes of its foreign keys tostate. The store passes the store contract (tests/store-contract.mjs), round-trips json → db → json byte-identically with a 64-character key, refuses a copy into a non-empty database withE_STORE_NOT_EMPTY, and rolls back a failed save.status-db(src/cli/status-db.mjs) givescheck,lockandcopy --to|--from json|mermaid --root; its DSN is the value of the environment variable thatstatus.config.jsonnames.make db-testruns its tests, which include the check that orm-gen reproduces the committed models. - S-06-1: the store contract is async.
load()andsave()of the JSON and Mermaid stores return Promises, andrunandcopy(src/core/run.mjs) are async, so the DB store can use the async orm client.status-genawaits the commands, prints an unexpected rejection and exits 1. validate, normalize, lock and vectors stay synchronous. The store contract tests (tests/store-contract.mjs) await a Promise from every store, and every test that calls a store or a command awaits it. - S-08: the Mermaid store (
src/store-mermaid/) reads and writesmachines/*.status.mmdin the canonical form ofdocs/spec/status.md, B-1:parse.mjsrefuses every line outside that form withE_MERMAID_UNSUPPORTEDand its line number, andwrite.mjswrites the form.savedeletes the undefined*.status.mmdfiles and passes the store contract (tests/store-contract.mjs). The lock read and write moved tosrc/store-lock/lock-file.mjs, which both file stores share.status-genaccepts{"store":"mermaid"}, andcopy --to json|mermaidmigrates in both directions into an empty target; every valid fixture round-trips json → mermaid → json byte-identically. - S-05-3: core
validatebounds the length of machine, state and event keys to 64 characters: a key longer than 64 failsE_KEY_FORMATat its path, with a message that names the length and the maximum. The DB store keeps keys invarchar(64), so the bound lets all three stores accept the same keys (docs/spec/status.md, B-5). - S-07: the root files of the conformance hold
status.config.json({"store":"json"}),machines/order.status.json(the order example of B-1, written by the JSON store), itsstatus.lock.jsonandcontracts/features.json. The corevectors(src/core/vectors.mjs) computes every case of C-4 from the IR and the lock;status-gen vectors --out <dir>writes<machine>.json, soconformance/vectors/order.jsonholds 89 cases.scripts/gen-check.mjs(make gen-check) regenerates the vector files intovar/gen-check/and requires the tracked files ofconformance/vectorsto equal them, withmake gen-check-testas its test. - S-06: core
normalizereturns the IR of a valid definition (src/core/normalize.mjs);lockMismatch,nextLock,nextCodeandcanonicalLockimplement the code lock of B-3 (src/core/lock.mjs), andnextLockrefuses a key whose code the lock holds and the definition changes withE_CODE_CHANGED;run(check,lock,save) andcopyrun the commands of B-2 over a store (src/core/run.mjs). The JSON store (src/store-json/store.mjs) reads and writesmachines/*.status.jsonandstatus.lock.jsonin canonical form and passes the store contract (tests/store-contract.mjs).status-gen(src/cli/status-gen.mjs) givescheck,lockandcopy; the mermaid store staysE_STORE_NOT_CONFIGUREDuntil S-08. - S-05-2: core
validatereports the name and code errors of B-5,E_KEY_FORMAT,E_DUPLICATE_KEY,E_KEY_RESERVED(the fixed lists are inpackages/status-compiler/src/core/reserved.mjs),E_DUPLICATE_CODEandE_CODE_RANGE. A machine key is compared after its underscores are removed, and a machine with a structural error is not compared with the other machines. - S-05-1: core
validatereports the graph errors of B-5,E_UNKNOWN_STATE_REF,E_SELF_LOOP,E_NONDETERMINISTIC,E_DUPLICATE_EDGE,E_INITIAL_COUNT,E_FINAL_OUTGOING,E_UNREACHABLEandE_DEAD_END, each with its own failing case. Reachability starts from the single initial state, and a machine with a structural error reports only itsE_SCHEMAfindings. - S-05: core
validate(packages/status-compiler/src/core/validate.mjs) reports every structural error of a definition asE_SCHEMAwith its JSON path and skips the later rules of that machine.schema/status.schema.jsonis the editor schema, written with the keywords of kitschema-validate.mjs; the correspondence test checks that it and the core agree on every fixture.make compiler-testruns the tests throughrun-tests.mjsand records.results/compiler-test.json. - The JSON store writes an edge as the object
{from, to}instead of a pair, so the editor schema checks it exactly and a GUI form edits it as a row (docs/spec/status.md, B-1). - S-04:
scripts/check-parity.mjs(make parity-check,make parity-<kind>with--kind) fails unless every kind ran every vector case ofconformance/vectorsonce and passed, from a current record that covers its package, every feature ofcontracts/features.jsonhas a vector case, every test id names one case or is the parent of one, every tool test ofTOOL_TESTSleft a current passing record, and no record ran over 10 minutes. There is no exemption. - S-03:
scripts/check-layout.mjs(make layout-check) checks the language packages againstcontracts/layout.json: the package directories are exactly the tools and the kinds, every file of a kind matches its canonical layout, every canonical and required file exists, and the php and php-ext tests are the same bytes.make layout-testruns its tests throughrun-tests.mjsand records.results/layout-test.json. - S-02:
docs/spec/status.mdfixes the definition: the model of three parts (machine, states with codes, events of edges), the three stores under one rule set, the lock of codes, the idempotent transitionapply(expected, event, actual), the validation codes and the naming rule of the six targets. The plan points to it, so the definition has one source. - S-01: the repository adopts kit v0.0.14 (
scripts/kit/,tests/kit/,.kit/kit.lock.json) withconfig/checklist.json,config/commits.json,config/documents.json,config/owner-checks.json,config/toolchain.json(Node.js only),config/dependency-policy.jsonand its review record; the hooks run the owner validation (pre-commit), the commit rules (commit-msg) and the push gate (pre-push).AGENTS.mdholds the development rules,docs/plans/status-v1.mdthe plan anddocs/plans/execution-checklist.mdits items.make checkruns the full suite once per tree.