Skip to content

변경 기록 ​

English

Unreleased ​

  • S-44-23: AGENTS.md가 kit docs/plans/local-links.md를 가리킨다. polyspec 버전이 0.0.x인 동안 개발 checkout은 설치된 polyspec package를 그 source에 link하며, link는 commit하지 않는다.

  • S-44-22: 공유 도구가 kit 0.0.53이다. 그 release proof는 release가 각 sibling wheel의 asset을 나열하는지 검사한다.

  • S-44-21: 공유 도구가 kit 0.0.52다. 그 kit-check는 fsm의 .php-version을 요구하며, 그 release workflow는 release 단계 전에 kit-check를 실행한다.

  • S-44-20: .php-version이 config/toolchain.json의 minor인 PHP 8.5를 선언하며, workflow의 모든 setup-php 단계가 이를 읽는다.

  • S-44-19: 공유 도구가 kit 0.0.51이다. make toolchain-lock은 machine의 node로 시작하고, config/knip-entries.json은 script와 test가 실행하는 go, make, php를 선언한다.

  • S-44-15: make lint-ts가 통과하고 make check가 이를 실행한다. config/knip-entries.json은 packages/db, packages/gui, packages/js, docs를 knip workspace로 선언한다.

  • S-44-17: 공유 도구가 kit 0.0.45다. 그 knip은 고정된 runtime의 binary를 무시하고, config/knip-entries.json이 workspace로 선언한 중첩 npm project를 읽는다.

  • S-44-15: 모든 kind에서 orm이 v0.0.15다(npm과 fsm-db tarball, go module, rapidyaml과 ruamel.yaml을 쓰는 python wheel, cargo tag, purrphp/yaml을 쓰는 Composer zip). 그 생성된 TypeScript model은 쓰는 것만 import하고, 생성된 Rust model에는 allow attribute가 없다. Go import는 github.com/polyspec/orm/packages/go/...를 읽고, config/knip-entries.json은 orm이 설계상 MANIFEST_TEXT와 MANIFEST_HASH를 export하는 생성된 models.ts를 선언한다.

  • S-44-15: config/knip-entries.json은 kit의 layout entry 밖에서 make나 테스트가 실행하거나 경로로 읽는 파일(fsm-gen과 fsm-gui 명령, GUI client entry, golden npm 파일, 생성된 declaration과 그 consumer, 문서 사이트 설정)과 손으로 실행하는 GUI 프로그램 두 개를 선언하므로 knip은 unused file을 보고하지 않는다. write-generated.mjs의 isGenerated는 export가 아니다.

  • S-44-8-1: config/release-assets.json이 orm과 ordered-json의 release zip을 고정하고, kit의 make release-assets-fetch가 그것을 packages/php와 packages/php-ext의 Composer artifact repository인 var/composer-artifacts에 받는다. composer-artifacts recipe와 config/composer-artifacts.txt는 제거했다.

  • S-44-16: 공유 도구가 kit 0.0.44다. make install-tools가 rust-toolchain.toml의 Rust toolchain을 설치하므로 CI는 자체 rustup step을 실행하지 않고, ruff는 cache를 var/ruff에 두며, knip은 config/knip-entries.json의 entry를 받는다.

  • S-44-14: fsm의 PHP가 phpcs를 통과한다. 생성된 ConformanceTest.php는 method testConformance 하나를 data provider cases() 위에서 실행하고 case마다 이름이 <feature>__<id>인 data set을 두며 parity는 이를 symbol로 실행한다(kit K137). 생성된 throw는 인자 하나에 한 줄을 쓴다.

  • S-44-13: rustls는 RUSTSEC-2026-0346~0349를 고친 0.23.46이고 playwright는 1.64.1이다.

  • S-44-12: fsm의 PHP 파일은 <?php 뒤에 빈 줄을 둔다(PSR-12). 그래서 생성된 PHP 파일의 fsm-gen header는 셋째 줄이다.

  • S-44-11: edge가 없는 machine의 생성된 rust가 clippy를 통과한다. target은 wildcard arm 하나의 match 대신 오류만 반환한다.

  • S-44-9: 각 kind의 모든 test가 parity binding의 test다. persist test는 여섯 kind 모두에 bind된 네 feature unit(한 row의 동시 transition 두 개와 deadlock 재시도)이고, npm 타입 검사는 root 도구 test tests/npm-types이며, rust conformance target은 module vectors.rs를 가진 tests/conformance/main.rs다.

  • S-44-10: 공유 도구는 kit 0.0.43이다. parity는 cargo target tests/<name>/main.rs의 module 파일을 그 binding의 파일로 세고, oxlint 설정은 node:test의 등록을 허용하며, knip은 layout의 scripts, benchmarks, test를 entry로 삼고, API Extractor는 더 이상 모든 export에 release tag를 요구하지 않는다.

  • S-44-5: 폴더가 kit의 layout을 따른다. package는 packages/compiler, db, gui, go, js(npm kind), php, php-ext, python, rust이고, 저장소의 fsm 프로젝트(fsm.config.json, fsm.lock.json, machines/, schema/, conformance/vectors.json)는 contracts/이며, db store의 table은 packages/db/schema/fsm.dbs다. 루트 script와 도구 테스트는 TypeScript(.mts)다. Go module 경로는 github.com/polyspec/fsm/packages/go다(이전에는 github.com/polyspec/fsm-go).

  • S-44-8: 의존성이 최신 release다. 모든 kind에서 orm v0.0.14와 ordered-json v0.0.8을 쓰며 그 generator는 set 파일 dbspec-set.json을 읽는다. Composer는 config/composer-artifacts.txt가 sha256으로 고정한 artifact repository에서 orm과 ordered-json의 release zip을 받으며, 그 metadata를 복사한 Composer package는 사라졌다. python은 release wheel을 받는다. hyper v0.0.13, mysql2 3.24.6, mermaid 12.2.0, PHPUnit 13.4.2이다. npm 설치는 --allow-remote=root를 주고, package 테스트는 kit의 run-tests를 checkout root에서 실행하며, CI는 자기 Go를 설치하지 않는다.

  • S-44-7: 공유 도구는 kit 0.0.42다. mise.lock은 linux-arm64와 macos-x64의 tool도 pin하고, bootstrap이 var/tools/go.mod를 써서 mise의 Go가 별도 module이 된다.

  • S-44-6: 공유 도구는 kit 0.0.40이며, 그 mise가 mise.toml의 toolchain을 mise.lock의 digest로 설치한다. python package는 mise shim의 uv와 Python을 실행한다. checkout Python의 test가 다시 parse된다. S-44-4에서 따옴표가 그것을 깨뜨렸다.

  • S-44-4: 공유 도구는 release asset과 integrity로 pin한 npm package @polyspec/kit 0.0.38이다. Makefile은 복사된 scripts/kit/kit-bootstrap.mk를 include하고, vendor한 scripts/kit/, tests/kit/, .kit/kit.lock.json, kit.json은 없어졌으며, packages/fsm-db의 오래된 TypeScript 예외를 지웠다. make layout-check는 S-44-5로 red가 기록되었다.

  • S-44-3: config/parity.json이 kit의 parity schema를 따른다: PYTHONPATH가 없고, go의 생성 vector와 php-ext의 stub과 header는 support 파일이며, compiler, DB, GUI, 루트 도구의 테스트는 tools root다.

  • S-44-2: toolchain이 kit의 pin을 따른다: Node.js 26.11.1, PHP 8.5, Python 3.14, Go 1.27.2(toolchain 줄 없음), Rust 1.99.0, TypeScript 7.0.2. packages/fsm-db의 orm TypeScript 6.0.3은 예외로 선언했다. uv와 alint는 config/toolchain.json에 선언하여 var/tools에 설치하며, CI 워크플로는 uv를 따로 설치하지 않는다.

  • S-44-1: compiler는 kit 파일을 import하지 않는다. src/io/files.mjs와 src/io/git.mjs가 node 내장 모듈로 파일을 원자적으로 쓰고 git을 실행하며, import 테스트는 scripts/kit로 가는 경로를 거부한다(plan C-5).

  • S-43: AGENTS.md(.ko) 규칙을 추가했다: 기존 외부 도구는 있는 그대로 쓰고(그 도구의 symbolic link는 받아들임), 저장소는 symbolic link와 상대 경로를 만들지 않으며, 경로는 추적 파일에 이름을 적거나 명시된 탐색 규칙으로 찾는다.

  • S-34-4: python lock은 uv 자체가 검사한다(uv lock --check와 pylock.toml에 대한 uv.lock의 export 비교). fsm 자체의 lock 스크립트를 제거했다.

  • S-42-2: fsm 자체의 vector parity 스크립트(scripts/check-parity.mjs, vector-parity-check, parity-<kind>)를 제거했다. kit의 parity 검사가 그 일을 하고, kind의 소유 검사는 패키지 테스트를 실행한다.

  • S-34-3: python lock은 registry package와 release archive만 담는다: orm과 ordered-json은 v0.0.7 tag의 archive와 sha256으로 받으므로 uv와 pip 모두 lock을 hash와 함께 설치하고, lock 검사는 git source를 거부한다.

  • S-42-1: vector parity가 kit의 parity 검사(K33)로 실행된다: config/parity.json, case마다 이름을 가진 vector 파일 하나 conformance/vectors.json, case마다의 PHPUnit method. scripts/check-parity.mjs는 기록, 시간 제한, feature 커버리지 규칙만 남긴다.

  • S-21: fsm은 https://github.com/polyspec/fsm 에 공개되어 있다. CI workflow(.github/workflows/ci.yml)는 main의 모든 push에서 PostgreSQL과 MySQL service와 함께 make install과 make check를 실행하고 전체 검사를 통과했으며(run 38121153369), Pages workflow는 문서 사이트를 https://polyspec.github.io/fsm/ 에 게시한다. 로컬 저장소 디렉터리 이름은 fsm이다.

  • S-21-1: CI와 Pages는 ubuntu-26.04에서 돈다. orm의 PHP client는 SQLite 3.46을 요구하는데 ubuntu-24.04의 PHP는 SQLite 3.45.1에 연결되기 때문이다. CI run 38121153369가 그곳에서 전체 검사 47개 target을 모두 통과했다(tree 4963dbe).

  • S-41-1: python package venv는 fsm Makefile의 python -m venv가 아니라 kit 도구(make python-venv DIR=packages/fsm-python/.venv, kit v0.0.24의 K44)로 만든다. Linux에서 CPython venv는 lib64 -> lib link를 만들며 run-tests --venv가 이를 거부했고, 이것이 S-41의 ubuntu CI를 실패시켰다(실행 38118092530). kit 도구가 그 link를 지운다. tests/python-tools의 테스트는 make -n install이 kit 도구를 부르는지 단언한다(-m venv일 때 RED). make install은 이전과 같이 새 venv를 만들기 전에 기존 venv를 지운다.

  • S-35-3: make check가 다시 vector-parity-check를 실행한다. rebase가 CHECK_TARGETS에 kit 이름 parity-check를 되돌려 놓아 스위트가 fsm의 vector parity 대신 kit K33의 검사를 실행했으며, 이제 make-install 테스트가 그 타깃을 단언한다.

  • S-42: parity 검사기는 make check에서 하나만 돈다. CHECK_TARGETS가 kit의 parity-check를 가리키고 있었다(S-35-2가 fsm 타깃의 이름은 바꿨지만 목록은 바꾸지 않았다). 그래서 벡터 기록은 한 번도 검사되지 않았다. 이제 vector-parity-check를 가리키며, tests/check-parity의 테스트가 Makefile을 읽어 이를 요구한다. kit K33은 채택하지 않는다: 각 binding을 스스로 실행하고 기록을 읽지 않으므로 기록, kinds, feature, 도구 테스트, 시간 규칙을 표현할 수 없다(계획 C-4가 규칙마다 적었다).

  • S-34-1: kit dependency gate는 python lock의 모든 전이 python package를 덮는다: 최신 릴리스와 advisory 리뷰는 pythonLocks를 통해 packages/fsm-python/pylock.toml을 읽는다(kit K36). RED: kit의 python-lock fixture 테스트(K36)가 kit-test에서 통과한다. fsm에서 pythonLocks가 비면 dependency-policy-check가 실패하고, lock을 나열하고 review한 뒤 통과한다.

  • S-34-2: python lock은 packages/fsm-python/pylock.toml이며, uv.lock의 PEP 751 export다(uv export --format pylock.toml --frozen --no-emit-project, kit K36). config/dependency-policy.json이 pythonLocks로 이름 붙이고, make dependency-review RECORD=1은 그 package와 두 git package(polyspec-orm과 polyspec-ordered-json, v0.0.7의 commit)를 review에 적는다. make python-lock-check는 커밋된 본문이 새 export와 다르면 실패한다(규칙이 없을 때 RED). package venv는 이 파일로 채운다(S-41).

  • S-41: python은 uv가 아니라 make install-tools(config/toolchain.json의 pythonBuild 3.14.8, 플랫폼별 sha256)에서 온다. install-python과 tests/install-python은 tests/python-tools(make python-tools-test)로 바뀌었다. package venv는 그 interpreter의 복사본이다(python3 -m venv --copies --without-pip packages/fsm-python/.venv). packages/fsm-python/pylock.toml(uv.lock에서 uv가 export)을 uv pip install --link-mode copy로 채운다. uv venv와 uv sync는 kit runner가 거부하는 link를 쓴다. python 테스트는 PATH 항목이 아니라 run-tests.mjs python --venv로 실행하며, main의 make test-python("No module named 'polyspec'")을 고친다. RED: python-tools-test가 uv로 만든 checkout venv에서 실패한다. GREEN: test-python 290 case, python-persist-test-postgres와 python-persist-test-mysql(실행 중인 서버), parity-python, toolchain-check, make-install-test, workflows-test가 통과한다.

  • S-35-2: vendored kit은 v0.0.23이다(make kit-sync KIT_TAG=v0.0.23, kit K36과 K37). config/dependency-policy.json에 필수 필드 pythonLocks를 더했다(S-34-2가 lock을 이름 붙이기 전까지 []): kit gate와 review가 그것이 나열한 PEP 751 pylock.toml 파일을 읽는다. review record config/dependency-review.json은 make dependency-review RECORD=1로 필수 vcs 목록([])을 받는다. fsm 타깃 parity-check는 vector-parity-check로 이름을 바꿨다(kit v0.0.23이 parity-check를 정의하며, 저장소 타깃이 그것을 make warning과 함께 대체했었다). tests/make-install의 테스트는 make -n check의 make warning마다 실패한다. kit의 parity-check는 아직 채택하지 않는다: S-42가 평가한다(계획 C-4). run-tests.mjs python --venv는 pyvenv.cfg가 install-tools Python을 가리킬 때만 venv interpreter를 실행한다. 알려진 상태: python package 테스트(make test-python, python persist 타깃)는 S-41까지 깨진 채 남는다. kit 0.0.18부터 python 도구는 var/tools/bin/python3로만 실행되며 PATH를 무시하는데, fsm-python 테스트는 venv를 PATH에 올리기 때문이다("No module named 'polyspec'", CI 실행 38115497717). RED: lock을 v0.0.20으로 둔 채 v0.0.23 파일로 kit-check를 돌리자 29개 finding. GREEN: kit-check, owner-validate, documents-check, toolchain-check, dependency-policy-check, hooks-check, name-check, commits-check와 kit-test가 통과한다.

  • S-35-1: vendored kit은 v0.0.20이다(make kit-sync KIT_TAG=v0.0.20). kit-test가 vendored 테스트를 scripts/kit/run-tests.mjs로 실행하므로(kit K32, 0.0.19로 릴리스) 다른 모든 테스트처럼 케이스별 진행과 timeout을 보고한다. 0.0.18과 0.0.19의 vendored python-real 테스트가 소비자의 toolchain을 읽어 여기서 실패했고 kit K34-1이 이를 vendored 테스트 밖으로 옮겼으므로 0.0.20을 받는다.

  • S-29-2: packages/fsm-php와 packages/fsm-php-ext의 composer vendor는 composer.lock뿐 아니라 composer.json도 키로 하고, 설치 뒤 stamp를 갱신한다. composer.json의 autoload는 lock에 없어서, 기존 checkout이 S-25 이전의 autoload를 유지했고 persist case가 정의되지 않은 함수로 실패했다.

  • S-37-2: fsm-db CLI와 command 테스트를 PostgreSQL과 MySQL에서도 실행한다(감사 23, 24). case마다 빈 database를 쓴다. SQLite에서는 새 파일이고, 서버에서는 admin DSN FSM_TEST_PG_ADMIN_DSN 또는 FSM_TEST_MYSQL_ADMIN_DSN을 통해 createEmptyDatabase(packages/fsm-db/tests/dialect.mjs)가 만든 새 database이며, 파일이 다시 지운다. db-test-postgres와 db-test-mysql target은 cli.test.mjs와 command.test.mjs도 실행하고 admin DSN을 요구한다. CLI 테스트는 테이블 없음 메시지의 인용된 table 이름을 확인한다. PostgreSQL 서버가 메시지를 한국어로 쓰기 때문이다. RED: PostgreSQL target의 테스트는 SQLite에서 돌아(조용한 통과), 그다음 서버 메시지 relation "machine" does not exist(서버는 한국어로 썼다)에서 실패했다. GREEN: 각 서버에서 36개 case가 통과한다. 결정과 CI service 줄은 계획 C-6에 있으며, fsm-db의 devDependencies pg와 mysql2는 고정되고 검토되었다.

  • S-37-1: go, rust, php, php-ext, python의 persist case도 PostgreSQL과 MySQL에서 실행한다(감사 23). 각 kind에 make <kind>-persist-test-postgres와 make <kind>-persist-test-mysql이 있고 TOOL_TESTS와 CHECK_TARGETS에 든다. 테스트는 FSM_TEST_DIALECT를 읽고, DSN을 FSM_TEST_PG_DSN 또는 FSM_TEST_MYSQL_DSN에서 받으며, 연결의 driver가 그 dialect인지 확인하고, persist table을 비운 뒤 case를 차례로 실행한다. DSN이 비어 있는 서버 target은 exit 2로 멈춘다(require_dsn). db와 npm의 서버 target도 같은 방식이다. RED: 테스트가 dialect를 읽기 전에는 PostgreSQL target이 SQLite case로 통과했다(go는 예제가 orm/pg를 import하기 전까지 driver "postgres" is not registered로 실패했다). PostgreSQL 17과 MySQL 8.4에서 모든 kind가 GREEN이다. 동시성 증명은 npm에 남는다(계획 C-4).

  • S-26-2: S-26-1이 찾은 README 사실을 실제 출력에 맞춘다. compiler README는 vectors --kind go에 --go-module을 적는다. python README는 명령이 쓰는 package ./app/order_fsm을 적는다(실제로 실행했다; 예제는 app/을 module 경로에 넣고 import한다). src/app_fsm은 checkout에 없는 경로였다. go README는 persistence 발췌가 test bed의 model package examples/model을 import하며 소비자는 자기 것을 import한다고 밝히고, 발췌는 파일과 대조했다. RED: compiler 명령이 --go-module 없이 실패했고, python 설명이 명령이 쓰지 않는 경로를 적었다. GREEN: 문서의 명령을 모두 실행했고, 한국어 fenced block은 영어와 같다.

  • S-26-1-1: go vectors 파일은 자기 module의 machine package(<module>/src/<machine>)만 import하므로, 소비자의 vectors --kind go 파일은 소비자 module에서 vet된다. 생성되는 decide는 examples.Decide가 이름 붙이던 함수 타입을 반환하므로 파일이 test bed의 examples(orm model이 필요한)를 더 이상 import하지 않는다. persistence 예제는 test bed의 것으로 남는다. RED: 소비자 module에서 go vet이 cannot find module providing package github.com/polyspec/fsm-go/examples로 실패; GREEN: packages/fsm-compiler/tests/go-backend.test.mjs의 소비자 테스트가 통과하고, golden vectors 파일과 packages/fsm-go/tests/vectors_test.go를 다시 생성했으며, make test-go, make parity-go, make gen-check가 통과한다. plan C-2와 C-5가 결정을 기록한다.

  • S-22: 계획의 GUI 수동 확인을 고정한 Playwright의 Chromium에서 json, mermaid, db store로 commit bc9f1b1에서 실행했고, 단계, 결과, 녹화를 docs/verification/gui.md에 기록했다: 다이어그램이 모든 state 이름을 보이고, 추가한 행이 제안 code 6을 가지며, 저장한 state가 표와 다이어그램에 나오고, 삭제한 state가 빠지고 다음 code가 7이며, 이름 바꾸기가 code를 유지하고, 각 store에서 fsm-gen check 또는 fsm-db check가 통과한다. driver는 packages/fsm-gui/scripts/manual-check.mjs다. 첫 실행들이 S-22-1, S-22-2, S-22-3, S-38을 찾았다.

  • S-20-2: docs 설치가 npm 경고를 출력하지 않는다. npm 11이 esbuild와 fsevents의 설치 script에 경고했고, 이는 docs/package.json이 결정하지 않은 것이었다. esbuild script는 고정 버전에 허용하고(allowScripts), fsevents script는 거부한다. 사이트는 watcher를 쓰지 않으며 이 package는 빌드된 binary를 담기 때문이다. 추적과 근거는 계획 C-8에 있다. tests/docs-build는 lock으로 새 npm ci를 실행하고 npm 경고가 있으면 실패한다.

  • S-29-1: gui-browsers는 고정된 Playwright 버전마다 Chromium을 한 번 내려받는다. 브라우저는 packages/fsm-gui/package-lock.json의 버전으로 키를 정한 file var/deps/playwright-<version>이며, gui-browser-test가 이 file에 의존하므로 stamp가 있는 이후 실행은 다운로드를 출력하지 않고 네트워크도 필요 없다(make -n gui-browser-test를 tests/make-install이 검사한다). gui-browsers는 stamp의 phony 별칭으로 남는다.

  • S-34: python 의존성을 잠갔다(감사 16). packages/fsm-python/uv.lock(uv lock)이 트리의 모든 package를 정확한 버전, 각 artifact의 sha256, 각 git package의 commit과 함께 담는다(orm과 ordered-json은 v0.0.7). make -C packages/fsm-python install은 이 lock으로 uv sync --locked를 실행하므로 uv가 각 artifact를 hash와 대조한다. make install의 python stamp는 pyproject.toml 대신 uv.lock으로 키를 정한다. kit dependency gate는 Python lock을 읽지 않으므로 scripts/check-python-lock.mjs(make python-lock-check)가 lock의 pin, commit, tag, current, closure 규칙을 검사한다(계획 C-1). 전이 package의 최신 릴리스와 advisory 리뷰는 S-34-1로 등록했다.

  • S-37: 영속화와 DB store를 SQLite, PostgreSQL, MySQL에서 증명한다(감사 23, 24). packages/fsm-npm/tests/persist-dialects.test.mjs는 한 row의 전이 두 개를 두 연결에서 동시에 실행한다. 같은 (expected, event)는 Changed 하나와 Unchanged 하나를, 한 expected 상태의 서로 다른 event 두 개는 Changed 하나와 E_CONFLICT 하나를 낸다. SQLite에서는 두 전이를 차례로 실행한다. SQLite driver가 동기식이기 때문이다. forUpdate()가 없으면 PostgreSQL과 MySQL에서 두 전이가 모두 Changed를 보고한다(RED). make db-test-postgres와 make db-test-mysql은 packages/fsm-db/tests/dialect.mjs를 통해 두 서버에서 DB store 테스트와 store 계약을 실행한다. FSM_TEST_PG_DSN이나 FSM_TEST_MYSQL_DSN이 없으면 target이 실패하며 case를 건너뛰지 않는다(RED: 변수 없이 실행하여 실패를 확인). 문서는 테스트한 dialect를 적는다(명세 B-2, B-4; fsm-db README; 루트 README). S-21을 위한 CI service 줄은 계획 C-6에 있다.

  • S-31-1: layout 계약이 rust retry test를 허용한다(S-31 감사). contracts/layout.json은 tests/persist_retry.rs를 차이로 선언했지만 check-layout은 그 파일이 rust의 허용 glob에도 있을 것을 요구한다. S-31 commit은 layout-check를 실행하지 않아 이를 놓쳤다. make layout-check와 make layout-test가 통과한다.

  • S-31: rust 영속화 예제가 deadlock을 재시도한다(명세 B-4). examples/persist.rs는 콜백을 한 번만 실행하는 transaction_once를 썼지만, B-4는 deadlock이면 orm이 콜백을 다시 실행한다고 적는다. 이제 orm v0.0.7의 transaction을 쓴다(기본 세 번 재시도). 콜백은 Fn이다. machine 오류는 transaction 콜백이 orm 오류로만 실패하므로 값으로 돌려준다. persist_runs_again_on_a_deadlock(RED: 한 번 시도, 기대는 두 번)과 persist_does_not_run_again_on_another_error가 packages/fsm-rust/tests/persist_retry.rs (make rust-persist-test)에 있다. 명세 B-4는 조건부 update 하나 대신 transaction + forUpdate()로 잠그는 이유를 적는다. orm v0.0.7에는 영향 행 수를 돌려주는 공개 update가 없고, 이를 다시 볼 조건을 함께 적는다.

  • S-39: go test 파일과 겹치는 machine key를 예약한다(스펙 B-5). examples, vectors, machines, expectation, lookup은 fsm-gen check를 통과하면서도 packages/fsm-go의 go test ./...를 깨뜨리므로(import 이름 중복, 또는 import로 이미 선언된 package 이름) 이제 E_KEY_RESERVED로 거부한다. tests는 측정에서 빌드되므로 예약하지 않는다. S-39 행은 이 이름을 들었으나, 측정이 그것을 바로잡는다.

  • S-33: fromCode은 모든 호출자에게서 정수가 아닌 값을 거부한다(감사 13, 14, 22). strict_types를 선언하지 않은 호출자에서 php와 php-ext는 "2"와 2.0을 2의 상태로, true를 1의 상태로 변환했다. 이제 python과 npm처럼 E_UNKNOWN_CODE를 던진다(스펙 B-6). GUI 편집 페이지는 저장 전에 거부된 다음 code(E_CODE_RANGE)를 보여 주며, core와 폼은 null을 돌려주지 않는다. 스펙 B-6은 생성된 코드가 가진 네 오류 상수를 적는다.

  • S-40: fsm-gui의 command 테스트는 machine 하나로 프로젝트를 만든다. packages/fsm-gui/tests/command.test.mjs는 root fsm.lock.json 전체를 order machine의 프로젝트에 복사했으므로, root lock에 machine을 더하면 테스트 프로젝트가 바뀌었다. 이제 테스트는 root lock의 order 항목만으로 프로젝트 lock을 쓰고({ order: rootLock.order }), 프로젝트 lock이 order만 가짐을 단언한다. RED: 프로젝트 lock이 root의 다섯 machine(in_review, limits, order, standby, ticket)을 담았다. GREEN: make gui-test가 통과하며, command 테스트는 233개 case 중 하나로 통과한다.

  • S-26-1: 언어 README가 S-25와 S-26을 따른다(계획 C-9). go README는 --go-module을 붙인 fsm-gen vectors --kind go와 실제 출력을 적고, tests/vectors_test.go의 key 표와 adapter를 설명하며, examples/persist.go의 Decide를 파일에서 다시 옮긴다(orderMachine.decide adapter와 함께). php README는 composer.json의 실제 autoload block을 옮기고(autoload.files의 loader src/functions.php는 모든 machine을 require한다), 파일 표가 loader를 적는다. 모든 언어의 Errors 절이 그 언어의 field 이름으로 spec B-6의 field 규칙(어떤 code가 expected, actual, event를 채우는지)을 적는다. RED: go 명령은 --go-module 없이 실패했고, composer 예시는 Order 함수만 읽었으며, 어떤 Errors 절도 B-6을 적지 않았다. GREEN: 모든 발췌가 파일과 글자 그대로 같고, 한국어 code block이 영어와 같으며, 명령과 snippet을 임시 소비자 프로젝트에서 실행했다. go vectors 파일은 소비자가 아니라 test bed의 examples를 import하며, 이는 checklist 항목 S-26-1-1이 기록한다.

  • S-29: 새 clone에서 make check를 실행할 수 있다(감사 8). make install은 install-tools의 toolchain과 모든 패키지 의존성을 한 번 설치한다: fsm-npm, fsm-db, fsm-gui, docs의 npm lock, fsm-php와 fsm-php-ext의 composer lock, fsm-python의 Python 3.14 venv, go module과 cargo crate(var/deps/의 stamp). 각각은 자기 lock을 키로 하는 file target이고, 패키지를 쓰는 타깃(gen-check, npm-types, test-<kind>, db-test, gui-test, docs-build)은 그것을 선행 조건으로 둔다. 전에는 패키지가 설치되지 않은 checkout에서 gen-check, npm-types, packages-test, parity-check가 실패했다. make make-install-test(tests/make-install/)는 Makefile 사본에서 선행 조건을 검사한다.

  • S-26: 모든 vector가 결과 전체를 단언한다(감사 4, 15). 오류 case는 fsm.Error의 네 필드(code, expected, actual, event)를 명명하고, 해당하지 않는 필드는 null이다(명세 B-6, 계획 C-4). 모든 언어 harness가 네 필드를 모두 단언한다. from_code에는 음수 code, 2^31, 64비트 최댓값 입력 셋이 더해졌고, 파일이 정확하도록 십진 문자열로 쓴다. 잘못된 state 값(0과 미지 code)은 code, key, is_final, apply에 go, npm, python으로 준다. 이 셋은 state 타입이 임의 정수를 담는 kind다. rust, php, php-ext에는 그런 값이 없으므로 그 native 모듈은 그런 case를 담지 않는다. case의 kinds 목록은 그 case를 돌리는 kind를 적고, check-parity가 이를 검사한다. 계약 contracts/features.json은 바뀌지 않았다.

  • S-25: 적합성 machine이 정의를 덮는다(감사 3). machines/에 standby(event 없음), in_review(모든 수준의 underscore key), ticket(reserved code 2와 4: 첫 lock 뒤 상태를 제거했고, 두 번째 lock이 code를 reserved로 유지한다), limits(code 2147483647)를 lock 항목과 conformance/vectors와 함께 더했다. go harness는 일반형이다. tests/vectors_test.go는 key 표와 machine마다 adapter 하나를 담고 go 백엔드가 생성한다(vectors --kind go는 --go-module을 받는다). tests/conformance_test.go는 각 case를 그 machine의 adapter로 실행하고, examples/persist.go는 machine을 Decide로 받는다. php 패키지는 적재기 src/functions.php로 모든 machine의 함수를 싣는다(명세 B-6, 계획 C-2). order 골든 파일과 비교하는 compiler 테스트는 order machine만 있는 프로젝트를 읽는다(tests/order-project.mjs). 각 kind는 다섯 machine의 209 케이스를 통과한다. S-23의 event 없는 패키지 machine은 standby다.

  • S-24: php-ext 백엔드가 모든 C 이름을 --php-namespace와 --php-ext-name에서 유도한다. 머신 파일이 php_polyspec_fsm_test.h와 polyspec_fsm_test_arginfo.h를 include했고, Error 클래스 심볼은 옵션과 무관하게 Polyspec_FsmTest_Fsm_Error였다. 이제 php_<ext>.h, <ext>_arginfo.h, <namespace의 _ 버전>_Fsm_Error다(명세 B-6). 백엔드 테스트는 두 번째 쌍 Acme\Tool과 acme_tool로 출력을 phpize, ./configure, make로 빌드하고 php에 올린다.

  • S-23: rust 백엔드가 이벤트가 없는 머신을 컴파일한다. variant가 없는 enum은 표현을 가질 수 없으므로(rustc E0084), 그런 머신의 Event는 #[repr(i32)] 없는 pub enum Event {}다(명세 B-6). 백엔드 테스트가 그 머신의 생성 파일을 cargo로 빌드한다.

  • S-22-3: 브라우저 테스트는 networkidle이 아니라 연결된 폼을 기다린다. client는 connectForm과 구독이 자리 잡으면 form host #fsm-form에 data-fsm-connected="true"를 설정하고, 연결이 끊기면 제거한다. layout은 표시가 설정되기 전까지 행 버튼이 클릭을 받지 못하게 한다. 첫 실행 실패는 묶기 전 클릭이 아니라 새로 받은 Chromium의 첫 탐색으로, 5초 케이스 제한에 대해 2.5~12.4초였다(새 다운로드 네 번 측정, plan C-7). setup이 이제 자기 제한으로 편집 페이지를 한 번 연다.

  • S-30-1: README가 S-30의 lock 기준선을 밝힌다(명세 B-3). 루트 README는 규칙을 fsm-gen lock의 실제 출력(맞바꾸고 지운 lock의 E_LOCK_REGRESSED), json과 mermaid의 git HEAD 기준선, git 밖의 한계, 커밋이 없어 E_LOCK_REGRESSED가 생기지 않는 db store와 함께 준다. fsm-db README의 table과 lock 절, fsm-gui의 저장·이름 바꾸기·삭제 규칙이 같은 내용을 말한다.

  • S-38: Mermaid 다이어그램이 모든 상태를 이름으로 보인다(명세 B-1, B-5). 고정한 mermaid 12.1.0에서 draft : 1은 code만 그려서 GUI와 모든 Mermaid viewer가 1..n만 보이고 key가 없었다. 이제 각 상태는 label 줄 state "draft" as draft와 draft : 1이다. 다이어그램은 key를 상자의 제목으로, code를 description으로 보인다. reader는 key와 다른 label을 E_MERMAID_UNSUPPORTED로 거부한다. 다른 형식 네 가지(description 반복, label 안의 code, description 하나에 key와 code, 이전 형식)를 검토해 B-1에서 기각했다. B-5의 예약 단어는 label 줄을 넣고 다시 쟀고 목록은 바뀌지 않는다. packages/fsm-gui/tests/browser/diagram.test.mjs는 모든 상태 key와 code가 그려진 label인지 확인한다(이전에는 실패했다). mermaid store, 정규 텍스트, GUI 고정 데이터, README와 명세의 모든 Mermaid 예제를 따른다.

  • S-20-1: 문서 사이트 빌드가 경고 없이 끝난다. vue alias가 CommonJS 감싸기인 vue/index.mjs를 가리켜 빌드가 Circular chunk: framework -> theme -> framework를 출력했다. alias는 이제 vue의 import 진입점 dist/vue.runtime.esm-bundler.js를 가리킨다. vite override와 vitepress는 근거와 함께 배제했고, 계획 C-8에 기록했다. docs-build-test는 빌드 출력의 경고 줄이 하나라도 있으면 실패한다.

  • S-35: 계획과 계약이 저장소와 맞는다(감사 17, 18, 19, 21). 계획 D 표는 상태를 담지 않고 체크리스트를 가리킨다. C-1 트리에 src/store-lock, 이름 검사기, docs-build와 install-python 테스트, 문서 사이트 파일을 넣었다. contracts/layout.json은 npm의 tests/types.test.mjs와 tests/types/**를 이유와 함께 differences에 선언하고, check-layout은 어떤 선언도 없는 kind의 테스트 파일을 거부한다(tests/check-layout/의 RED 테스트). S-11-3에는 반복 가능한 테스트 make install-python-test가 생겼다. 임시 checkout에서 stub uv로 타깃을 돌리고, --no-bin이 없는 레시피는 링크를 쓴다는 것을 보인다. kit-test를 CHECK_TARGETS에 넣었다.

  • S-32: 컴파일러의 import를 명시하고 테스트한다(계획 C-5). packages/fsm-compiler/src는 node: 내장 모듈, 자기 파일, 이 clone의 벤더 kit(scripts/kit/)만 import하고 npm 패키지는 import하지 않는다. 계획은 의존성 0이라 했지만 kit import와 모순이었다. tests/imports.test.mjs가 src의 모든 import specifier(정적, 동적, 상대 경로)를 검사하고 그 집합 밖이면 실패한다. src/core는 node: 모듈을 import하지 않는다(감사 5).

  • S-27: name-check는 줄이 아니라 옛 이름의 출현마다 판정한다. 허용 구절은 줄에서 지운 뒤 남은 부분을 다시 검사한다. CHANGELOG 항목은 rename까지(S-01부터 S-18까지)의 bullet만 허용하고, model 항목은 orm-gen이 상태 열을 위해 쓰는 helper와 열 이름만 허용한다. 줄 전체를 허용하던 항목은 정확한 구절만 허용하며, 감사 probe 줄은 finding이다(tests/check-name/의 RED 테스트). rename 잔재를 고쳤다: Makefile 주석, 컴파일러와 gen-check 테스트 helper 주석, python과 rust 예제의 상태 열용 status_of(감사 6, 12).

  • S-19: 사용자 문서(R11, 계획 C-9). 루트 README와 모든 패키지 README(영어와 한국어)가 사용자에게 라이브러리를 설명한다: fsm이 무엇이고 무엇이 아닌지, JSON, Mermaid, database의 order 예제로 보는 모델, code와 lock, apply로 하는 전이와 expected가 재시도를 멱등하게 만드는 이유, 모든 검증 code, 프로젝트 파일, 요구사항과 설정, 모든 fsm-gen 명령과 옵션의 실제 출력과 종료 코드, 생성 이름, 검사. packages/fsm-compiler/README.md는 새로 만들었다(구조, store 계약, 명령, backend 옵션, vector). database와 GUI README는 필수 절을 추가했다(GUI는 Tests 절을 유지). 각 언어 README는 생성 명령, 생성 파일, apply 예제가 있는 API, 오류, examples/persist.*의 영속화 패턴, 테스트를 설명한다. scripts/check-readmes.mjs(make readme-check, 테스트는 tests/check-readmes/)는 각 README의 절과 같은 제목을 가진 한국어 짝을 요구한다. 문서를 쓰기 전에는 54개 검사 결과를 냈다. make docs-build는 새 페이지로 사이트를 빌드한다.

  • S-30: 커밋된 code는 lock을 잃어도 의미가 바뀌지 않는다(R1, 명세 B-3·B-5). 모든 store의 load()는 커밋된 lock을 baseline으로 돌려준다(json·mermaid는 git HEAD의 fsm.lock.json, db는 저장된 행). check, gen, vectors, lock, save, copy는 커밋된 code를 잃거나, 커밋된 reserved code를 상태에 주거나, 커밋된 key를 다른 code로 옮기는 lock을 E_LOCK_REGRESSED로 거부한다. 이름 변경은 커밋 전에도 받아들인다. 감사 사례(code 두 개를 바꾸고 fsm.lock.json을 비운 뒤 lock)는 lock: ok를 출력했으나 이제 거부된다. git 밖이나 추적되지 않는 lock은 커밋된 것이 없으므로 저장된 lock만 판정한다. db store에서 SQL로 바뀐 행은 감지하지 못한다. lock은 커밋된 code를 유지할 때만 삭제된 lock을 복구한다.

  • S-22-2: GUI 폼을 쓸 수 있다. make gui-build가 crudui stylesheet @polyspec/crudui-generator-core/styles/crudui.css를 public/assets/crudui.css로 복사해 hyper-build-assets --static에 넘기고, layout은 /assets/crudui.css를 링크하므로 행 버튼이 아이콘과 크기를 보인다. 상태 행의 새 field는 New key로 label을 붙인다(저장된 key는 Key 그대로다). 한 행의 두 field가 label을 공유하지 않는다. public/favicon.ico(scripts/make-favicon.mjs가 쓰는 ICO)가 /favicon.ico에 404 대신 200으로 응답하며, layout이 이를 링크한다. 브라우저 테스트가 세 가지를 모두 확인한다.

  • S-22-1: GUI 폼이 행 변경 뒤 다시 렌더링한다. 편집 폼(packages/fsm-gui/client/main.ts)은 crudui instance를 구독하고, patchContent(host, renderForm(instance, render))로 다시 렌더링한 뒤 connection.sync()를 호출한다. connectForm의 계약이다. 수정 전에는 행의 add·remove 버튼 클릭이 instance만 바꾸고 페이지는 바꾸지 않았다. render는 페이지의 crudui render 옵션(action, _csrf token, 오류)이며 페이지 state에 실리고, 폼은 #fsm-form host 요소 안에 있다. packages/fsm-gui/tests/browser/browser.test.mjs(make gui-browser-test)는 고정된 Playwright 1.64.0(정확한 버전 devDependency, make gui-browsers가 브라우저를 내려받음)의 headless Chromium으로 폼을 조작하며 add, save, remove, 제안 code를 확인한다. make gui-test는 최상위 테스트만 실행한다. 수정 전 첫 브라우저 단정은 5 !== 6으로 실패했다.

  • S-36: fsm-db CLI를 README가 문서화한 모든 명령에 대해 process로 테스트한다. packages/fsm-db/tests/cli.test.mjs는 출력, 종료 코드, 이후의 행을 단언한다. lock --rename order.draft=created는 [fsm-db] lock: ok를 출력하고, 이름이 바뀐 state의 code를 유지하며, 새 key를 행에 쓴다. 알 수 없는 rename은 README의 E_UNKNOWN_STATE_REF 줄을 출력하고 행을 쓰지 않는다. copy --to mermaid --root <dir>는 정규형 .fsm.mmd 파일과 lock을 쓴다. copy --from mermaid --root <dir>는 빈 database를 채우고, 두 번째 복사는 README의 E_STORE_NOT_EMPTY 줄을 출력하며 아무것도 쓰지 않는다. RED: process 케이스가 없었고, 기대 출력을 바꾸면 rename 케이스가 실패한다. README는 명령과 일치했으므로 틀린 README 문장은 없었다. 대신 copy --to mermaid 예시가 추가되었다(en, ko).

  • S-28: spec B-2의 store 계약을 세 store에서 증명한다. packages/fsm-db/tests/store-contract-all.test.mjs(컴파일러에 의존하는 fsm-db 패키지가 소유하며 make db-test에서 실행)는 packages/fsm-compiler/tests/fixtures/valid의 모든 valid fixture를 각각, 그리고 전부 함께 json → db → mermaid → json으로 왕복시키고 파일을 바이트로 비교한다. 또한 필수 옵션을 준 모든 kind의 run gen과 vectors(모든 kind의 vectors 파일 포함)가 json, mermaid, db store에서 바이트 동일한 출력을 내는지 확인한다. RED: save에서 final을 버리는 db store는 네 개의 동등성 케이스에서 실패한다. core가 E_DEAD_END로 왕복을 거부하거나 바이트가 달라지며, 탐지 케이스도 같은 것을 증명한다. 실제 store는 통과하므로 store 결함은 발견되지 않았다. spec B-2(en, ko)가 파일을 명시한다.

  • S-20: 문서 사이트(R11, 계획 C-8). docs/.vitepress/config.mts는 저장소 문서를 제자리에서 VitePress 1.6.4로 빌드한다(make docs-build, 패키지는 docs-deps가 docs/package-lock.json으로 설치): 루트와 패키지 README(영어와 한국어), 명세, 계획, 검증 페이지, changelog. README.md는 자기 디렉터리의 index 페이지이며, 사이트 페이지가 아닌 저장소 파일로 가는 링크는 공개 저장소를 가리키고, dead link는 빌드를 실패시킨다. make docs-build-test(tests/docs-build)는 dead link의 실패와 실제 빌드를 확인한다. docs/package.json은 vite를 6.4.4로 override한다. vitepress 1.6.4가 moderate 및 high advisory가 있는 5.4.x 줄을 고정하기 때문이다. 계획은 자리표시자 <major>를 코드 스팬에 쓴다. 사이트가 이를 Vue 마크업으로 컴파일하기 때문이다. config/documents.json은 siteLinks를 설정한다.

  • S-18-1: 명령 fsm-gen, fsm-db, fsm-gui가 존재한다. 각각 packages/fsm-compiler/package.json(새 파일, 의존성 없음), packages/fsm-db/package.json, packages/fsm-gui/package.json의 bin 항목이며, 각 파일은 node shebang으로 시작하고 실행 권한을 가지며, clone에서 npm install -g ./packages/<package>가 명령을 PATH에 link한다. S-18은 문서에서만 이름을 바꿨었다. 각 package의 테스트가 임시 npm prefix에 offline으로 설치하고 명령을 실행한다. GUI usage 줄은 fsm-gui를 쓴다.

  • S-15-1: fsm-db install [--project <dir>]은 fsm.config.json이 가리키는 database에 schema/fsm.dbs의 schema를 orm utils().schema().install로 설치하므로, database는 코드 없이 table을 얻는다. 새 database에서 fsm-db check는 no such table: machine로 실패하고(종료 코드 1), install은 [fsm-db] install: ok를 출력하며(종료 코드 0), 이후 check가 통과한다. 두 번째 install은 아무것도 바꾸지 않고 종료 코드 0으로 끝난다. table이 일부만 있는 database에서는 orm이 install을 CONFIG로 거부하며, 오류는 stack과 함께 출력되고(종료 코드 1) 대체 동작은 없다(orm v0.0.7에서 관찰). packages/fsm-db/README.md와 README.ko.md가 DB store의 사용자 안내서다. 명세(B-2)와 계획(C-6)은 명령을 나열하며, 명세의 copy 줄은 CLI가 가진 옵션(--root와 함께 쓰는 --to 또는 --from, DSN은 dsnEnv가 가리키는 변수에서만 온다)에 맞게 고쳤다. install은 SQLite에서 프로세스로 실행하는 테스트가 packages/fsm-db/tests/cli.test.mjs에 있다.

  • S-18: 라이브러리 이름을 status에서 fsm으로 전부 바꾼다(R12). 패키지는 packages/fsm-*, 명령은 fsm-gen, fsm-db, fsm-gui, 파일은 fsm.config.json, fsm.lock.json, machines/*.fsm.json, *.fsm.mmd, schema/fsm.schema.json과 schema/fsm.dbs(dbspec fsm), 문서는 docs/spec/fsm.md와 docs/plans/fsm-v1.md, 생성 공용 모듈은 fsm(fsm.Error, <ns>\Fsm\Error, 백엔드마다 모듈 fsm), 예약 머신 key는 fsm이다. status는 다시 일반 key다. 매니페스트 이름은 polyspec_fsm, polyspec-fsm, @polyspec/fsm-*, github.com/polyspec/fsm-go, 네임스페이스 Polyspec\FsmTest, extension polyspec_fsm_test다. 소비자의 status 열과 프로세스·테스트의 status는 status를 유지한다. scripts/check-name.mjs(make name-check)는 허용 목록 밖에서 라이브러리를 status로 부르는 추적 파일이나 경로가 있으면 실패하며, golden과 생성 파일은 fsm-gen으로 다시 생성했다. AGENTS.md와 계획은 name-check를 규칙을 강제하는 도구로 적고, 범위는 R1–R12이며, 문서는 옛 이름을 "옛 이름 status"로만 언급한다.

  • S-11-2: python 패키지가 color 우회 설정을 제거한다. kit v0.0.15(K22)가 run-tests python에서 NO_COLOR=1과 PYTHON_COLORS=0을 직접 설정하므로, packages/status-python/Makefile의 make test는 더 이상 PYTHON_COLORS=0을 설정하지 않는다. kit 설정이 없으면 FORCE_COLOR=3에서 unittest가 verbose 줄에 color를 넣고 reporter가 이를 읽지 못한다. 줄을 제거한 상태에서 v0.0.14 runner는 10번 중 10번 실패했고, v0.0.15 runner는 10번 중 10번 통과했다. make test-python은 FORCE_COLOR=3에서도, 없어도 89개 case를 모두 통과한다.

  • S-16-1: GUI test hang을 원인에서 고쳤다. kit v0.0.15 sync(K23)는 test child의 stdout 파이프를 blocking으로 만든다(scripts/kit/test-load-check.mjs). 비동기 파이프에서는 test child의 process.exit이 파이프에 남은 결과를 버렸다. 그래서 파일이 실행한 test보다 적은 결과를 보고할 수 있었고, runner는 "the test did not finish"를 출력할 수 있었다. case 600개짜리 파일에서 v0.0.14는 30번 중 12번, v0.0.15는 30번 중 0번 결과를 잃었고, blocking 줄만 되돌린 v0.0.15는 30번 중 10번 잃었다. 이 sync는 K21(--cwd 아래 node 파일 group)과 K22(run-tests python이 NO_COLOR=1과 PYTHON_COLORS=0을 설정)도 가져온다. test 변경 전에는 make gui-test가 4개씩 병렬로 20번 중 20번 통과했고(각 210개 결과), make compiler-test도 20번 중 20번 통과했다(각 302개 결과). 이 checkout에서는 hang이 재현되지 않았다. packages/status-gui/tests/mermaid-parse.test.mjs의 호출별 20초 deadline은 hang을 가렸을 뿐이어서 제거했다. runner가 이미 case마다 자체 timeout을 준다. flat test(예약 단어와 위치마다 하나, 일반 key마다 하나)는 각 case가 자기 이름과 결과를 가지므로 유지했다. 변경 후 make gui-test는 20번 중 20번 통과했다.

  • S-11-3: make install-python은 uv python install --no-bin 3.14를 실행하므로, uv가 사용자 bin 디렉터리(기본 ~/.local/bin)에 python3.14 실행 파일을 쓰지 않는다. 인터프리터는 여전히 uv 관리 설치이며, var/tools/bin/python3만 그것을 가리킨다. 변경 전에는 UV_PYTHON_BIN_DIR를 임시 디렉터리로 지정하면 target이 그곳에 python3.14를 썼고, 변경 후에는 그 디렉터리가 비어 있고 ~/.local/bin은 바뀌지 않는다.

  • S-14-3: gen과 vectors --kind는 출력 디렉터리의 git-ignored 파일을 건너뛴다(packages/status-compiler/src/cli/write-generated.mjs). 무시된 파일은 손으로 쓴 파일이 아니라 빌드 산출물이므로 E_NOT_GENERATED로 생성기를 막지도 않고 stale 파일로 지우지도 않는다. 그래서 packages/status-php-ext에서는 make test 뒤에 make clean 없이 make gen을 실행한다. 추적 파일이나 git이 무시하지 않는 파일은 여전히 E_NOT_GENERATED이며, git work tree 밖에서는 무시되는 파일이 없다. 무시된 파일은 출력 디렉터리의 git ls-files --others --ignored --exclude-standard로 구한다. make clean은 phpize 출력을 지우는 목표로 남는다. 테스트는 packages/status-compiler/tests/write-generated.test.mjs에 있다.

  • S-14-1: php-ext 패키지 packages/status-php-ext는 정규 구조(docs/plans/status-v1.md, C-2)의 kind php-ext다. make gen은 src/와 tests/Vectors.php를 status-gen(--php-namespace 'Polyspec\StatusTest' --php-ext-name polyspec_status_test)으로 쓰고, php 패키지의 tests/ConformanceTest.php와 examples/persist.php를 복사하며(gen-check가 같은 byte임을 확인한다), examples/model/을 루트 schema/persist.dbs에서 orm-gen으로 쓴다. make test는 src/에서 phpize, ./configure, make로 extension을 빌드하고 --php-extension으로 vector case 89개를 실행하며, make parity-php-ext가 이를 확인한다. composer.lock은 php 패키지와 같은 30개 패키지(polyspec/orm 0.0.7과 PHPUnit 12.5.38)를 고정하며, config/dependency-policy.json과 config/dependency-review.json이 php-ext manifest와 lock을 기록한다. contracts/layout.json은 생성 glob과 함께 kind를 선언한다. toolchain은 config/toolchain.json의 php와 composer 선언이며, 다시 선언하지 않았다.

  • S-14-2: php-ext backend의 C 출력을 첫 build 뒤에 고쳤다. 객체 인자는 zval *이다. Z_PARAM_OBJECT_OF_CLASS가 zval *을 쓰기 때문이다. Error method 표는 생성자 뒤에 둔다. ZEND_LONG_FMT의 format에는 %를 더 붙이지 않는다. Step property의 class 타입은 영구 class 이름 문자열의 ZEND_TYPE_INIT_CLASS이다. literal 이름은 property에서 해석되지 않기 때문이다. Code enum 값은 영구 interned 문자열이어서 shutdown에서 해제되지 않는다. .gitignore는 packages/status-php-ext/src/의 phpize 출력을 정확히 고정된 anchored 패턴으로 적는다. config.m4와 생성된 소스는 추적된 채로 남는다. golden 파일을 다시 썼다.

  • S-14: php-ext backend(packages/status-compiler/src/backends/php-ext.mjs)가 machine마다 php-ext 패키지의 src/ 파일을 생성한다. <machine>.c는 zend_register_internal_enum으로 enum State와 Event를, ZEND_NS_NAMED_FE로 B-6의 namespace 함수와 final class Step을 등록하며, apply와 target은 status_throw로 B-4 code를 던진다. status.c는 enum Status\Code, exception Status\Error와 module entry를 담고, module entry는 정의 순서대로 모든 machine을 초기화한다. php_<ext>.h, <ext>_arginfo.h(arginfo는 gen_stub 없이 직접 쓴다), <ext>.stub.php(편집기용 선언)와 config.m4가 나머지 집합이다. status-gen gen --kind php-ext는 --php-namespace와 --php-ext-name을 요구하고, vectors --kind php-ext는 php kind의 Vectors.php와 byte 단위로 같은 test module을 쓴다. packages/status-compiler/tests/golden/php-ext/의 golden 파일은 order 예시이며, 쓰기 전에 B-6과 대조해 검토했다. write-generated.mjs는 config.m4의 dnl header를 받는다.

  • S-17-1: 편집 페이지가 기계의 다음 code를 담는다. 내장 폼 상태에 core nextCode(machine, definitions, lock)를 nextCode로 담고(src/forms.mjs의 nextCodeOf와 editState), client/main.ts는 브라우저에서 추가한 상태 행마다 다음 code와 그 뒤의 code를 준다(client/new-codes.ts의 codesOfAddedRows). 저장은 여전히 모든 code를 검사하며, 빈 code는 nextCode로 채운다. tests/edit-next-code.test.mjs는 json, mermaid, db 저장소에서 내장 code를, 삭제 뒤의 code를 포함해 검사하고, tests/delete-state.test.mjs는 편집 폼에서 상태를 지우면 그 code가 세 저장소 모두에서 codes에서 빠지고 reserved로 옮겨짐을 보인다. make gui-test는 210개 테스트를 실행한다.

  • S-17: GUI로 기계를 편집한다(packages/status-gui). /machines/{machine}/edit는 crud/machine.yml의 crudui 폼이다. 상태와 event는 반복 행이며, 저장된 상태의 key와 code는 읽기 전용이다. 저장은 form[...] 필드를 파싱하고(src/form-data.mjs, 형식이 틀린 본문은 400), 새 상태에 core nextCode가 제안한 code를 주며, 전체 정의 세트로 core run save를 부른다. 거부된 저장은 아무것도 쓰지 않고 모든 finding과 함께 422로 응답하며, 필드를 가리키는 finding은 그 필드에 보여준다. /machines/{machine}/rename은 renames로 run lock을 부른다(code는 유지). /new는 초기이면서 final인 상태 하나를 가진 기계를 만들고, 상세 페이지는 그 기계 없이 저장하여 삭제하므로 그 code들은 reserved 목록으로 옮겨간다. 저장, 이름 바꾸기, 삭제마다 그 페이지의 _csrf 토큰이 필요하다(없으면 403). client/main.ts는 htmx settle마다 편집 폼의 행을 crudui connectForm으로 묶는다. make gui-test는 json, mermaid, db store에서 194개 테스트를 실행한다.

  • S-11-1: python 패키지 packages/status-python은 정규 구조(docs/plans/status-v1.md, C-2)의 kind python이다. pyproject.toml은 orm client를 git tag v0.0.7로, build backend를 setuptools==84.0.0으로 고정한다. make install은 Python 3.14 venv(.venv, gitignore 대상)를 만들고 이것들을 설치하며, make test는 그 venv를 PATH의 맨 앞에 두고 오프라인으로 실행한다. make gen은 status-gen으로 src/polyspec_status/와 tests/vectors.py를, orm-gen으로 examples/model/을 쓴다. tests/test_conformance.py는 vector case 89개를 각각 test_<feature>__<id>로 실행하고, examples/persist.py는 polyspec orm으로 SQLite에서 B-4 transaction을 실행하며, persist case가 실행 후 저장된 값을 검사한다. config/toolchain.json은 python 3.14를 선언하고, config/dependency-policy.json은 pyproject를 나열하며, contracts/layout.json은 C-2가 python에 허용하듯 lock 파일 없이 kind를 선언한다. make test-python과 make parity-python은 패키지를 실행한다.

  • S-11: python backend(packages/status-compiler/src/backends/python.mjs)가 machine마다 python 패키지의 src/polyspec_status/ 파일을 생성한다. <machine>.py는 B-6에 따라 member가 대문자인 IntEnum인 State와 Event, to와 changed를 가진 동결된 Step dataclass, 그리고 status.Error를 던지는 여덟 함수를 담는다. 공용 status.py(E_ code의 StrEnum인 Code와 Error)와 __init__.py도 만든다. status-gen gen --kind python --out <dir>는 python header # Code generated by status-gen from <machine>; DO NOT EDIT.를 가진 이 세트를 writeAtomic으로 쓰고, 오래된 generated 파일을 지우며, header가 없는 파일은 거부한다(E_NOT_GENERATED). vectors --kind python --out <file>는 native 모듈 tests/vectors.py를 Python literal로 쓴다. 작성기의 header 검사는 //와 함께 # 주석 문법도 받아들인다. packages/status-compiler/tests/golden/python/의 golden 파일은 order 예시이며, 쓰기 전에 B-6과 대조해 검토했고, 테스트는 이를 byte 단위로 비교한다.

  • S-09-2: make npm-types(packages/status-npm/tests/types.test.mjs)는 packages/status-npm의 고정 devDependency인 typescript 7.0.2로 tests/types/consumer.ts를 strict 모드에서 출력 없이(--noEmit), 생성된 src/*.d.ts에 대해 컴파일한다. 소비자 파일은 B-6의 모든 공개 이름을 쓰고, 컴파일되지 않아야 하는 @ts-expect-error 줄 세 개를 가진다. 그래서 너무 느슨한 선언은 지시문을 쓰이지 않은 채로 만들어 테스트를 실패시킨다. 이 테스트는 자기 기록인 .results/npm-types.json에서 실행된다. make test-npm은 conformance 파일만 지정하므로 npm 기록에는 vector case만 남아 parity-npm을 통과한다. contracts/layout.json은 두 테스트 경로를 허용한다.

  • S-13-1: rust 패키지 packages/status-rust는 정규 구조(docs/plans/status-v1.md, C-2)의 kind rust다. make gen은 src/와 tests/vectors.rs를 status-gen으로 쓴다. build.rs는 루트 schema/persist.dbs의 orm model을 OUT_DIR에 생성하므로, 이 패키지에는 examples/model/이 없다(contracts/layout.json에 선언된 rust의 차이). tests/conformance.rs는 check 함수를 담으며, vector case 89개가 test_<feature>__<id>로 실행된다. examples/persist.rs는 orm(v0.0.7, tag로 고정)으로 SQLite에서 B-4 transaction을 실행하며, persist case가 실행 후 저장된 code를 검사한다. rust-toolchain.toml은 루트에서 Rust 1.98.0을 선언하고, config/toolchain.json은 Cargo.lock의 의존 검토를 위해 cargoAudit 0.22.2를 선언한다. make test-rust와 make parity-rust가 이 패키지를 실행하며, make packages-test는 다른 kind와 함께 실행한다.

  • S-13: rust backend(packages/status-compiler/src/backends/rust.mjs)가 machine마다 rust 패키지의 src/ 파일을 생성한다. B-6 표기에 따라 <machine>.rs(#[repr(i32)]의 State와 Event, Step struct, 여덟 함수, i64를 받는 from_code, Result<Step, Error>를 돌려주는 apply), 공용 status.rs(Code와 Error)와 lib.rs를 만든다. status-gen vectors --kind rust --out <file>는 tests/vectors.rs를 쓰며, case마다 #[test](persist case는 #[tokio::test]) 하나를 두어 tests/conformance.rs의 check 함수를 호출한다. packages/status-compiler/tests/golden/rust/의 golden 파일은 order 예시이며, 쓰기 전에 B-6과 대조해 검토했고, 테스트는 이를 byte 단위로 비교한다.

  • S-12-1: php 패키지 packages/status-php는 정규 구조(docs/plans/status-v1.md, C-2)의 kind php다. make gen은 src/와 tests/Vectors.php를 status-gen(--php-namespace 'Polyspec\StatusTest')으로, examples/model/을 루트 schema/persist.dbs에서 orm-gen으로 쓴다. tests/ConformanceTest.php는 89개 vector case 각각을 PHPUnit data set test_<feature>__<id>로 실행하며, check-parity는 id마다 case 이름 하나를 읽는다. 생성된 namespace의 공개 API만 쓴다. persist case는 polyspec orm v0.0.7(release zip, composer.json에 sha1을 고정, ordered-json v0.0.7과 함께)로 SQLite에서 B-4 transaction을 실행하고 실행마다 저장된 state를 확인한다. composer.json은 PHPUnit 12.5.38(검토 시점의 최신 stable)과 config.platform.php 8.4.0을 고정하고, config/toolchain.json은 PHP 8.5와 Composer 2.10.3을 선언하며, config/dependency-review.json은 검토 기록이다. make test-php와 make parity-php가 패키지를 실행한다.

  • S-12: php backend(packages/status-compiler/src/backends/php.mjs)가 machine마다 php 패키지의 src/ 파일을 생성한다. <Pascal>/State.php와 Event.php는 B-6 code와 순번을 값으로 갖는 backed enum이고, Step.php와 functions.php는 B-6의 함수를 namespace 함수로 담으며, 공용 Status/Code.php(오류 code enum)와 Status/Error.php(getCode()가 code 문자열인 \Exception)를 만든다. status-gen gen --kind php --php-namespace <ns> --out <dir>는 namespace를 요구하므로(테스트 벤치는 Polyspec\StatusTest), 없거나 형식이 틀리면 usage 오류다. vectors --kind php --php-namespace <ns> --out <file>는 Tests/Vectors.php를 쓰며, 그 cases()는 모든 case를 test_<feature>__<id>로 키 붙인다. php 파일은 첫 줄이 <?php이므로 header를 둘째 줄에서 읽고, header가 없는 파일은 여전히 거부한다(E_NOT_GENERATED). packages/status-compiler/tests/golden/php/의 golden 파일은 order 예시(89 case)이며, 쓰기 전에 B-6과 대조해 검토했고, php -l은 생성된 모든 파일을 통과시킨다.

  • S-10-1: go 패키지 packages/status-go는 정규 구조(docs/plans/status-v1.md, C-2)의 kind go이며, module은 github.com/polyspec/status-go다. make gen은 src/(order/order.go, status/status.go)와 tests/vectors_test.go를 status-gen으로, examples/model/을 루트 schema/persist.dbs에서 orm-gen(go.mod의 tool 지시문을 통한 go tool orm-gen)으로 쓴다. model은 출력 디렉터리 안에서 생성되므로 gen-check는 module 사본에서 실행한다. tests/conformance_test.go는 모든 vector case를 test_<feature>__<id> subtest로 생성된 함수에 대해 실행하고, examples/persist.go는 SQLite 위에서 polyspec orm v0.0.7로 B-4 transaction을 실행하며, persist case가 실행 후 저장된 값을 확인한다. contracts/layout.json은 generated glob으로 kind를 선언한다. 언어 때문에 생긴 차이는 둘이다. gen --kind go는 --go-module을 받고(생성된 machine이 공용 패키지를 module path로 import한다), toolchain은 packages/status-go/go.mod의 toolchain 줄(go1.27.2, go1.27.0이 보고하는 stdlib 권고 GO-2026-6607을 고친 버전)이며 config/toolchain.json의 go가 이를 읽는다. config/toolchain.json은 의존 검토가 실행하는 govulncheck 1.8.0도 고정한다. make test-go(GOPROXY=off), make parity-go, make packages-test가 패키지를 실행하며, lock은 go.sum이고 config/dependency-review.json에서 검토한다.

  • S-10: go backend(packages/status-compiler/src/backends/go.mjs)가 go 패키지의 src/ 파일을 생성한다. B-6 표기에 따라 machine마다 <machine>/<machine>.go를 쓴다: type State int32와 멤버 StateDraft, type Event int32와 EventNext(위치는 1부터), 구조체 Step{To, Changed}, 그리고 Initial, Code, Key, FromCode, IsFinal, EventsFrom, Target, Apply 함수이며, 이들은 *status.Error와 함께 (value, error)를 돌려주고 인자를 npm과 같은 순서로 검사한다. 공용 status/status.go에는 Error 타입과 Code* 상수가 있다. status-gen gen --kind go --go-module <module path> --out <dir>가 이 집합을 쓴다. machine이 공용 패키지를 그 module path로 import하므로 module path가 필수다. status-gen vectors --kind go --out <file>은 native tests/vectors_test.go를 쓴다: oracle의 key를 그대로 둔 case마다 literal 하나이며, conformance test가 이를 상수에 대응시킨다. packages/status-compiler/tests/golden/go/의 golden 파일은 order 예시이며, 쓰기 전에 B-6에 맞춰 검토했고 테스트는 바이트 단위로 비교한다.

  • S-16: GUI 골격(packages/status-gui)은 hyper v0.0.11 애플리케이션이다. /는 crud/machines.yml의 crudui 목록으로 프로젝트의 기계를 보여주고, /machines/{machine}는 상태, edge가 있는 event, 정규형 Mermaid 텍스트를 <pre class="mermaid">에 보여준다. client는 Mermaid 12.1.0으로 첫 로드와 htmx swap마다 이를 렌더링한다. node src/main.mjs --project <dir>은 status.config.json이 지정한 store(json, mermaid 또는 db)를 고르고 그 store와 core run만 호출한다. 프로젝트의 모든 finding은 오류로 보여주며, 없는 기계는 404로 응답한다. make gui-test는 asset을 빌드하고 133개 테스트를 실행한다. 여기에는 고정한 mermaid 패키지로 order 예제를 파싱하는 테스트와, B-5의 모든 Mermaid 예약 상태 key를 다섯 위치에서 파싱하는 테스트가 포함된다. 고정 버전은 hyper v0.0.11, crudui v0.0.5, template v0.0.5와 mermaid·htmx.org·yaml의 정확한 버전이며, config/dependency-review.json에 검토 기록이 있다. Mermaid 파싱 테스트의 기준은 docs/spec/status.md의 B-5에 기록했다.

  • S-09-1: npm 패키지 packages/status-npm은 정규 구조(docs/plans/status-v1.md, C-2)의 kind npm이다. make gen은 src/와 tests/vectors.mjs를 status-gen으로, examples/model/을 루트 schema/persist.dbs에서 orm-gen으로 쓴다(이 항목에서 만들며 모든 kind가 공유한다). tests/conformance.test.mjs는 모든 vector case를 test_<feature>__<id>로 생성된 함수에 대해 실행하고, examples/persist.mjs는 orm으로 SQLite에서 B-4 transaction을 실행하며, persist case가 실행 후 저장값을 검사한다. contracts/layout.json은 kind를 generated glob과 함께 선언한다. check-layout은 이를 요구하고, gen-check는 그 glob이 맞는 파일을 정확히 비교한다. make test-npm, make parity-npm, make packages-test가 이 패키지를 실행하며, lock은 검토 기록과 함께 config/dependency-policy.json에 있다.

  • S-09: npm backend(packages/status-compiler/src/backends/npm.mjs)가 machine마다 npm 패키지의 src/ 파일을 생성한다. B-6 표기에 따라 <machine>.js와 <machine>.d.ts(동결된 State와 Event, 여덟 함수, apply는 {to, changed}를 돌려주거나 status.Error를 던진다), 공용 status.js와 status.d.ts(Error와 Code 상수), index.js와 index.d.ts를 만든다. status-gen gen --kind npm --out <dir>는 이 세트를 writeAtomic으로 쓰고, generated header가 있는 오래된 파일을 지우며, header가 없는 파일은 거부한다(E_NOT_GENERATED, 아무것도 쓰지 않음). status-gen vectors --kind npm --out <file>는 native 모듈 tests/vectors.mjs를 쓴다. packages/status-compiler/tests/golden/npm/의 golden 파일은 order 예시이며, 쓰기 전에 B-6과 대조해 검토했고, 테스트는 이를 byte 단위로 비교한다.

  • S-15: DB store(packages/status-db/src/db-store.mjs)는 정의와 lock을 orm client(v0.0.7)를 통해 schema/status.dbs의 여섯 테이블에 보관한다. dbStore(db)는 orm-gen이 src/models/models.ts에 생성한 model로 읽고 쓰며, save는 모든 행을 한 transaction 안에서 바꾸므로 쓰기가 실패하면 기존 행이 그대로 남는다. 모든 key 열은 varchar(64)이며(S-05-3의 상한), edge 테이블은 state를 향한 외래 키의 인덱스 두 개를 가진다. store는 계약 테스트(tests/store-contract.mjs)를 통과하고, 64자 key를 포함해 json → db → json을 바이트 단위로 같게 왕복하며, 비어 있지 않은 데이터베이스로의 copy는 E_STORE_NOT_EMPTY로 거부하고, 실패한 save를 되돌린다. status-db(src/cli/status-db.mjs)는 check, lock, copy --to|--from json|mermaid --root를 제공하며, DSN은 status.config.json이 이름 붙인 환경 변수의 값이다. make db-test가 테스트를 실행하며, 여기에는 orm-gen이 커밋된 model을 재생성하는지 확인하는 테스트가 포함된다.

  • S-06-1: store 계약이 비동기가 되었다. JSON·Mermaid store의 load()와 save()가 Promise를 돌려주고, run과 copy(src/core/run.mjs)도 비동기이므로 DB store가 비동기 orm client를 쓸 수 있다. status-gen은 명령을 await하며, 예상하지 못한 거부는 출력하고 종료 코드 1로 끝난다. validate, normalize, lock, vectors는 동기로 남는다. store 계약 테스트(tests/store-contract.mjs)는 모든 store에서 Promise를 await하며, store나 명령을 부르는 모든 테스트가 await한다.

  • S-08: Mermaid store(src/store-mermaid/)는 docs/spec/status.md의 B-1 정규 형식으로 machines/*.status.mmd를 읽고 쓴다. parse.mjs는 정규 형식 밖의 모든 줄을 줄 번호와 함께 E_MERMAID_UNSUPPORTED로 거부하고, write.mjs는 정규 형식으로 쓴다. save는 정의되지 않은 *.status.mmd 파일을 지우고 store 계약(tests/store-contract.mjs)을 통과한다. lock 읽기·쓰기는 두 파일 store가 함께 쓰도록 src/store-lock/lock-file.mjs로 옮겼다. status-gen은 {"store":"mermaid"}를 받고, copy --to json|mermaid는 빈 대상으로 두 방향 모두 옮긴다. 모든 valid fixture는 json → mermaid → json에서 바이트 단위로 왕복한다.

  • S-05-3: core validate는 machine·상태·이벤트 key의 길이를 64자로 제한한다. 64자를 넘는 key는 해당 경로에서 E_KEY_FORMAT으로 실패하며, 메시지는 길이와 상한을 밝힌다. DB store는 key를 varchar(64)에 담으므로, 이 상한으로 세 store가 같은 key를 받아들인다(docs/spec/status.md, B-5).

  • S-07: 루트 conformance 파일은 status.config.json({"store":"json"}), machines/order.status.json(B-1의 order 예시, JSON store가 기록), 그 status.lock.json과 contracts/features.json이다. core vectors(src/core/vectors.mjs)는 IR과 lock에서 C-4의 모든 케이스를 계산하며, status-gen vectors --out <dir>는 <machine>.json을 쓰므로 conformance/vectors/order.json은 89 케이스를 가진다. scripts/gen-check.mjs(make gen-check)는 vector 파일을 var/gen-check/에 다시 만들고 conformance/vectors의 추적 파일이 그와 같아야 함을 요구하며, 테스트는 make gen-check-test다.

  • S-06: core normalize는 유효한 정의의 IR을 돌려주고(src/core/normalize.mjs), lockMismatch, nextLock, nextCode, canonicalLock은 B-3의 code lock을 구현하며(src/core/lock.mjs), nextLock은 lock이 가진 code를 정의가 바꾸는 key를 E_CODE_CHANGED로 거부한다. run(check, lock, save)과 copy는 B-2의 명령을 store 위에서 실행한다(src/core/run.mjs). JSON store(src/store-json/store.mjs)는 machines/*.status.json과 status.lock.json을 정규 형식으로 읽고 쓰며 store 계약(tests/store-contract.mjs)을 통과한다. status-gen(src/cli/status-gen.mjs)은 check, lock, copy를 제공하고, mermaid store는 S-08 전까지 E_STORE_NOT_CONFIGURED로 남는다.

  • S-05-2: core validate는 B-5의 이름·code 오류 E_KEY_FORMAT, E_DUPLICATE_KEY, E_KEY_RESERVED(고정 목록은 packages/status-compiler/src/core/reserved.mjs), E_DUPLICATE_CODE, E_CODE_RANGE를 보고한다. machine key는 _를 뺀 뒤 비교하며, 구조 오류가 있는 machine은 다른 machine과 비교하지 않는다.

  • S-05-1: core validate는 B-5의 그래프 오류 E_UNKNOWN_STATE_REF, E_SELF_LOOP, E_NONDETERMINISTIC, E_DUPLICATE_EDGE, E_INITIAL_COUNT, E_FINAL_OUTGOING, E_UNREACHABLE, E_DEAD_END을 보고하며, 각 코드마다 자기 실패 케이스가 있다. 도달 가능성은 단일 initial 상태에서 시작하며, 구조 오류가 있는 machine은 E_SCHEMA finding만 보고한다.

  • S-05: core validate(packages/status-compiler/src/core/validate.mjs)는 정의의 구조 오류를 모두 JSON 경로와 함께 E_SCHEMA로 보고하고, 그 machine의 뒤 규칙은 건너뛴다. schema/status.schema.json은 kit schema-validate.mjs의 키워드만 쓰는 에디터 schema이며, 대응 테스트가 모든 fixture에서 core와 일치함을 확인한다. make compiler-test는 run-tests.mjs로 테스트를 실행하고 .results/compiler-test.json을 기록한다.

  • JSON store는 엣지를 쌍 대신 객체 {from, to}로 쓴다. 그래서 에디터 schema가 정확히 검사하고 GUI 폼은 행으로 편집한다(docs/spec/status.md, B-1).

  • S-04: scripts/check-parity.mjs(make parity-check, --kind를 쓰는 make parity-<kind>)는 다음을 만족하지 않으면 실패한다: 모든 kind가 자기 패키지를 덮는 최신 기록에서 conformance/vectors의 모든 케이스를 한 번씩 실행해 통과하고, contracts/features.json의 모든 기능이 vector 케이스를 가지며, 모든 test id가 케이스 하나를 가리키거나 그 부모이고, TOOL_TESTS의 모든 도구 테스트가 통과한 최신 기록을 남기고, 10분을 넘긴 기록이 없다. 면제는 없다.

  • S-03: scripts/check-layout.mjs(make layout-check)가 언어 패키지를 contracts/layout.json과 대조한다: 패키지 디렉터리는 정확히 tools와 kinds이고, kind의 모든 파일은 정규 layout에 맞으며, 정규·필수 파일이 모두 있고, php와 php-ext의 테스트는 바이트가 같다. make layout-test는 그 테스트를 run-tests.mjs로 실행하고 .results/layout-test.json을 기록한다.

  • S-02: docs/spec/status.md가 정의를 고정한다: 세 부분의 모델(machine, code를 가진 상태, edge로 된 event), 규칙 하나 아래의 세 store, code lock, 멱등 전이 apply(expected, event, actual), 검증 코드, 여섯 타깃의 이름 규칙. 계획은 이 문서를 가리키므로 정의의 원본은 하나다.

  • S-01: 저장소가 kit v0.0.14(scripts/kit/, tests/kit/, .kit/kit.lock.json)를 채택하고 config/checklist.json, config/commits.json, config/documents.json, config/owner-checks.json, config/toolchain.json(Node.js만), config/dependency-policy.json과 그 review 기록을 둔다. hook은 owner 검증(pre-commit), commit 규칙(commit-msg), push gate(pre-push)를 실행한다. AGENTS.md는 개발 규칙, docs/plans/status-v1.md는 계획, docs/plans/execution-checklist.md는 그 항목을 담는다. make check는 전체 스위트를 트리당 한 번 실행한다.