Execution checklist
Wave 1 — 좁은 viewport를 위한 stylesheet와 stylesheet의 Tailwind 버전
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C1.1 | 좁은 viewport를 명세한다. 공유 fixture의 모든 block은 viewport 폭 360 CSS pixel과 1280 pixel에서 page의 가로 overflow 없이 렌더되고, 모든 control과 action은 viewport 안에 있다 | — | make docs-check |
[o] |
| C1.2 | 공유 fixture에 대한 좁은 viewport browser case를 더하고, crudui.css가 overflow하는 곳에서 실패하는지 확인한 뒤 crudui.css를 고친다 |
— | browser case, make docs-check |
[o] |
| C1.3 | Tailwind 버전을 명세한다. @crudui/generator-core/crudui.tailwind.css는 crudui.css의 rule을 Tailwind CSS 4의 cascade layer components에 담고, crudui.css에서 생성되며 그것과 대조된다. 그래서 이 file은 tailwindcss 뒤에 import되고, Tailwind utility가 CRUDUI rule보다 우선한다. markup은 바뀌지 않고, 최신 stable Tailwind CSS의 theme과 utility로 compile한 결과는 두 폭에서 공유 fixture의 모든 element에 crudui.css와 같은 computed style을 준다. 2026-10-04 수정: 처음 문장은 @apply로 쓴 두 번째 stylesheet를 요구했는데, 약 900개 rule을 손으로 두 벌 쓰면 둘이 어긋날 수 있다 |
— | make docs-check |
[o] |
| C1.4 | 커밋된 file도 검사하는 script로 crudui.tailwind.css를 생성한다. 최신 stable Tailwind CSS로 compile해 세 engine에서 두 폭으로 computed style을 crudui.css와 비교하는 case를 더한다. file이 없을 때 둘 다 실패하는지 확인한다 |
— | 생성 검사, 비교 case | [o] |
| C1.4-1 | viewport test, Tailwind test, 생성 script가 저장소 검사를 통과하게 한다. make ci는 npm run lint에서 실패했다. 두 test가 browser global을 쓰는 함수를 page에 넘기면서 Node와 browser file 목록에 없었고 console.log로 썼기 때문이다. npm run test:runtimes에서도 실패했다. test:forms가 실행하는 script가 scripts/test-progress/progress.mjs로 출력하지 않았기 때문이다. test를 목록에 넣고, t.diagnostic으로 보고하고, progress line으로 출력한다 |
— | make ci |
[o] |
| C1.5 | package export, feature status, operations 문서, changelog, full test suite | — | full test suite, make docs-check |
[o] |
Wave 2 — test별로 제한되고 관측되는 test 실행
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C2.1 | AGENTS에 test rule을 적는다. 작업 중에는 바뀐 것을 소유한 Red와 Green test만 실행한다. full suite는 활성 작업이 모두 끝났을 때 정확히 한 번 실행한다. 긴 작업은 자기 timeout에 더해 step log를 출력한다. make docs-check는 문서나 공개 API 문서가 바뀌었을 때만 실행한다. Verification 열에는 소유 명령을 적는다. Cause: AGENTS는 feature가 끝날 때마다 full suite를 실행하게 했고 step log가 timeout을 대신하게 했다. Red: AGENTS.md와 AGENTS.ko.md에 그 문장이 있다. Green: 새 rule이 있다 |
— | node scripts/check-documents.mjs, node scripts/run-tests.mjs node --timeout 10 -- tests/docs/changelog.test.mjs tests/docs/repository-writing.test.mjs |
[o] |
| C2.1-1 | AGENTS에 적는다. 장기 작업(build, install, toolchain setup, browser close, server stop, 전체 suite)은 상세 step log를 출력하고 timeout을 두지 않으며 출력 없음 한도도 두지 않고, 관측한 결과와 오류로 판정한다. 각 test case는 자기 timeout을 유지한다. Cause: C2.1이 장기 작업에 자기 timeout에 더해 step log를 두게 해서, 한도보다 오래 걸린 정상 실행이 실패했다. Red: AGENTS.md와 AGENTS.ko.md에 C2.1의 문장이 있었다. Green: 새 rule이 있고 그 문장은 없다 | — | node scripts/check-documents.mjs, node scripts/run-tests.mjs node --timeout 10 -- tests/docs/changelog.test.mjs tests/docs/repository-writing.test.mjs |
[o] |
| C2.1-2 | 한 번의 전체 실행을 guard scripts/full-run.mjs로 강제한다. make ci는 어떤 명령보다 먼저 이 guard를 시작한다. guard는 이 checklist의 작업 행이 진행 중이면 거부하고 활성 ID를 작업과 함께 나열하며, 추적 파일의 변경이 커밋되지 않았으면 거부하고, 같은 tree(git rev-parse HEAD^{tree})의 두 번째 전체 실행을 앞선 실행을 밝히며 거부한다. 이전 실행의 적합성 증거를 지우고, CI_COMMANDS의 각 명령을 sh -c로 끝까지 실행하며, 각 명령의 앞뒤에 var/full-run.json(tree, commit, 결과, 각 명령의 상태와 시각)을 쓰므로 멈춘 실행은 incomplete로 남는다. make rerun-failed는 현재 tree에서 통과하지 못한 명령만 다시 실행하고, 통과한 명령의 증거를 유지하며, 그것이 통과하면 결과를 완성한다. 원인: AGENTS는 전체 suite를 활성 작업이 모두 끝났을 때 정확히 한 번 실행한다고 적지만 이를 강제하는 것이 없었다. 진행 중인 작업이 있는 실행, 커밋되지 않은 변경의 실행, 같은 tree의 두 번째 실행이 모두 suite를 시작했다. Red: make -n ci는 적합성 증거의 삭제와 CI_COMMANDS에 대한 loop를 출력했으므로 suite는 checklist의 내용과 상관없이 시작했고, make -n rerun-failed는 No rule to make target으로 실패했으며, tests/build/full-run.test.mjs는 scripts/full-run.mjs에 대한 ERR_MODULE_NOT_FOUND로 실패했다. Green: 그 11개 case가 통과한다. make -n ci는 guard만 출력하고, 진행 중 작업이 있는 fixture checklist, 더러운 tree, 한 tree의 두 번째 실행, record 없는 rerun-failed는 stub 명령이 실행되기 전에 거부되며, rerun-failed는 실패했거나 끝나지 않은 stub 명령만 실행하고, 전체 실행은 이전 증거를 지우고 재실행은 유지한다. tests/build/ci-local.test.mjs도 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/full-run.test.mjs tests/build/ci-local.test.mjs |
[o] |
| C2.2 | suite를 실행하는 모든 CI job에서 job timeout을 없애고 setup step마다 짧은 자기 timeout을 둔다. tests/build/test-commands.test.mjs는 suite step이나 suite step을 가진 job에 timeout-minutes가 있거나 그 job의 setup step에 없으면 실패한다. test:form-comparison:pipeline은 build를 제한하는 scripts/require-current-build.mjs로 build하고, 그 Go와 Cargo test는 scripts/run-tests.mjs로 실행한다. Cause: test 검사가 모든 job에 timeout-minutes를 요구해서 13개 job이 test 위에 10~30분 한도를 두었다. Red: 새 case가 test를 실행하면서 timeout-minutes를 가진 13개 job과 timeout이 없는 그 setup step을 나열했다. Green: 바뀐 workflow에서 같은 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/form-comparison-ci.test.mjs |
[o] |
| C2.2-1 | CI workflow의 timeout-minutes를 모두 없앤다. step은 test를 실행하거나(각 case는 test runner에서 자기 timeout을 가진다) 장기 작업(checkout, toolchain setup, install, build, lint, type check, upload, deployment)을 실행하며 장기 작업에는 시간 한도가 없고, 어떤 job도 step 위에 한도를 두지 않는다. tests/build/test-commands.test.mjs는 timeout-minutes를 가진 job이나 step에서 실패하고, 어떤 step이나 job에도 그것을 요구하지 않는다. Cause: C2.2가 setup, build, upload step에 5분이나 10분 한도를 두고 job 한도를 남겨서, 느리지만 정상인 install이나 build가 실패했다. Red: 새 case가 timeout-minutes를 가진 step 84개와 job deploy-docs, conformance를 나열했다. Green: 바뀐 workflow에서 같은 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/form-comparison-ci.test.mjs |
[o] |
| C2.3 | Go와 Rust benchmark driver를 step log와 build 한도를 가진 별도 단계에서 build하고, tests/build/bench-drivers.test.mjs는 build된 driver를 초 단위 timeout으로 실행한다. 600초나 590초 한도를 가진 case는 없다. Cause: accepted case가 go run과 cargo run --release를 실행해서 600초 timeout과 590초 deadline을 가진 test 안에서 driver를 compile했고, Rust의 rejected case마다 Cargo를 다시 실행했다. Red: tests/build/test-commands.test.mjs의 새 case가 node scripts/run-tests.mjs node --timeout 600 -- tests/build/bench-drivers.test.mjs에서 실패했다. 그 실행은 Cargo target이 준비된 상태에서 Rust accepted case에 8.1초, Rust rejected case에 10.6초가 걸렸다. Green: 같은 case가 통과한다. npm run test:bench는 package를 build한 뒤 Go driver를 1.0초, Rust driver를 0.6초에 build하며 각각 시작, 결과, 경과 시간을 출력하고, driver test의 모든 test는 30초 timeout 아래에서 1.9초 안에 끝난다. tests/build/bounded-commands.test.mjs는 끝나지 않는 driver build를 한도에서 멈춘다 |
— | node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/bounded-commands.test.mjs, npm run test:bench |
[o] |
| C2.3-1 | 장기 작업의 모든 명령을 시간 한도 없이 끝까지 실행한다. scripts/bounded-command.mjs는 scripts/run-command.mjs가 되며, 명령은 종료에서 끝나고 종료 상태가 결과이고, 명령이 끝나면 그 group에 남긴 process를 멈춘다. tools/bench/build-drivers.mjs는 step 줄과 함께 compiler 출력을 그대로 흘린다. scripts/require-current-build.mjs, scripts/gen-api-docs.mjs, scripts/run-contract-tests.mjs, tools/bench/run.js는 600초와 30초 한도와 CRUDUI_COMMAND_LIMIT_SECONDS를 잃는다. driver test case는 test runner의 30초 timeout을 유지한다. Cause: C2.3이 driver build와 다른 script가 함께 쓰는 module에 한도를 두어서, 느리지만 정상인 build가 실패했다. Red: tests/build/run-command.test.mjs는 1.2초 뒤 상태 0으로 끝나는 명령으로 다섯 script를 각각 실행한다. 이전 module과 CRUDUI_COMMAND_LIMIT_SECONDS=1에서 다섯 case 모두 exceeded its 1s limit로 실패했다. Green: 다섯 case가 통과한다. SIGTERM을 무시하고 출력을 붙잡는 자식이 멈추는 것을 시간 한도 대신 script 출력의 close로 확인한다. npm run test:bench는 Go driver를 1.6초, Rust driver를 11.6초에 출력과 still-running 줄과 함께 build하고, 9개 driver case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/run-command.test.mjs tests/build/test-commands.test.mjs tests/build/rust-node-entry-points.test.mjs scripts/gen-api-docs.test.mjs, npm run test:bench |
[o] |
| C2.4 | build readiness를 state file의 변경(fs.watch)과 주입 가능한 clock으로 기다린다. readiness test는 명시적인 event와 fake clock으로 상태를 바꾸고 wall-clock 시간을 비교하지 않는다. Cause: readyBuild가 1초 timer로 state file을 다시 읽었고, AGENTS는 producer가 변경을 게시하면 이를 금지한다. 그 test는 20 ms interval로 file을 다시 쓰고 대기의 wall-clock 시간을 한도와 비교했다. Red: 모든 Node.js process의 event loop를 400 ms마다 350 ms씩 막는 scratch module을 넣자 readiness case 6개가 실패했다. 2개는 1.2초와 1.3초 뒤 stalled로 멈춰 한도를 넘었고, 4개는 interval writer가 state directory 삭제와 경합해 실패했다. Green: 7개 case(실제 file과 directory event를 쓰는 새 case 1개 포함)가 부하 없이 0.1초에, 같은 부하에서 세 번 모두 통과한다. 멈춘 step은 fake clock으로 정확히 400 ms에 멈춘다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs |
[o] |
| C2.4-1 | readiness 대기에서 build state file 자체를 감시한다. examples/form-comparison/verify-tree.mjs의 watchStateFile은 watch가 돌아올 때 등록이 끝나는 file을 감시하고(macOS는 kqueue, Linux는 inotify), 교체될 때마다 대기가 읽기 전에 새 file을 감시한다. Cause: C2.11 뒤 2026-10-05 full run에서 실제 file case the build readiness wait reads the state file at each change of its directory가 30초를 넘었다. macOS의 directory watch는 fs.watch가 돌아온 뒤에 event stream을 시작하므로, 대기 시작 직후의 rename을 잃을 수 있었고 대기는 ready 상태를 읽지 못했다. Red: directory watch로 load average 약 145에서 check-verification.test.mjs를 10번 실행하니 5번이 그 case에서 실패했다. Green: file watch로 load average 80~120에서 20번 모두 통과하고, readiness case 7개가 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs |
[o] |
| C2.6 | CI test step이 닿는 test 아닌 명령을 모두 제한한다. cross-check console pretest의 Go와 Rust validator build, test:form-comparison:source의 package build, make test-native-suites의 composer reinstall, make docs-check의 문서 build에는 자기 한도가 없다. Cause: C2.2가 이들을 제한하던 job 한도를 없앴다. 변경 없이 닫는다: 이 명령들은 build와 install, 곧 C2.1-1의 rule에 따라 log를 출력하고 시간 한도를 두지 않는 장기 작업이므로, 한도가 없는 것은 결함이 아니다 |
— | 없음: 변경 없음 | [o] |
| C2.7 | 모든 teardown hook을 hook timeout 없이 그 event로 끝낸다. scripts/test-progress/teardown.mjs의 teardown은 browser close, server stop, directory 삭제를 timeout이 Infinity인 파일이나 test의 after hook으로 등록하고, 시작, 5초마다의 줄, 경과 시간과 함께 끝을 출력하며, 오류로 파일을 실패시킨다. examples/form-comparison/pipeline.browser.mjs, tests/form-inspector/browser.test.mjs, examples/form-comparison/record-stores.test.mjs, examples/form-comparison/src/browser-job.browser.mjs, tests/form-styles.test.mjs, tests/tailwind-styles.test.mjs, tests/viewport.test.mjs, tests/widget-scripts.test.mjs, tests/widget-script-runs.test.mjs, tests/docs/web-build.test.mjs의 hook이 이를 쓰고, cross-check console server와 form binding의 Vitest afterAll hook은 timeout Infinity를 가진다. Cause: 2026-10-05의 full run(make ci, 2778초)에서 모든 test는 통과했지만, server process를 멈추는 pipeline browser test의 after hook이 10초 한도를 넘었고, browser를 닫는 form inspector test의 after hook이 node:test가 --test-timeout에서 hook에 주는 기본 30초를 넘었다. Red: tests/build/teardown.test.mjs는 teardown이 1.5초 뒤 resolve되거나 실패하는 파일을 1초 test timeout으로 실행한다. inspector test처럼 자기 timeout 없는 hook으로 등록하면 세 case가 test timed out after 1000ms로 실패했다. Green: 세 case가 통과한다. inspector test와 문서 server test는 teardown 줄과 함께 통과한다(부하가 있는 기기에서 browser close 4.8초) |
— | node scripts/run-tests.mjs node -- tests/build/teardown.test.mjs tests/form-inspector/browser.test.mjs tests/docs/web-build.test.mjs |
[o] |
| C2.7-1 | 모든 setup hook(browser launch, server start, stylesheet compile, pipeline test의 stack build)을 hook timeout 없이 그 event로 끝낸다. scripts/test-progress/teardown.mjs는 setup과 teardown을 가진 scripts/test-progress/hooks.mjs가 되며, 각각은 시작, 5초마다의 줄, 끝을 출력하는 timeout Infinity의 before나 after hook이다. style, Tailwind, viewport, widget script, form inspector, pipeline, record store test의 setup hook이 이를 쓰고, browser job test는 browser를 setup에서 한 번 launch하고, protocol timeout test를 위해 1초 protocol timeout으로 한 번 더 connect하며, test에서는 page를 연다. 1초 protocol timeout으로 launch하면 부하에서 launch 자체의 protocol 호출이 1초를 넘었다. cross-check console server와 form binding의 Vitest beforeAll hook은 timeout Infinity를 가진다. Cause: setup hook이 60초나 120초 한도, browser start 한도, 또는 node:test가 자기 timeout 없는 hook에 주는 30초를 가졌고, browser job test 4개는 10초나 30초 test 안에서 browser를 launch했으므로, 부하가 있는 기기에서 느리지만 정상인 launch가 실패했다. Red: tests/build/hooks.test.mjs의 새 case 2개는 setup이 1.5초 뒤 resolve되거나 실패하는 파일을 1초 test timeout으로 실행한다. 자기 timeout 없이 등록하면 둘 다 실패했고, teardown case 3개도 실패했다. Green: 5개 case가 통과한다. browser job, form inspector, 문서 server test는 setup과 teardown 줄과 함께 통과한다(부하가 있는 기기에서 browser launch 2.4초, browser close 9.1초와 14.1초) |
— | node scripts/run-tests.mjs node -- tests/build/hooks.test.mjs tests/form-inspector/browser.test.mjs examples/form-comparison/src/browser-job.browser.mjs tests/docs/web-build.test.mjs |
[o] |
| C2.7-2 | examples/form-comparison/check-verification.test.mjs에서 local pipeline stack의 setup 계약을 확인한다. pipeline과 record store test는 scripts/test-progress/hooks.mjs에서 setup과 teardown을 import하고 자기 hook을 등록하지 않으며, pipeline test는 setup과 teardown에서 browser를 launch하고 닫는다. Cause: C2.7-1이 pipeline.browser.mjs에서 browser-start와 browser-close 한도를 없앴는데 이 test는 여전히 그것을 요구했다. C2.11 뒤 2026-10-05 full run에서 실패했다. Red: the local pipeline stack has no summed hook limit and starts its browser with the measured limit가 browserUnitLimitsMs['browser-start']가 없어 실패했다. Green: 바뀐 case가 파일의 16개 case와 함께 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs |
[o] |
| C2.8 | 수십 분 동안 실행되는 native generator suite(make crudui-test-native가 실행하는 tests/native-generators/run.mjs)를 줄인다. Go와 Rust 프로그램은 어떤 check보다 먼저 build 단계에서 한 번 build하며, build 도구의 출력, 5초마다의 줄과 함께 시간 한도 없이 실행한다(300초와 900초의 준비 예산과 30초의 process 한도는 없앤다). PHP 프로그램은 check 예산 안의 probe로 확인한다. 각 check는 자기 예산을 가진 하나의 test이고, 모든 target의 check는 하나의 pool에서 os.availableParallelism()개씩 실행하며, 각 check의 signal은 그 비동기 context에 둔다. Cause: 2026-10-05의 full run에서 959초가 걸렸다. 각 target의 check 738개가 차례로 실행되었고 javascript, html, php, php-native target은 요청마다 interpreter를 시작하기 때문이다. worktree에서 load average 93~161로 측정하니 check에 javascript 254.9초, html 195.5초, php 218.3초, php-native 234.5초, go는 1.4초 build 뒤 16.7초, rust는 21.1초 build 뒤 19.6초가 걸렸으므로, 시간은 build가 아니라 check에 들었다. Red: 그 실행은 962.5초가 걸렸고 check가 차례로 실행되었다(check 중앙값 114 ms). Green: 같은 4428개 check가 load average 93~187에서 199.8초에 통과한다. 11개씩 실행되며(중앙값 241 ms, 가장 긴 check는 예산 아래의 5.5초), Go build 0.7초와 Rust build 0.4초 뒤에 실행되고 입력은 바뀌지 않는다 |
— | 바뀐 target으로 실행하는 node tests/native-generators/run.mjs |
[o] |
| C2.9 | 경과 시간에 한도를 두는 모든 assertion을 원인 확인으로 바꾸고, 그런 assertion에서 실패하게 한다. tests/build/test-commands.test.mjs는 performance.now()나 Date.now()에서 시작 시각을 뺀 값, 또는 elapsedMs나 durationMs를 0이 아닌 값과 비교하는 assert.ok 호출을 찾는다. tests/build/run-tests.test.mjs의 멈춘 test와 hook case의 hook은 끝나지 않고 event loop를 막지 않는 spawn으로 실행되므로, case 자신의 timeout 아래에서 runner의 timeout만 그것을 끝낸다. server-startup.test.mjs의 readiness, health, stop case, unit-pool.test.mjs의 timeout case, step-runner.test.mjs의 timeout과 stall case는 시험하는 멈춤으로만 끝난다. step-runner.test.mjs에서 한 stage의 두 step은 서로의 file이 생겨야만 끝난다. record-contract.mjs의 너무 큰 저장은 413과 case 자신의 timeout에 의존한다. Cause: 이 test들은 실행, 멈춤, stage, 응답의 wall-clock 시간을 0.5초에서 20초 사이로 제한했고, code가 옳아도 부하가 있는 기기는 이를 넘는다. Red: 새 case가 record-contract.mjs, server-startup.test.mjs(3), step-runner.test.mjs(4), unit-pool.test.mjs, run-tests.test.mjs의 assertion 10개를 나열했다. Green: case가 통과하고 바뀐 test file 다섯 개가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/run-tests.test.mjs examples/form-comparison/src/step-runner.test.mjs examples/form-comparison/src/unit-pool.test.mjs examples/form-comparison/src/server-startup.test.mjs |
[o] |
| C2.9-1 | examples/form-comparison/src/step-runner.test.mjs에서 한 stage의 두 step을 named pipe로 만나게 한다. 한 step은 읽기로, 다른 step은 쓰기로 열고, 각 open은 다른 끝이 열릴 때까지 막히므로 stage는 두 step이 동시에 실행될 때만 끝난다. Cause: C2.9는 각 step이 directory watch로 상대의 file을 기다리게 했는데, macOS에서 그 event stream은 fs.watch가 돌아온 뒤에 시작하므로 watch 시작 직후 쓰인 file을 잃을 수 있었고 step은 case timeout까지 기다렸다. Red: directory watch로 load average 17~53에서 파일을 10번 실행하니 4번 실패했다. Green: pipe로 10번 모두 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/step-runner.test.mjs |
[o] |
| C2.10 | form comparison supervisor가 1초마다 실행하는 mount된 저장소 비교를 게시된 변경 event로 바꾸고, service가 commit되지 않은 변경을 따르는 계약은 유지한다. host의 source 감시기 examples/form-comparison/source-events.mjs(make deploy-watch)는 working tree의 file event(fs.watch recursive, macOS에서는 FSEvents)를 구독하고, event마다 container 안에서 source-changed.mjs를 실행한다(container exec). 이는 supervisor가 state/supervisor.pid에 기록한 process id에 SIGUSR2를 보낸다. supervisor는 신호마다 저장소를 비교하고, 비교 중이나 시작 중의 신호는 그 뒤에 비교를 한 번 더 일으킨다(src/change-requests.mjs). 1초 loop와 15초마다 state file을 다시 쓰던 heartbeat는 없앤다. 감시기에는 시간 한도가 없고 시작, 모든 event, 모든 전달을 출력한다. tests/build/test-commands.test.mjs는 sleep하거나 진행 줄을 출력하지 않는 interval을 실행하는 program에서 실패한다. Cause: macOS host의 file event가 VM file share를 통해 Linux container에 닿지 않으므로 supervisor.mjs가 mount를 1초마다(checkInterval) Git으로 비교했다. AGENTS는 producer가 게시할 수 있으면 주기적 상태 읽기를 금지한다. 2026-10-05 결정: commit되지 않은 변경을 계속 따르고 host에서 게시한다. Red: 이전 supervisor에서 guard가 supervisor.mjs:195: sleeps와 supervisor.mjs:58: an interval that prints no progress line을 나열했고, case the supervisor compares the repository at change signals, not on a timer가 실패했다. Green: 둘 다 src/source-events.test.mjs의 다른 case 4개(합쳐지는 요청, 신호되는 event, 실패한 전달, container 명령)와 함께 통과하고, npm run test:form-comparison은 201, 10, 4개 test를 통과한다. 실행 중인 container를 거치는 신호 경로는 다음 배포에서 실행된다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/source-events.test.mjs examples/form-comparison/src/server-layout.test.mjs tests/build/test-commands.test.mjs |
[o] |
| C2.10-1 | 실행 중인 container를 재사용하는 배포에서 supervisor에 신호하고, source 감시기를 깨끗하게 멈춘다. sync mode의 make deploy는 빌드를 기다리기 전에 container 안에서 source-changed.mjs를 실행하고(sourceChangeCommand, 이제 comparison-deployment.mjs에 있다), source-events.mjs는 SIGINT나 SIGTERM에서 감시를 닫고 stopped after N deliveries를 출력한다. Cause: 실제 배포에서 검증하니 C2.10 경로는 동작했다(README.md의 touch가 175 ms에 전달되었고, supervisor가 source change signalled를 남긴 뒤 바뀐 경로를 비교해 cycle 2 ready를 보고했고, 기다리던 배포가 통과했다). 그러나 감시기를 띄우기 전, 새 commit의 make deploy는 재사용한 supervisor에 아무도 신호하지 않아 cycle 1 is ready for <이전 commit>에서 10분을 기다렸고, SIGINT로 멈춘 감시기는 멈춤을 출력하지 않았다. Red: src/source-events.test.mjs의 새 case 2개가 실패했다(배포에서 신호할 sourceChangeCommand를 module이 export하지 않았고, 감시기가 stop을 받지 않았다). Green: 그 파일과 check-deployment.test.mjs의 다른 case와 함께 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/source-events.test.mjs examples/form-comparison/check-deployment.test.mjs, make deploy, make deploy-watch |
[o] |
| C2.10-2 | container를 재사용하는 배포뿐 아니라 모든 배포에서 supervisor에 신호한다. containerctl도 이전 checkout에서 시작한 supervisor의 바뀌지 않은 container를 재사용하고, C2.13-3부터 새 supervisor는 port를 열기 전에 process를 기록하므로 containerctl이 돌아오면 신호가 닿는다. Cause: C2.10-1은 sync mode에서만 신호해서, containerctl로 정의를 적용하고 기존 container를 돌려받은 배포는 이전 checkout에서 기다렸을 것이다. Red: src/source-events.test.mjs의 바뀐 case가 sync 전용 신호에서 실패했다. Green: 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/source-events.test.mjs, make deploy |
[o] |
| C2.11 | viewport와 Tailwind 검사의 공유 render case를 각각 짧은 test로 만든다. tests/viewport.test.mjs와 tests/tailwind-styles.test.mjs는 한 engine과 폭의 page를 suite의 setup에서 열고, 432개 case를 각각 runner의 30초 timeout을 가진 test로 실행하며, 300초나 600초 timeout을 가진 test는 없다. Cause: 각 파일이 한 engine과 폭의 432개 case를 300초나 600초 timeout의 test 하나 안에서 실행해서, 멈춘 case가 실행을 최대 10분 붙잡았고 실패는 끝날 때까지 case를 밝히지 않았다. 느린 부분은 build가 아니었다. Red: engine과 폭의 test 12개가 5분과 10분 timeout 아래에서 각각 1.5~11.7초가 걸렸고 시작과 끝만 출력했다. Green: 5184개 case가 통과하며 가장 긴 것은 0.4초이고, 두 파일은 합쳐서 전과 같은 수준인 57.1초(전 47.6초)가 걸린다 |
— | node scripts/run-tests.mjs node -- tests/viewport.test.mjs tests/tailwind-styles.test.mjs |
[o] |
| C2.12 | 재현 가능한 build 검사를 위해 package를 시간 한도 없이 log를 남기는 단계에서 두 번 build한다. scripts/repeat-build.mjs는 scripts/run-command.mjs로 npm run build를 두 번 실행하고, 각 build 뒤에 공개 package의 모든 산출물 file 경로와 SHA-256을 기록한다(scripts/package-outputs.mjs). tests/build/reproducible-build.test.mjs는 두 기록을 서로, 그리고 현재 산출물과 각 test의 30초 timeout 아래에서 비교하고, test:build:repeat에는 --timeout이 없다. Cause: test가 막는 execFileSync로 두 build를 120초 timeout의 test case 하나 안에서 실행했고, build가 event loop를 막는 동안 runner는 그 timeout조차 적용할 수 없었다. Red: tests/build/test-commands.test.mjs의 새 case가 node scripts/run-tests.mjs node --timeout 120 -- tests/build/reproducible-build.test.mjs에서 실패했다. 그 test case는 28.6초가 걸렸다. Green: case가 통과한다. npm run test:build:repeat는 build 출력과 함께 14.7초와 10.8초에 build하고 매번 329개 file을 기록하며, test 3개는 0.1초에 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs, npm run test:build:repeat |
[o] |
| C2.13 | form comparison의 장기 작업에서 시간 한도를 없앤다. src/build-targets.mjs의 build target timeout(60~900초), supervisor, verify-tree.mjs, comparison-deployment.mjs, pipeline과 record store test의 local stack이 쓰는 src/step-runner.mjs step의 전체 한도와 비활동 한도(stepSilenceLimitMs는 출력 없음 한도다), build state가 readiness 대기에 게시하는 step 한도(progress.limitMs), 30초 process start 한도, deployment step 한도, 배포된 검사의 browser-start와 browser-close unit이 대상이다. 각 작업은 step을 출력하고 그 event로 끝난다. Cause: C2.1-1의 rule. C2.7이 pipeline test의 teardown을 바꾸는 중에 발견했다. 변경은 supervisor, C2.4의 readiness 대기, deployment에 닿는다. C2.13-1과 C2.13-2로 나누었고 둘 다 끝났다 |
— | C2.13-1과 C2.13-2의 test | [o] |
| C2.13-1 | form comparison의 대기에서 한도를 없앤다. verify-tree.mjs의 build readiness 대기는 비활동 한도와 step 한도가 없고, 이 source의 상태가 ready이면 끝나며, 실패한 주기나 감시·읽기·해석할 수 없는 state file에서 실패하고, 새 step마다와 15초마다 줄을 출력한다. supervisor는 limitMs 없이 progress를 게시한다. process start(waitForChildReadiness, local stack의 startProcess)는 30초 한도 없이 readiness event를 기다리며 15초마다 줄을 출력한다. check.mjs와 check-pipeline.mjs의 browser start와 close는 src/unit-pool.mjs의 runOperation으로 한도 없이 실행하고, 신호 뒤 10초 강제 종료는 없앤다. browser report의 unit은 자기 한도를 유지한다. Cause: C2.1-1의 rule. Red: 이전 source로 check-verification.test.mjs, server-startup.test.mjs, unit-pool.test.mjs의 case 7개가 실패했다. step이 10시간 실행되는 동안 대기가 stalled로 멈췄고, progress가 limitMs를 가졌고, browser unit에 start와 close가 있었고, heartbeat 줄 뒤의 readiness가 거부되었고, runOperation이 없었다. Green: 모두 통과하고, npm run test:form-comparison은 194, 10, 4개 test를 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs examples/form-comparison/src/server-startup.test.mjs examples/form-comparison/src/unit-pool.test.mjs, npm run test:form-comparison |
[o] |
| C2.13-2 | form comparison의 step에서 한도를 없앤다. src/step-runner.mjs의 runStep은 step을 끝까지 실행하고 assertStep은 timeoutMs나 silenceLimitMs를 거부한다(stepSilenceLimitMs와 진행 줄 비활동 검사는 없앤다). src/build-targets.mjs의 build target은 60~900초 timeout을 잃는다. verify-tree.mjs의 verification 검사, src/local-servers.mjs의 local build, pipeline.browser.mjs의 public build, verification.mjs의 host verification step, comparison-deployment.mjs의 build readiness와 deployment step은 한도를 가지지 않는다. deploymentStepLimitsMs, deploymentHealth, containerctlHealthLimits는 없애고, Compose 정의는 첫 build를 제한하던 예산의 healthcheck를 선언하지 않으므로 배포는 대신 checkout의 build 상태를 기다린다. 소스 비교와 OrderedJSON checkout의 Git 호출은 120초와 600초 timeout을 잃는다. 단위는 자기 한도를 유지한다. C3이 main에 들어간 뒤 그 위로 rebase했다. Cause: C2.1-1의 rule. Red: 이전 source로 step-runner.test.mjs, build-targets.test.mjs, check-deployment.test.mjs의 case 7개가 실패했다. step이 한도를 요구했고, 조용한 step이 끝까지 실행되지 않았고, build target이 timeout을 가졌고, 정의가 healthcheck를 선언했다. Green: 모두 통과하고, npm run test:form-comparison은 203, 10, 4개 test를 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/step-runner.test.mjs examples/form-comparison/src/build-targets.test.mjs examples/form-comparison/check-deployment.test.mjs examples/form-comparison/check-verification.test.mjs examples/form-comparison/src/source-events.test.mjs, npm run test:form-comparison |
[o] |
| C2.13-3 | supervisor가 시작부터 서비스 port에서 답하게 한다. supervisor는 process id를 기록한 뒤 port 8080에서 listen하고, 공개 서버에 port를 넘길 때까지 모든 요청에 503과 빌드 상태로 답하며, 공개 서버를 시작하기 직전에 listener와 연결을 닫는다. Cause: C2.13-2가 Compose healthcheck를 없앴으므로, containerctl은 container 시작 직후 고정된 1분 안에 서비스 port를 확인했고, 그때는 빈 볼륨의 첫 빌드가 끝나지 않아 공개 서버가 listen하지 않았다. make deploy가 containerctl-edge did not open a connection to ...:8080 for crudui.test within 1m0s로 실패했다. Red: src/source-events.test.mjs의 새 case가 실패했다. supervisor는 첫 빌드 전에 port를 열지 않았다. Green: 통과하고, 다음 make deploy가 containerctl의 1분 안에 연결된다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/source-events.test.mjs, make deploy |
[o] |
| C2.14 | version 명령이 실패하면 benchmark 실행을 실패시킨다. tools/bench/run.js의 toolVersion은 명령, 그 실패, 그 출력과 함께 throw하고, version을 출력하지 않는 명령도 실패하며, results.md는 실행된 backend의 도구 version만 기록한다. Cause: 실패한 version 명령은 unavailable로, 빈 출력은 unknown으로 기록되어 보고서가 오류를 숨겼고, 실행되지 않은 backend에 대해서도 네 도구의 version을 모두 조회했다. Red: tests/build/run-command.test.mjs의 새 case 2개가 실패했다. JavaScript backend 실행이 php, go, rust/cargo 줄을 기록했고, 상태 3으로 끝나는 node --version에서도 보고서를 쓰고 0으로 끝났다. Green: 두 case가 tests/build/rust-node-entry-points.test.mjs의 Rust toolchain case와 함께 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/run-command.test.mjs tests/build/rust-node-entry-points.test.mjs |
[o] |
| C2.15 | PHP record server를 process의 readiness 줄로 시작한다. servers/php/main.mjs는 PHP-FPM과 nginx의 standard error를 복사하고, PHP-FPM이 NOTICE: ready to handle connections를, notice log level의 nginx가 start worker processes를 쓰면 ready가 된다. 자기 줄 전에 종료한 process는 이유와 함께 server를 멈춘다. Cause: launcher가 20 ms마다 연결을 시도하고 10초 start 한도 뒤에 실패했다. 이는 부하가 있는 기기가 넘을 수 있는 deadline을 가진 polling loop다. C2.10이 interval loop를 찾다가 발견했다. Red: 새 examples/form-comparison/src/php-server.test.mjs는 readiness 줄을 쓰고 연결을 받지 않는 대역 program으로 launcher를 실행한다. 이전 launcher에서 ready case가 php-fpm did not accept connections within 10000 ms로 실패했다. Green: 두 case가 1.4초에 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/php-server.test.mjs |
[o] |
| C2.16 | group에 종료된 process만 남은 process tree의 멈춤을 완료한다. examples/form-comparison/src/step-runner.mjs의 signal은 scripts/run-command.mjs처럼 group 신호의 EPERM을 ESRCH와 같이 다룬다. macOS는 모든 process가 종료되었지만 아직 회수되지 않은 group에 보낸 신호에 EPERM으로 답하기 때문이다. Cause: killProcessTree가 tree를 kill한 뒤 group에 신호하다가 kill EPERM으로 실패했다. form comparison의 모든 server stop, teardown, step stop이 이를 쓴다. C2.13-2가 tree stop case를 다시 쓰다가 발견했다. Red: step-runner.test.mjs의 새 case는 SIGTERM을 무시하는 child와 detached grandchild의 tree 10개를 멈춘다. 3번 중 3번 kill EPERM으로 실패했다. Green: 5번 모두 통과한다. grandchild가 붙잡은 child 출력의 끝이 둘 다 사라졌음을 보인다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/step-runner.test.mjs |
[o] |
| C2.17 | comparison toolchain image의 Chromium을 Debian trixie가 제공하는 version으로 고정한다. examples/form-comparison/Containerfile과 check-toolchain.test.mjs의 chromium, chromium-sandbox 154.0.8037.92-1~deb13u1이다. Cause: security archive가 153.0.8010.47-2~deb13u1을 교체해서, C2.10을 실제 배포에서 검증하려고 실행한 make deploy가 toolchain image에서 Version '153.0.8010.47-2~deb13u1' for 'chromium' was not found로 실패했다. node:26-trixie-slim의 apt-cache policy는 154.0.8037.92-1~deb13u1을 candidate로 보인다. Red: check-toolchain.test.mjs의 바뀐 case가 이전 pin에서 실패했다. Green: 통과하고, image의 container build가 통과한다 |
— | node --test examples/form-comparison/check-toolchain.test.mjs, container build --file examples/form-comparison/Containerfile |
[o] |
| C2.18 | comparison service의 route를 containerctl이 읽는 방식으로 선언한다. Compose service는 x-containerctl: domains: ["crudui.test"]를 적고, 배포 검사는 containerctl status --json이 보고하는 domains와 urls를 읽는다. Cause: containerctl은 services.<name>.x-containerctl.domains에 적힌 domain만 route하고 그것이 없는 service는 internal로 다루므로, containerctl.domain label은 아무것도 route하지 않았다. C2.10을 검증하려고 실행한 make deploy는 service를 build하고 시작한 뒤 Deployment route domain differs(routed: false, internal: true)로 실패했다. Red: check-deployment.test.mjs의 바뀐 case가 label에서 실패했다. Green: 통과하고, make deploy가 https://crudui.test/를 route한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-deployment.test.mjs, make deploy |
[o] |
| C2.18-1 | containerctl이 선언한 domain을 route할 때만 실행 중인 comparison container를 재사용한다. shouldReuseDeployment는 containerctl status가 보고하는 service가 ["crudui.test"] domain으로 routed인 것도 요구하고, 아니면 배포가 containerctl로 정의를 적용한다. Cause: C2.18이 정의를 바꾼 뒤 make deploy가 image가 같다는 이유로 실행 중인 container를 재사용해 새 route를 적용하지 않았고 Deployment route is not active로 실패했다. Red: check-deployment.test.mjs의 바뀐 case가 route가 적용되지 않은 예상 image의 실행 중 container에서 실패했다. Green: 통과하고, 다음 make deploy가 route를 적용한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-deployment.test.mjs, make deploy |
[o] |
| C2.19 | local stack의 server를 시작한 test process가 멈추지 않고 종료하면 그 server를 멈춘다. src/local-servers.mjs는 startProcess가 시작한 모든 server의 process group을 기록하고, process 종료 시 아직 멈추지 않은 group을 kill한다. Cause: 2026-10-05 05:37의 examples/form-comparison/pipeline.browser.mjs 실행에서 server stop hook이 아직 10초 한도를 넘었고(C2.7) 그 process가 강제 종료되어, PHP, PHP 확장, Go record server가 7시간 넘게 남았다(coordinator가 PHP server를 멈췄고, crudui-pipeline-h0rCc4의 Go server는 여기서 멈췄다). Red: 새 src/local-servers.test.mjs는 child에서 startProcess로 대역 server를 시작한 뒤 child를 종료한다. server가 남았고 case가 10초를 넘겼다. Green: server가 붙잡은 named pipe의 끝으로 관측해 case가 4번 모두 1.1초에 통과하고, 남은 대역 server가 없다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/local-servers.test.mjs |
[o] |
| C2.5 | scripts/run-tests.mjs의 PHPUnit mode에서 class나 data provider suite가 아니라 PHPUnit test만 세고, TeamCity message가 아닌 PHPUnit 줄을 출력한다. Cause: testSuiteFinished message에는 location이 없어서 class나 data provider method의 끝이 통과한 test로 세어졌고, TeamCity message가 아닌 줄은 모두 버려졌다. Red: TeamCity case가 1개인 통과 test를 3개로 셌고 PHPUnit banner와 Test file "/missing.php" not found 줄을 잃었다. validator-php 실행은 PHPUnit의 763개에 대해 804 passed를 보고했다. Green: 같은 case가 1 passed, 1 failed를 세고 두 줄을 출력한다. validator-php 실행은 763 passed를 보고한다 |
— | node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs |
[o] |
Wave 3 — test와 deployment 실행이 함께 쓰는 resource
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C3.1 | make docs-verify-idempotent는 두 snapshot과 그 차이를 실행이 mktemp -d로 만들고 끝날 때 지우는 directory에 쓴다. Cause: 고정된 /tmp/crudui-docs-run1, /tmp/crudui-docs-run2, /tmp/crudui-docs-diff.txt를 썼으므로, 두 번째 실행이 첫 번째 실행 도중에 그것을 지우고 다시 썼다. Red: tests/build/shared-resources.test.mjs의 새 case가 그 path를 쓰는 Makefile 12줄을 나열했다. Green: case가 통과하고, scratch copy에서 sub-make를 stub(MAKE=true)으로 둔 recipe가 snapshot을 비교하고 directory를 남기지 않는다 |
— | node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs |
[o] |
| C3.2 | holder lock(scripts/holder-lock.mjs)을 추가한다: record(checkout, pid, process 시작 시각, 획득 시각, command, token)를 전용 file에 쓰고 lock path로 link하므로 lock 획득은 atomic하고 record는 완전하다. 잡힌 lock은 holder의 record와 함께 거부한다. holder process가 더 이상 실행되지 않는 lock(재사용된 pid 포함)은 보고하고 남겨 두며, remove-dead가 명시적으로 지우고 실행 중인 holder는 거부한다. holder만 해제한다. hold는 lock을 잡은 채 command를 실행한다. Cause: comparison deployment, Playwright image, package dist directory는 하나뿐인 resource인데, 어떤 실행도 다른 실행이 그것을 쓰는지 알 수 없었다. Red: module이 없을 때 tests/build/holder-lock.test.mjs가 load되지 않았다. Green: 8개 case가 통과한다: record, 다른 process holder를 밝히는 거부, 동시 실행 8개 중 holder 하나, remove-dead까지 보고하고 남겨 두는 실행되지 않는 holder, 재사용된 pid, 실행 중인 holder 제거의 거부, 바뀐 record에 대한 해제 거부, record가 없는 lock file |
— | node scripts/run-tests.mjs node -- tests/build/holder-lock.test.mjs |
[o] |
| C3.3 | Linux style check는 Playwright image를 유지한다: 실행이 pull한 image를 지우지 않고, 그 image의 user 범위 holder lock을 잡은 채 container를 실행한다. make remove-form-styles-image는 같은 lock 아래에서 image를 지우므로 check가 실행되는 동안 거부한다. Cause: image를 받은 실행이 exit trap에서 image를 지웠고, 다른 checkout의 실행이 그 image를 쓰는 동안에도 그랬다. Red: image가 없다고 보고하는 container CLI stub으로 새 case가 image rm mcr.microsoft.com/playwright:v1.63.0-noble을 기록했고, 다른 process가 image lock을 잡은 동안의 실행이 container를 시작했다. Green: 두 case가 통과한다: 실행은 image를 남기고 lock을 해제한다. 다른 process가 lock을 잡은 동안 check와 제거는 holder의 pid와 process 시작 시각과 함께 실패하고 container command를 호출하지 않는다. 해제 뒤 제거는 image rm을 한 번 호출한다 |
— | node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs |
[o] |
| C3.4 | make deploy와 make deploy-verify는 다른 step보다 먼저 comparison deployment(volume을 가진 하나뿐인 Compose project crudui)의 user 범위 holder lock을 잡는다. Cause: 다른 checkout의 deployment가 verification 도중에 container와 Compose project, volume을 바꿨다. Red: test가 lock을 잡은 동안 comparison-deployment.mjs는 container runtime을 찾으러 진행했고 verification.mjs는 입력을 읽으러 진행했으며, 어느 쪽도 holder를 밝히지 않았다. Green: lock이 잡힌 동안 두 program은 holder의 pid와 process 시작 시각과 함께 실패하고, lock이 비어 있으면 lock을 잡고 test PATH에 container runtime이 없어 실패한 뒤 lock을 해제한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-deployment-lock.test.mjs |
[o] |
| C3.5 | 모든 package build는 build 내내 그 package dist의 lock을 잡고, scripts/package-dist.mjs pack은 같은 lock 아래에서 package를 pack하므로, build가 비운 dist를 읽는 pack은 없다. package install check와 다른 repository는 이것으로 pack한다. Cause: tsup --clean과 svelte-package는 dist를 쓰기 전에 비우는데, package install check와 다른 repository는 build를 알지 못한 채 dist에 npm pack을 실행했다. Red: build script는 lock을 잡지 않았고, test가 generator-html의 dist lock을 잡은 동안 npm run build -w @crudui/generator-html이 dist를 다시 build했으며 scripts/package-dist.mjs는 없었다. package install case는 그것을 통한 pack을 기대했다. Green: case가 통과한다: build는 holder의 pid와 process 시작 시각과 함께 실패하고 dist를 바꾸지 않으며, pack은 실패하고 archive를 쓰지 않는다. 해제 뒤 pack은 dist file을 담은 archive 하나를 쓰고 lock을 남기지 않는다. npm run build는 7개 lock을 차례로 잡고 해제한다. comparison의 toolchain image에는 ps가 없으므로 Linux에서 시작 시각은 /proc에서 온다: holder lock case 9개가 ps 없는 node:26.8.1-trixie-slim에서 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs tests/build/package-install-pack.test.mjs tests/build/holder-lock.test.mjs, npm run build |
[o] |
Wave 4 — 도구가 읽을 수 있는 작업 상태 표시
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C4.1 | 이 checklist에서 작업 상태 표시는 작업 행 마지막 칸 첫머리의 상태로만 쓴다. AGENTS가 상태를 정의한다. 범례를 없애고 문장은 상태를 말로 적으며, scripts/check-documents.mjs가 두 언어에서 다른 모든 표시에 대해 file, 줄, 열을 적고 예외 없이 실패하게 한다. scripts/checklist-markers.test.mjs는 변경 전에 실패한다. Red: test는 ERR_MODULE_NOT_FOUND로 실패했다. 범례, 작업의 inline code, 일시 우회 작업의 원인, 작업이 아닌 행의 마지막 칸에 표시를 담은 그 fixture를 두 checklist로 쓰면 이전 scripts/check-documents.mjs는 53 document pairs: links, examples and status fields passed로 통과했다. Green: 2개 case가 통과한다. 같은 fixture는 언어마다 위치 5개로 실패한다. 검사를 넣고 문장이 이전 것일 때 문서 검사는 언어마다 10, 35번 줄의 표시 14개로 실패했고, 변경 후에는 통과한다 |
— | node scripts/run-tests.mjs node -- scripts/checklist-markers.test.mjs, node scripts/check-documents.mjs |
[o] |
| C4.1-1 | GitHub의 task list 상태도 상태 표시로 다룬다. C4.1은 이 저장소의 네 상태만 검사하지만 Markdown reader는 대괄호 안의 x나 대문자 X도 task list 항목의 상태로 읽으므로, 작업 상태 밖의 그런 표시가 문서 검사를 통과했다. 검사가 그 표시에 대해 file, 줄, 열을 적고 실패하게 하며, 변경 전에 실패하는 case를 scripts/checklist-markers.test.mjs에 둔다. Red: inline code로 대문자 X를 적은 작업과 x 형식의 목록 항목을 fixture에 더하자 검사가 두 표시를 보고하지 않아 case가 실패했다. Green: 2개 case가 통과하고 fixture는 위치 7개로 실패하며, 문서 검사가 통과한다 |
— | node scripts/run-tests.mjs node -- scripts/checklist-markers.test.mjs, node scripts/check-documents.mjs |
[o] |
| C4.1-2 | checklist에는 제목과 작업 table만 둔다. checklist에는 번역 link, 내용을 설명하는 문단, 사용법 규칙, 웨이브마다 의존과 배경을 적은 문단이 있었고 그런 문장을 작업과 구별하는 검사가 없었다. 규칙은 AGENTS로, 웨이브의 의존과 배경은 각 웨이브 제목이 link하는 docs/plans/execution-checklist/plan.md로 옮긴다. 문서 검사가 빈 줄, 제목, 작업 행, table 머리 행과 그 구분 행이 아닌 모든 줄에 대해 file, 줄, 열을 적고 실패하게 하며, 변경 전에 실패하는 case를 scripts/checklist-markers.test.mjs에 둔다. Red: module에 textErrors가 없어 test가 실패했고, 번역 link, 문단, 사용법 아래의 목록 항목, 의존 줄, 작업이 아닌 행, 구분 행 없는 table을 담은 그 fixture를 두 checklist로 쓰면 이전 문서 검사를 통과했다. Green: 3개 case가 통과하고 fixture는 위치 6개로 실패한다. 검사를 넣고 checklist가 이전 것일 때 문서 검사는 언어마다 9줄로 실패했고, 옮긴 뒤에는 통과한다 |
— | node scripts/run-tests.mjs node -- scripts/checklist-markers.test.mjs, node scripts/check-documents.mjs |
[o] |
Wave 5 — 모든 실패를 모으고 설명하는 CI 실행
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C5.1 | 앞의 실패 뒤에도 CI의 모든 검사 step과 모든 native suite를 실행한다. 검사 명령을 실행하는 workflow step은 모두 if: ${{ !cancelled() }}를 가지며, make crudui-test-native는 test-php-extension과 test-native-suites를 끝까지 실행하고 하나가 실패하면 실패한다. 원인: CI는 step 하나가 실패한 뒤 Lint, Type-check, dependency 검사, benchmark driver를 건너뛰었고, make crudui-test-native는 실패한 prerequisite test-php-extension에서 멈춰 tests/native-generators/run.mjs가 실행되지 않았으므로 report도 conformance evidence도 쓰이지 않았다. Makefile 대상은 test를 실행하는 대상을 prerequisite로 두지 않으므로 make docs-check도 두 갈래를 모두 실행한다. tests/build/dependency-health.test.mjs의 nativeTestNodeEntrypoints는 recipe 줄이 없는 crudui-test-native의 recipe 줄만 읽어 빈 목록을 검사했으므로, prerequisite와 $(MAKE) 대상을 따라가고 빈 목록이면 실패한다. Red: tests/build/ci-local.test.mjs는 조건이 없는 검사 step 26개를 나열했고, tests/build/test-commands.test.mjs는 docs-check: docs-check-documents, crudui-test-native: test-php-extension, crudui-test-native: test-native-suites를 나열했으며, PHP extension test 명령이 실패할 때 tests/build/make-tool-path.test.mjs의 make crudui-test-native는 tests/native-generators/run.mjs를 시작하지 않았다. 이전 nativeTestNodeEntrypoints는 []를 반환했다. Green: 세 file의 21개 case와 tests/build/dependency-health.test.mjs의 entry point case 2개가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/test-commands.test.mjs tests/build/make-tool-path.test.mjs, node scripts/run-tests.mjs node -- --test-name-pattern="native test root imports" --test-name-pattern="Node.js programs of a target" tests/build/dependency-health.test.mjs |
[o] |
| C5.2 | tests/build/shared-resources.test.mjs의 dist lock test를 hermetic하게 만든다. packages/generator-html/dist를 읽지 않고 임시 directory의 fixture package를 build하고 pack한다. 원인: CI는 npm run build보다 먼저 npm run test:runtimes를 실행하고, test는 ENOENT: no such file or directory, scandir '.../packages/generator-html/dist'로 실패했다. local 실행은 이전 build의 dist를 읽었다. Red: build 결과물이 없는 worktree에서 test는 111번 줄에서 ENOENT: no such file or directory, scandir '.../packages/generator-html/dist'로 실패했다. Green: 여전히 dist가 없는 같은 worktree에서 6개 case가 통과한다. test는 scripts/package-dist.mjs와 scripts/holder-lock.mjs를 임시 checkout으로 복사하고 fixture package를 build하며, lock 아래에서 build와 pack이 거부되고 dist가 바뀌지 않는지 확인하고 해제 뒤 dist/index.js를 pack한다 |
— | node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs |
[o] |
| C5.2-1 | dist lock test가 npm pack report를 npm의 두 형식 모두로 읽게 한다. npm 11은 archive report의 배열을, npm 12는 package 이름을 key로 하는 object를 출력하므로, CI가 npm i -g npm@latest로 설치하는 npm 12에서 test는 CI에서 tests/build/shared-resources.test.mjs:154의 TypeError: object is not iterable로 실패했고, 그 message는 무엇을 기대했는지 말하지 않는다. test는 두 형식을 받아들이고 기대한 archive 수와 package 이름으로 실패하는 scripts/package-install-pack.mjs의 reader로 report를 읽는다. Red: npm 12.2.0을 PATH 앞에 두자 test는 local에서도 154번 줄의 같은 TypeError: object is not iterable로 실패했다. Green: 두 file의 9개 case가 npm 12.2.0과 npm 11.19.0에서 통과하고, packReport는 두 형식을 읽으며 실패에서 @crudui/package를 밝힌다 |
— | node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs tests/build/package-install-pack.test.mjs |
[o] |
| C5.2-2 | local, CI, container image에서 하나의 정확한 npm release를 쓴다. package.json의 packageManager가 그 release를 정하고, scripts/install-npm.mjs가 정확히 그 release를 설치하며, CI와 container가 그것을 설치하고, tests/build/runtime-version-policy.test.mjs는 실행 중인 npm, workflow step, container 정의가 다른 release를 고르면 실패한다. 원인: CI는 npm i -g npm@latest를 실행했으므로 그 npm이 registry에 따라 바뀌었고, CI의 npm 12.2.0이 local npm 11.19.0에서 통과한 dist lock test를 깨뜨렸다(C5.2-1). packageManager는 최신 stable release인 npm 12.2.0을 기록하고, 비교 서비스의 toolchain image 검사는 npm 설치를 toolchain 단계로 받아들인다. Red: package.json에 packageManager가 없어 새 case가 실패했고, 기록한 뒤에는 local 검사가 npm 11.19.0 runs here and package.json records npm 12.2.0; fix: node scripts/install-npm.mjs로 실패했다. Green: node scripts/install-npm.mjs가 개발 machine에 12.2.0을 설치했고, 네 file의 34개 case와 비교 image 검사 16개가 통과하며, npm 12.2.0의 npm ci는 package-lock.json을 바꾸지 않고, npm run test:runtimes는 123개, tests/build/dependency-health.test.mjs는 13개 case로 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs tests/build/ci-local.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/test-commands.test.mjs examples/form-comparison/check-toolchain.test.mjs, node scripts/install-npm.mjs |
[o] |
| C5.3 | engine fixture의 긴 C string의 byte를 문자 상수로 쓴다. packages/php-ext/tests/engine.test.mjs의 cString은 4000 byte보다 긴 값을 정수로 써서 127보다 큰 UTF-8 byte가 char를 초기화했고, GCC는 overflow in conversion from 'int' to 'char' changes value from '236' to '-20' [-Werror=overflow]로 실패했다. Apple clang은 같은 source를 받아들인다. 문자 배열의 각 byte는 octal escape의 문자 상수 '\354'이며, 그 값은 char의 부호와 관계없이 char에 들어간다. Red: 이전 cString에서 case a long C string initializes each char with a character constant of its byte는 an integer above 127 initializes a char와 원소 195, 172, 195로 실패했고 35개 case가 통과했다. Green: file의 36개 case가 통과하고 render fixture는 Apple clang으로 compile된다. 개발 machine에는 GCC가 설치되어 있지 않으므로 GCC compile은 CI가 보여 준다 |
— | node scripts/run-tests.mjs node -- packages/php-ext/tests/engine.test.mjs |
[o] |
| C5.4 | engine test의 모든 C program을 math library를 link하는 하나의 함수로 compile한다. template test와 value test는 compiler 명령을 따로 썼고, -lm이 없는 template test는 Linux에서 undefined reference to 'log10'로 실패했다. macOS에서는 math 함수가 system library에 들어 있다. compileAndRunEngineProgram은 program file을 이름으로 지정한 extension source, -lm과 함께 compile하고 실행하며, fixture, template, value test가 이를 호출한다. Red: case every C program of the engine tests compiles through one function that links the math library는 compiler commands on lines 282, 2224, 2252로 실패했고 36개 case가 통과했다. Green: file의 37개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- packages/php-ext/tests/engine.test.mjs |
[o] |
| C5.5 | 빠진 conformance evidence의 suite를 밝힌다. 모든 evidence writer는 suite의 시작과 끝을 기록하고, scripts/check-conformance.mjs는 빠진 묶음마다 그것을 기록하는 suite가 실행되지 않았는지, 끝나지 않았는지, 실패로 끝났는지 보고한다. 원인: tests/native-generators/run.mjs가 실행되지 않았을 때 검사는 runtime마다 185 missing을 보고했고 이유를 말하지 않았다. scripts/run-tests.mjs와 tests/native-generators/run.mjs는 tests/conformance/runs.mjs로 시작할 때 null, 끝날 때 종료 상태를 담은 실행 기록을 쓰고, 검사의 evidenceSuites는 suite마다 명령과 runtime을 선언한다. Red: scripts/check-conformance.mjs에 export evidenceSuites가 없어 test가 실패했다. evidence 없이 통과한 validator PHP 실행과 실패한 PHP extension 실행만 담은 directory에서 이전 검사는 suite 없이 compileForm · tests/fixtures/compose/cases.json · php: 20 missing (…)을 출력했다. Green: 8개 case가 통과하고, 검사는 … · php: 20 missing (…) — suite validator PHP passed; suite native generators did not run과 … · php-native: 20 missing (…) — suite PHP extension ended with failure (exit 1); suite native generators did not run을 출력한다 |
— | node scripts/run-tests.mjs node -- tests/build/conformance-evidence.test.mjs |
[o] |
| C5.6 | engine test의 comma locale build를 그 결과로 보고한다. localedef -c는 경고와 함께 locale을 쓰면 status 1로 끝나고, step은 test가 통과하기 전에 comma locale: compiling: failed (1)을 출력했다. step은 LC_NUMERIC이 쓰인 status 1을 wrote the locale with warnings (exit 1)로 보고하고, 0이 아닌 다른 status는 locale build를 실패시킨다. Red: locale을 쓰고 경고를 출력한 뒤 1로 끝나는 stub으로 localedef를 바꾸자 case a comma locale that localedef wrote with warnings is reported as written이 comma locale: compiling: failed (1) 줄로 실패했고 37개 case가 통과했다. Green: file의 38개 case가 통과하고, stub 실행은 comma locale: compiling: wrote the locale with warnings (exit 1)을 출력하며, 아무것도 쓰지 않는 stub은 여전히 localedef wrote no locale로 실패한다 |
— | node scripts/run-tests.mjs node -- packages/php-ext/tests/engine.test.mjs |
[o] |
| C5.7 | commit된 tree의 새 clone에서 make ci를 실행해 local full run이 CI checkout의 상태에서 시작하게 한다. 원인: make ci는 이전 실행의 무시되는 build 출력이 있는 working tree에서 실행됐으므로, build가 필요한 test가 local에서 통과하고 CI에서 실패했다. scripts/full-run.mjs는 첫 명령 전에 checkout을 var/full-run/clone에 clone하고 commit된 commit을 checkout한 뒤 그곳에서 make install을 실행하고, CI_COMMANDS의 모든 명령을 그 clone을 directory로 실행한다. 같은 commit의 rerun은 설치가 끝난 clone(clone의 Git directory에 있는 marker)을 다시 쓰고, 실패한 make install은 어떤 명령보다 먼저 clone과 commit을 밝히며 실행을 멈춘다. Red: tests/build/full-run.test.mjs의 새 case는 이전 guard에서 실패했다. 이전 guard의 명령은 무시되는 var/dist/stale.js와 추적되지 않는 file이 있는 working tree에서 실행됐다. Green: case 15개가 통과한다. 명령은 commit된 file만 가진, commit된 commit의 clone에서 실행되고, make install은 한 번 실행되며, rerun은 설치된 clone을 다시 쓰고, 실패한 설치는 clone을 밝힌다 |
— | node scripts/run-tests.mjs node -- tests/build/full-run.test.mjs |
[o] |
| C5.8 | 의존성을 실시간 registry가 아니라 review 당시 알려진 상태로 판단한다. tests/build/dependency-health.test.mjs의 test tracked npm lock files have no moderate or higher vulnerability는 make ci와 CI의 test:dependencies에서 registry에 npm audit --package-lock-only를 실행했으므로, 같은 tree가 실행한 날에 공개된 advisory에 따라 통과하거나 실패했고, docs/operations/package-build.md가 요구하는 최신 stable release는 아무것도 검사하지 않았다. docs/operations/package-build.md의 spec 절 "의존성 review"가 먼저 규칙을 정한다. registry 의존성은 루트와 workspace manifest 8개의 dependencies와 devDependencies, Composer package 2개의 require와 require-dev이고, peer·URL·local 의존성과 Composer platform 요구 사항은 정의상 review 밖에 있다. test:dependencies의 case인 scripts/check-dependencies.mjs는 manifest, lock, 예외 policy config/dependency-policy.json, review 기록 config/dependency-review.json을 읽고 모든 finding을 규칙과 고치는 방법과 함께 보고하며, composer validate --strict는 COMPOSER_DISABLE_NETWORK=1로 실행한다. make dependency-review(scripts/dependency-review.mjs)는 게시자가 deprecated로 표시하지 않은 최신 stable release와 npm audit, composer audit의 advisory를 registry에 묻고, RECORD=1은 기록을 쓰며 UPDATE=1은 먼저 갱신한다. 예약된 workflow .github/workflows/dependency-review.yml이 매일 실행한다. policy는 루트와 generator-react의 react, react-dom 19.2.8을 C5.8-2-1의 재현된 실패로 예외로 둔다. C5.8-3의 TypeScript 6.0.3은 typescript-eslint, typedoc, svelte-check, @sveltejs/package를 위한 루트의 peer 해석이고 manifest의 의존성이 아니므로 review가 읽지 않고 policy에 예외도 없다. workspace 7개는 최신 release인 typescript ^7.0.2를 선언한다. make dependency-review UPDATE=1로 실행한 첫 review는 typescript-eslint를 8.71.0에서 8.71.1로 올리고 registry 의존성 86개와 lock 3개를 advisory 없이 기록했다. Red: 이전 tree에서 새 기록으로 node scripts/check-dependencies.mjs는 review 뒤에 바뀐 package-lock.json과 기록된 8.71.1과 다른 typescript-eslint 8.71.0을 보고했다. tests/build/dependency-review.test.mjs의 case들은 바뀐 lock, 없는 review 항목, 예외 없는 오래된 release, stale하거나 불완전한 예외, 기록된 advisory, 기록되지 않은 manifest, 다른 version의 local package, 최신이 아닌 Composer lock에서 실패한다. Green: npm run test:dependencies가 21개 case로 통과하고 registry에 묻지 않으며, 한 case가 그 file들에 대해 이를 확인한다 |
— | node scripts/run-tests.mjs node --timeout 60 -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs |
[o] |
| C5.8-1 | 추적되는 Cargo lock 6개의 보안 권고를 npm, Composer와 같이 review한다. crudui는 Rust package를 배포하지만 그 보안 권고를 읽는 검사가 없었다. review는 config/toolchain.json의 cargoAudit이 기록하고 make install이 .tools/cargo-audit에 설치하는(scripts/install-cargo-audit.mjs, machine에는 설치하지 않음) cargo-audit 0.22.2를 .tools/rustsec-advisory-db에 clone한 RustSec database로 실행하고, checkout의 각 Cargo lock(scripts/tracked-files.mjs)을 sha256, 취약점, unmaintained·unsound·yanked crate와 함께 C5.8의 기록에 남긴다. UPDATE=1은 영향받는 crate에 cargo update -p를 실행하고, 예약된 workflow는 Rust toolchain과 cargo-audit을 설치한다. spec 절 "의존성 review"가 먼저 Cargo lock을 명시한다. Red: 확장한 검사로 node scripts/check-dependencies.mjs는 review 기록에 항목이 없는 Cargo lock 6개를 보고했다. Green: make dependency-review RECORD=1이 registry 의존성 86개와 lock 9개를 보안 권고 없이 기록했고 검사가 통과하며, npm run test:dependencies가 21개 case로 통과한다. 그 fixture는 바뀌었거나 review되지 않은 Cargo lock에서 실패하고, 무시되는 directory 아래의 Cargo lock은 무시하며, 기록된 RustSec 보안 권고에서 실패한다 |
— | node scripts/run-tests.mjs node --timeout 60 -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs |
[o] |
| C5.8-2 | 모든 npm, Composer 의존성을 잠긴 major의 최신 stable release로 올린다. 2026-10-05에 registry는 root와 workspace manifest의 npm 의존성 19개에 같은 major의 더 새 release(puppeteer 25.12.0, eslint 10.12.0, react와 react-dom 19.3.0, vite 8.3.2, @types/node 25.9.9 등)를, 두 Composer package에 PHPUnit 10.5.66을 보고한다. 각 manifest는 range operator를 유지하고, 정확한 고정은 정확하게 남으며, peer range는 바꾸지 않는다. 결과에서 build, lint, type 검사와 package test를 실행한다. @testing-library/jest-dom 6.10.0은 게시자가 breaking change가 있는 minor release로 deprecated했으므로 6.x는 6.9.1에 남고 major 갱신과 함께 옮긴다. react와 react-dom의 override는 direct range를 따르고, puppeteer의 install script 승인은 25.12.0을 밝힌다. Green: npm 12.2.0의 npm install이 lock을 갱신했고 npm ls --all이 통과하며, deprecated된 것을 빼면 manifest의 어떤 의존성도 자기 major에 더 새 release가 없다. build, lint, type 검사가 통과하고, puppeteer 25.12.0의 Chrome 154.0.8037.57을 설치한 뒤 workspace suite가 통과하며(그 전에는 form binding browser test가 그것을 찾지 못했다), npm run test:dependencies는 13개, npm run test:runtimes는 123개, npm run test:packages는 설치 검사 7개, PHPUnit suite는 763개와 311개 case로 통과한다 |
— | npm run build, npm run lint, npm run typecheck, npm test, npm run test:dependencies, npm run test:packages, composer --working-dir=packages/validator-php test, composer --working-dir=packages/generator-php test |
[o] |
| C5.8-2-1 | React와 React DOM을 19.2.8에 둔다. C5.8-2가 올린 React 19.3.0에서 tests/widget-script-runs.test.mjs의 React case는 Chromium, Firefox, WebKit에서 browser 오류 Encountered a script tag while rendering React component. Scripts inside React components are never executed when rendering on the client. Consider using template tag instead로 실패한다. React renderer가 widget script를 <script> 요소로 render하기 때문이다. C5.8-2는 npm run test:forms가 실행하는 그 test를 실행하지 않았다. root range, override, @crudui/generator-react의 개발 range는 19.2.8을 고정하고, React 19.3으로의 갱신은 React renderer가 client render에서 <script> 요소 없이 widget script를 실행하게 되면 다시 시도한다. Red: React 19.3.0의 commit된 tree에서 test는 24개 중 6개 case로 실패했다. Green: React 19.2.8에서 24개 case가 통과하고, React suite는 1088개 case로, type 검사가, npm run test:packages는 설치 검사 7개로, npm run test:dependencies는 13개로 통과한다 |
— | node scripts/run-tests.mjs node --timeout 120 -- tests/widget-script-runs.test.mjs, npm test -w @crudui/generator-react, npm run typecheck, npm run test:packages |
[o] |
| C5.8-3 | TypeScript workspace 7개의 TypeScript를 6.0.3에서 최신 stable release인 7.0.2로 올린다. typescript-eslint 8.71.0은 peer range >=4.8.4 <6.1.0을, typedoc은 6.0.x 이하를, svelte-check는 ^5.0.0 || ^6.0.0을, @sveltejs/package는 ^6.0.0을 선언하므로, 이 도구들은 npm이 root에 그 peer로 설치하는 TypeScript 6.0.3을 쓰고, 각 workspace의 build와 type 검사의 tsc는 7.0.2다. Green: packages/validator-ts에서 npx tsc --version은 Version 7.0.2를 출력하고, build, type 검사, lint, workspace suite가 통과하며, npm run test:packages는 설치 검사 7개, npm run test:build는 36개, npm run test:dependencies는 13개, npm run test:runtimes는 123개 case로 통과한다 |
— | npm run build, npm run typecheck, npm run lint, npm test, npm run test:packages, npm run test:build |
[o] |
| C5.8-4 | root와 workspace 8개의 Vitest를 4.1.11에서 최신 stable release인 5.0.3으로 올린다. Vitest 5는 실패한 hook을 stack의 Error: 접두어와 함께 출력하므로, 제한 시간을 넘긴 Vitest hook에 대한 tests/build/run-tests.test.mjs의 runner case는 Hook timed out in 300ms 앞의 그 접두어를 받아들인다. Red: Vitest 5에서 그 case는 기대가 줄 첫머리의 message를 요구해 실패했다. Green: npx vitest --version은 vitest/5.0.3을 출력하고, workspace suite는 Vitest 4와 같은 case 수(40, 103, 293, 621, 1088, 817, 14, 824, 690)로 통과하며, type 검사와 lint가 통과하고, npm run test:runtimes는 123개, npm run test:packages는 설치 검사 7개, npm run test:dependencies는 13개 case로 통과한다 |
— | npm test, npm run typecheck, npm run lint, npm run test:runtimes, npm run test:packages |
[o] |
| C5.8-4-1 | 실패한 Vitest hook에 대한 tests/build/run-tests.test.mjs의 runner case는 Vitest 문구 대신 구조화된 값을 단언한다. Vitest reporter는 시작했고(onHookStart) 그 file이나 suite가 실패하기 전에 끝나지 않은 hook에 대해 <hook name> hook of <entity> started and failed를 출력하고, case는 hook 이름, 실패한 entity, 요약 수를 단언한다. 원인: case는 Vitest 4에서 5로 문구가 바뀐(C5.8-4) 사람이 읽는 Vitest message (?:Error: )?Hook timed out in 300ms를 단언했다. 경과 시간과 실패 종류는 단언하지 않는다. Vitest 5.0.3에서 probe reporter는 timeout된 afterAll과 throw한 afterAll, beforeAll 모두에서 onHookStart만 받고 onHookEnd는 받지 않았으므로 timeout과 throw는 같은 event를 보내며, event는 worker에서 묶여 도착하므로 300 ms timeout의 hook을 212 ms로 쟀다 Red: 구조화된 단언으로 바꾸자 case는 실패 줄에 Vitest 오류만 담던 이전 reporter에서 실패했다. Green: reporter는 file과 suite마다 시작했고 끝나지 않은 hook을 기록하고 실패한 것의 Vitest 오류 앞에 출력한다. tests/build/run-tests.test.mjs의 17개 case가 통과하고, generator-svelte suite는 reporter를 거쳐 817개와 14개 case로 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs |
[o] |
| C5.8-5 | root(정확한 고정)와 generator-react의 jsdom을 29.1.1에서 form-binding이 이미 쓰는 30.1.2로 올려 tree에서 jsdom의 major 하나만 실행되게 한다. Red: npm ls jsdom은 root, generator-react, Vitest에 jsdom 29.1.1을, form-binding에 30.1.2를 나열했다 Green: npm ls jsdom은 jsdom 30.1.2만 나열하고, npm install은 package 12개가 줄어든 lock을 다시 만들었으며, npm run test:forms는 모든 workspace suite와 node case 5264개, 24개로, npm run test:inspector는 27개로, npm run test:dependencies는 13개로 통과하고 type 검사와 lint가 통과한다 |
— | npm run test:forms, npm run test:inspector, npm run typecheck, npm run lint, npm run test:dependencies |
[o] |
| C5.8-6 | root와 cli의 js-yaml을 4.2.0에서 5.4.2로 올리고 YAML을 이름 있는 load export로 읽는다. js-yaml 5에는 default export가 없고, 기본 loader schema는 timestamp와 << merge key가 있는 js-yaml 4의 schema 대신 YAML 1.2 core schema다. 날짜 모양의 scalar는 문자열로 남고 <<는 평범한 key이므로, cli와 cross-check console은 core schema를 읽는 yaml package의 scripts/check-schema.mjs와 맞춰진다. 추적되는 YAML은 <<를 쓰지 않는다. cli(packages/cli/tests/yaml-schema.test.ts)와 console(coerceSpec)의 case가 이 맞춤을 고정한다. Red: js-yaml 5와 default import로 cli와 console suite가 Cannot read properties of undefined (reading 'load')로 실패했다. js-yaml 4.2.0은 2026-01-01을 Date로 읽고 <<: {a: 1}을 병합하므로 새 case가 거부한다. Green: cli suite는 41개 case로, cross-check console suite는 1226개로 통과하고, form 검사, dependency 검사, type 검사, lint가 통과한다 |
— | npm test -w @crudui/cli, npm test --prefix examples/cross-check-console/server, npm run test:forms, npm run typecheck, npm run lint, npm run test:dependencies |
[o] |
| C5.8-7 | form-binding, generator-core, generator-html, generator-react, generator-vue, validator-ts의 @types/node를 ^25.9.9에서 code를 실행하는 기록된 Node.js 26.8.1의 major인 ^26.6.4로 올리고 그것을 요구한다. tests/build/runtime-version-policy.test.mjs의 새 case는 .node-version과 다른 major의 @types/node range에 대해 실패한다. Red: case는 ^25.9.9인 manifest 6개를 나열했다. Green: tree에는 @types/node 26.6.4만 있고, runtime case 17개가 통과하며, type 검사, build, lint, dependency 검사, form 검사가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs, npm run typecheck, npm run build, npm run lint, npm run test:dependencies, npm run test:forms |
[o] |
| C5.8-8 | generator-svelte의 @sveltejs/package를 2.5.8에서 리뷰 때 알려진 최신 major인 3.0.0으로 올린다. 그 peer typescript ^6.0.0은 root가 유지하는 TypeScript 6.0.3(C5.8-3)이고, 2.5.8도 declaration에 이미 그것을 불렀으므로 package 출력의 TypeScript는 바뀌지 않는다. 업데이트 전에 실패하는 case는 없고 증거는 출력이다. 2.5.8과 3.0.0으로 만든 generator-svelte dist를 diff -r로 비교하니 57개 file이 같았다. Green: build, 817개와 14개 case의 generator-svelte suite, type 검사, lint, 13개 case의 dependency 검사, 38개의 공개 package 검사, form 검사가 통과한다 |
— | npm run build, npm test -w @crudui/generator-svelte, npm run typecheck, npm run lint, npm run test:dependencies, npm run test:build, npm run test:forms |
[o] |
| C5.8-9 | generator-react의 @testing-library/jest-dom을 6.9.1에서 게시자가 6.10.0을 deprecated한 변경을 담은 major인 7.0.1로 올린다(C5.8-2). peer는 Vitest 5.0.3과 @testing-library/dom 10.4.1이 충족하고, suite는 이전처럼 @testing-library/jest-dom/vitest를 import한다. 업데이트 전에 실패하는 case는 없고, 증거는 같은 suite가 같은 1088개 case로 7.0.1에서 통과하는 것이다. Green: generator-react suite가 1088개 case로 통과하고, type 검사, lint, 13개 case의 dependency 검사, form 검사가 통과한다 |
— | npm test -w @crudui/generator-react, npm run typecheck, npm run lint, npm run test:dependencies, npm run test:forms |
[o] |
| C5.8-10 | validator-php와 generator-php의 PHPUnit을 10.5.66에서 리뷰 때 알려진 최신 major인 13.4.1로 올리고, data provider 18개를 PHPUnit 12가 없앤 @dataProvider annotation 대신 attribute #[DataProvider(...)]로 선언한다. Red: PHPUnit 13과 annotation으로 validator-php의 data provider test 12개와 generator-php의 6개가 인수 없이 실패했다. Green: validator-php는 763개 case로, generator-php는 311개로 통과하며, 이는 main의 PHPUnit 10.5 수와 같으므로 scripts/run-tests.mjs의 TeamCity reader는 PHPUnit 13을 그대로 읽는다 |
— | composer --working-dir=packages/validator-php test, node scripts/run-tests.mjs phpunit --cwd packages/generator-php, node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs, npm run test:dependencies |
[o] |
| C5.8-11 | 한 version이 만족하는 npm package를 한 version으로 설치한다. make dependency-review UPDATE=1은 workspace 의존성을 npm install --workspace로 올렸고, 이 명령은 루트가 설치한 release를 남긴 채 올린 release를 그 옆에 추가하므로 svelte 5.57.1과 5.57.2처럼 한 package가 두 version으로 설치되었다. installed packages form one valid dependency graph 사례는 두 version이 각자의 범위를 만족하면 받아들이는 npm ls --all만 실행했다. scripts/dependency-review.mjs의 updatePlan은 npm 갱신 뒤에 npm dedupe를 실행하고, scripts/dependency-state.mjs의 npmDuplicates는 package-lock.json을 읽어 각 package가 위로 가장 가까운 node_modules에서 읽는 사본을 찾고, 두 version으로 설치된 package 가운데 한 version이 두 사본을 읽는 package들의 모든 범위를 만족하는 package를 보고한다. npmSatisfies는 잠금 파일의 npm 범위 형식을 평가한다. 잠금 파일은 @sveltejs/vite-plugin-svelte(^1.0.0)에 magic-string 1.4.3을, @types/jsdom(^8.9.0)에 hoist된 undici-types 8.9.0을 설치하며, make dependency-review RECORD=1은 보안 권고와 예외 없는 새 release 없이 잠금 파일을 기록했다. Red: 어떤 package도 보고하지 않는 npmDuplicates에서는 루트에 svelte 5.57.1, workspace에 5.57.2를 둔 fixture 잠금 파일의 사례와 범위 사례가 실패했다. 완성한 검사는 commit된 잠금 파일에서 magic-string 1.3.1과 1.4.3, undici-types 8.9.0과 8.11.2를 보고했다. Green: npm run test:dependencies가 26개 사례로 통과한다 |
— | node scripts/run-tests.mjs node --timeout 60 -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs |
[o] |
| C5.9 | scripts/run-tests.mjs 아래에서 node:test file의 등록된 모든 test를 실행하거나 file을 실패시킨다. runner는 node --test를 --test-force-exit로 시작하고, 이 option은 알려진 test가 끝나면 file의 process를 끝낸다(Node.js 26.8.1의 lib/internal/test_runner/test.js: "It is theoretically possible that a ref'ed handle could asynchronously create more tests, but the user opted into this behavior"). packages/php-ext/tests/engine.test.mjs는 top-level await 뒤에 test를 등록했으므로, 나중 test는 앞의 test가 아직 실행 중일 때만 실행되었다. 첫 await 앞의 동기 case는 혼자 실행되어 file에 1 passed를 보고했고, --test-name-pattern을 주면 case가 실행되지 않았는데 file이 통과했다. local과 CI의 전체 실행은 등록된 모든 case를 실행했다. runner는 module의 evaluation이 끝나기 전에 process가 끝난 file을 실패시키는 scripts/test-progress/load-check.mjs를 preload하고, reporter는 case를 하나도 보고하지 않은 file을 실패시키며, lint rule crudui/no-await-after-test-registration은 첫 등록 뒤의 top-level await를 거부하고, engine.test는 fixture를 동기로 읽는다. Red: 300 ms 대기 뒤에 test를 등록하는 file은 runner에서 1 passed로 통과했고, case를 고르지 않는 pattern도 1 passed로 통과했으며, lint rule은 engine.test의 367, 448, 525, 917, 1492, 1672, 1795, 1904, 2275번 줄의 대기 9개를 보고했다. Green: tests/build/run-tests.test.mjs의 12개, tests/build/test-commands.test.mjs의 13개, engine.test의 39개 case가 통과하고 npm run lint가 통과한다. --test-name-pattern을 준 engine.test는 고른 case를 실행하고 case가 없는 pattern에는 the file ran no test case로 실패하며, handle을 열어 둔 test도 file을 끝내고, npm run test:runtimes는 111개 case로 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs tests/build/test-commands.test.mjs packages/php-ext/tests/engine.test.mjs, npx eslint --max-warnings 0 packages/php-ext/tests/engine.test.mjs |
[o] |
| C5.10 | 앞의 검사가 실패한 뒤에도 Makefile 대상의 모든 검사를 실행한다. make는 처음 실패한 recipe 줄에서 멈추므로, make docs-check-documents는 npm run manifest:check가 실패하면 문서 검사, 문서 test, 문서 build를 건너뛰었고, make format-check는 rustfmt와 다른 첫 Rust crate에서 멈춰 다른 crate와 gofmt를 건너뛰었다. 검사를 실행하는 recipe 줄은 그 대상의 마지막 줄이며 각 검사를 || status=1로 실행하고 exit $$status로 끝난다. 그 앞의 준비 줄은 여전히 대상을 멈추며, make docs-verify-idempotent처럼 뒤 단계가 앞 단계의 결과를 읽는 && 연결은 하나의 검사다. tests/build/test-commands.test.mjs는 다른 모든 형식에 대해 실패한다. Red: 새 case는 npm run manifest:check가 통과할 때만 실행되는 docs-check-documents의 줄 5개, Rust loop 뒤의 format-check gofmt 줄과 요약 줄, format-check: a failing check ends the recipe with || exit를 나열했다. Green: 두 file의 19개 case가 통과하고, make format-check는 [make] format-check: Rust crates and Go files are formatted로 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/make-tool-path.test.mjs |
[o] |
| C5.11 | tests/tailwind-styles.test.mjs의 두 stylesheet의 style을 engine과 너비마다 보이는 page 하나에서 계산한다. test는 같은 browser에서 crudui.css의 page를 연 뒤 Tailwind 판의 page를 열었으므로 첫 page는 Chromium과 Firefox에서 hidden이었고, memory 압박에서 hidden Chromium page의 호출은 renderer의 page-in을 기다리며 case의 30 s timeout을 넘겼다. 2026-10-05에 swap 31.7 GB 중 30 GB가 쓰이는 상태에서 측정했다. page 둘이면 호출 4320개 중 19개가 1 s를 넘었고 가장 긴 것은 호출 중 page-in 98,227개와 함께 44.2 s였으며 모두 hidden page였다. 그 case는 평소 4~70 ms 걸린다. 보이는 page 하나면 load average 64~87에서 호출 8640개 중 1 s를 넘은 것이 없었고 가장 긴 것은 140 ms였다. 이제 page는 두 stylesheet를 담고 media로 하나를 적용하며, 각 호출은 page가 보이고 요청한 stylesheet만 적용될 때만 진행한다. Red: visibility 검사와 page 둘에서 Chromium과 Firefox의 너비마다 432개 case가 the page is hidden, not visible로 실패했다. Green: 2599개 case가 통과하고, 두 stylesheet를 모두 적용하면 검사는 the stylesheets plain, layered apply instead of plain으로 실패한다 |
— | node scripts/run-tests.mjs node -- tests/tailwind-styles.test.mjs |
[o] |
Wave 6 — 진행 중 작업이 없는 push
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C6.1 | checklist의 작업이 진행 중인 동안 push를 거부한다. 추적되는 pre-push hook .githooks/pre-push는 node scripts/push-gate.mjs hook을 실행하고, 이 entry는 push되는 모든 commit과 working tree의 checklist를 scripts/full-run.mjs의 activeItems로 읽어 진행 중인 각 작업의 ID와 제목, 이유, 해결 방법을 출력하며 push를 거부한다. checklist가 없는 push commit, Git 오류, parser 오류도 push를 거부한다. 모든 make 실행은 core.hooksPath를 .githooks로 설정한다. make hooks는 이를 설치하고 검사하며, make hooks-check는 설치되지 않았을 때 실패하고, make ci의 guard는 설치되지 않은 동안 거부한다. workflow .github/workflows/push-gate.yml은 push되는 모든 commit과 pull request에 node scripts/push-gate.mjs commit <sha>를 실행하므로, hook을 거치지 않은 push도 같은 message로 job push-gate를 실패시킨다. Red: push 검사가 없는 임시 checkout에서 checklist에 진행 중인 작업이 있는 commit의 git push가 status 0으로 bare remote에 도달했고, scripts/push-gate.mjs가 없어 tests/build/push-gate.test.mjs의 7개 case가 실패했다. Green: 7개 case가 통과한다. 진행 중인 작업이 있는 commit의 push는 그 ID, 제목, 이유, 해결 방법과 함께 거부되고 bare remote에는 ref가 남지 않는다(R1). 깨끗한 push는 도달한다(G1). working tree에만 진행 중인 작업이 있어도 push가 거부된다(R2). core.hooksPath가 설정되지 않았거나 hook이 실행 가능하지 않으면 hooks-check가 실패하고, make -n help와 make hooks가 설치한다(R3). checklist가 없는 push commit은 거부된다(R4). commit <sha>는 진행 중인 작업과, 없거나 mode 100755가 아닌 hook에 ::error:: 줄과 함께 1로, 깨끗한 commit에 0으로 끝난다. tests/build/full-run.test.mjs의 12개 case가 hook이 없는 checkout의 거부를 포함해 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/push-gate.test.mjs tests/build/full-run.test.mjs |
[o] |
| C6.2 | 모든 변경을 pull request와 merge queue로 게시한다. ruleset main은 .github/repository.json과 make github-settings로 옮긴다. 이 ruleset은 승인 없는 pull request(merge, squash, rebase 허용), REBASE 방식의 merge queue, 선형 이력, integration 15368의 push-gate와 .github/workflows/ci.yml의 모든 job(matrix 항목마다 하나)의 check를 요구하고, bypass actor 없이 force-push와 삭제를 거부한다. 설정에 allow_auto_merge와 delete_branch_on_merge를 더한다. ci.yml은 pull request와 merge group에서 실행되고 pull request 실행만 취소하며, push-gate.yml은 merge group에서도 실행되고 queue의 branch는 무시하며, .github/workflows/pages.yml이 main의 문서 웹을 build해 배포한다. Red: 이전 선언, script, workflow에서 tests/build/github-repository.test.mjs와 tests/build/ci-local.test.mjs의 사례 15개 중 9개가 실패했다. ruleset을 계획하지 않았고, 선언에 merge queue가 없었고, CI가 main push에서 실행되어 모든 실행을 취소하고 ci.yml에서 배포했으며, Pages workflow가 없었다. Green: 사례 15개가 통과한다. 필수 check는 push check와 CI workflow의 job과 같고, rule, check, merge 방식의 순서는 차이가 아니며, 다른 ruleset은 id로 교체하되 다른 이름의 ruleset은 그대로 둔다. CI, push check, runtime, command, owner, 문서 unit test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/github-repository.test.mjs tests/build/ci-local.test.mjs |
[o] |
| C6.2-1 | 각 workflow의 trigger를 정확히 선언한다. .github/workflows/ci.yml의 on: block은 pull_request, merge_group, workflow_dispatch이고, .github/workflows/push-gate.yml은 branches-ignore: ['gh-readonly-queue/**']를 가진 push, pull_request, merge_group, .github/workflows/pages.yml은 main push와 workflow_dispatch, .github/workflows/dependency-review.yml은 schedule과 workflow_dispatch이며, 다른 workflow는 없다. .github/repository.json의 ruleset main은 push-gate와 ci.yml의 모든 job과 matrix 항목을 요구한다. 운영 문서는 trigger를 적는다. Red: tests/build/ci-local.test.mjs의 새 사례가 추가된 workflow에서 실패했다. Green: tests/build/ci-local.test.mjs와 tests/build/github-repository.test.mjs의 사례가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/github-repository.test.mjs |
[o] |
Wave 7 — 같은 tree에 같은 결과를 내는 실행
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C7.1 | make -n의 출력을 단언하는 모든 test는 make --no-print-directory -n을 하나의 helper로 실행하고, 그 helper의 환경은 MAKEFLAGS=w를 설정하고 MAKELEVEL, GNUMAKEFLAGS, MAKEFILES를 지우므로 sub-make 아래의 GNU Make 4도 GNU Make 3.81과 같은 명령을 출력한다. static case는 helper 밖의 make dry run에 대해 실패한다. 원인: tests/build/full-run.test.mjs는 make -n ci의 첫 줄과 줄 수를 단언했고, sub-make 아래의 GNU Make 4는 Entering directory 줄을 더한다 Red: GNU Make 4.4.1을 PATH의 맨 앞에 두고 MAKELEVEL=1로 실행하면 make ci and make rerun-failed start the guard before any step case가 make -n ci의 첫 줄 make[1]: Entering directory로 실패했고, 새 static case는 tests/build/full-run.test.mjs:33과 tests/build/push-gate.test.mjs:111을 나열한다. Green: tests/build/make-dry-run.mjs가 모든 dry run을 실행하고, 세 file의 22개 case가 macOS make 3.81과 MAKELEVEL=1 아래의 GNU Make 4.4.1에서 통과하며, npm run test:runtimes가 새 file을 실행한다 |
— | node scripts/run-tests.mjs node -- tests/build/full-run.test.mjs tests/build/push-gate.test.mjs tests/build/make-dry-run.test.mjs |
[o] |
| C7.2 | 정확한 toolchain version을 기록하고 실행 중인 tool이 그것과 같기를 요구한다. specification은 release channel을 정확한 version으로 바꾼다. .node-version 26.8.1, 모든 go.mod의 toolchain 줄과 GOTOOLCHAIN=local을 갖춘 .go-version 1.27.0, component를 갖춘 rust-toolchain.toml 1.98.1, Makefile이 export하고 CI가 설정하는 RUSTUP_AUTO_INSTALL=0, rustup toolchain install --no-self-update를 실행하는 make install, 실행 중인 release를 검사하는 정확한 PHP와 Composer release, digest로 고른 container stage, commit SHA로 고른 action, ubuntu-24.04 runner, 정확한 Node.js archive와 그 checksum을 쓰는 Linux style 검사. scripts/run-contract-tests.mjs는 login shell 대신 sh -c를 실행한다. 원인: specification이 release channel을 골랐으므로 실행의 toolchain이 그 날짜에 따라 달라졌다 container의 PHP 단계와 실행 중인 PHP는 C7.3과 C7.2-1에서 옮긴다. Red: 새 record를 복사한 이전 tree에서 tests/build/runtime-version-policy.test.mjs의 새 case 15개 중 10개가 실패했다. .node-version은 26이었고, 어떤 go.mod에도 toolchain 줄이 없었으며, Makefile은 RUSTUP_AUTO_INSTALL도 GOTOOLCHAIN도 export하지 않았고 install이 없었고, workflow는 ubuntu-latest에서 major tag의 action과 dtolnay/rust-toolchain@stable로 실행되었고, 어떤 job도 자기 도구를 검사하지 않았으며, container 단계는 digest가 없고 live Debian archive를 읽었고, Linux style 검사는 latest-v26.x, firefox@stable, runner의 Chrome을 받았고, contract runner는 sh -lc를 실행했다. Green: 15개 case가 통과한다. node 26.8.1, npm 12.2.0, go 1.27.0, rustc 1.98.1, Composer 2.10.3이 기록대로 실행되고, stub version 실행은 기록, 기대한 version과 실행 중인 version, 해결 방법으로 실패하며, make -n install은 rustup toolchain install --no-self-update를 출력한다. runtime, CI, browser, contract, command file의 65개 case가 통과하고, 6개 Go module은 모두 GOTOOLCHAIN=local로 list되며, node scripts/install-browsers.mjs chrome firefox는 Chrome 154.0.8037.57과 Firefox stable_156.0.1을 설치했고, tests/viewport.test.mjs의 2598개 case가 고정한 Chrome, Firefox와 WebKit에서 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs |
[o] |
| C7.2-1 | 다른 도구처럼 실행 중인 PHP가 config/toolchain.json이 기록한 release이기를 요구한다. every tool runs at the version that the checkout records case의 도구에 php를 더한다. 우회 원인: machine은 machine의 모든 repository가 함께 쓰는 Homebrew PHP 8.5.10을 실행하고, CI의 setup-php와 registry의 PHP release는 8.5.11을 준다. 재시도 조건: machine에서 php -r 'echo PHP_VERSION;'이 기록한 8.5 release를 출력할 때 C7.2-3이 수행했다. patch 고정이 없어졌고, PHP는 minor로 고정되며, 실행 중인 PHP 검사가 PHP를 다룬다. |
— | node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs |
[o] |
| C7.2-2 | form comparison의 toolchain image test는 기록한 release의 digest 단계를 받아들인다. 원인: C7.2는 examples/form-comparison/Containerfile의 단계를 정확한 tag와 digest로 지정했는데, 그 file을 소유하는 examples/form-comparison/check-toolchain.test.mjs는 여전히 golang:1.27-trixie, rust:1-slim-trixie, node:26-trixie-slim을 요구했고, C7.2는 그것을 실행하지 않고 commit되었다. Red: builds one toolchain image without repository source가 세 FROM 줄에서 실패했다. Green: case는 세 단계를 digest가 있는 정확한 release로 요구하고, check-toolchain.test.mjs와 check-deployment.test.mjs의 16개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-toolchain.test.mjs examples/form-comparison/check-deployment.test.mjs |
[o] |
| C7.2-3 | 모든 polyspec repository처럼 PHP를 minor release로 고정한다. config/toolchain.json은 검사하는 minor 8.4와 8.5를 기록하고, node scripts/check-toolchain.mjs는 실행 중인 PHP의 major와 minor를 그것과 비교해 기대한 release와 실제 release를 밝히며, 실행이 쓴 patch는 evidence다. 검사는 실행 중인 release를 출력하고 var/full-run.json은 그것을 기록한다. container image는 digest로 재현되는 정확한 tag와 digest를 유지한다. 원인: setup-php와 Homebrew는 같은 patch를 설치할 수 없으므로 정확한 patch 기록은 둘 중 하나에서 실패했다(C7.2-1) Red: 이전 code에서 새 full-run case는 record에서 toolchain을 찾지 못했고, runtime file은 toolchainVersions 없이 load에 실패했으며, config/toolchain.json은 8.4.26과 8.5.11을 기록했다. Green: runtime, full-run, push, npm file의 46개 case가 통과한다. machine의 PHP 8.5.10은 minor 8.5로 통과하고, 기록한 minor의 다른 patch는 통과하며, 8.3.30은 records 8.4 or 8.5로 실패하고, 실행 중인 release가 보고되며, run record와 각 재실행이 그것을 가지고, digest가 있는 container 단계 8.5.11-cli-trixie와 8.5.11-fpm-trixie는 minor 8.5의 정확한 release다. node scripts/check-toolchain.mjs node npm go rust php composer는 php 8.5.10을 출력하고 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs tests/build/full-run.test.mjs |
[o] |
| C7.2-4 | Chrome sandbox helper를 install 명령 하나로 설치한다. sudo install -o root -g root -m 4755 <chrome_sandbox> /usr/local/sbin/chrome-devel-sandbox이고 root에서는 sudo 없이 같다. 원인: CI는 모든 browser job에서 Puppeteer가 고정한 browser를 설치하는 step에서 install: target '/usr/local/sbin/chrome-devel-sandbox': No such file or directory로 실패했다. C7.2의 scripts/install-browsers.mjs는 명령에 install을 두 번 넣었으므로(sudo install install -o root ...), install은 operand 세 개를 읽고 helper 경로를 directory로 받았다. 그 뒤 Chrome은 The SUID sandbox helper binary is missing으로 중단했다 그 Chrome의 set-user-ID sandbox가 runner의 사전 검사를 통과하는지는 다음 CI 실행에서만 보인다. Red: tests/build/install-browsers.test.mjs의 새 case 2개가 실패했다. 명령은 sudo install install -o root ...였고, 올바른 명령의 operand로 install을 실행하는 sudo stand-in은 CI처럼 install: 4755: No such file or directory로 실패했다. Green: 3개 case가 통과한다. browser는 고정한 build이고, helper 명령은 sudo install -o root -g root -m 4755, root에서는 install -o root -g root -m 4755이며, stand-in은 mode 4755의 helper file을 썼다 |
— | node scripts/run-tests.mjs node -- tests/build/install-browsers.test.mjs |
[o] |
| C7.3 | test case나 명령을 하나도 실행하지 않은 실행은 실패한다. scripts/test-progress/progress.mjs의 close()는 go, cargo, phpunit, vitest 실행을 ran no test case로 실패시키고, PHP container 검사는 php8.N- package 이름을 읽어 빈 목록에 대해 실패하며, container는 PHP 8.5를 설치하고, scripts/run-contract-tests.mjs는 명령을 하나도 실행하지 않으면 실패한다. 원인: 그 실행들과 container 검사는 아무것도 검사하지 않고 통과했다 Debian에는 PHP 8.5 package가 없고 다른 archive의 apt 고정은 그 최신 build만 유지하므로, 두 image는 PHP를 기록한 8.5.11의 php image에서 digest로 받는다(native 검사는 cli, 비교는 php-fpm이 /usr/local/sbin에 있는 fpm). Composer는 composer 2.10.3 image에서, Node.js는 그 단계에서 받으며, PHP_EXTENSION_PHP_CONFIG와 비교 build는 /usr/local/bin/php-config를 쓰고 php-fpm link는 없어졌다. Red: tests/build/run-tests.test.mjs의 새 case는 이전 code에서 실패했다. test case가 없는 실행의 close는 ok를 반환했고, test가 없는 module에 대한 run-tests.mjs go는 통과했다. contract runner case는 명령이 없는 선택에 대해 status 1을 기대했지만 0을 받았다. 다시 쓴 container case는 두 정의의 php8.4-* package 9개와 Debian Composer를 나열했고, 이전 case는 빈 FROM php: 단계 목록을 돌며 통과했다. Green: run-tests.test.mjs의 14개, run-command.test.mjs의 8개, runtime-version-policy.test.mjs의 16개, 비교 image와 deployment test의 16개 case, build target test, packages/php-ext/tests/engine.test.mjs의 38개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/contract-manifest.test.mjs |
[o] |
| C7.4 | 앞의 검사가 실패한 뒤에도 package script와 contract runner의 독립된 모든 검사를 실행하고, 뒤 단계가 앞 단계의 출력을 읽는 연결은 하나의 검사로 둔다. C5.10의 규칙 test는 package.json의 script와 scripts/run-contract-tests.mjs를 다룬다. 원인: test:forms, test:form-comparison, test:form-comparison:pipeline, test:form-comparison:source, test:bench, generator-svelte의 test script, CI step test:build && test:build:repeat는 독립된 suite를 &&로 이었고, contract runner는 처음 실패한 명령에서 끝났다 test:bench(require-current-build, driver build, driver test)와 test:build:repeat(두 build 뒤 그 기록을 읽는 test)는 검사가 하나인 build 뒤 test 연결이다. Red: tests/build/test-commands.test.mjs의 새 case는 위반 25개를 나열했다. test:forms, test:form-comparison, test:form-comparison:build, test:form-comparison:pipeline, docs:check:all의 연결된 검사, generator-svelte의 test, form-binding의 typecheck, CI step npm run test:build && npm run test:build:repeat이다. run-command.test.mjs의 새 case는 runner가 a/fails 뒤에 멈춰 두 번째 명령이 실행되지 않아 실패했다. Green: script는 build하는 곳에서 require-current-build || exit 1 뒤에 각 검사를 || status=1로 실행하고 모은 상태로 끝나며, test:form-comparison:build는 제거되고 form comparison의 각 suite는 자신이 읽는 것을 build하고, CI와 make ci는 test:build와 test:build:repeat를 두 명령으로 실행하며, runner는 모든 명령을 실행하고 마지막 명령 뒤에 실패한다. command, contract, CI, comparison, runtime, guard file의 80개 case가 통과하고, 바뀐 root script 4개는 sh -n으로 해석되며, form-binding type 검사와 generator-svelte suite(817개, 14개 case)가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/contract-manifest.test.mjs |
[o] |
| C7.5 | 모든 검사는 자신이나 선언된 준비가 만든 상태만 읽는다. make test-php-extension은 generator-php의 validator 복사본을 다시 설치하고, test:build는 require-current-build를 실행하며, reproducible-build.test는 자기 기록을 만들고, scripts/run-rust-command.mjs는 checkout 밖의 CARGO_TARGET_DIR을 거부한다. 원인: 그 검사들은 다른 명령이 남긴 출력을 읽었다 Red: tests/build/php-extensions.test.mjs의 새 case는 api.test.mjs가 다시 설치 없이 packages/generator-php/vendor를 load하는 test-php-extension을 밝혔다. test-commands.test.mjs의 새 case는 test:build가 모든 package를 exports로 import하는 public-packages.test.mjs를 build 없이 실행하므로 실패했다. 이전 reproducible-build.test.mjs는 repeat-build.mjs만 쓰는 node_modules/.cache/crudui/repeat-build/*.json을 읽었고, 이전 tree에서 새 reproducible build file은 buildDifferences가 없어 load에 실패했다. 새 Rust case는 runRustCommand에 거부가 없어 다른 임시 directory의 CARGO_TARGET_DIR로 cargo를 실행했으므로 실패했다. Green: test-php-extension은 test 전에 crudui/validator를 다시 설치하고, test:build는 먼저 require-current-build를 실행하며, repeat-build.mjs는 자신의 두 build를 비교해 다르거나 빠지거나 더해진 각 file과 digest로 실패하고, 그 test는 build 없이 package 목록과 비교를 검사하며, run-rust-command.mjs는 어떤 도구도 실행하기 전에 그 directory의 Git working tree 밖(Git 밖에서는 그 directory 밖)의 target을 거부한다. 네 file의 29개 case가 통과하고 node scripts/repeat-build.mjs는 두 번 build하여 329개 output file이 같음을 확인했다 |
— | node scripts/run-tests.mjs node -- tests/build/test-commands.test.mjs tests/build/reproducible-build.test.mjs tests/build/rust-node-entry-points.test.mjs |
[o] |
| C7.6 | 공유 출력을 atomic하게 게시한다. PHP extension builder는 process마다의 이름으로 link한 뒤 crudui.so로 rename하고, ordered-json source checkout은 임시 directory에 만든 뒤 제자리로 rename하며, require-current-build의 build stamp와 repeat-build의 기록은 임시 file에 쓴 뒤 rename하고, package dist를 읽는 쪽은 그 lock을 잡는다. 원인: 읽는 쪽이 일부만 쓰인 module, checkout, stamp, 기록, dist를 볼 수 있었다 C7.5부터 repeat-build.mjs는 기록을 남기지 않으므로 게시하는 것이 없다. Red: 새 builder case는 generated path인 .build와 modules를 지운 뒤 -o modules/sample.so로 제자리에 link하던 이전 builder에서 실패했다. 두 OrderedJSON case는 directory를 지운 뒤 그 안에서 Git을 실행하던 이전 checkout에서 실패했다. 이전 package build는 tsup --clean과 svelte-package로 dist에 썼으므로 읽는 쪽은 build 내내 비워진 dist를 보았고, 새 fixture build는 이전 package-dist.mjs 아래에서 CRUDUI_DIST를 받지 못했다. Green: builder는 .build/run-<pid>에 compile하고 <module>.so.<pid>로 link한 뒤 load하여 검사하고 rename하며, manifest도 rename으로 게시한다. OrderedJSON checkout은 <directory>.next-<pid>에 만든 뒤 rename하고, 실패한 checkout은 이전 것을 남긴다. 모든 package build는 dist.next에 쓰고 package-dist.mjs는 lock 아래에서 그것으로 dist를 바꾸며, 실패한 build 뒤에는 dist를 유지한다. require-current-build의 stamp는 쓴 뒤 rename한다. builder 8개, OrderedJSON 2개, shared resource 7개, atomic publication 2개 case와 관련 command, lock, pack, lint, Vite case가 통과하고, node scripts/build-crudui-php-extension.mjs는 crudui.so를 build하고 load했으며 .build에 build.json만 남겼고, npm run build는 staging directory와 output 안의 dist.next 경로를 남기지 않았으며, 그 build에서 public package case 10개가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/native-generators/php-extension-builder.test.mjs tests/build/atomic-publish.test.mjs |
[o] |
| C7.7 | npm을 machine이 아니라 checkout에 설치한다. scripts/install-npm.mjs는 npm install --prefix .tools/npm npm@<recorded>를 임시 directory에 실행한 뒤 무시되는 .tools/npm으로 rename하고, 이미 설치된 기록 release는 유지하며, 설치된 release를 검사하고, npm install --global은 실행하지 않는다. Makefile은 .tools/npm/node_modules/.bin을 맨 앞에 둔 PATH를 export하고, npm을 시작하는 모든 script는 scripts/checkout-npm.mjs의 useCheckoutNpm으로 그것을 맨 앞에 두며, 모든 CI job은 첫 npm 명령 전에 그것을 GITHUB_PATH에 더한다. 원인: scripts/install-npm.mjs는 npm install --global npm@12.2.0을 실행했으므로 machine의 모든 repository의 npm을 바꿨고 다른 repository의 npm ci를 깨뜨렸다 GNU Make 3.81은 shell 문법이 없는 recipe 줄의 program을 make가 시작할 때의 PATH에서 찾으므로, 모든 recipe는 npm을 checkout 안의 경로인 $(NPM)으로 시작한다. Red: 이전 scripts/install-npm.mjs는 40번째 줄에서 ['install', '--global', ...]을 실행했고 installNpm을 export하지 않았으므로 새 file은 load에 실패했다. 새 case는 useCheckoutNpm 없이 npm을 시작하는 script 6개를 나열했고, make의 첫 PATH 항목은 ~/.local/bin이었으며, make 3.81 아래의 probe recipe npm --version은 export된 PATH가 12.2.0을 가리키는 동안 machine npm 11.19.0을 출력했다. Green: tests/build/checkout-npm.test.mjs의 9개 case가 통과한다. stub npm은 rename되는 임시 directory를 거쳐 .tools/npm에 설치하고, 설치된 release는 유지되며, 틀린 release는 기대한 release와 설치된 release로 실패하고 아무것도 남기지 않고, 어떤 script, make 대상, CI step도 npm을 global로 설치하지 않으며, make는 .tools/npm/node_modules/.bin을 PATH의 맨 앞에 두고, 어떤 recipe도 맨 npm을 시작하지 않으며, make 3.81 아래의 recipe $(NPM) --version은 12.2.0을 출력하고, 모든 CI job은 첫 npm 명령 전에 그 경로를 GITHUB_PATH에 더한다. node scripts/install-npm.mjs는 machine npm이 11.19.0으로 남은 채 12.2.0을 .tools/npm에 설치했고, checkout npm을 PATH의 맨 앞에 두면 runtime, CI, command, dry-run file의 46개 case와 바뀐 script test의 54개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/checkout-npm.test.mjs tests/build/runtime-version-policy.test.mjs |
[o] |
| C7.7-1 | npm을 시작하는 script의 복사본을 실행하는 test는 scripts/checkout-npm.mjs도 함께 복사한다. 원인: C7.7은 check-doc-coverage.mjs, gen-api-docs.mjs, require-current-build.mjs가 scripts/checkout-npm.mjs를 import하게 했고, tests/build/rust-node-entry-points.test.mjs와 tests/build/run-command.test.mjs는 그것 없이 그 script들을 임시 checkout에 복사했다. Red: rust-node-entry-points의 case 1개와 run-command의 case 2개가 scripts/checkout-npm.mjs에 대한 ERR_MODULE_NOT_FOUND로 실패했다. Green: 두 file의 13개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/run-command.test.mjs tests/build/rust-node-entry-points.test.mjs |
[o] |
| C7.8 | 모든 실패는 무엇이 왜 실패했는지 밝힌다. timeout은 명령, 한도, 경과 시간을 출력하고, run-tests.mjs는 시작할 수 없는 tool을 그 경로와 설치 명령으로 보고하며, Rust file 읽기는 경로를 밝히고, test가 없는 Go package는 통과로 세지 않는다. 원인: 그 실패들은 명령이나 경로를 출력하지 않았거나 통과를 출력했다 다른 한도도 그 명령이나 검사, 한도, 경과 시간을 밝힌다. native generator budget, form comparison unit, deployment 요청, cross-check console의 validator process, PHP mode 검사, package 설치 검사의 명령이다. native Rust program의 standard input 읽기는 UTF-8이 아닌 요청을 계약대로 protocol 오류로 바꾸므로, 규칙은 file 읽기(fs::read_to_string)를 다룬다. Red: tests/build/run-tests.test.mjs의 새 case 세 개가 실패했다. test를 실행하지 않은 Go package는 ✔ empty를 출력했고, timeout 줄은 명령도 경과 시간도 밝히지 않았으며, 없는 vendor/bin/phpunit은 처리되지 않은 error event로 runner를 멈췄다. tests/build/failure-messages.test.mjs의 새 case는 이전 tree에서 unwrap하거나 고정 문구로 expect하거나 오류를 버리는 file 읽기 7개(file 6개)를 나열한다. Green: 시작한 test가 없는 package는 ○ <package>: ran no test case를 출력하고 세지 않으며, timeout 줄은 ⏱ <test> ran <elapsed> and exceeded its <limit> timeout; stopping followed by <command>이고, 시작할 수 없는 tool은 cannot start <path>: <error>; fix: <install command>를 출력하며, 모든 Rust file 읽기는 read <path>: <error>로 panic하거나 기록한다. run-tests case 17개, Rust 읽기 case, unit pool case 7개, cross-check console suite의 1225개 case가 통과하고, 바뀐 crate 네 개에서 cargo check --tests가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/run-tests.test.mjs tests/build/failure-messages.test.mjs examples/form-comparison/src/unit-pool.test.mjs |
[o] |
| C7.9 | owner check를 추가한다. scripts/owner-check.mjs는 선언된 입력이 바뀐 경로와 맞는 scripts/owner-checks.json의 검사를 고르고, owner가 없는 바뀐 경로에 대해 실패하며, 어떤 규칙도 소유하지 않는 지워진 경로는 건너뛰고, AGENTS는 그것을 commit 전의 검사로 정한다. 원인: 변경의 소유 test를 손으로 골랐으므로, 변경을 읽는 검사 없이 commit될 수 있었다 검사는 make 대상, root npm script, workspace와 package directory의 test script, node test file이다. inputs는 make crudui-test-validators, make crudui-test-native, npm run test:forms, npm run test:form-comparison, npm run test:bench, cross-check console, make docs-check가 읽는 경로를 밝힌다. 선언을 만들면서 어떤 project 명령도 실행하지 않는 tests/ordered-json/test_check.py를 찾았고, 이것은 이제 make test-ordered-json으로 실행된다(그 Python release는 C7.12). make install은 make docs-check가 읽는 phpDocumentor를 설치하지 않았고, 이제 scripts/install-phpdocumentor.sh를 실행한다. Red: tests/build/owner-check.test.mjs는 scripts/owner-check.mjs 없이 load에 실패했다. Green: 9개 case가 통과한다. 경로는 그 규칙의 검사만 정확히 실행하고, 모든 검사는 하나가 실패한 뒤에도 실행되며, owner가 없는 추적된 경로나 새 경로는 어떤 검사보다 먼저 실패하고, 지워진 경로는 아무것도 고르지 않고 지워진 test는 실행되지 않으며, 경로가 없는 glob, full suite 대상, 알 수 없는 script, 없는 test는 그 이름으로 실패하고, 그 경로의 어떤 규칙도 고르지 않는 input은 실패하며 prerequisite 대상은 인정되고, repository의 선언은 검증을 통과한다. 이 변경에 대한 make owner-check는 make docs-check, npm run test:dependencies, test file 10개를 실행했고 phpDocumentor를 설치한 뒤 통과했다 |
— | node scripts/run-tests.mjs node -- tests/build/owner-check.test.mjs |
[o] |
| C7.10 | form comparison step runner의 killProcessTree는 모든 자손이 끝날 때까지 기다린다. 원인: 자손에 SIGKILL을 보내고 그들이 끝나기 전에 반환했으므로, 다음 step이 멈춘 step의 process를 만날 수 있었다 끝의 event는 step output의 close다. pipe는 그것을 가진 모든 process가 끝나면 닫히고, tree 밖의 죽은 자손에는 이 process가 받을 수 있는 다른 event가 없다. SIGKILL 뒤 grace 동안 output이 열려 있으면 중지는 그것을 가진 process를 밝히며 실패한다. local server case는 writer가 열 때까지 runner의 thread를 막는 FIFO를 읽었으므로, 시작하지 않은 stand-in이 file을 timeout 너머까지 붙잡았다. 이제 stand-in이 여는 Unix socket 연결의 끝을 기다린다. Red: examples/form-comparison/src/step-runner.test.mjs의 새 case는 중지가 group을 떠나 SIGTERM을 무시하는 손자에 SIGKILL을 보낸 직후 반환했으므로 네 번 중 네 번 run 0: the output of the tree had not ended when the stop resolved로 실패했다. Green: killProcessTree는 output이 열린 채 이미 끝난 process에 대해서도 기록한 tree와 group에 SIGKILL을 보내고 close에서 끝난다. step runner case 9개가 세 번의 실행에서 통과하며 각각 열 개의 tree를 멈추고 그 output이 끝나고 손자가 더 이상 실행되지 않음을 확인했고, local server case 3개와 verification, response, startup test의 34개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/step-runner.test.mjs |
[o] |
| C7.11 | 공유 자원의 race를 없앤다. form comparison의 local server는 port 0에 bind하고 준비 줄에 주소를 알리며, composer reinstall과 scripts/full-run.mjs는 checkout lock을 잡고, 부분 conformance 기록은 process마다의 이름을 가진다. 원인: 빈 port는 확인과 사용 사이에 다른 쪽이 잡을 수 있었고, 두 실행이 동시에 다시 설치하거나 기록할 수 있었다 tests/conformance/runs.mjs의 부분 conformance 기록은 이미 process의 이름(<pid>-<16진수 12자리>.json.partial)을 가졌으므로 바꾸지 않는다. 모든 server program(Go, Rust, JavaScript record server, public server, PHP launcher)은 port 0을 포함한 자기 주소에 bind하고 잡은 주소와 함께 CRUDUI_READY {server} {host}:{port}를 출력한다. PHP launcher는 주소에 직접 bind하고 그 socket을 NGINX가 가리키는 file descriptor 3으로 nginx에 넘긴다. startProcess는 그 줄의 주소로 server에 접근하고, local stack은 모든 server를 127.0.0.1:0에서, public server는 알린 port로 시작한다. supervisor는 고정 port의 정확한 주소를 기대한다. make test-php-extension, make test-native-suites, local record server, 비교 build target은 checkout lock composer-generator-php 아래에서 generator-php vendor를 다시 설치하고, make ci의 guard는 record를 읽고 정하고 쓰는 동안 checkout lock full-run을 잡으며, scripts/holder-lock.mjs의 lock 줄은 progress 줄로 출력된다. Red: 이전 code에서 새 full-run case는 다른 holder가 lock을 잡은 동안 대상을 실행했고, launcher case는 127.0.0.1:1 bind에 실패했으며, static case는 Makefile, local-servers.mjs, build-targets.mjs의 lock 없는 다시 설치를 나열했고, freePort는 port를 확인하고 닫은 뒤 나중 process에 넘겼다. Green: guard와 push case 20개, PHP extension과 build target case 6개, local server case 4개, 넘겨받은 socket의 launcher case 2개, server layout, response, startup case, shared resource와 lock case가 통과한다. record-stores.test.mjs는 130개 case로 통과했고 nginx는 using inherited sockets from "3;"를 기록했으며 모든 server는 알린 port로 접근되었다. pipeline.browser.mjs는 47개 case로 통과했다 |
— | node scripts/run-tests.mjs node -- tests/build/shared-resources.test.mjs tests/build/full-run.test.mjs examples/form-comparison/src/local-servers.test.mjs |
[o] |
| C7.12 | tests/ordered-json 처리기 검사의 Python release를 기록하고, 다른 도구처럼 실행 중인 Python이 그것이기를 요구하며, CI에서 make test-ordered-json을 실행한다. 원인: C7.9의 owner 선언은 어떤 project 명령도 실행하지 않는 tests/ordered-json/test_check.py를 찾았다. C7.9는 그것에 대상 make test-ordered-json을 주었고, 이 대상은 기록한 release 없이 machine의 Python(여기서는 3.9.6)을 실행한다. ssr과 ordered-json처럼 Python은 minor version으로 고정된다. config/toolchain.json은 "python": "3.9"를 기록하고, node scripts/check-toolchain.mjs python은 python3 --version의 major와 minor를 비교하고 patch를 출력하며, build-lint job은 commit SHA로 지정한 actions/setup-python과 python-version: '3.9'로 그것을 설치하고 검사한 뒤 make test-ordered-json을 실행하고, make ci는 그것을 npm run typecheck 뒤에 실행한다. Red: 이전 tree에서 runtime case는 config/toolchain.json에 python이 없어 실패했고, 어떤 workflow step도 Python을 설치하거나 ordered-json test를 실행하지 않았다. Green: runtime, CI, command, full-run file의 50개 case가 통과한다. machine의 Python 3.9.6은 minor 3.9로 받아들여지고, 다른 patch도 받아들여지며, 3.1.4는 기록과 함께 거부되고, workflow step은 기록과 비교된다. make test-ordered-json은 test 5개를 실행했다 |
— | node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs |
[o] |
| C7.13 | tests/build/checkout-npm.test.mjs와 tests/build/install-browsers.test.mjs의 stub program이 30초 동안 끝나지 않은 정지의 원인을 찾아 없앤다. 증거: 2026-10-06 C7.2-4의 make owner-check 한 번에서 두 install-npm case는 npm stub이 실행되는 동안 각각 30000 ms 뒤 timeout되었고, 같은 때 실행된 crudui-install-browsers의 helper case는 첫 timeout 직후 30.1초에 끝났다. 그 뒤 같은 file을 네 번 실행하면 3초에서 7초에 통과했다. 가설: macOS는 새로 쓴 실행 file을 첫 실행 때 검사하고 그것이 오래 걸릴 수 있으며, template은 새 build 출력에서 같은 것을 찾았다(T19.6-1). stub은 임시 directory의 새 shell script다. 결과: stall은 재발하지 않았다. C7.13-1이 기록한 첫 실행은 C7.13-1부터 C7.18까지 Linux의 CI 실행 12번에서 5~81 ms, 2026-10-06 macOS의 local 실행 3번에서 338~979 ms였고, case 12개가 모두 2.0~2.5 s에 통과했다. macOS는 새 shell script의 첫 실행에 1초 가까이 쓰므로 가설을 뒷받침하지만, 기록된 어떤 첫 실행도 stall의 30 s에 가깝지 않으므로 제거할 30 s stall의 원인은 관측되지 않았다. C7.13-1의 기록은 남으므로 재발은 그 경과 시간을 밝히고 새 작업을 얻는다. Green: 두 file의 case 12개가 위의 기록된 시간으로 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/checkout-npm.test.mjs tests/build/install-browsers.test.mjs |
[o] |
| C7.13-1 | tests/build/checkout-npm.test.mjs와 tests/build/install-browsers.test.mjs의 각 stub 첫 실행의 경과 시간을 standard error에 [stub] the first execution of <path> ended in <ms> ms로 기록하여, C7.13의 정지가 스스로 설명되게 한다. Green: 12개 case가 통과하고 두 npm stub에 1209 ms와 842 ms, sudo stand-in에 905 ms를 출력한다. 새 shell script의 첫 실행 비용으로 C7.13의 가설을 뒷받침한다 |
— | node scripts/run-tests.mjs node -- tests/build/checkout-npm.test.mjs tests/build/install-browsers.test.mjs |
[o] |
| C7.14 | tree를 도는 모든 검사는 Git이 무시하지 않는 추적된 file이나 새 file(scripts/tracked-files.mjs)을 읽고, ESLint는 Git이 무시하는 모든 경로를 무시한다. 원인: C5.8-8이 무시되는 var/에 둔 dist 복사본을 npm run lint가 검사해 실패했다. ESLint 설정은 손으로 관리하는 output directory 목록을 무시했기 때문이다. contract manifest 검사와 runtime, 공개 package, Vite, PHP extension test는 건너뛸 directory 이름 목록을 각자 두고 tree를 돌았으므로, 무시되는 directory 아래의 남은 file이 그 결과를 바꿨다 scripts/tracked-files.mjs는 git ls-files --cached --others --exclude-standard로 checkout의 file을, --ignored --directory로 무시되는 경로를 나열한다. eslint.config.mjs는 손으로 관리하던 목록 대신 무시되는 경로를 정확히 무시하고, contract manifest 검사, runtime test의 container와 Composer manifest 찾기, package 명령, Vite, PHP extension test, native report의 source digest는 추적된 file을 읽는다. native report의 dist digest와 문서 build는 설계상 build 출력을 읽는다. Red: 새 lint case는 ESLint가 무시하지 않는 .tools/, var/, 남은 var/lint-coverage-stray/Actions.svelte.d.ts를 찾았고, npm run lint는 var/ 아래의 dist 복사본에서 실패했다(C5.8-8). 이전 tree에는 건너뛸 이름 목록을 가진 walker가 여섯 개 있었다. Green: lint coverage case 3개, tracked-files case 2개, runtime, package, Vite, PHP extension, manifest, lint file의 49개 case가 통과하고, npm run manifest:check가 통과하며, npm run lint는 var/ 아래에 generator-svelte dist 복사본이 있어도 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/lint-coverage.test.mjs tests/build/tracked-files.test.mjs |
[o] |
| C7.15 | 개발은 unit test만 실행하고, 다른 모든 검사는 push 뒤에 CI가 실행한다. AGENTS는 모든 commit 전에 make owner-check를, push 전에 full suite를 한 번 요구했고, docs/operations/testing.md는 commit 전에 make owner-check를, push 전에 Linux style 검사를 실행했으므로, 변경은 CI가 push마다 실행하는 local end-to-end 검사를 기다렸다. AGENTS와 docs/operations/testing.md는 개발이 바뀐 것을 소유한 unit test(그 Red와 Green case)를 실행하고, end-to-end 검사(browser, container, form comparison, native와 cross-check suite, 전체 build), make owner-check, make ci는 push 뒤에 CI에서 실행하며, push나 commit 전에 local 검사를 요구하는 규칙은 없다고 밝힌다. pre-push hook은 작업이 진행 중인 동안 push를 거부하는 검사로 남고, make ci와 make owner-check는 요청할 때 실행할 수 있다. Red: AGENTS는 개발과 idempotency 규칙에서 모든 commit 전에 make owner-check를 요구했고, test 절차는 commit 전에 그것을 실행했다. Green: AGENTS와 test 절차의 어떤 규칙도 commit이나 push 전에 local 검사를 요구하지 않고, 문서 검사가 통과한다 |
— | node scripts/check-documents.mjs, node scripts/run-tests.mjs node -- scripts/checklist-markers.test.mjs scripts/documentation-links.test.mjs |
[o] |
| C7.15-1 | C7.15와 C7.16의 pre-push hook과 문서 job을 tests/docs/repository-writing.test.mjs가 acceptance-check metaphor로 거부하는 단어 없이 서술한다. 그 행은 hook과 문서 job을 그 단어로 불렀고, CI의 문서 job이 C7.16에서 실패했다. 그 행의 Red와 Green은 scripts/check-documents.mjs와 checklist test를 실행했고, checklist도 읽는 npm run test:docs는 실행하지 않았다. 행은 그것을 검사라고 부른다. Red: 그 case는 두 checklist의 130행과 131행을 밝혔다. Green: tests/docs/repository-writing.test.mjs의 case 3개가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/docs/repository-writing.test.mjs |
[o] |
| C7.16 | 검사는 offline으로 실행하고, download는 install 대상에서만 한다. recipe는 cargo, go, npm, Composer를 network와 함께 실행했으므로, 없는 crate, module, package는 그것을 처음 필요로 한 검사가 download했고, comparison pipeline은 고정된 OrderedJSON checkout이 없으면 GitHub에서 fetch했다. Makefile은 CARGO_NET_OFFLINE=true, GOPROXY=off, npm_config_offline=true, COMPOSER_DISABLE_NETWORK=1을 export하고, install, crudui-install-crates, crudui-install-ordered-json, install-cargo-audit, dependency-review만 $(ONLINE)으로 이를 푼다. make crudui-install-crates는 Rust record server의 lock이 읽는 OrderedJSON checkout(examples/form-comparison/install-ordered-json.mjs) 다음에 모든 Cargo.lock의 crate를 download한다. make install이 이를 실행하고, 문서 검사와 native package의 CI job은 make 대상 전에 이를 실행하며, pipeline job은 make crudui-install-ordered-json을 실행한다. cargo를 실행하는 모든 대상은 cargo-downloads-check(scripts/check-cargo-downloads.mjs)에 의존하며, 이 검사는 각 lock, cargo의 첫 오류 줄, run make install, which downloads them과 함께 실패한다. pipeline은 checkout이 없으면 run make crudui-install-ordered-json과 함께 실패하고 아무것도 download하지 않는다. cargo-audit은 make install에서 make dependency-review의 선행 대상인 make install-cargo-audit로 옮긴다. 감사: Go module은 replace로 checkout의 module만 요구한다. make ci와 make rerun-failed는 어떤 단계보다 먼저 guard를 시작하고, make를 거쳐 cargo를 실행하는 그 명령은 검사에 의존한다. Red: tests/build/offline-checks.test.mjs는 offline export를 찾지 못했고 검사 없이 cargo를 실행하는 대상 17개를 밝혔다(bench, bench-go, bench-js, bench-php, bench-rust, ci, conformance, docs-api, docs-check-documents, docs-check-libs, docs-dev, docs-preview, docs-web, fmt를 구분하기 전의 format-check, owner-check, test-native-suites, crudui-test-validators). 빈 CARGO_HOME으로 node scripts/check-cargo-downloads.mjs는 lock 6개와 run make install을 밝혔다. Green: tests/build/offline-checks.test.mjs의 case 3개가 통과하고, CI, make, owner, full-run, form-comparison unit test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/offline-checks.test.mjs tests/build/ci-local.test.mjs tests/build/full-run.test.mjs tests/build/make-dry-run.test.mjs |
[o] |
| C7.16-1 | npm run test:packages의 설치 프로젝트를 루트 lock의 release로 offline 설치한다. C7.16의 offline 설정으로 설치 프로젝트의 npm install은 C7.18의 CI에서 ERESOLVE와 react@undefined로 실패했다. 이것은 패키징한 package의 range와 framework version을 registry에 resolve했고, 그래서 설치되는 release가 날마다 바뀔 수 있었다. scripts/install-lock.mjs는 루트 package-lock.json에서 설치 lock을 만든다. 패키징한 package는 integrity와 함께 file: tarball로, 각 registry 의존성은 npm이 resolve하는 루트 lock 항목으로 담는다. 직접 의존성은 루트에서, 패키징한 package의 의존성은 그 workspace directory에서 resolve하며, 그 중첩 항목은 package 아래로 옮긴다. scripts/check-packages.mjs는 이 lock을 쓰고 make install이 채운 npm cache에서 npm ci --offline으로 설치한다. Red: CI job은 npm install에 대해 npm error code ERESOLVE와 Found: react@undefined를 밝혔고, lock 생성기는 없었다. Green: tests/build/install-lock.test.mjs의 case 3개가 통과하고, 생성기는 루트 lock에서 빠진 의존성 없이 항목 128개를 만들며, CI가 push된 commit에서 make crudui-test-packages를 실행한다 |
— | node scripts/run-tests.mjs node -- tests/build/install-lock.test.mjs |
[o] |
| C7.17 | 모든 CI step을 make 대상으로 실행한다. .github/workflows/ci.yml, dependency-review.yml, push-gate.yml의 step은 node, npm, composer, rustup, sh를 직접 시작했으므로(GITHUB_PATH와 함께 node scripts/install-npm.mjs, npm ci, npm run test:runtimes, npm run build, npm run lint, npm run typecheck, node scripts/check-toolchain.mjs, rustup toolchain install 등), Makefile의 offline 설정, $(NPM), cargo-downloads-check(C7.16)가 그것에 적용되지 않았다. Makefile은 설치 대상(install-npm, install-node-modules, crudui-install-composer, install-rust, crudui-install-phpdocumentor, BROWSERS를 받는 crudui-install-browsers), TOOLS를 받는 toolchain-check, check-ci-browser, push-gate-check, 그리고 검사 명령마다 대상 하나(crudui-test-runtimes, crudui-test-dependencies, build, lint, typecheck, crudui-test-validator-js, crudui-test-validator-php, crudui-test-validator-go, crudui-test-validator-rust, crudui-test-cross-check, manifest-test, require-build, crudui-test-cli, manifest-check, manifest-docs-check, crudui-test-forms, crudui-test-form-comparison, crudui-test-form-comparison-pipeline, crudui-test-packages, crudui-test-build, crudui-test-build-repeat, crudui-test-inspector, crudui-test-bench, check-conformance)를 선언한다. make install은 설치 대상을 실행하고, CI_COMMANDS는 대상을 지정하며, cargo를 실행하는 job은 먼저 make crudui-install-crates를 실행한다. tests/build/ci-local.test.mjs의 case는 make 없이 node, npm, npx, cargo, go, php, composer, rustup, python3, sh를 시작하는 step에서, sudo, env, xvfb-run 뒤와 여러 줄의 step에서도 실패하고, workflow test의 검사는 make 형식을 읽는다. Red: 이전 workflow에서 그 case는 step 83개의 명령 90개를 밝혔다. Green: 그 case가 세 workflow에서 통과하고, CI, runtime, offline, command, full-run, form-comparison, npm unit test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/checkout-npm.test.mjs |
[o] |
| C7.18 | CI의 모든 실패의 이유를 남긴다. 어떤 job도 검사의 출력을 job log 밖에 남기지 않았고, conformance 증거와 native report만 올렸다. make ci-targets TARGETS="..."(scripts/ci-targets.mjs, scripts/target-report.mjs)는 각 대상을 make -k <target>로 끝까지 실행하고, 그 출력을 var/report/ci-targets/targets/<target>.log에, 각 대상과 결과, 시간, 실패한 대상마다의 첫 실패 줄을 summary.md와 GitHub Actions의 job summary에 쓴다. ci.yml, dependency-review.yml, push-gate.yml의 모든 job은 검사를 그런 step 하나로 실행하고 보고서를 report-<job>으로 if: ${{ !cancelled() }}와 if-no-files-found: error로 올린다. cross-check과 native job의 PHP extension 설정은 job 환경으로 옮긴다. tests/build/ci-local.test.mjs의 case는 make ci-targets 밖에서 검사를 실행하거나 보고서를 올리지 않는 job에서 실패하고, tests/build/target-report.test.mjs는 실패하는 probe 대상과 통과하는 probe 대상을 실행해 두 log, summary의 실패 줄, job summary를 확인한다. Red: 이전 workflow에서 그 case는 job 16개의 발견 30개를 밝혔다. Green: 그 case가 세 workflow에서 통과하고, 보고서 case 2개가 통과하며, CI, command, runtime, owner unit test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/target-report.test.mjs tests/build/ci-local.test.mjs |
[o] |
| C7.19 | runner가 적으므로 ref의 새 push는 이전 push의 CI 실행을 멈춘다. .github/workflows/ci.yml은 group ${{ github.workflow }}-${{ github.ref }}와 cancel-in-progress: true로 concurrency를 선언하고, .github/workflows/push-gate.yml의 push 검사는 이를 선언하지 않으므로 push된 모든 commit을 검사한다. workflow는 group에 ci- 접두어를 붙였고, 이 설정을 지키는 test가 없었다. Red: tests/build/ci-local.test.mjs의 새 case가 group ci-${{ github.workflow }}-${{ github.ref }}로 실패했다. Green: case 7개가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs |
[o] |
| C7.20 | 비교 배포와, 개발 기기의 container 도구인 containerctl에 대한 모든 의존을 제거한다. make deploy, make deploy-verify, make deploy-watch, examples/form-comparison/comparison-deployment.mjs의 Compose 정의, supervisor, toolchain image examples/form-comparison/Containerfile, source watcher, tree verification(verification.mjs, verify-tree.mjs와 그것만 실행한 검사 check.mjs, check-interaction.mjs, check-generation.mjs, check-servers.mjs, check-pipeline.mjs, check-browser-reports.mjs, test-php-modes.mjs), container runtime 해석, build target, scripts/install-ordered-json-js.mjs, 그 test와 docs/operations/verification.md가 대상이다. 비교 test는 기준을 유지한다. make crudui-test-form-comparison과 make crudui-test-form-comparison-pipeline은 모든 레코드 서버를 127.0.0.1의 빈 port에서 local process로 시작하고, 서버를 비교한 뒤 멈춘다. src/server-layout.mjs는 공개 서버의 요청 routing을, src/source-tree.mjs는 source 식별자를, docs/operations/form-comparison.md와 docs/operations/examples.md는 레코드 리소스, 정본 페이지, 정본 흐름 검사, 검증기 응답 계약을, docs/operations/testing.md는 폼 검사 설명을 유지한다. tests/docs/repository-writing.test.mjs의 case는 checklist와 change log 밖의 tracked file이 개발 기기의 container 도구를 지명하면 실패한다. Cause: 배포는 containerctl이 있는 기기에서만 실행되었고, 어떤 CI job도 이를 실행하지 않았다. Red: 새 case가 파일 10개의 57줄을 지목했다. Green: case가 통과하고, form-comparison source suite가 test 114개를 통과한다 |
— | node scripts/run-tests.mjs node -- tests/docs/repository-writing.test.mjs examples/form-comparison/src/local-servers.test.mjs examples/form-comparison/src/server-layout.test.mjs examples/form-comparison/src/source-tree.test.mjs examples/form-comparison/src/browser-job.test.mjs |
[o] |
| C7.20-1 | C7.20이 배포와 함께 제거한 검사를 CI에서 로컬 구성에 대해 실행한다. examples/form-comparison/local-verification.mjs는 레코드 서버 프로그램과 공개 디렉터리를 빌드하고, 네이티브 레코드 서버 네 개와 공개 서버를 시스템이 정한 127.0.0.1 port의 local process로 시작하며(pipeline.browser.mjs도 쓰는 src/local-servers.mjs의 startLocalStack), test-php-modes.mjs, check-generation.mjs, 고정 port와 고정 디렉터리 대신 --origin, --data, --report를 받는 check-servers.mjs, check-pipeline.mjs, check-interaction.mjs를 포함한 check.mjs 브라우저 검사 네 개, check-browser-reports.mjs, 실행기가 없던 check-typing.mjs를 실행하고, 입력 보고서를 포함한 모든 보고서의 근거를 확인한 뒤(verification-evidence.mjs) 서버를 멈춘다. make crudui-test-form-comparison-checks(npm run test:form-comparison:checks)가 이를 실행하고, CI에서는 ci-passed가 필요로 하는 job form-comparison-checks가 실행한다. CI_COMMANDS와 scripts/owner-checks.json이 이를 지명한다. 검사의 unit test(browser-report.test.mjs, check-generation.test.mjs, check-interaction.test.mjs, check-servers.test.mjs, 로컬 실행 단계와 입력 근거를 담은 check-verification.test.mjs)가 돌아오고, image, domain route, supervisor, 배포 lock은 제거된 상태로 남는다. 기능 행 server-template-browser, native-generation-integration, form-typing, form-focus는 이 검사를 근거로 지명한다. src/frame.mjs의 시나리오 검사 serverInvalid는 명세가 숨긴 스토어 이름만 hidden에 기대했지만, C8부터 검증기는 enabled가 꺼진 스토어의 detail도 보고하므로 기대값은 같은 레코드의 검증에서 그 경로를 가져온다. Cause: C7.20은 CI가 실행하지 않던 검사를 CI에서 실행하는 대신 제거했다. Red: 첫 로컬 실행에서 serverInvalid가 시나리오 보고서 64개 모두(서버마다 16개)에서 a hidden required field skips its rules로 실패했다. Green: 다음 실행이 모든 단계를 통과했다. PHP 처리 모드, 요청 899개의 생성 결과 450개, 저장 결과 120개, 정본 흐름 조합 40개, 서버 네 개의 브라우저 검사 7,008개, 입력 결과 24개이며 근거 확인도 통과했다. check-verification.test.mjs의 case 7개가 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs examples/form-comparison/browser-report.test.mjs examples/form-comparison/check-generation.test.mjs examples/form-comparison/check-interaction.test.mjs examples/form-comparison/check-servers.test.mjs, make crudui-test-form-comparison-checks |
[o] |
| C7.20-2 | Linux 스타일시트 배치 검사를 CI Linux runner에서만 실행하고 개발 기기의 container 도구를 호출하지 않는다. 그 검사를 Playwright image에서, 있으면 macOS의 container CLI로, 없으면 다른 container CLI로 실행하던 scripts/test-form-styles-linux.sh와 그 target make test-form-styles-linux, make remove-form-styles-image를 tests/build/shared-resources.test.mjs, tests/build/runtime-version-policy.test.mjs의 case, 함께 쓰는 resource의 image 행과 함께 제거한다. CI job form-runtime은 tests/form-styles.test.mjs를 포함한 make crudui-test-forms를 ubuntu-24.04에서 Puppeteer의 Chromium·Firefox와 Playwright의 WebKit으로 실행한다. tests/docs/repository-writing.test.mjs의 case는 tracked tool, Makefile 줄, workflow가 macOS의 container CLI, other container CLIs을 호출하면 실패한다. Cause: script가 자신을 실행한 기기의 container 도구를 골랐다. Red: 이전 tree에서 새 case가 Makefile, scripts/test-form-styles-linux.sh, tests/build/shared-resources.test.mjs의 7줄을 지목했다. Green: tests/docs/repository-writing.test.mjs의 case 5개가 통과하고, shared-resource, runtime-version, holder-lock test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/docs/repository-writing.test.mjs tests/build/shared-resources.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/holder-lock.test.mjs |
[o] |
| C7.20-3 | Linux는 CI runner에서 실행되므로 컨테이너 정의를 두지 않고 개발 기기의 container 도구를 지시하지 않는다. macOS의 container CLI로 make crudui-test-native를 실행하던 tests/containers/native.Containerfile, .dockerignore, docs/operations/native-generators.md의 Apple container 절, docs/operations/package-build.md의 container build 규칙을 제거한다. CI job php-engine, native-generators, php-api가 native suite를 ubuntu-24.04에서 실행한다. 정의와 .dockerignore를 읽던 packages/php-ext/tests/engine.test.mjs의 case와 tests/build/runtime-version-policy.test.mjs의 container case는 tracked container 정의가 없기를 요구하는 case로 바뀌고, PHP 범위 case는 가장 새로 검사한 minor의 검사를 유지하며, scripts/owner-checks.json은 제거한 파일의 행을 뺀다. tests/docs/repository-writing.test.mjs의 case는 checklist와 change log가 아닌 문서의 code block이나 inline code가 macOS의 container CLI, other container CLIs을 실행하면 실패한다. Cause: native suite에 개발 기기의 container 도구를 위한 정의와 절차가 있었다. Red: 새 문서 case가 docs/operations/native-generators.md와 docs/operations/native-generators.ko.md의 4줄을, 새 runtime case가 tests/containers/native.Containerfile을 지목했다. Green: 두 case가 통과하고, runtime-version, dependency-health, checkout-npm, owner, lint-coverage test와 engine test의 builder case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/docs/repository-writing.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/dependency-health.test.mjs tests/build/checkout-npm.test.mjs tests/build/owner-check.test.mjs tests/build/lint-coverage.test.mjs |
[o] |
Wave 8 — 표시 전환
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C8.1 | 브라우저 바인딩이 표시를 갱신한다. 변경마다 @crudui/validator의 새 export hiddenPaths(spec, data)로 design.show에 따른 각 노드의 hidden 속성을 쓰고, 숨게 된 노드와 그 안의 노드의 오류를 지우며, 값은 바꾸지 않는다 |
— | make crudui-test-form-binding, make crudui-test-validator-js |
[o] |
| C8.1-1 | public build test에서 @crudui/validator의 public export hiddenPaths를 검사한다. C8.1이 이를 public entry와 contracts/features.json에 추가했지만 tests/build/public-packages.test.mjs는 C8.1 이전의 export 목록을 유지했으므로 C8.5에서 make crudui-test-build가 실패했다. test는 이제 계약의 export를 기대하고 build된 ESM과 CommonJS entry로 hiddenPaths를 호출한다 |
— | make crudui-test-build |
[o] |
| C8.2 | 반복 그룹의 필드에서 위로 올라가는 상대 경로를 행을 한 단계로 하여 결정한다. 행 안의 ..x는 행 키가 무엇이든 collection 옆의 필드를 읽으며, 검증기 다섯 개와 렌더러가 같다. expression 계약의 경로 결정을 고치고 공유 사례를 추가한다 |
— | make crudui-test-validators, make test-php-extension, npm test -w @crudui/generator-core |
[o] |
| C8.3 | 검증기 다섯 개가 검증 결과와 함께 숨은 필드의 데이터 경로를 돌려주어, 서버가 숨은 가지의 값을 저장하지 않을 수 있게 한다 | — | make crudui-test-validators, make test-php-extension |
[o] |
| C8.3-1 | @crudui/validator의 interface ValidationResult를 문서화한다. C8.3이 ValidationResult의 주석과 interface 사이에 ListValidationResult를 넣어 그 주석이 ListValidationResult를 두 번 설명하고 ValidationResult에는 주석이 없었으므로, C8.5에서 make docs-check의 문서 coverage 검사가 ValidationResult (Interface) ... does not have any documentation으로 실패했다 |
— | make docs-check |
[o] |
| C8.3-2 | public build test에서 검증 결과의 public type을 compile한다. C8.3이 validateList와 validateDetail에 type ListValidationResult를, validate에 member hidden을 주었지만 tests/build/public-types.mts와 .cts는 목록과 상세 결과를 ValidationResult에 대입했으므로 C8.5에서 make crudui-test-build가 TS2741로 실패했다. 두 파일은 이제 ListValidationResult를 쓰고 ValidationResult의 hidden을 읽는다 |
— | make crudui-test-build |
[o] |
| C8.3-3 | form comparison의 레코드 저장에 모든 record server가 hidden을 포함한 검증기 결과로 응답한다. C8.3 이후 PHP server는 hidden을 보냈고, JavaScript server는 성공 시 { valid: true, errors: [] }를 보냈으며, Go와 Rust server는 { valid, errors }를 다시 만들어 hidden을 버렸으므로 C8.5에서 make crudui-test-form-comparison과 make crudui-test-form-comparison-pipeline이 실패했다. form comparison의 저장 계약을 고치고, record contract, Rust server test, interaction check, PHP generation check가 hidden을 기대한다 |
— | make crudui-test-form-comparison, make crudui-test-form-comparison-pipeline |
[o] |
| C8.3-4 | cross-check console 검증기 process의 응답 { valid, errors }를 PHP process와 input text 사례에서 지킨다. C8.3 이후 Go process는 validators/README.md가 정한 대로 hidden을 뺐지만 PHP process는 hidden을 포함한 Validator::validate 결과 전체를 썼고, input text 사례는 이제 hidden을 담은 공유 fixture의 expect를 기대했으므로 C8.5에서 make crudui-test-cross-check가 invalid php validation response로 실패했다 |
— | make crudui-test-cross-check |
[o] |
| C8.3-5 | 문서가 폼 검증 결과를 적는 모든 곳에서 hidden을 설명한다. C8.3 이후에도 TypeScript, Go, Rust 검증기의 README, PHP 확장 명세, fixture 명세, 공유 검증 사례의 README가 { valid, errors }를 적고 있었다 |
— | make docs-check |
[o] |
| C8.3-6 | cross-check console의 폼 검증 사례를 tests/fixtures/validate/cases.json 형태로 내보낸다. 검증기 process가 폼에 { valid, errors }로 응답해 폼 탭의 fixture 내보내기는 hidden 없는 expected를 썼지만, C8.3 이후 cases.json의 모든 사례는 expected를 { valid, errors, hidden }으로 적는다. JavaScript, PHP, Go, Rust process는 각 validator의 validate가 돌려주는 대로 폼에 { valid, errors, hidden }, 목록과 상세에 { valid, errors }로 응답하고, gateway는 폼 결과의 hidden을 요구해 결과에 담고 언어 사이에서 순서대로 비교하며, client/fixture-export.js는 expected를 validator의 순서대로 { valid, errors, hidden }, expectFailure를 { code, message, at }으로 쓴다. Red: server/fixture-export.test.mjs의 결과 사례 세 개가 내보낸 expected에 hidden이 없어 실패했다. Green: 그 5개 사례가 통과하고 make crudui-test-cross-check가 1,267개 test로 통과한다 |
— | make crudui-test-cross-check |
[o] |
| C8.4 | Rust 검증기가 다른 런타임처럼 condition expression일 때만 design.show 문자열을 expression으로 결정한다. 공유 사례 show: "enabled"를 추가한다 |
— | make crudui-test-validator-rust |
[o] |
| C8.4-1 | native generator suite의 폼 fixture 수를 188개로 기록한다. C8.2가 design-show-row-parent-on과 design-show-row-parent-off를, C8.4가 design-show-literal-identifier-visible을 tests/fixtures/form-render/cases.json에 추가했지만 tests/native-generators/run.mjs는 185를 유지했으므로 C8.5에서 make crudui-test-native가 어떤 target도 실행하기 전에 멈추었고 conformance evidence에 native 결과가 없었다. 세 사례는 모든 native target에서 실행된다 |
— | make crudui-test-native |
[o] |
| C8.5 | 부모 값이 켜고 끄는 필수 필드의 공유 검증 사례를 추가한다. 그룹 subtree, 두 단계 중첩 그룹, 반복 행 안의 전환, 행의 부모를 읽는 전환 | — | make crudui-test-validators, make test-php-extension |
[o] |
Wave 9 — polyspec 저장소의 패키지 이름
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C9.1 | template과 hyper처럼 패키지 이름을 짓는다. npm 패키지는 @polyspec/crudui-cli, @polyspec/crudui-form-binding, @polyspec/crudui-generator-core, @polyspec/crudui-generator-html, @polyspec/crudui-generator-react, @polyspec/crudui-generator-svelte, @polyspec/crudui-generator-vue, @polyspec/crudui-validator이고, workspace root는 @polyspec/crudui-workspace이며, Composer 패키지는 polyspec/crudui-generator와 polyspec/crudui-validator이고 PHP 패키지와 PHP 확장이 등록하는 클래스의 PHP namespace는 Polyspec\Crudui\(클래스 Generator, Validator, Form, FormError, 내부 namespace Generator\와 Validator\)이며, crate는 polyspec-crudui-generator와 polyspec-crudui-validator, library는 polyspec_crudui_generator와 polyspec_crudui_validator이다. Go module은 이미 github.com/polyspec/crudui/...였다. 코드, test, fixture, example, 문서가 이 이름으로 패키지를 가리키고, npm, Composer, Cargo의 lock은 각 도구가 다시 쓰며, crudui_arginfo.h는 stub에서 다시 생성한다. 변경 기록, 체크리스트, wave 설명은 기록한 이름을 유지한다. Red: 새 tests/build/package-names.test.mjs의 사례 2개가 실패했다. 패키지가 이전 이름이었고, 유지되는 파일의 2199곳이 이전 형태로 패키지를 가리켰다. Green: 사례 2개가 통과한다. validator와 generator workspace, CLI, form binding의 unit test(3731개), PHP validator(781개)와 generator(314개), PHP 확장(50개, address sanitizer 사례는 Linux에서만 실행), Rust validator(126개)와 generator(37개), crate를 쓰는 Rust program 넷의 cargo check, contract manifest와 그 문서, 문서 test, 패키지 이름을 쓰는 build와 example test와 owner, command test(143개)가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/package-names.test.mjs |
[o] |
| C9.1-1 | API 문서에 namespace Polyspec\Crudui\의 PHP API page를 요구한다. C9.1 뒤에 phpDocumentor는 classes/Polyspec-Crudui-Generator.html과 다른 class page를 쓰지만 scripts/gen-api-docs.mjs는 classes/CRUDUI-Generator.html을 요구했으므로 make docs-check-documents가 Documentation output is missing으로 실패했다. package 이름 검사는 이전 namespace의 page 형태 CRUDUI-도 거부한다. Red: tests/build/package-names.test.mjs가 scripts/gen-api-docs.mjs:119에서 실패했다. Green: 그 2개 case가 통과하고 make docs-check-documents가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/package-names.test.mjs, make docs-check-documents |
[o] |
| C9.2 | examples/, tests/, tools/의 프로그램도 같은 규칙으로 이름을 짓는다. npm package는 @polyspec/crudui-cross-check-console, crate는 polyspec-crudui-cross-check-validator, polyspec-crudui-form-comparison, polyspec-crudui-native-generator, polyspec-crudui-bench, Go module은 github.com/polyspec/crudui/tools/bench/go이다. tests/build/package-names.test.mjs는 추적하는 모든 package.json, composer.json, Cargo.toml, go.mod에 이 규칙을 요구한다. Red: 이 case는 manifest 이름 일곱 개로 실패했다 |
— | node scripts/run-tests.mjs node -- tests/build/package-names.test.mjs |
[o] |
Wave 10 — polyspec/ordered-json tag의 OrderedJSON
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C10.1 | OrderedJSON을 polyspec/ordered-json의 branch main에서 받는다. installOrderedJson은 main의 head를 fetch하고 그 안의 package manifest 다섯 개를 확인하며, make crudui-install-ordered-json은 실행할 때마다 이를 설치하고 make install-node-modules는 이를 먼저 실행한다. root package.json은 @polyspec/ordered-json을 file:.form-comparison/sources/ordered-json/js로 적고, build tree는 js/ package를 node_modules/@polyspec/ordered-json에 복사하며, tests/ordered-json/check.py는 깨끗한 저장소 checkout을 요구하고 그 commit을 보고한다. record server는 그 branch의 package 이름을 쓴다. PHP namespace Polyspec\OrderedJson(ordered_json 확장이 로드되었을 때에만 그것으로 parse한다), Go module github.com/polyspec/ordered-json/go, crate polyspec-ordered-json이다. Red: 새 installer test는 orderedJsonBranch를 export하지 않는 이전 module에서 실패했다. Green: installer, build target, server layout, verification, offline check test(38개)가 통과하고, Rust record server의 OrderedJSON 사례가 통과하며, Go server의 go vet이 통과하고 PHP adapter가 parse된다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs examples/form-comparison/src/build-targets.test.mjs tests/build/offline-checks.test.mjs |
[o] |
| C10.2 | OrderedJSON을 polyspec/ordered-json의 tag v0.0.1에서 받는다. examples/form-comparison/src/ordered-json-source.mjs의 orderedJsonTag는 orderedJsonVersion에서 tag를 선언하고, installOrderedJson은 refs/tags/v0.0.1을 depth 1로 fetch하여 tag의 commit을 체크아웃하고 checkout의 HEAD가 그 commit이기를 요구하며, 그 commit에 있고 tracked change가 없는 checkout은 유지하고 다른 checkout은 교체한다. orderedJsonAtTag는 HEAD가 tag ref의 commit이고 tracked change가 없는 checkout만 받아들이므로 local pipeline은 checkout을 거부할 때 tag를 밝힌다. operations, comparison, package build 문서는 tag를 적는다. version 0.1까지 polyspec 저장소는 다른 polyspec 저장소에 그 저장소의 GitHub tag로 의존한다. Red: 새 installer test는 orderedJsonAtTag를 export하지 않는 C10.1의 module에서 실패했다. Green: 그 7개 사례가 통과하며, 그중 tag와 다른 commit의 checkout은 교체되고 다른 commit의 Git checkout은 받아들여지지 않는다. make crudui-install-ordered-json은 다른 commit의 checkout을 tag의 commit으로 교체하고 다음 실행에서 이를 유지한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs, make crudui-install-ordered-json |
[o] |
| C10.3 | OrderedJSON tag v0.0.1에 맞춰 build, encode, review한다. scripts/build-ordered-json-php-extension.mjs는 checkout의 config.m4에서 PHP_NEW_EXTENSION([ordered_json], ...)이 선언한 C source(v0.0.1에서는 ordered_json.c와 hydrate.c)를 compile하고, load 검사 php -n -d extension=... --ri ordered_json이 통과한다. scripts/tool-resolution.mjs의 runCommand는 실패한 명령의 standard error가 비어 있으면 PHP가 startup 실패를 쓰는 standard output을 보고한다. OrderedJSON이 구성한 문자열은 출력 가능한 ASCII 밖의 모든 code unit을 \u escape로 쓰므로 examples/form-comparison/src/json.test.mjs는 UTF-8 source와 escape source 모두에서 서울에 대해 \uc11c\uc6b8을 기대한다. 의존성 검사는 @polyspec/ordered-json을 GitHub tag에서 가져온 polyspec package로 읽는다. 루트 package.json은 그것을 tag checkout에 연결하고, checkout은 tag의 버전을 가지며 package-lock.json은 그 버전을 기록한다. 어떤 npm 매니페스트도 URL 또는 Git 의존성을 선언하지 않고, 잠금 파일의 모든 registry package는 SHA-512 무결성을 기록하며, .npmrc는 allow-remote=none과 allow-git=none을 선언한다. make dependency-review UPDATE=1 RECORD=1은 js-yaml을 5.4.3, vite를 8.3.3, svelte를 5.57.2로 올리고 모든 lock의 review를 기록한다. Red: module load 검사가 1로 종료했고, encode한 text는 사례가 서울을 기대한 곳에 \uc11c\uc6b8을 담았으며, URL 의존성 사례는 root package must declare a URL dependency로 실패했고, review 검사는 @polyspec/ordered-json의 file: 연결을 버전 0.0.1과 비교해 보고하고 review 뒤에 바뀐 lock 9개를 보고했다. Green: module이 PHP 8.5로 build되고 load되며, examples/form-comparison/record-stores.test.mjs의 130개 사례가 php, php-ext, go, rust store에서 통과하고, form comparison source test의 215개 사례, 의존성 test의 22개 사례, PHP extension builder test의 9개 사례가 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/json.test.mjs examples/form-comparison/record-stores.test.mjs tests/native-generators/php-extension-builder.test.mjs, node scripts/run-tests.mjs node --timeout 60 -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs |
[o] |
Wave 11 — main tag의 release
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C11.1 | main commit의 tag로 release한다. 변경 기록은 다음 release의 변경을 ## Unreleased 아래에 두고(C11.1-1), ruleset main은 check push-gate와 다른 모든 job을 needs로 둔 .github/workflows/ci.yml의 마지막 job ci-passed를 요구하며(C11.1-2), .github/workflows/release.yml은 push된 tag vX.Y.Z 또는 <디렉터리>/vX.Y.Z를 확인하고 package archive와 함께 GitHub Release를 만들며(C11.1-3), 어느 깊이의 Go module tag, 네 step release-verify, release-versions, release-assets, release-publish, npm tarball과 Composer zip만을 쓴다(C11.1-4) |
— | node scripts/run-tests.mjs node -- tests/docs/changelog.test.mjs tests/build/ci-passed.test.mjs tests/build/ci-local.test.mjs tests/build/github-repository.test.mjs tests/build/release.test.mjs |
[o] |
| C11.1-1 | 다음 release의 변경을 ## Unreleased 아래에 둔다. CHANGELOG.md와 CHANGELOG.ko.md는 section ## Unreleased로 시작하고, 그 section은 386개 항목을 각각 heading ### <날짜> — <제목> (<작업 ID>)과 본문으로 담으며, release는 그 아래에 section ## X.Y.Z를 더한다. tests/docs/changelog.test.mjs는 제목, 첫 section ## Unreleased, 나머지 section이 모두 ## X.Y.Z이고 위 section보다 오래된 것, 모든 항목 heading이 section 안의 ### <날짜> — <제목>이고 다른 heading이 없는 것, 두 파일의 section과 항목이 같은 것, 항목이 최신순인 것을 요구한다. AGENTS는 항목의 위치를 적는다. Red: 새 case가 CHANGELOG.md의 heading ## <날짜> — <제목> 386개를 지목했다. Green: 4개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/docs/changelog.test.mjs |
[o] |
| C11.1-2 | CI workflow의 완료 check 하나를 요구한다. .github/workflows/ci.yml의 마지막 job ci-passed는 다른 14개 job을 needs로 두고 ubuntu-24.04에서 if: ${{ always() }}로 실행되며 make ci-passed RESULTS='${{ toJSON(needs) }}'를 실행한다. scripts/ci-passed.mjs는 needs의 모든 job 결과가 success일 때만 0으로 끝나므로 실패, 취소, 건너뜀 job이 이 check를 실패시키고, RESULTS가 없거나 읽을 수 없거나 job을 하나도 담지 않아도 실패한다. .github/repository.json의 ruleset main은 정확히 check push-gate와 ci-passed를 요구하고, make ci의 검사 명령은 완료 job을 뺀다. Red: make ci-passed target이 없었고, tests/build/ci-local.test.mjs의 새 case는 마지막 job ci-passed를 찾지 못했으며, tests/build/github-repository.test.mjs의 case는 push check와 모든 CI job·matrix 항목의 check 17개를 찾았다. Green: tests/build/ci-passed.test.mjs의 3개 case, tests/build/ci-local.test.mjs의 10개 case, tests/build/github-repository.test.mjs의 7개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-passed.test.mjs tests/build/ci-local.test.mjs tests/build/github-repository.test.mjs |
[o] |
| C11.1-3 | push된 tag에서 GitHub Release를 게시한다. .github/workflows/release.yml은 push된 tag v* 또는 */v*에서 권한 contents: write로 실행되고, make release-check, 이어서 make install-node-modules와 make build, make release-assets, make release-publish(scripts/release.mjs)를 실행한다. 검사는 commit이 origin/main에 있는 것, commit의 check run push-gate와 ci-passed가 conclusion success로 끝난 것, tag가 덮는 모든 package.json, version이 있는 composer.json, Cargo.toml, VERSION, pyproject.toml의 version이 tag와 같은 것, CHANGELOG.md에 section ## X.Y.Z가 있는 것을 요구하고, 빠졌거나 실패한 check와 두 version을 담은 파일을 각각 지목한다. archive는 packages/에서 private이 아닌 npm package 7개의 npm pack, Composer package 2개의 zip git archive, crate 2개의 cargo package --no-verify --exclude-lockfile이고 이름은 <package>-<version>.<확장자>이며, Go module의 tag <디렉터리>/vX.Y.Z는 archive를 쓰지 않는다. gh release create <tag> --verify-tag --title <tag> --notes-file은 section ## X.Y.Z를 쓴다. --exclude-lockfile 없는 cargo package는 dependency polyspec-crudui-validator가 crates.io index에 없는 polyspec-crudui-generator에서 실패한다. AGENTS와 docs/operations/repository.md는 release 절차를 적고, go.mod 6개의 module path는 tests/build/package-names.test.mjs가 요구하는 대로 각 디렉터리의 github.com/polyspec/crudui/<디렉터리>이다. Red: tests/build/release.test.mjs는 scripts/release.mjs에 대해 ERR_MODULE_NOT_FOUND로 실패했고, release.yml 없이 tests/build/ci-local.test.mjs의 3개 case가 실패했다. Green: tests/build/release.test.mjs의 10개 case와 tests/build/ci-local.test.mjs의 11개 case가 통과하고, v0.0.1에 대한 make release-assets가 archive 11개를 썼다 |
— | node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/build/ci-local.test.mjs |
[o] |
| C11.1-4 | release step, Go module tag, archive를 맞춘다. tag filter에서 *는 /와 맞지 않으므로 trigger tags: ['v*', '*/v*']는 Go module tag packages/<디렉터리>/vX.Y.Z를 놓쳤고, job은 GITHUB_REF_NAME의 tag로 make release-check, make build, make release-assets, make release-publish를 실행했으며, make release-assets는 git 의존성을 해석되지 않는 crates.io 요구로 바꾸는 cargo package --exclude-lockfile로 crate 2개를 묶었다. .github/workflows/release.yml은 tags: ['v*', '**/v*']에서 실행되고 TAG: ${{ github.ref_name }}을 두며, 준비 step make install-npm, make toolchain-check TOOLS="node npm", make install-node-modules 뒤에 make release-verify, make release-versions, make release-assets, make release-publish로 끝나고, 그 recipe는 node scripts/release.mjs <step> "$$TAG"를 실행한다. make release-assets는 make build를 실행하고 npm tarball 7개와 Composer zip 2개를 쓴다. crate는 archive로 release하지 않고 git tag로 사용하며, Go module tag는 아무것도 build하거나 첨부하지 않는다. Red: trigger */v*에서 tests/build/ci-local.test.mjs의 trigger case 2개가 실패했고, tests/build/release.test.mjs는 없는 export releaseManifests로 실패했다. Green: tests/build/release.test.mjs의 case 10개와 tests/build/ci-local.test.mjs의 case 12개가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/build/ci-local.test.mjs |
[o] |
| C11.2 | 0.0.1을 release한다. repository package의 모든 package.json, Cargo.toml, lock 항목은 version 0.0.1을 가지며, CHANGELOG.md와 CHANGELOG.ko.md의 ## Unreleased는 396개 항목과 함께 새 빈 ## Unreleased 아래의 ## 0.0.1이 된다. Red: make release-versions의 검사인 scripts/release.mjs의 versionProblems가 v0.0.1, packages/generator-go/v0.0.1, packages/validator-go/v0.0.1에 대해 CHANGELOG.md에 section ## 0.0.1이 없음을 지목했다. Green: 세 tag에 대해 문제를 지목하지 않고, tests/build/release.test.mjs와 tests/docs/changelog.test.mjs의 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/docs/changelog.test.mjs |
[o] |
| C11.3 | release notes를 GitHub 본문 한도 안에 둔다. GitHub는 125000자를 넘는 release 본문을 거부하고, CHANGELOG.md의 section ## 0.0.1은 360629자이다. scripts/release.mjs의 releaseNotes는 code point로 센 길이가 NOTES_LIMIT = 125000자 이하이면 section을, 아니면 tag의 path segment마다 URL encoding한 한 줄 The changes of X.Y.Z are listed in [CHANGELOG.md](https://github.com/polyspec/crudui/blob/<tag>/CHANGELOG.md#<점을 뺀 version>).을 돌려주고, make release-publish가 이를 notes로 쓴다. AGENTS와 docs/operations/repository.md가 규칙을 밝힌다. Red: tests/build/release.test.mjs가 없는 export NOTES_LIMIT로 실패했다. Green: tests/build/release.test.mjs의 12개 case가 통과하며, 정확히 125000자인 section은 그대로, 더 긴 section은 한 줄로 쓴다 |
— | node scripts/run-tests.mjs node -- tests/build/release.test.mjs |
[o] |
| C11.4 | 0.0.2를 release한다. repository package의 모든 package.json, Cargo.toml, lock 항목, polyspec/crudui-validator의 Composer path version과 lock 항목, contracts/features.json의 version과 이를 읽는 검사, packages/php-ext의 module version, 문서가 밝히는 package version은 0.0.2이며, CHANGELOG.md와 CHANGELOG.ko.md의 ## Unreleased는 새 빈 ## Unreleased 아래, ## 0.0.1 위의 ## 0.0.2가 된다. Red: make release-versions의 검사인 scripts/release.mjs의 versionProblems가 v0.0.2에 대해 package 파일 16개의 version 0.0.1과 없는 section ## 0.0.2를, packages/generator-go/v0.0.2와 packages/validator-go/v0.0.2에 대해 없는 section을 지목했다. Green: 세 tag에 대해 문제를 지목하지 않고, tests/build/release.test.mjs, tests/docs/changelog.test.mjs, tests/build/contract-manifest.test.mjs의 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/docs/changelog.test.mjs tests/build/contract-manifest.test.mjs |
[o] |
| C11.4-1 | 0.0.2 lock의 dependency review를 기록한다. config/dependency-review.json은 repository package의 0.0.2 항목을 가진 lock 9개의 review를 담으며, make dependency-review RECORD=1이 이를 썼고 예외 없는 더 새 stable release와 advisory를 찾지 않았다. Red: make crudui-test-dependencies가 tests/build/dependency-health.test.mjs의 case "the dependencies match their recorded review, without a registry query"에서 기록된 lock이 package-lock.json, packages/generator-php/composer.lock, Cargo lock과 달라 실패했다. Green: make crudui-test-dependencies의 25개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs |
[o] |
| C11.4-2 | OrderedJSON을 release된 tag v0.0.2에서 받는다. examples/form-comparison/src/ordered-json-source.mjs의 orderedJsonVersion은 0.0.2이므로 orderedJsonTag는 v0.0.2이고 make crudui-install-ordered-json은 그 tag를 체크아웃한다. Rust record server의 polyspec-ordered-json 의존성, 그 Cargo.lock 항목, 연결된 @polyspec/ordered-json의 package-lock.json 항목, tests/ordered-json/check.py의 VERSION, OrderedJSON operations 문서, package build 명세와 wave 계획은 0.0.2를 적고, config/dependency-review.json은 make dependency-review RECORD=1이 쓴 바뀐 lock의 review를 기록한다. polyspec/ordered-json의 tag v0.0.1에는 GitHub Release가 없다. 두 tag 사이에 OrderedJSON은 release script, 문서, package version을 바꾸었고 package source는 바꾸지 않았다. Red: tag v0.0.2를 요구하는 examples/form-comparison/src/ordered-json-source.test.mjs의 case가 v0.0.1로 실패했다. Green: 그 7개 case, examples/form-comparison/src/json.test.mjs와 tests/native-generators/php-extension-builder.test.mjs의 case, php, php-ext, go, rust store에 대한 examples/form-comparison/record-stores.test.mjs의 130개 case, make crudui-test-dependencies의 25개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs examples/form-comparison/src/json.test.mjs examples/form-comparison/record-stores.test.mjs tests/native-generators/php-extension-builder.test.mjs, make crudui-test-dependencies |
[o] |
| C11.5 | package manifest를 바꾸지 않고 게시하고 release archive를 저장소 밖에서 설치한다. 게시되는 manifest는 packages/의 package.json과 composer.json이며 바꾸지 않고 pack되고, scope @polyspec와 vendor polyspec의 의존성을 모두 정확한 version으로 적는다. 두 PHP manifest는 version을 선언하고 repositories가 없다. 개발 해석은 root composer.json(polyspec/crudui-workspace, 게시하지 않음)에 있으며, packages/validator-php의 path repository, autoload로 읽는 생성기 source, root의 composer.lock과 vendor/를 둔다. PHP package lock은 없고, make crudui-install-composer는 root를 설치하며, PHPUnit은 root의 vendor/bin/phpunit을 bootstrap scripts/php-package-autoload.php로 실행하고 이 bootstrap은 test하는 package를 source 디렉터리에서 load한다. 비교, native 생성기 test, PHP 확장 test, benchmark, scripts/php-doc-coverage.php는 root vendor/를 load한다. config/dependency-policy.json은 root manifest와 lock을 review하고, make crudui-test-dependencies는 게시되는 composer.json을 lock 없이 검사한다. make release-assets는 package manifest와 다르거나, 그런 의존성을 URL, 경로, git 출처, 범위, 개발 version으로 적거나, repositories가 있거나 version이 없는 composer.json인 pack된 manifest를 하나씩 적어 실패한다. release workflow의 step인 make release-install-check는 저장소 밖에서 commit된 tests/release-install 사용자 fixture로 var/release/assets의 archive를 npm ci(빈 cache, 닿지 않는 registry의 scope @polyspec)와 composer install(artifact repository, 빈 COMPOSER_HOME과 COMPOSER_CACHE_DIR)로 설치하고, make release-install-lock은 archive에서 fixture lock을 다시 만든다. Red: check가 v0.0.2 release archive에서 문제 3개, polyspec/crudui-generator의 path repository와 두 Composer zip의 빠진 version을 지목했고, 설치 test는 local package를 pack하고 warm cache가 충족한 npm install --offline을 실행했다. Green: 검증 파일의 사례가 통과하고, make release-install-check는 make release-assets TAG=v0.0.2 RELEASE_COMMIT=HEAD의 archive를 빈 cache로 fixture에서 설치한다 |
— | node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/build/dependency-health.test.mjs tests/build/dependency-review.test.mjs tests/build/php-extensions.test.mjs tests/build/run-tests.test.mjs scripts/php-doc-coverage.test.mjs, make release-install-check |
[o] |
| C11.6 | 0.0.3을 release한다. 저장소 package의 모든 package.json, packages/의 composer.json, Cargo.toml과 lock 항목, root composer.json의 polyspec/crudui-validator 요구와 composer.lock 항목, contracts/features.json과 그것을 읽는 check, packages/php-ext의 module version, 문서에 적힌 package version, make release-assets TAG=v0.0.3 RELEASE_COMMIT=HEAD의 archive로 make release-install-lock이 쓴 tests/release-install의 사용자 fixture가 version 0.0.3을 갖고, CHANGELOG.md와 CHANGELOG.ko.md의 ## Unreleased는 새 빈 ## Unreleased 아래의 ## 0.0.3이 되어 v0.0.3과 Go module tag packages/generator-go/v0.0.3, packages/validator-go/v0.0.3에서 make release-versions가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/release.test.mjs, make release-install-check |
[o] |
| C11.6-1 | CI에서 release archive를 설치한다. 깨진 archive가 tag 뒤에야 발견되었다. CI job build-lint는 make test-ordered-json 뒤에 make release-install-head을 실행하며, 이것은 RELEASE_COMMIT=HEAD node scripts/release.mjs assets로 package.json version의 HEAD archive를 쓰고 scripts/release-install.mjs check로 tests/release-install의 사용자 fixture에서 설치한다. CI_COMMANDS가 그것을 나열하고, 설치를 위해 offline 설정을 풀며, release workflow는 make release-install-check를 유지한다. Red: archive를 설치하는 CI job이 없었고, tag 뒤의 release workflow만 설치했다. Green: 검증 파일의 사례가 통과하고, make release-install-head은 빈 cache로 0.0.3 archive를 설치한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs tests/build/owner-check.test.mjs, make release-install-head |
[o] |
| C11.6-2 | OrderedJSON을 release된 tag v0.0.3에서 받는다. examples/form-comparison/src/ordered-json-source.mjs의 orderedJsonVersion은 0.0.3이므로 orderedJsonTag는 v0.0.3이고 make crudui-install-ordered-json이 그것을 체크아웃한다. Rust record server의 version 요구와 그 Cargo.lock, @polyspec/ordered-json의 package-lock.json 항목, tests/ordered-json/check.py의 VERSION, 문서는 0.0.3을 적고, make dependency-review RECORD=1이 바뀐 lock의 review를 기록한다. tests/release-install의 fixture lock은 OrderedJSON package를 적지 않는다. Green: make test-ordered-json, make crudui-test-dependencies, ordered-json-source.test.mjs의 7개 사례, record-stores.test.mjs의 130개 사례, Rust record server의 test 18개, make release-install-head가 통과한다 |
— | make test-ordered-json, make crudui-test-dependencies, node scripts/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs examples/form-comparison/record-stores.test.mjs, node scripts/run-tests.mjs cargo -- --locked --manifest-path examples/form-comparison/servers/rust/Cargo.toml, make release-install-head |
[o] |
| C11.6-3 | 폼 서버마다 브라우저 검사를 자기 CI 작업에서 실행한다. CI 작업 form-comparison-checks는 PHP, PHP 확장, Go, Rust의 브라우저 검사 네 개를 runner 하나에서 동시에 실행했고, 거기서 각 검사의 초기화 보고서 8개가 약 75초씩 걸려 하나가 제한 시간을 넘었다. CI 작업 form-comparison-browser는 서버(php, php-ext, go, rust, formServers와 같음)마다 작업 하나인 matrix이며, 각 작업은 make crudui-test-form-comparison-browser를 실행한다. 이는 FORM_SERVERS의 --servers로 local-verification.mjs를 자기 로컬 구성에 실행하고, 작업은 그 서버의 보고서를 올린다. CI 작업 form-comparison-checks는 그 네 작업을 필요로 하고, 보고서를 내려받아 make crudui-test-form-comparison-summary를 실행하며, 이는 local-verification.mjs를 --browser-reports로 실행한다. PHP 처리 모드, 생성, 저장, 정본 흐름, 보고서 네 개의 브라우저 집계, 입력, 모든 보고서의 근거를 각각 한 번 실행한다. 모든 공개 서버가 주소 FORM_ADDRESS(--address)를 쓰므로 보고서는 브라우저 집계가 요구하는 origin을 명시한다. 단일 실행과 두 부분의 검사는 같다. php-modes, generation, persistence, pipeline, browser-php, browser-php-ext, browser-go, browser-rust, browser-summary, typing, evidence이며 검사, 보고서, 제한 시간, 한도는 바뀌지 않는다. ci-passed는 두 작업을 필요로 하고 CI_COMMANDS는 두 target을 나열한다. Red: CI 실행 37617382051에서 browser-php가 Browser report php/createForm/react/initialization exceeded its 100000 ms limit after 100007 ms로 실패했고 나머지 세 검사는 1752개 검사를 모두 통과했다. Green: 이 변경의 CI 실행에서 form-comparison-browser 작업 네 개와 form-comparison-checks가 통과한다. check-verification.test.mjs, form-comparison-ci.test.mjs, ci-local.test.mjs의 case가 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/ci-local.test.mjs, CI 작업 form-comparison-browser와 form-comparison-checks |
[o] |
| C11.6-4 | Go module tag를 archive 설치 검사 실패 없이 release한다. make release-install-check는 tag와 상관없이 package.json version의 archive를 읽었지만 make release-assets는 Go module tag <directory>/vX.Y.Z에 archive를 쓰지 않으므로, packages/generator-go/v0.0.3과 packages/validator-go/v0.0.3의 release run이 var/release/assets/polyspec-crudui-generator-html-0.0.3.tgz is missing으로 실패하고 GitHub Release를 만들지 않았다. make release-install-check는 TAG를 요구하고 scripts/release-install.mjs check에 넘기며, 이 검사는 Go module tag이면 release-install: <tag> is a Go module tag, which releases no archive to install을 쓰고 아무것도 설치하지 않고, tag vX.Y.Z이면 이전처럼 archive를 설치한다. make release-install-head는 tag 없이 HEAD를 검사한다. Red: tests/build/release.test.mjs의 새 사례가 이 변경 전 검사에서 빈 assets 디렉터리를 읽고 실패했다. Green: 그 사례가 통과하고 tests/build/release.test.mjs, tests/build/ci-local.test.mjs, tests/build/offline-checks.test.mjs가 통과하며(34), TAG=packages/generator-go/v0.0.3 make release-install-check가 tag를 밝히고, make release-install-head와 TAG=v0.0.3 make release-install-check가 0.0.3 archive를 설치한다 |
— | node scripts/run-tests.mjs node -- tests/build/release.test.mjs tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs, make release-install-head |
[o] |
| C11.6-5 | form comparison 실행의 서버를 system이 정한 port에서 시작한다. 브라우저 요약이 모든 보고서에 자기 origin을 요구했으므로 make crudui-test-form-comparison-browser와 make crudui-test-form-comparison-summary의 모든 공개 서버가 고정 주소 FORM_ADDRESS(--address, 127.0.0.1:47100)를 썼고, 동시에 실행한 두 run이 그 port에서 충돌했다. Makefile, local-verification.mjs, src/local-servers.mjs의 startLocalStack은 주소를 받지 않으며, 모든 서버는 system이 정한 port를 쓰고 그것을 알린다. check-browser-reports.mjs의 summarizeBrowserReports는 모든 보고서에 port를 뺀 자기 origin의 scheme과 host를 요구하고, 같은 트리의 소스 식별자는 그대로 요구한다. Red: 127.0.0.1:47100에서 동시에 시작한 두 run의 공개 서버 중 두 번째가 readiness 전에 종료했고, browser-report.test.mjs의 새 case는 다른 port의 보고서에서 php verification: report origin으로, form-comparison-ci.test.mjs의 case는 인자 --address에서 실패했다. Green: 두 로컬 구성의 공개 서버가 동시에 각자의 port에서 시작하고, 요약은 다른 port의 보고서를 통과시키며 다른 scheme, host, 소스 식별자를 거부하고, 네 파일의 30개 case가 통과한다 |
— | node scripts/run-tests.mjs node -- examples/form-comparison/browser-report.test.mjs examples/form-comparison/src/local-servers.test.mjs examples/form-comparison/check-verification.test.mjs tests/build/form-comparison-ci.test.mjs, CI 작업 form-comparison-browser와 form-comparison-checks |
[o] |
Wave 12 — wall time이 더 짧은 CI job
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C12.1 | CI workflow의 wall time을 줄인다. native suite는 세 job에서 실행되어 PHP 확장의 C 엔진과 JavaScript, HTML, Go, Rust 대상의 생성기는 한 번, PHP API는 PHP release마다 실행되고, 각 job의 conformance 증거는 별도 디렉터리에 놓이며(C12.1-1), npm package를 설치하는 모든 job은 ~/.npm을 package-lock.json으로 cache한다(C12.1-2) |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/conformance-evidence.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/make-tool-path.test.mjs tests/build/offline-checks.test.mjs tests/build/release.test.mjs |
[o] |
| C12.1-1 | native suite를 세 CI job에서 실행한다. job Native generation and PHP API는 PHP 8.4와 PHP 8.5에서 각각 make crudui-test-native와 make crudui-test-bench를 489초와 454초 동안 실행했고, 각 실행의 335초는 PHP가 필요 없었다. php-engine은 Node.js와 C compiler로 make crudui-test-php-engine(packages/php-ext/tests/engine.test.mjs, CI에서 171초)을 한 번 실행하고, native-generators는 make crudui-test-native-generators(Go·Rust 생성기 test, protocol·widget test, tests/native-generators/run.mjs --target javascript,html,go,rust)와 make crudui-test-bench를 한 번 실행하며, php-api는 PHP 8.4와 8.5에서 make crudui-test-php-api(crudui.so build, builder·API test, PHP 생성기 test, run.mjs --target php,php-native)를 실행한다. make crudui-test-native와 make conformance는 세 대상을 실행하고, CI_COMMANDS는 job 순서로 그것들을 나열하며, PHP_NATIVE_REPORT는 PHP 대상의 보고서를 지정한다. 각 job은 고유한 artifact 이름으로 증거를 보존하고, job conformance는 세 job을 needs로 두고 artifact마다 별도 디렉터리에 내려받으며, scripts/check-conformance.mjs는 디렉터리와 하위 디렉터리의 증거 파일과 실행 기록을 읽는다. merge-multiple은 모든 artifact를 한 디렉터리에 풀었으므로 한 job의 파일 php-<pid>.jsonl이 다른 job의 같은 이름 파일을 덮어쓴다. tests/build/offline-checks.test.mjs의 cargo 사례는 run.mjs 실행을 대상에 rust가 있을 때만 cargo 명령으로 센다. Red: tests/build/ci-local.test.mjs, tests/build/form-comparison-ci.test.mjs, tests/build/make-tool-path.test.mjs의 5개 사례가 이전 workflow와 Makefile에서 실패했고, tests/build/conformance-evidence.test.mjs는 export readEvidence가 없어 load되지 않았으며, cargo 사례는 crudui-test-php-api를 지목했다. Green: 다섯 파일의 37개 사례가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/conformance-evidence.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/make-tool-path.test.mjs tests/build/offline-checks.test.mjs |
[o] |
| C12.1-2 | npm package를 package-lock.json으로 cache한다. setup-node의 cache npm은 먼저 끝난 job의 npm 디렉터리를, npm package를 설치하지 않는 job의 것도 저장했으므로 모든 job이 700 byte를 복원했다. .github/workflows/ci.yml, pages.yml, release.yml에서 make install-node-modules를 실행하는 모든 job은 make install-npm 전에 actions/cache v6.1.0으로 ~/.npm을 key npm-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('package-lock.json') }}로 복원하고 저장하며, 어떤 setup-node step도 cache하지 않는다. 실행은 자기 ref와 main의 cache를 읽고, pages.yml이 main에서 이 key를 저장한다. Cargo cache는 job id, Rust toolchain, workspace의 Cargo lock으로 된 key를 유지한다. native-generators는 자기 key의 유일한 job이며, 전에는 matrix의 두 PHP 항목이 같은 key를 저장했고 그중 하나는 Unable to reserve cache로 실패했다. Red: tests/build/ci-local.test.mjs의 새 사례가 cache: npm을 둔 setup-node step 18개와 ~/.npm cache 없이 npm package를 설치하는 job 14개를 지목했다. Green: tests/build/ci-local.test.mjs의 15개 사례와 tests/build/*.test.mjs의 291개 사례가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/release.test.mjs |
[o] |
Wave 13 — 검증기와 생성기의 Python 구현
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C13.1 | 다른 언어 구현과 같은 자격의 검증기와 생성기 Python 구현을 더한다. packages/validator-python은 namespace package polyspec.crudui.validator로 polyspec-crudui-validator를(C13.1-1, C13.1-2), packages/generator-python은 polyspec.crudui.generator로 polyspec-crudui-generator를(C13.1-3, C13.1-4) 내놓고, 두 제품의 모든 공통 fixture 사례가 통과하며(C13.1-5), 저장소의 언어 목록과 검사가 python을 이름하고(C13.1-6) CI workflow가 3.11과 .python-version의 release에서 Python test를 실행한다(C13.1-7) 범위: tests/fixtures/form-outline은 C13.1의 범위 밖이며 어떤 Python package도 읽지 않는다. C13.1의 기준 제품(packages/validator-ts, packages/validator-php, packages/generator-php, packages/generator-go)은 이를 읽지 않는다. HTML, React, Vue, Svelte generator는 이를 읽으며, C13.1의 기준 제품이 아니다. |
— | 하위 항목의 verification 명령 | [o] |
| C13.1-1 | Python 검증기 packages/validator-python을 내놓는다. pyproject.toml(PEP 621, 고정된 setuptools, requires-python >= 3.11, py.typed), polyspec/에 __init__.py가 없는 namespace package polyspec.crudui.validator, 모듈 errors(ComposeLoadError, FormInputError), compose($ref/$patch 확장, memory loader), forbidden(금지 meta key 검사), text(입력 text와 값 한도 검사), unicode_data(contracts/unicode-properties.json의 Unicode 16.0.0 범위를 생성한 것), pattern(pattern 언어 recognizer, 선형 시간 matcher, code point 집합), parser(64단계 깊이 한도의 조건 lexer와 parser), resolver(경로 해석과 조건 평가), values(canonical text, whitespace, 빈 값, 길이, membership, 수와 ECMAScript 숫자 text), rules(등록된 24개 rule), validator(필드 순회, validate slot 평가, 선언된 parameter 검사)로 이루어진다. 공개 표면은 validate, hiddenPaths, validateList, validateDetail, Validator, ComposeLoadError, FormInputError다. Red: Python package가 없어 python3 -m unittest discover -s packages/validator-python/tests가 디렉터리를 찾지 못했다. Green: module이 import되고 단위 test가 통과한다(C13.1-2) 사실(과정): 이 체크리스트의 어떤 커밋도 C13.1-1부터 C13.1-4까지의 in-progress marker 줄을 추가하지 않았다. 이 행들은 done marker로 처음 나타난다. 사실(committer와 본문): 이 과제의 수정인 커밋 the C13.1-1 fix commit의 committer는 max <max@blue.tools>이며, 본문은 수정 전의 이름(validate_list, validate_detail, hidden_paths)을 설명한다. |
— | python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' |
[o] |
| C13.1-2 | Python 검증기를 공통 검증 fixture로 검증한다. 단위 test는 tests/fixtures/validate/cases.json(309사례), tests/fixtures/expr/cases.json(54), tests/fixtures/compose/cases.json(20), tests/fixtures/spec-validity/cases.json(34), tests/fixtures/list-validity/cases.json(20), tests/fixtures/detail-validity/cases.json(13)과 value-graphs.json을 곁들인 tests/fixtures/text-validity의 validate, validateList, validateDetail 파일을 읽어, 모든 결과를 기록된 기대값과 비교하고 모든 실패의 code, message, at까지 비교한다. validate는 309사례(178개 결과를 그대로 재현하고 131개 실패를 code, message, at로), expr은 54사례(47개의 token·tree·평가 사례와 7개 거부 식의 message 첫 줄), compose는 20사례(13개 compose 결과와 7개 load code), spec-validity는 34사례(20개 통과와 14개 금지 key 실패의 점선 경로), list-validity는 20사례(16과 4), detail-validity는 13사례(6과 7), text-validity는 validate 21·validateList 6·validateDetail 5사례와 공유·자기참조 container로 만든 value-graphs.json 12사례를 검증한다. Red: package가 없던 때 discovery 명령이 test를 찾지 못했고, 처음 실행에서 309개 중 19개 사례가 실패했다(pattern matcher가 atom을 잘못 읽었고, unique가 keyed collection의 값 대신 key를 읽었으며, compose 결과가 명세 member 순서 대신 삽입 순서를 유지했다). 숫자 text는 1e+21을 1.0e+21로 썼고 list·detail 결과가 hidden member를 실었다. Green: 다섯 파일의 23개 test가 3초 안에 통과한다 사실: test 디렉터리 packages/validator-python/tests는 이 커밋이 추가했다. the C13.1-1 package commit의 tree에는 그 디렉터리가 없어, 그 tree의 discover 명령은 ImportError: Start directory is not importable로 실패했다. |
— | python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' |
[o] |
| C13.1-3 | Python 생성기 packages/generator-python을 내놓는다. pyproject.toml은 검증기와 같고, namespace package polyspec.crudui.generator에 template 컴파일, 필드 binding, 버튼, 행 조작을 가진 form instance, interface message, list·detail 모델, widget·style·rendering 내부, 값 도우미 모듈을 둔다. 공개 표면은 Generator(compileForm, bindForm, bindButtons, formButtonsHtml, createForm, renderForm, renderList, buildList, buildDetail, renderDetail, sequenceRowKey, createRowKey), Form, FormError와 polyspec.crudui.validator의 검증 실패다 사실(committer): 커밋 the C13.1-3 package commit의 committer는 max <max@blue.tools>다. 이 작업의 커밋에 대해 스펙은 min-0-max <max@blue.tools>를 요구한다. |
— | python3 -m unittest discover -s packages/generator-python/tests -p 'test_*.py' |
[o] |
| C13.1-4 | Python 생성기를 공통 생성 fixture로 검증한다. 단위 test는 tests/fixtures/form-render/cases.json(188), tests/fixtures/list-render/cases.json(157), tests/fixtures/detail-render/cases.json(62), tests/fixtures/form-complete/cases.json(28)과 tests/fixtures/text-validity의 compileForm, bindForm, createForm, buildList, buildDetail 파일을 읽어, 모든 결과를 기대값과 비교하고 렌더링된 HTML은 parse하여 정규화한 tree로, 모든 실패는 code와 message로 비교한다 사실: test 디렉터리 packages/generator-python/tests는 이 커밋이 추가했다. the C13.1-3 package commit의 tree에는 그 디렉터리가 없어, 그 tree의 discover 명령은 ImportError: Start directory is not importable로 실패했다. 사실(committer): 커밋 the C13.1-4 test commit의 committer는 max <max@blue.tools>다. 이 작업의 커밋에 대해 스펙은 min-0-max <max@blue.tools>를 요구한다. |
— | python3 -m unittest discover -s packages/generator-python/tests -p 'test_*.py' |
[o] |
| C13.1-5 | Python 구현을 교차 언어 검사에 넣는다. examples/cross-check-console의 validator process validators/python/validate.py와 validatorProcesses의 python을 더해, console의 모든 요청이 Python 검증기를 JavaScript, PHP, Go, Rust process와 같은 응답으로 실행하고, Python test의 conformance 증거가 conformance job이 비교하는 증거에 합류한다 |
— | node scripts/run-tests.mjs node -- examples/cross-check-console/server/validate-runner.test.mjs examples/cross-check-console/server/validator-processes.test.mjs |
[o] |
| C13.1-6 | 저장소의 모든 언어 목록에 python을 이름한다. contracts/features.json은 각 feature의 python 지원과 packages 항목을 얻고, docs/features.md와 docs/features.ko.md는 같은 깊이로 Python 지원을 적으며, README 쌍은 Python package를 이름하고, .python-version은 최신 안정 release(3.14)를 한 줄로 두고, config/toolchain.json의 python이 3.14가 되어 tests/ordered-json/check.py가 통과하며, scripts/owner-checks.json이 Python 경로를 그 검사로 연결한다 |
— | node scripts/run-tests.mjs node -- tests/build/contract-manifest.test.mjs, make records-check |
[o] |
| C13.1-7 | CI에서 Python test를 실행한다. .github/workflows/ci.yml의 job python은 3.11과 .python-version의 release를 matrix로 두고, CI_COMMANDS가 나열하는 make 대상으로 두 package의 검증기·생성기 단위 test를 실행하며 conformance 증거를 보존하고, ci-passed가 이를 needs로 둔다. generator는 polyspec-crudui-validator를 v0.0.4 tag URL로 의존한다(아직 게시되지 않음, C13.1-9). generator의 어떤 module도 import하지 않는 polyspec-ordered-json은 선언하지 않는다. 그 tag가 생길 때까지 단위 test는 저장소 checkout의 validator package로 실행하며, manifest는 tag URL을 유지한다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs, CI job python |
[o] |
| C13.1-7-1 | Python 단위 test가 실행하는 공통 사례마다 증거를 기록한다. tests/conformance/evidence.py와 runs.py는 tests/fixtures/{validate,expr,compose,spec-validity,list-validity,detail-validity,form-render,form-complete,list-render,detail-render}와 Python package가 증명하는 text-validity file의 사례마다, 그 사례를 증명하는 기능별로 기록을 쓰며, 단위 test 모듈은 각 사례를 Proving으로 감싼다. 실패한 사례는 passed: false로 기록된다 |
— | PYTHONPATH=packages/validator-python/src CRUDUI_CONFORMANCE_EVIDENCE=<디렉터리> python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py'와 같은 변수로 generator suite를 실행: 기록 1637건, passed: false인 기록 없음 |
[o] |
| C13.1-8 | 커밋 이력 감사에서 찾은 Python 생성기 결함을 고친다: createForm 인스턴스에 대한 renderForm이 결측 데이터의 빈 행을 empty-collections 명세대로 키 __0000000000000__로 렌더링한다(form.py의 Form._normalize_fields), form-render 단위 test가 공개 경로 createForm·renderForm으로 비교한다(test_form_render.py), text-validity 단위 test가 FormError.path를 at으로 읽는다(test_text_validity.py), conformance.py가 자식 노드를 죽은 분기 없이 정규화한다. Red: multiple-leaf-empty-placeholder가 기대 키 __0000000000000__ 대신 __0a592ccb0f808__로 실패했다. |
— | packages/generator-python에서 PYTHONPATH=src:../validator-python/src python3 -m unittest discover -s tests -p 'test_*.py' |
[o] |
| C13.1-9 | packages/generator-python에서 쓰지 않는 polyspec-ordered-json 의존성을 제거하고 검증기 의존성을 태그 URL로 적는다. generator의 어떤 module도 polyspec.ordered_json을 import하지 않으므로 pyproject.toml은 그 의존성을 선언하지 않는다. generator는 실행 시 polyspec.crudui.validator를 import하므로 의존성은 polyspec-crudui-validator @ git+https://github.com/polyspec/crudui@v0.0.4#subdirectory=packages/validator-python이며, 관리자가 태그 v0.0.4를 만들면 해석된다 |
— | packages/generator-python에서 PYTHONPATH=src:../validator-python/src python3 -m unittest discover -s tests -p 'test_*.py', 선언된 의존성을 tomllib로 읽기 |
[o] |
| C13.1-10 | 커밋된 tree의 ImportError: Start directory is not importable의 원인을 C13.1-2와 C13.1-4 행에 기록한다. 각 package의 test 디렉터리는 그 package를 만든 커밋 뒤에 추가되어, 그 커밋의 검증 명령이 존재하지 않는 디렉터리를 가리켰다 |
— | C13.1-1부터 C13.1-4까지의 package와 test commit에 대해 git ls-tree -r --name-only <commit> -- <test 디렉터리> |
[o] |
| C13.1-11 | 커밋 이력의 사실 세 가지를 C13.1-1부터 C13.1-4까지의 행에 기록한다. 이 행들에 the done marker 이전의 the in-progress marker 줄을 추가한 커밋은 없다(과정), 커밋 the C13.1-3 package commit와 the C13.1-4 test commit의 committer는 스펙이 요구하는 신원이 아닌 max <max@blue.tools>다(committer), the C13.1-1 fix commit의 본문은 수정 전의 이름을 설명한다(본문) |
— | 체크리스트에 대해 C13.1-1~4의 the in-progress marker 줄을 git log -G로, 커밋의 git log --format='%cn <%ce>'를 확인 |
[o] |
| C13.1-12 | C13.1-1의 수정을 자체 sub-item과 CHANGELOG 항목으로 기록한다. Python validator의 공개 이름이 validateList, validateDetail, hiddenPaths(공통 표면의 이름)로 바뀌었으며, 이 수정은 C13.1-1을 닫은 뒤에 이루어졌다 |
— | 저장소 루트에서 PYTHONPATH=packages/validator-python/src python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py' |
[o] |
| C13.1-13 | C13.1-2 행의 파일 수를 바로잡는다. discover가 패턴 test_*.py로 실행하는 단위 test 파일은 다섯 개다(conformance.py 파일은 그 패턴에 없다). 그래서 행은 여섯 파일이 아니라 다섯 파일이라고 쓴다 |
— | 저장소 루트에서 PYTHONPATH=packages/validator-python/src python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py'가 test 23개를 보고한다 |
[o] |
| C13.1-14 | 웨이브 계획에 .python-version 파일이 아직 없다고 적는다. docs/plans/execution-checklist/plan.md는 그것이 최신 안정 release를 이름한다고 쓰지만, 그 파일은 C13.1-6이 만들며 C13.1-6은 완료되지 않았다 |
— | grep -n 'python-version' docs/plans/execution-checklist/plan.ko.md로 파일이 아직 없다고 적힌 것을 확인 |
[o] |
| C13.1-15 | C13.1의 form-outline fixture 범위를 선언한다. tests/fixtures/form-outline은 C13.1의 범위 밖이다. C13.1의 기준 제품(packages/validator-ts, packages/validator-php, packages/generator-php, packages/generator-go)은 이를 읽지 않는다. HTML, React, Vue, Svelte generator는 이를 읽으며, C13.1의 기준 제품이 아니다 |
— | 기준 제품 디렉터리에 대해 grep -rl form-outline을 실행하면 파일이 나오지 않는다 |
[o] |
| C13.1-16 | PHP 확장을 그 PHP 실행 파일의 아키텍처로 빌드한다. 빌더는 cc에 대상 아키텍처를 넘기지 않고 PHP 실행 파일에서도 읽지 않아, 객체와 링크된 module이 빌드를 시작한 프로세스의 아키텍처를 따른다. Intel 바이너리인 /usr/local/bin/timeout 아래에서 cc는 x86_64 객체를 만들고 module은 arm64 PHP에 로드되지 않는다. 빌더는 lipo -archs로 PHP 실행 파일의 아키텍처를 읽어 컴파일과 링크에 -arch로 넘기고, 링크된 module의 아키텍처가 다르면 실패한다 |
— | node scripts/run-tests.mjs node -- tests/build/php-extensions.test.mjs(아키텍처 단위 test), 이어서 timeout 없이 make crudui-build-php-extension, 이어서 examples/cross-check-console/server/validator-processes.test.mjs의 php-native 프로세스 |
[o] |
| C13.1-17 | packages/validator-python/src와 packages/generator-python/src의 모든 공개 정의에 매개변수와 반환 타입 힌트를 준다. 주석 없는 공개 정의는 AST로 세면 validator 10개 파일에서 82개, generator 19개 파일에서 98개이며, 각 주석은 함수 본문에서 타입을 정한다 |
— | 주석 없는 공개 정의의 AST 세기가 0이고, 저장소 밖 scratch 가상환경에서 두 src tree에 대해 mypy --strict가 통과한다 |
[o] |
| C13.1-18 | C13.1-1, C13.1-2, C13.1-3, C13.1-4, C13.1-10, C13.1-11 행의 기록에서 커밋 id를 뺀다. 각 커밋은 구현하는 과제의 체크리스트 id로 가리킨다(예: validator package를 추가한 커밋은 C13.1-1로 가리킨다). 기록 가이드가 그렇게 요구한다 | — | docs/plans/execution-checklist/checklist.md에 대해 grep -nE '[0-9a-f]{7,8}'를 실행하면 C13.1의 행에 커밋 id가 나오지 않는다 |
[o] |
| C13.1-19 | Form.setData가 객체가 아닌 값을 받으면 공통 계약이 정한 메시지로 실패하게 한다. setData는 INVALID_FORM_INPUT과 Form data must be an object로 실패하며, 이는 공통 action 사례 action-failure-keeps-state(native generator 검사)의 메시지다. createForm은 Expected an object를 유지한다 |
— | node tests/native-generators/run.mjs --target python이 request:action-failure-keeps-state를 Python 프로그램으로 실행해 통과로 보고한다 |
[o] |
| C13.1-7-2 | Python 프로그램에 대한 native generator 검사를 실행한다. tests/native-generators/python.py는 두 package의 공개 API로 run.mjs의 operation protocol에 답하고, run.mjs에 python 대상이 추가되며, make crudui-test-native-generators는 Python 대상을 JavaScript, HTML, Go, Rust 대상과 함께 실행한다 |
— | node tests/native-generators/run.mjs --target python이 741건 통과와 종료 코드 0을 보고한다 |
[o] |
| C13.1-20 | C13.1-10과 C13.1-11 행의 본문에 쓴 상태 표기를 단어로 바꾼다. 체크리스트 규칙이 요구하듯 표기는 과제 행의 상태로만 나타난다 | — | node scripts/check-documents.mjs가 문서 쌍을 통과하고, C13.1의 어떤 행도 상태 칸 밖에 표기를 두지 않는다 |
[o] |
| C13.1-6-1 | 기록된 증거로 각 기능의 Python 지원을 적는다. 기능은 나열한 JSON fixture의 모든 사례에 통과한 Python 기록이 있을 때만 Python에서 pass이고, 사례가 모두 기록되지 않은 경우(createForm)는 partial, Python package가 구현하지 않는 경우(buildOutline, runAction, viewState, formHistory)는 unsupported, 클라이언트 전용 기능(connectForm, patchContent, updateView, connectOutline)은 해당 없음이다. 기능 문서와 README 짝은 Python package를 적는다 |
— | node scripts/generate-feature-contract-docs.mjs, node scripts/check-documents.mjs, 그리고 pass 기능마다 JSON fixture의 사례별 Python 기록 수를 세어 불일치 0건 |
[o] |
| C13.1-7-3 | make crudui-test-native-generators의 native generator 명령 줄이 대상 목록 javascript,html,go,rust,python을 실행하므로, 그 줄을 고정하는 CI 매핑 test도 python 대상을 포함하게 맞춘다 |
— | node scripts/run-tests.mjs node -- tests/build/ci-local.test.mjs가 test 15개를 통과하고 종료 코드 0 |
[o] |
| C13.1-21 | 이전 세션이 커밋하지 않은 generator 동작 변경을 타입 주석과 분리해 커밋한다. binding의 root·header·body 마크업과 language 옵션, lists의 목록 model(build_public, 셀 표시와 render_cell), value의 object_value, widget의 위젯 속성, __init__의 Generator 메서드, dates의 datetime 범위 밖 시각을 위한 civil 날짜 계산과 요일 검사, style의 괄호 짝 검사 |
— | cd packages/generator-python && PYTHONPATH=src:../validator-python/src python3 -m unittest discover -s tests -p 'test_*.py'(test 11개)와 node tests/native-generators/run.mjs --target python(741건 통과, 종료 코드 0)을 이 상태에서 실행 |
[o] |
| C13.1-17-1 | packages/validator-python/src의 모든 함수에 매개변수와 반환 타입을 준다. JSON 값은 JsonValue 별칭, 합성 loader는 DocumentLoader protocol, 규칙 컨텍스트와 식 노드는 dict 별칭, 진입점은 Mapping 옵션을 받는다. package의 동작은 바뀌지 않는다 |
— | mypy --strict packages/validator-python/src가 scratch 가상환경에서 종료 코드 0이고, PYTHONPATH=packages/validator-python/src python3 -m unittest discover -s packages/validator-python/tests -p 'test_*.py'가 test 23개를 통과 |
[o] |
| C13.1-17-2 | packages/generator-python/src의 기초 module에 매개변수와 반환 타입을 준다. 여섯 module(errors, messages, input_text, numbers, dates, style)에는 strict 오류가 없다. module의 동작은 바뀌지 않는다 |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src가 이 여섯 module에 오류를 보고하지 않고, generator 단위 test가 11개 통과 |
[o] |
| C13.1-17-3 | packages/generator-python/src의 value, design, template module에 매개변수와 반환 타입을 준다. 세 module에는 strict 오류가 없다. 결측 표지는 클래스 인스턴스, 폼 경로 값은 Value 별칭, 사양 객체는 dict, design의 평가 결과는 object다. 동작은 바뀌지 않는다 |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/{value,design,template}.py가 세 module에 오류를 보고하지 않고, generator 단위 test가 11개 통과 |
[o] |
| C13.1-17-4 | packages/generator-python/src의 buttons, choice_list module에 매개변수와 반환 타입과 그 타입의 import를 준다. 두 module에는 strict 오류가 없다. 동작은 바뀌지 않는다 |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/{buttons,choice_list}.py가 두 module에 오류를 보고하지 않고, generator 단위 test가 11개 통과 |
[o] |
| C13.1-17-5 | packages/generator-python/src의 rendering, display_declaration module에 매개변수와 반환 타입을 준다. 두 module에는 strict 오류가 없다. 뷰 모델은 mapping이고, 이스케이프 도우미는 str을 받아 str을 돌려주며, 선언 검사는 돌아오지 않는 함수를 통해 FormError를 낸다. 동작은 바뀌지 않는다 |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/{rendering,display_declaration}.py가 두 module에 오류를 보고하지 않고, generator 단위 test가 11개 통과 |
[o] |
| C13.1-17-6 | packages/generator-python/src의 widget module에 매개변수와 반환 타입을 준다. 이 module에는 strict 오류가 없다. 문자열이 아닌 날짜·날짜시각 값은 더 이상 파싱하지 않고 control의 값으로 그대로 둔다(이전에는 parse_utc가 문자열만 받아 실패했다) |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/widget.py가 이 module에 오류를 보고하지 않고, generator 단위 test가 11개 통과 |
[o] |
| C13.1-17-7 | packages/generator-python/src의 lists module에 매개변수와 반환 타입을 준다. 이 module에는 strict 오류가 없다. 목록 사양과 행은 동적 mapping과 sequence이며, 합성된 멤버는 동적 타입으로 두고, 결측 검사는 검사된 값을 한 번 읽는다. 동작은 바뀌지 않는다 |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/lists.py가 이 module에 오류를 보고하지 않고, generator 단위 test 11개가 통과하며, node tests/native-generators/run.mjs --target python이 741건 통과, 종료 코드 0 |
[o] |
| C13.1-17-8 | packages/generator-python/src의 details, form_render module에 매개변수와 반환 타입을 준다. 두 module에는 strict 오류가 없다. 렌더 옵션은 module이 검사하는 외부 입력이고, 상세의 record는 JSON 값으로 타입을 준다. 동작은 바뀌지 않는다 |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/{details,form_render}.py가 두 module에 오류를 보고하지 않고, generator 단위 test 11개가 통과하며, node tests/native-generators/run.mjs --target python이 종료 코드 0 |
[o] |
| C13.1-17-9 | packages/generator-python/src의 binding module에 매개변수와 반환 타입을 준다. 이 module에는 strict 오류가 없다. 모델 객체는 타입이 있는 도우미 하나로 만들고, widget module이 읽는 widget과 합성된 사양은 dict로 넘긴다. 동작은 바뀌지 않는다 |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/binding.py가 이 module에 오류를 보고하지 않고, generator 단위 test 11개가 통과하며, node tests/native-generators/run.mjs --target python이 종료 코드 0 |
[o] |
| C13.1-17-10 | packages/generator-python/src의 form module에 매개변수와 반환 타입을 준다. 이 module에는 strict 오류가 없다. form 인스턴스의 record는 동적 mapping이고, record 값의 복사는 도우미 하나를 거친다. 반복 행 항목은 다른 값을 이미 거부하는 검사로 좁혀지므로 동작은 바뀌지 않는다 |
— | MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src/polyspec/crudui/generator/form.py가 이 module에 오류를 보고하지 않고, generator 단위 test 11개가 통과하며, node tests/native-generators/run.mjs --target python이 종료 코드 0 |
[o] |
| C13.1-17-11 | packages/generator-python/src의 패키지 초기화 module(Generator 연산과 그 도우미)에 매개변수와 반환 타입을 준다. 두 Python package의 모든 module이 mypy --strict를 종료 코드 0으로 통과하는지 확인한다. 연산은 JSON 사양, 데이터, 옵션을 module이 검사하는 값으로 받는다 |
— | scratch 가상환경에서 mypy --strict packages/validator-python/src와 MYPYPATH=packages/validator-python/src mypy --strict packages/generator-python/src가 각각 종료 코드 0이고, validator suite(test 23개)와 generator suite(test 11개)가 통과 |
[o] |
| C13.1-22 | C13.1-11의 상태 칸을 완료 표기로 되돌린다. 본문에 쓴 표기를 단어로 바꾸는 치환이 상태 칸까지 바꿔, 칸이 표기 대신 단어를 담고 있었다 | — | 두 체크리스트에서 C13.1-11의 상태 칸이 완료 표기를 담고, node scripts/check-documents.mjs가 통과한다 |
[o] |
| C13.1-23 | 버전 bump pull request에 오래된 pin 검사를 추가한다. scripts/release.mjs(make release-pins, scripts/release-pins.mjs로 대체됨)는 각 pyproject.toml의 version 필드만 검사하므로, 저장소 package의 의존성 polyspec-crudui-validator @ git+https://github.com/polyspec/crudui@vX.Y.Z가 다른 version을 가리켜도 통과한다. 검사는 각 pin을 파일, pin, tag와 함께 알리고, 이전 version을 pin한 파일에서 실패하는 unit test를 tests/build/python-git-pins.test.mjs에 둔다. 이 검사는 branch feat/python-C13.1에 추가하지 않는다. packages/generator-python의 pin v0.0.4가 가리키는 tag는 version bump pull request가 merge된 뒤에 만들어지므로(C13.1-24), 이 branch에서는 검사가 branch를 거부하게 된다. branch chore/release-0.0.4의 version 올림 행 C13.1-34와 함께 추가한다 |
— | node --test tests/build/python-git-pins.test.mjs: test a Python manifest pins this repository at the version of the tag, and another repository at any version가 통과하고, 릴리스 버전이 아닌 polyspec/crudui@vX.Y.Z Python manifest pin은 make release-versions를 실패시킨다 |
[o] |
| C13.1-24 | AGENTS.md의 Releases 절에 Python release 단계의 순서를 문서화한다. 관리자는 version 올림 pull request가 병합된 뒤에만 저장소 package의 tag vX.Y.Z를 만든다. packages/generator-python의 의존성 pin이 그 tag를 가리키기 때문이다 |
— | node scripts/check-documents.mjs가 통과하고, AGENTS.md와 AGENTS.ko.md의 Releases 절이 그 순서를 기술한다 |
[o] |
| C13.1-25 | CI job build, lint and types에서 release unit test를 실행한다. job은 make ci-targets TARGETS="crudui-test-runtimes crudui-test-dependencies build lint typecheck test-ordered-json kit-test"를 실행하고, crudui-test-runtimes는 Python pin의 release 검사인 tests/build/python-git-pins.test.mjs(make release-pins)를 실행하며, ci-passed는 계속 모든 job을 필요로 한다 |
— | node scripts/kit/run-tests.mjs node -- tests/build/python-git-pins.test.mjs, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs |
[o] |
| C13.1-26 | feat/python-C13.1의 pull request를 main에 대해 연다. 본문은 C13.1-24의 release 단계 순서를 기술한다 |
— | gh pr list -R polyspec/crudui --head feat/python-C13.1 --state open이 pull request 하나를 나열한다 |
[o] |
| C13.1-27 | packages/validator-python/src/polyspec/crudui/validator/resolver.py의 타입 주석이 쓰는 이름을 첫 사용 전에 정의하여 Python 3.11이 module을 import하게 한다. 별칭 Node는 parser에서 import하여 정의를 하나로 두고, 별칭 Context는 이를 쓰는 주석 위로 옮긴다. CI job python은 3.11에서 NameError: name 'Node' is not defined로 실패했다. Red: Python 3.11에서 validator suite가 resolver.py의 import에서 실패한다. Green: Python 3.11에서 validator와 generator suite가 통과한다 |
— | Python 3.11에서 packages/validator-python과 packages/generator-python의 PYTHONPATH=src python3 -m unittest discover -s tests -p 'test_*.py' |
[o] |
| C13.1-28 | make-tool-path 테스트에서 native generator 명령의 Python target을 고정한다. make crudui-test-native는 tests/native-generators/run.mjs --target javascript,html,go,rust,python을 실행하는데(C13.1-7-3), 테스트는 python이 없는 목록을 기대하여 CI job build, lint and types에서 실패했다. Red: crudui-test-native case에서 테스트가 실패한다. Green: 테스트가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/make-tool-path.test.mjs |
[o] |
| C13.1-29 | packages/generator-python/scripts/generate_interface_messages.py와 packages/validator-python/scripts/generate_unicode_data.py의 docstring이 module을 dotted 이름으로 적는다. 기존 docstring은 src/polyspec/crudui/... 경로를 적었고, package 이름 규칙은 이를 Composer package 형식으로 읽어 test package-names가 CI job build, lint and types에서 실패했다. Red: test가 두 줄을 지목한다. Green: test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/package-names.test.mjs |
[o] |
| C13.1-30 | CI job python에서 .node-version의 Node.js release를 설정하고 make toolchain-check로 node python을 검사한다. Job은 Node.js가 필요한 make ci-targets를 실행하고, toolchain policy test는 모든 job이 node를 설정하고 검사할 것을 요구한다. Policy test는 기록된 Python minor와 함께 requires-python의 가장 낮은 minor를 matrix leg로 허용한다. matrix가 3.11을 실행하기 때문이다(C13.1-7). Red: CI job build, lint and types에서 test every CI job sets up the recorded toolchains and checks the tools it set up가 ci.yml python: setup-python must install the recorded minor 3.14와 checks python, sets up node python으로 실패한다. Green: test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs(test every CI job sets up the recorded toolchains and checks the tools it set up) |
[o] |
| C13.1-31 | Python conformance suite를 선언한다. scripts/check-conformance.mjs는 suite validator Python(packages/validator-python/tests)과 generator Python(packages/generator-python/tests)을 선언하고, 두 suite의 실행 기록은 program tests/conformance/runner.py(C13.1-33)와 맞으며, 둘 다 런타임 python을 증명한다. 이 행 이전에는 선언된 suite가 python을 증명하지 않아, test every runtime that a feature supports is proven by a declared suite가 CI job build, lint and types에서 실패했다 |
— | node scripts/run-tests.mjs node -- tests/build/conformance-evidence.test.mjs |
[o] |
| C13.1-32 | partial 런타임을 기록한 사례로 측정한다. contracts/features.json은 createForm에 python: partial을 선언한다(Python Form에는 공용 fixture가 다루지 않는 undo와 redo 기록이 없다). Python 런타임의 native 검사는 tests/native-generators/cases.mjs의 form 시나리오에 대한 증거를 기록한다. scripts/check-conformance.mjs는 그 증거를 evidence without a declaration(runtime declared partial)으로 거부하여 CI job conformance evidence against the standard가 실패했다. 검사는 partial 런타임이 기록한 사례를 받아들인다. 기록된 사례마다 통과해야 하고 요구되는 사례는 없다(docs/operations/conformance.ko.md의 "증거"). Red: test a partial runtime is measured by the cases it records, and each recorded case must pass가 실패한다. Green: test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/conformance-evidence.test.mjs |
[o] |
| C13.1-33 | Python suite의 실행 기록에 program, tool, directory와 종료 상태를 남긴다. tests/conformance/runs.py는 program을 sys.executable의 이름(matrix leg에 따라 python3.11, python3.14, local에서는 python3)으로 기록하므로 선언이 그 이름을 지정할 수 없었고, finish_suite_run에는 호출자가 없어 모든 기록의 status가 null로 남아 did not finish로 읽혔다. runner tests/conformance/runner.py <test directory>는 directory의 test_*.py 파일을 찾고, program이 tests/conformance/runner.py, directory와 인자인 기록을 남기며, suite가 끝날 때 종료 상태를 쓴다. make target crudui-test-validator-python과 crudui-test-generator-python은 runner를 실행하며, tests/conformance/evidence.py는 import할 때 실행을 기록하지 않는다. Red: test the Python runner leaves a run record with the exit status of its suite가 실패한다. Green: test가 통과한다 |
— | node scripts/run-tests.mjs node -- tests/build/conformance-evidence.test.mjs |
[o] |
| C13.1-34 | 저장소 package 파일의 version을 0.0.4로 올린다. 릴리스 0.0.3(0137999d)과 같다. package.json, Cargo.toml과 pyproject.toml 파일을 그 lock 항목과 함께, polyspec/crudui-validator 요구를 담은 Composer manifest를, contracts/features.json과 그 version을 읽는 검사를, packages/php-ext의 module version을, 문서에 적은 package version을 바꾼다. CHANGELOG.md와 CHANGELOG.ko.md의 ## Unreleased는 ## 0.0.4로 바꾸고 그 위에 빈 ## Unreleased를 둔다 |
— | node --test tests/build/release.test.mjs가 C13.1-35 뒤에 통과한다 |
[o] |
| C13.1-35 | 0.0.4의 consumer fixture tests/release-install를 HEAD의 archive에서 다시 쓰고 lock을 다시 생성한다. make release-assets TAG=v0.0.4 RELEASE_COMMIT=HEAD는 9개 archive를 var/release/assets에 쓰고, make release-install-lock은 fixture의 package.json, package-lock.json, composer.json과 composer.lock을 쓴다. tag는 만들지 않는다 |
— | node --test tests/build/release.test.mjs가 통과하고, test the consumer fixtures of tests/release-install name the archives of the version of package.json을 포함한다 |
[o] |
| C13.1-36 | 0.0.4 lock의 dependency review를 기록한다. make dependency-review RECORD=1은 version bump로 바뀐 lock 8개의 sha256과 각 advisory(없음)를 config/dependency-review.json에 쓴다. review는 exception이 없는 newer stable release 4개를 보고한다. package.json의 puppeteer 25.13.0과 playwright 1.64.0, package.json과 packages/generator-svelte/package.json의 vite 8.3.4이며, 이들은 별도 변경에서 make dependency-review UPDATE=1로 올린다 |
— | node --test tests/build/dependency-review.test.mjs와 node --test tests/build/release.test.mjs가 통과한다 |
[o] |
| C13.1-37 | 0.0.4 bump 뒤 npm lock이 저장소 패키지를 릴리스 version으로 기록하게 합니다. bump는 패키지와 manifest의 version을 바꿨지만 package-lock.json의 의존성 항목(root와 workspace 패키지의 14개)은 여전히 0.0.3이었습니다. Red: tests/build/dependency-health.test.mjs의 "the dependencies match their recorded review"가 리뷰 뒤 lock이 바뀌었다고 보고합니다. Green: 항목이 0.0.4를 가리키고, 리뷰 기록은 C13.1-38에서 다시 씁니다. |
package-lock.json, config/dependency-review.json |
node --test tests/build/dependency-health.test.mjs |
[o] |
| C13.1-38 | 의존성을 최신 stable release로 올리고 install 스크립트를 정확한 version으로 승인합니다. 리뷰가 puppeteer 25.13.0, playwright 1.64.0, vite 8.3.4를 manifest보다 새 버전으로 찾았습니다. make dependency-review UPDATE=1 RECORD=1이 manifest와 lock을 갱신하고 기록을 씁니다. npm install-scripts approve는 puppeteer@25.13.0과 esbuild(0.27.7, 0.28.2)의 install 스크립트를 승인합니다. Red: "tracked npm dependency graphs approve every install script by exact version"이 puppeteer@25.13.0에서 ESTRICTALLOWSCRIPTS로 실패합니다. Green: 승인. |
package.json, packages/*/package.json, config/dependency-review.json, package-lock.json |
node --test tests/build/dependency-health.test.mjs |
[o] |
Wave 14 — polyspec/kit으로 공유하는 도구
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C14.1 | polyspec/kit v0.0.4에서 모든 polyspec 저장소가 공유하는 도구를 가져온다(C14.1-1부터 C14.1-6). 저장소는 scripts/kit과 tests/kit의 vendored copy를 두고, kit이 제공하는 도구의 자체 사본을 제거하며, 다른 저장소와 config/*.json에서만 다르다. 0.x workflow에는 pull request, merge queue, ruleset이 없다 |
— | make kit-check, make kit-test, make docs-check, make dependency-policy-check, make owner-validate |
[o] |
| C14.1-1 | polyspec/kit v0.0.4를 vendor한다. kit.json, .kit/kit.lock.json, scripts/kit, tests/kit는 tag의 파일이고 Makefile은 scripts/kit/kit.mk를 include한다. make kit-sync KIT_TAG=v0.0.4는 lock을 한 번 쓰고 두 번째 실행에서 unchanged를 출력한다 |
— | make kit-sync KIT_TAG=v0.0.4, make kit-test |
[o] |
| C14.1-2 | toolchain, 의존성, test runner 도구를 kit의 것으로 바꾼다. config/toolchain.json은 kit schema를 따르고(Go는 packages/validator-go/go.mod, Composer는 object), make install-tools가 npm, Go, cargo-audit을 var/tools에 설치한다. config/dependency-policy.json은 kit 형태를 따르고(root manifest와 lock의 PHP 8.4.1을 가진 composerPlatforms, OrderedJSON tag의 taggedNpmPackages, React 예외 4개), make dependency-review RECORD=1이 npm, Composer, Cargo lock과 Go module 6개의 review를 기록한다. scripts/kit/run-tests.mjs가 scripts/run-tests.mjs와 scripts/test-progress를 대체하고, tests/conformance/run-suite.mjs가 conformance evidence를 기록하는 suite의 실행 기록을 남긴다. 제거: install-npm, checkout-npm, check-toolchain, check-cargo-downloads, install-cargo-audit, dependency-state, dependency-review, check-dependencies, run-tests, test-progress, tracked-files와 그 test |
— | make dependency-policy-check, make kit-check, `node scripts/kit/run-tests.mjs node -- tests/build/tool-path.test.mjs tests/build/offline-checks.test.mjs tests/build/dependency-health.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/conformance-evidence.test.mjs tests/build/lint-coverage.test.mjs tests/build/repository-files.test.mjs |
[o] |
| C14.1-3 | push 검사, 전체 실행의 guard, holder lock, CI 보고 도구를 kit의 것으로 바꾼다. config/checklist.json이 checklist와 hook pre-push를 선언하고, .githooks/pre-push는 make hooks가 쓰는 hook이며, make ci는 conformance-reset과 CI_TARGETS(make 대상)를 scripts/kit/full-run.mjs에 넘기고, scripts/package-dist.mjs와 comparison server는 lock을 scripts/kit/holder-lock.mjs에서 가져온다. 제거: push-gate, full-run, holder-lock, ci-targets, ci-passed, target-report와 그 test |
— | make hooks-check, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/shared-resources.test.mjs tests/build/make-dry-run.test.mjs tests/build/offline-checks.test.mjs tests/build/test-commands.test.mjs |
[o] |
| C14.1-4 | document 검사를 kit의 것으로 바꾼다. config/documents.json은 kit의 vendored test와 한국어 twin이 없는 두 file을 뺀 모든 Markdown 문서, docs/features.md의 상태 table, CHANGELOG.md를 선택한다. 모든 영어 문서와 한국어 twin은 같은 doc-id를 가지고 한국어 twin은 영어 file의 source-sha256을 가지며, config/commits.json이 commit message 규칙을 선언하고, 기능 계약 page의 generator가 두 marker를 쓴다. 제거: scripts/check-documents.mjs, scripts/checklist-markers.mjs와 그 test. 문서 네 곳을 바로잡는다: cross-check console README(renderer 4개, code block 하나), 한국어 form markup link, 한국어 release tag 명령, tests/build/README의 link |
— | make docs-check, node scripts/kit/run-tests.mjs node -- tests/build/contract-manifest.test.mjs tests/docs/repository-writing.test.mjs tests/docs/changelog.test.mjs |
[o] |
| C14.1-5 | release 도구와 GitHub 설정 도구를 kit과 0.x workflow로 바꾼다. config/release.json은 archive 9개(<package>-npm-<version>.tgz인 npm package 7개, <package>-php-<version>.zip인 Composer package 2개), 모드별 manifest, tag <directory>/vX.Y.Z를 가진 Go module packages/generator-go와 packages/validator-go, tests/release-install의 사용자 project, crate 2개와 Python package 2개의 git-tag proof를 적는다. make release-assets는 먼저 build하고, release.yml은 make release-consumer를 실행하며, ci.yml은 main으로의 push에서, push-gate.yml은 모든 push에서 실행한다. ruleset, merge queue, .github/repository.json, scripts/github-repository.mjs, scripts/release.mjs, scripts/release-install.mjs를 제거하고, tests/build/python-git-pins.test.mjs가 pyproject.toml의 git pin 검사를 유지한다 |
— | make release-coverage, make release-versions TAG=v0.0.4, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs tests/build/python-git-pins.test.mjs |
[o] |
| C14.1-6 | owner 검사를 kit의 것으로 바꾸고 0.x workflow의 규칙을 적는다. config/owner-checks.json(make owner-validate가 검사)이 scripts/owner-checks.json을 대체하며 vendored copy, 설정 file, scripts/repository-files.mjs의 owner를 둔다. AGENTS.md와 AGENTS.ko.md는 main을 모든 row가 완료일 때 한 번 push하고, tag는 CI check ci-passed 뒤에 만들며, 공유 도구는 polyspec/kit에서만 바꾸고, 이 저장소는 config/*.json에서만 다르다고 적고 pull request, merge queue, ruleset 규칙을 뺀다. 제거: scripts/owner-check.mjs, scripts/owner-checks.json, owner 검사의 test |
— | make owner-validate, make docs-check, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/test-commands.test.mjs |
[o] |
| C14.1-7 | polyspec/kit v0.0.8을 vendor한다. 이 version은 dependency policy의 mutation 검사, policy schema의 root manifest, make install-tools TOOLS의 도구 선택을 고치고 make documents-stamp를 더한다. make kit-sync KIT_TAG=v0.0.8은 lock을 쓰고 두 번째 실행에서 unchanged를 출력한다 |
— | make kit-sync KIT_TAG=v0.0.8, make kit-check |
[o] |
| C14.1-8 | kit v0.0.8의 수정을 사용한다. composerPlatforms는 composer.json을 지정하고 pythonManifests는 packages/validator-python과 packages/generator-python의 manifest를 지정하며(그 setuptools==84.0.0 pin이 review에 들어가 registry 의존성은 87개), make dependency-policy-mutation-check가 통과하고, 모든 workflow job은 쓰는 도구만 설치한다(make install-tools TOOLS="npm", dependency review job은 make install-rust 뒤에 npm cargoAudit). 그래서 어떤 job도 쓰지 않는 Go나 cargo-audit을 설치하지 않는다 |
— | make dependency-policy-check, make dependency-policy-mutation-check, node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/form-comparison-ci.test.mjs |
[o] |
| C14.1-9 | push-gate.yml을 branch push에서만 시작한다(push: branches: ['**']). tag push가 이 workflow를 시작해 tag가 가리키는 commit에 완료되지 않은 push-gate 실행을 남겼고 make release-verify가 이를 거부했다. Red: tests/build/ci-local.test.mjs의 test only the release workflow starts on a tag push가 push-gate.yml에서 실패한다. Green: 통과하며 tag에서 시작하는 workflow는 release.yml뿐이다 |
— | node scripts/kit/run-tests.mjs node -- tests/build/ci-local.test.mjs |
[o] |
| C14.1-10 | 로컬 pipeline stack test가 kit의 hook module을 이름으로 쓰게 한다. examples/form-comparison/src/local-servers.test.mjs는 pipeline.browser.mjs와 record-stores.test.mjs가 setup과 teardown을 scripts/test-progress/hooks.mjs에서 import할 것을 요구했다. C14.1-2가 file의 import는 scripts/kit/test-hooks.mjs로 바꿨지만 test의 pattern은 바꾸지 않아 CI job Form comparison runner regressions가 ../pipeline.browser.mjs uses setup and teardown으로 실패했다. Red: test가 kit의 module에서 실패한다. Green: npm run test:form-comparison:source와 :library가 통과한다 |
— | node scripts/kit/run-tests.mjs node -- examples/form-comparison/src/local-servers.test.mjs, npm run test:form-comparison:source |
[o] |
| C14.2 | 릴리스 0.0.5: config/release.json이 적은 모든 manifest와 그 lock(package-lock.json, composer.lock, Cargo.lock 6개)의 version, packages/generator-python의 git pin, contracts/features.json의 version, packages/php-ext의 module version, 이를 읽는 검사를 0.0.5로 정한다. CHANGELOG.md와 CHANGELOG.ko.md의 ## Unreleased를 ## 0.0.5로 바꾸고 그 위에 빈 ## Unreleased를 새로 쓴다. dependency review를 기록하고, 나중에 지우는 로컬 tag에서 make release-assets와 make release-consumer-lock으로 tests/release-install의 사용자 project를 0.0.5 archive에 맞게 쓴다. tag는 push하지 않는다 |
— | make release-versions TAG=v0.0.5, make release-consumer TAG=v0.0.5, make release-go-tags TAG=v0.0.5, make dependency-policy-check |
[o] |
| C13.1-23-1 | C13.1-23의 scripts/release-pins.mjs에 config/owner-checks.json의 owner를 준다. C13.1-23에서 더한 검사는 스크립트에 owner를 주지 않아 make owner-validate가 실패했다. 규칙은 대상 release-pins와 test tests/build/python-git-pins.test.mjs를 적는다. Red: make owner-validate가 scripts/release-pins.mjs: the path matches no owner로 exit 2다. Green: exit 0이다 |
— | make owner-validate |
[o] |
| C14.3 | 공유 도구를 package @polyspec/kit으로 받고 kit의 consumer migration(kit K79)을 적용한다. vendor된 scripts/kit, tests/kit, kit.json, .kit/kit.lock.json을 지운다. Makefile은 copy scripts/kit/kit-bootstrap.mk를 include하고, 모든 recipe는 kit 도구를 $(KIT)/<tool>.mjs로 실행하며, make check는 CHECK_TARGETS를 kit의 guard로 실행하고, 모든 workflow job은 make install-tools TOOLS="npm"과 make install-packages를 먼저 실행한다. code는 @polyspec/kit/<module>을 import하고 자기 root를 스스로 정한다. kit.copies의 copy, config/layout.yml, config/toolchain.json의 선언된 도구를 더한다. 하한은 Node.js 26.11.1, PHP >=8.5와 platform 8.5.0, Python >=3.14, toolchain 줄 없는 Go 1.27.2, Rust 1.99.0, TypeScript 7.0.2이며, packages/generator-svelte의 TypeScript 6.0.3은 exception이다. Go module은 replace 대신 go.work로 서로를 찾는다. .npmrc는 strict-allow-scripts를 설정한다. kit 동작의 test는 kit으로 옮긴다. Red: tests/build/offline-checks.test.mjs, tests/build/repository-files.test.mjs, tests/build/test-commands.test.mjs의 새 case는 vendor된 tree에서 실패한다. Green: package를 설치하면 통과한다 |
— | make kit-check, make owner-validate, make docs-check, node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/offline-checks.test.mjs tests/build/repository-files.test.mjs tests/build/test-commands.test.mjs tests/build/runtime-version-policy.test.mjs tests/build/ci-local.test.mjs |
[o] |
| C14.3-1 | generator-core, form-binding, cli의 Vitest suite를 root에서 실행한다(C14.8에서 발견): 그 package script는 kit의 run-tests를 package 폴더에서 --cwd packages/<name>로 실행했고, kit은 root를 작업 디렉터리에서 정하므로 npm test -w가 spawn node ENOENT로 실패했다. package script를 지우고, make crudui-test-cli, make crudui-test-form-binding, test:forms, contracts/features.json의 verification 명령은 root에서 run-tests.mjs vitest --cwd packages/<name>을 실행한다. |
— | node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/test-commands.test.mjs, make manifest-check |
[o] |
| C14.4 | TypeScript와 JavaScript를 ESLint 대신 kit의 make lint-ts로 lint한다. make lint는 lint-ts(copy된 .oxlintrc.json의 oxlint, knip, API Extractor)와 packages/generator-svelte의 Svelte lint를 실행하며, 그 package는 자기 component에만 eslint-plugin-svelte와 typescript-eslint를 쓰는 ESLint를 유지한다. 루트 eslint.config.mjs, 루트와 다른 workspace의 ESLint package, 그 lint script, 규칙 crudui/no-await-after-test-registration을 지운다(kit은 그 규칙의 원인인 --test-force-exit 없이 node:test를 실행한다). Red: make lint가 npm run lint를 실행하므로 tests/build/lint-coverage.test.mjs가 실패한다. Green: make lint는 두 lint를 실행하고, 각각은 심어 둔 위반에서 실패한다 |
— | node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/lint-coverage.test.mjs tests/build/test-commands.test.mjs, make owner-validate |
[o] |
| C14.5 | TypeScript API를 TypeDoc 대신 API Extractor와 API Documenter로 검사하고 생성한다. 일곱 TypeScript package는 각각 copy된 config/api-extractor.kit.json(ae-undocumented와 ae-forgotten-export는 error)을 extend하는 api-extractor.json을 갖고 report etc/<package>.api.md를 commit한다. make lint-ts가 이를 실행하므로 scripts/check-doc-coverage.mjs는 Go, Rust, PHP만 검사한다. scripts/gen-api-docs.mjs는 doc model을 var/api-model/<package>에 쓰고 API Documenter로 page를 쓴다. typedoc, typedoc-plugin-markdown과 그 설정을 지운다. TypeDoc과 typescript-eslint가 없어졌으므로 root는 TypeScript 7.0.2를 선언하고, npm은 TypeScript 6.0.3, svelte-check, @sveltejs/package, svelte2tsx, typescript-eslint를 packages/generator-svelte에 둔다. Red: 설정이 없으면 scripts/check-doc-coverage.test.mjs와 scripts/gen-api-docs.test.mjs가 실패한다. Green: 통과하고, API Extractor는 심어 둔 문서 없는 export에서 실패한다 |
— | node node_modules/@polyspec/kit/dist/run-tests.mjs node -- scripts/check-doc-coverage.test.mjs scripts/gen-api-docs.test.mjs, make owner-validate |
[o] |
| C14.6 | kit의 review로 config/dependency-review.json을 다시 쓴다(kit 0.0.38에서 K79가 기록한 red): make kit-check가 config/dependency-review.json: / must have required property 'vcs'로 실패하고, make dependency-review RECORD=1이 examples/cross-check-console/validators/go/go.mod requires modules and examples/cross-check-console/validators/go/go.sum is missing에서 멈춘다. 이 Go module은 go.work로 형제를 resolve하며, kit은 0.0.39부터 이를 읽는다(kit K129). |
— | make dependency-review RECORD=1, make kit-check |
[o] |
| C14.7 | 폴더를 kit layout의 S5, S7, S9에 맞춘다(kit 0.0.38에서 K79가 기록한 red): make layout-check가 161개 finding을 보고하며 첫 줄은 schema: error: layout-root-folders: S5: the root holds only packages/, contracts/, docs/, scripts/, tests/, examples/, benchmarks/ and config/이다. |
— | make layout-check |
[o] |
| C14.8 | dependency review가 가리키는 stable release를 받는다(kit K131에서 발견): make dependency-policy-check가 20개 finding을 보고하며 첫 줄은 packages/generator-vue/package.json vitest 5.0.3 < 5.0.4: a newer stable release exists이다. |
— | make dependency-review UPDATE=1, make dependency-policy-check |
[o] |
| C14.9 | kit 0.0.40과 그 mise toolchain으로 옮긴다(kit K131): 게시된 파일의 integrity로 pin하고, mise.toml은 kit pin의 Go, uv를 가진 Python, ruff, vulture, alint, golangci-lint, deadcode, cargo-audit, Composer를 선언하며 mise.lock이 이를 잠그고, config/toolchain.json은 mise의 release, PHP, Node.js, TypeScript 예외만 둔다. .go-version과 .python-version은 지운다. CI는 setup-go, setup-python, setup-php의 Composer 대신 make install-tools로 그 도구를 설치하고, Go module cache는 actions/cache로 옮긴다. kit이 가져오는 sprintf-js advisory는 kit처럼 받아들인다. |
— | make kit-check, make toolchain-check, make docs-check, make owner-validate, node --test tests/build/runtime-version-policy.test.mjs tests/build/form-comparison-ci.test.mjs tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs |
[o] |
| C14.10 | 빌드된 checkout에서 tests/build의 도구 test가 통과하게 한다(kit K131에서 발견): checkout이 빌드하지 않은 package(dist, vendor, bench program) 말고도, TypeScript 7에 없는 TypeScript JavaScript API(Cannot read properties of undefined (reading 'ReactJSX') 외 14건), 옛 kit의 vendor 경로(node scripts/kit/full-run.mjs rerun-failed, scripts/kit/run-tests.mjs cargo), examples/form-comparison/src/local-servers.mjs에서 checkout lock 밖의 Composer reinstall, scripts/gen-api-docs.mjs:58의 crate 이름 crudui-validator로 실패한다. |
— | node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build |
[o] |
| C14.10-1 | contract manifest의 값 export를 typescript/unstable/sync, 그 가상 file system과 .svelte 선언 stub 대신 es-module-lexer로 각 package.json 진입점의 빌드된 ES module 파일에서 읽고, 각 export * from은 Node 자체의 해석으로 따라간다. npm run manifest:check는 먼저 패키지를 빌드한다(C14.10). |
— | node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/contract-manifest.test.mts, make manifest-check |
[o] |
| C14.11 | kit 0.0.42로 옮긴다: 게시된 파일의 integrity로 pin하고, make toolchain-lock은 mise.toml의 도구를 linux-arm64와 macos-x64에도 잠그며(kit K134), 복사된 bootstrap은 var/tools/go.mod를 써서 checkout의 Go module의 ./...가 mise가 설치한 Go를 걷지 않게 한다(kit K135). |
— | make kit-check, make toolchain-check, make owner-validate, node node_modules/@polyspec/kit/dist/run-tests.mjs node -- tests/build/runtime-version-policy.test.mjs tests/build/ci-local.test.mjs tests/build/offline-checks.test.mjs |
[o] |
| C14.12 | make lint-ts가 통과하게 한다(C14.10에서 발견): oxlint는 약 700건을 보고하며 그중 516건은 node:test 호출의 typescript(no-floating-promises)이고, knip은 약 230건, API Extractor는 일곱 package 모두에서 주로 187건의 ae-missing-release-tag 경고로 실패한다. node:test와 release tag 지적은 kit 0.0.43이 닫는 kit의 빈틈이며, 나머지는 code에서 고치고 실제 dead code는 지운다. |
— | make lint-ts |
[o] |
| C14.13 | kit 0.0.44로 옮긴다(kit K138, K139, K142–K147, K149–K151, 0.0.43 포함): 게시된 파일의 integrity로 pin하고, make kit-sync가 bootstrap, node:test 등록을 허용하는 .oxlintrc.json, layout의 entry를 담은 knip.json, ae-missing-release-tag가 없는 config/api-extractor.kit.json, cache를 var/ruff에 두는 ruff.toml, 추가형 root 파일을 가진 config/layout.kit.yml을 복사하므로 config/layout.yml은 vars.root_files에 .npmrc, composer.json, composer.lock만 적는다. make install-tools는 rust-toolchain.toml의 Rust toolchain을 설치한다. 새 lock의 검토는 최신 stable release puppeteer 25.14.0, playwright 1.64.1, tsup 9.0.0을 받는다. Red: push의 CI run 38571337025는 toolchain-check가 toolchain '1.99.0-x86_64-unknown-linux-gnu' is not installed로 실패했고, Pages run 38571336972는 API Extractor가 ae-missing-release-tag로 실패했다. |
— | make kit-check, make toolchain-check, make layout-check, make dependency-policy-check, make build, make crudui-test-build, make crudui-test-packages |
[o] |
| C14.14 | Pages workflow가 문서 web을 build한다. scripts/docs-web/build.mts는 API Documenter의 API page를 그대로 받아 그 첫 heading을 level과 상관없이 제목으로 삼고, 손으로 쓴 page에는 level-one heading이 정확히 하나라는 규칙을 유지한다. 생성된 markdown은 다시 쓰지 않는다. Red: Pages run 38594695043의 docs-web이 Documentation requires exactly one level-one heading: docs/api/crudui-form-binding/crudui-form-binding.bindform.md로 실패했고, C14.5가 API page를 API Documenter로 바꾼 뒤로 red였다. |
— | make docs-web |
[o] |
| C14.15 | equalTo, notEqual, enddate, unique의 rule parameter를 모든 언어에서 specification을 load할 때 spec 문장과 공유 vector로 검사한다. 그 뒤 TypeScript rule은 String(ruleParam) 대신 검사된 문자열을 읽는다. Red: equalTo.ts와 enddate.ts의 oxlint no-base-to-string. 문자열이 아닌 parameter가 rule에 닿았다. |
— | 모든 언어의 공유 vector, make lint-ts |
[o] |
| C14.16 | C14.12의 oxlint finding이 드러낸 결함을, 각각 이전에 실패한 test와 함께 고친다. 배열이나 객체의 TypeScript ==, !=, in, connectForm이 control에 쓰는 값, form comparison의 Svelte adapter의 dispose, 그 PHP 서버의 stop이다. |
— | 각 결함의 test | [o] |
| C14.17 | CI job이 toolchain-check 뒤까지 실행되며(C14.13) 닿는 검사가 통과한다. test:forms는 C14.3-1이 package script를 없앤 form-binding의 Vitest suite를 root에서 실행하고, PHP engine test는 build entry point scripts/build-crudui-php-extension.mts를, local-servers test는 TypeScript 이식(C14.7)의 import @polyspec/kit/test-hooks를 적는다. Red: CI run 38597830858: @polyspec/crudui-form-binding의 npm error Missing script: "test", The input did not match the regular expression /node scripts\/build-crudui-php-extension\.mjs/, ../pipeline.browser.mjs uses setup and teardown. |
— | make crudui-test-forms, make crudui-test-php-engine, make crudui-test-form-comparison |
[o] |
| C14.18 | crudui의 Go module은 ordered-json의 무시되는 clone 없이 resolve된다. go.work는 checkout의 module만 쓰고, C14.7이 tag가 아직 없는 packages/{validator,generator}/go로 옮겼으므로 각 workspace module은 다른 module을 main의 push된 commit의 pseudo-version으로 요구한다(go list -m이 resolve한 version으로 go mod edit -require). form-comparison Go 서버는 github.com/polyspec/ordered-json/go v0.0.3을 요구하고, make install은 go mod download로 Go module을 내려받으며, 검사는 offline으로 남는다. 다음 crudui release가 pseudo-version을 tag packages/{validator,generator}/go/vX로 바꾼다. Red: kit 0.0.45에서 make dependency-policy-mutation-check가 .form-comparison/sources/ordered-json/go의 use로 실패했고, 0.0.44에서는 benchmarks/validators/go/go.mod requires modules and benchmarks/validators/go/go.sum is missing로 실패했다. |
— | make dependency-policy-mutation-check, Go build와 test |
[o] |
| C14.19 | kit 0.0.45로 옮긴다(kit K152–K154): 게시된 파일의 integrity로 pin하고, make kit-sync가 pin된 런타임의 binary를 무시하는 knip.json을 복사하며, config/knip-entries.json은 make, test, page, 도구가 경로로 실행하는 파일과 workspace packages/svelte, packages/svelte/tools, packages/form-binding, cross-check 서버를 그 entry와 무시하는 의존성과 함께 선언한다. knip이 찾은 쓰이지 않는 의존성은 없애고 목록에 없는 의존성은 선언한다. make dependency-review RECORD=1이 lock을 기록한다. |
— | make kit-check, make dependency-policy-check, make dependency-policy-mutation-check |
[o] |
| C14.20 | form comparison은 knip, Node, esbuild, Vite가 읽는 root package.json의 표준 subpath import로 alias를 resolve한다. #adapter와 #stage는 각 frame·stage build가 condition을 넘기는(resolve.conditions) 조건부 import이고, #html, #react/*, #vue/*, #svelte/*는 source에 대응하며, Vite alias는 없어진다. benchmark console은 source를 import하고 build.mjs가 그것을 bundle하며, record-view.mjs는 public/에 있으므로 모든 import가 source tree와 서비스될 때 모두 resolve된다. Red: knip이 examples/form-comparison에서 resolve되지 않는 import 41개를 보고했다. |
— | make lint-ts(knip), form-comparison build와 그 test |
[o] |
| C14.21 | benchmark console은 docs/operations/form-comparison.md가 요구하는 대로 pointer가 비교 frame 위에 있을 때 capture와 비교를 멈춘다. d5fd5ca3 이후 어떤 page도 pointerOverFrame을 부르지 않았다. Red: guard의 Chromium test가 requirePointerOutside is not a function으로 실패했고 check-interaction의 연결 test가 실패했다. |
— | src/browser-job.browser.mjs, check-interaction.test.mjs |
[o] |
| C14.22 | docs/spec/schema.md의 작성 type은 공개된다. @polyspec/crudui-validator의 entry는 FieldSpec, RangeFieldSpec, DesignSlot, Items, Multiple, Lang, ConditionMap과 이것들이 가리키는 모든 type을 각각 TSDoc과 함께 export하고, API report etc/crudui-validator.api.md가 이것들을 나열한다. ae-undocumented는 error로 남는다. 원인: docs/spec/schema.md:66과 :84-110은 schema.ts를 작성 type으로 정하는데, entry는 그중 아무것도 export하지 않았다. |
— | packages/validator/ts의 API Extractor, types.roundtrip.test.ts의 type test |
[o] |
| C14.23 | docs/operations/form-comparison.md는 C14.20이 build하는 대로 제공되는 module을 설명한다. public/의 module은 브라우저가 해석할 수 있는 import로 그대로 제공되고, build는 benchmark console을 그것이 import하는 source module(browser-job.mjs, frame-readiness.mjs, frame-pointer.mjs, runtime-paths.mjs, snapshot module)과 함께 bundle하므로 어떤 source module도 공개 디렉터리에 복사되지 않는다. src/browser-job.browser.mjs의 Chromium case는 src/의 frame-readiness.mjs 대신 public/의 module을 로드한다. 원인: spec(305-307행)과 그 case는 C14.20이 bundle로 바꾼, 공개 디렉터리에 복사되는 module을 여전히 설명했다. |
— | src/browser-job.browser.mjs, make docs-check |
[o] |
| C14.24 | TypeScript validator의 type assertion이 실행된다. 그 Vitest 설정은 Vitest의 typecheck mode(test.typecheck)를 켜고, 이 mode는 src/*.test-d.ts의 expectTypeOf assertion을 tsc와 package의 tsconfig.json으로 검사하며, types.roundtrip.test.ts의 assertion은 src/types.test-d.ts로 옮겨진다. 원인: runtime Vitest 실행은 expectTypeOf를 평가하지 않고 package의 tsconfig.json은 test 파일을 제외하므로 아무것도 그것을 검사하지 않았다. Red: 틀린 assertion 하나가 Vitest 실행을 실패시킨다. |
— | src/types.test-d.ts와 src/types.roundtrip.test.ts에 대한 packages/validator/ts의 Vitest suite |
[o] |
| C14.25 | ==, !=, in은 모든 port에서 두 배열이나 두 객체를 같게 비교한다. docs/spec/expressions.md는 JSON 값의 구조 동등성(같은 type이고, 배열은 같은 원소가 같은 순서로, 객체는 같은 key 집합과 같은 값을 가지며, 숫자는 값으로 비교하고, 배열은 객체와 같지 않다)과 문자열 비교에서 boolean의 text true나 false를 정의한다. tests/fixtures/expr의 공유 vector가 둘을 검사하고 TypeScript, PHP, Go, Rust, Python, PHP extension의 C engine이 이를 통과한다. 원인: 두 배열이나 두 객체를 TypeScript와 Python은 identity로, PHP, Rust, C는 구조로 비교했고 Go에서는 결코 같지 않았으며, C engine은 boolean을 1이나 빈 text로 썼다. |
— | TypeScript, PHP, Go, Rust, Python의 expression conformance, packages/php-ext/tests/engine.test.mjs의 C expression fixture test |
[o] |
| C14.26 | K151 이후 kit의 make install-tools(kit가 rustup으로 rust-toolchain.toml의 Rust toolchain을 설치한다)와 중복되는 make install-rust를 없앤다. target, make install에서의 자리, .github/workflows/ci.yml, dependency-review.yml, pages.yml의 step(그 Cargo cache step은 key를 위해 toolchain이 있도록 make install-tools 뒤에 온다), 이를 가리키는 문서와 test가 대상이다. tests/build/runtime-version-policy.test.mts는 Rust toolchain을 make install-tools의 도구로 세고 make install-rust를 실행하는 step에서 실패한다. |
— | node --test tests/build/runtime-version-policy.test.mts tests/build/form-comparison-ci.test.mts, make docs-check |
[o] |
| C14.27 | CHECK_TARGETS의 kit lint target이 모두 통과한다. make install-tools와 Composer 설치 뒤 make lint-python(ruff check, ruff format, vulture), make lint-php(phpcs, PHPStan), make lint-rust(-D warnings를 준 cargo clippy), make lint-go(golangci-lint, deadcode)는 finding을 보고하지 않는다. 각 finding은 disable이나 file별 ignore 없이 code에서 고치고, #[expect(..., reason)]는 lint가 틀린 곳에서만 구체적인 이유와 함께 쓴다. .github/workflows/ci.yml의 build-lint job은 어떤 CI job도 실행하지 않던 CHECK_TARGETS의 lint target을 모두 실행한다. tests/fixtures/expr/generate.ts의 JSON.rawJSON cast는 TypeScript 7의 표준 lib entry 중 어느 것도 이를 선언하지 않을 때만 그 이유와 함께 남는다. make lint-ts는 kit K159와 K161에서만 red로 남는다. make lint-php는 package마다 vendor/autoload.php를 요구하는 PHPStan step(kit K163(0.0.46)이 가장 가까운 상위 folder의 autoloader로 바꾼다)과, C14.30이 재구성하는 package 밖 PHP program의 PSR-1 finding과 spl_autoload_register에서만 red로 남는다. make lint-python은 C14.29가 없애는 sys.path 변경의 TID251과 E402에서만 red로 남는다. make lint-go는 staticcheck ST1005에서만 red로 남는다. crudui의 error 문자열은 모든 port가 그대로 반환하는 명세의 message이고, kit K164(0.0.46)가 이 검사를 끈다. Red: lint-python, lint-php, lint-rust, lint-go가 각각 실패했다. |
— | make lint-python, make lint-php, make lint-rust, make lint-go |
[o] |
| C14.28 | 컴파일된 program은 추적되지 않고 Go build는 var/에 쓴다. 0ca0ca2f(C14.3)가 examples/cross-check-console/validators/go/, examples/form-comparison/servers/go/, tests/native-generators/programs/go/에 commit한 go라는 이름의 Mach-O 실행 파일 세 개를 없앤다. 이것은 각 module에서 실행한 -o 없는 go build가 썼고 저장소의 어떤 명령도 쓰지 않는다. cross-check console의 build:go는 validator를 var/build/cross-check-console/go-validate로, benchmarks/validators/drivers.mjs는 Go benchmark driver를 var/build/benchmarks/polyspec-crudui-bench로 build하므로 두 옛 경로의 .gitignore 줄은 없어진다. tests/build/tracked-files.test.mts는 Mach-O, ELF, PE 실행 파일의 magic number로 시작하는 추적 파일에서 실패한다. Red: 이 case가 세 파일을 지목했다. |
— | node --test tests/build/tracked-files.test.mts, node benchmarks/validators/build-drivers.mjs, cross-check console의 npm run build:go |
[o] |
| C14.29 | Python suite는 설치된 package로 실행된다(S3). root pyproject.toml은 packages/validator/python, packages/generator/python, tests/conformance/python의 uv workspace이고, 그 [tool.uv.sources]는 generator의 git pin 대신 workspace의 validator를 쓰며 uv.lock을 가진다. make crudui-install-python은 kit의 make python-venv로 var/python을 만들고 workspace를 그 안에 sync한다. suite는 tests/conformance/run-suite.mts를 거쳐 run-tests python --venv 아래에서 상대 import를 쓰는 package tests로 실행되고, evidence helper는 tests/conformance/python의 package polyspec.crudui.conformance이며, tests/conformance/runner.py, runs.py, evidence.py는 없어진다. native generator program과 cross-check console의 Python program은 sys.path 없이 module을 import한다. tests/ordered-json과 make test-ordered-json은 없어진다. 이것은 OrderedJSON checkout의 registry와 adapter를 구동했는데, 이는 OrderedJSON의 parity 검사와 테스트가 소유한다(R7). record server의 소비자 테스트(json.test.mjs, test-json.php, Rust JSON 테스트, Go 서버에 새로 추가한 json_test.go)가 crudui의 패키지 사용을 확인한다. Red: ruff check가 TID251 20개와 E402 하나를 보고했고, 새 명령에서 각 suite가 실패했다. |
— | make lint-python, make crudui-test-validator-python, make crudui-test-generator-python, Go record server의 go test |
[o] |
| C14.30 | package 밖의 PHP program이 S2와 S3을 따른다. examples/form-comparison의 class(form-shape.php, generation.php, json.php, records.php, repository.php, request-body.php)는 Polyspec\Crudui\ 아래 namespace의 PSR-4 class로 file마다 class 하나이고, 그 test-*.php script와 packages/php-ext/tests/api.php는 PHPUnit test가 된다. 다른 PHP entry script(api.php, 두 generate-*.php generator, scripts/php-doc-coverage.php, benchmarks/validators/bench-php.php, cross-check console과 native generator의 PHP program)는 side effect 옆에 symbol을 선언하지 않는다. scripts/php-package-autoload.php는 autoloader를 등록하지 않는다(PHPStan, S2). PHP package는 기본 symlink인 Composer path repository로 연결되고, OrderedJSON PHP는 config/release-assets.json의 release zip을 artifact repository에서 받으며, scripts/php-package-autoload.php는 없어지고, php-ext mode에서는 FormComparison을 뺀 Polyspec\Crudui\ 아래의 모든 class가 internal이어야 한다(ReflectionClass::isInternal()). form comparison과 packages/php-ext의 PHPUnit suite의 php-ext leg는 kit K165의 반복 가능한 --php-extension을, make lint-php는 kit K163을 기다린다. phpcs와 root autoloader를 쓰는 PHPStan은 아무것도 보고하지 않는다. Red: phpcs가 PSR-1 finding 23개(MissingNamespace 6개, SideEffects 17개)를, PHPStan이 scripts/php-package-autoload.php의 spl_autoload_register를 보고했다. |
— | make lint-php(kit K163과 함께), PHP package와 form comparison의 PHPUnit suite, make crudui-test-php-api |
[o] |
| C14.31 | tests/fixtures의 TypeScript fixture generator 열 개(compose, detail-render, expr, form-outline, form-render, list-render, validate의 module 네 개)는 cast, any, @ts-expect-error 없이 type 검사를 받는 S9의 script다. detail-render, list-render, form-outline, validate의 것은 공개 package entry를 쓰는 .mts file이고, root tsconfig.json의 include가 CI가 실행하는 make typecheck에서 이들을 검사한다. 이들은 node로 실행된다. internal entry는 package code만 import하므로(contracts/features.json), package 내부가 필요한 generator는 그 package에 속한다(R7). compose와 expr의 것은 validator의 internal entry를 쓰는 packages/validator/ts/scripts/generate-{compose,expr}-cases.mts이고, 그 entry는 Lexer, Parser, ParseError, MAX_EXPRESSION_DEPTH를 export하며, tsconfig.scripts.json이 validator의 typecheck에서 root option으로 이들을 검사한다. form-render의 시나리오는 tests/fixtures/form-render/cases.json을 쓰는 유일한 script인 packages/react/scripts/generate-form-render-cases.mts에 합쳐지고, 이 script는 사례 순서를 유지한다. tests/fixtures/json-raw.d.mts가 두 호출자를 위해 JSON.rawJSON을 선언한다. 어떤 generator도 package source를 경로로 import하지 않으므로 root option은 package source에 적용되지 않는다. 다시 생성한 각 cases.json은 byte 단위로 같다. Red: 어떤 tsc도 generator를 검사하지 않았고, root option으로 이들을 검사한 tsc는 error 555개를 보고했다. 그중 42개는 generator에, 나머지는 이들이 import한 package source에 있었다. |
— | make typecheck, make manifest-check, 각 generator 실행과 그 cases.json의 git diff --exit-code |
[o] |
| C14.31-1 | tests/fixtures/form-render/cases.json의 사례 순서는 packages/react/scripts/generate-form-render-cases.mts의 source에서만 나온다. scenario-form-render-cases.ts의 시나리오가 그 순서대로 오고, 그 뒤에 위젯 사례와 작성한 사례가 그 순서대로 오며, 같은 이름의 위젯 사례나 작성한 사례는 시나리오의 자리를 대신한다. generator는 cases.json을 읽지 않는다. 다시 정렬한 cases.json을 commit하고, 두 번째 실행은 이를 byte 단위로 같게 쓰며, 이를 읽는 suite가 통과한다(C14.31). Red: generator가 commit된 cases.json의 순서를 유지해서 그 출력이 file의 이력에 따라 달라졌다. |
— | 두 번째 generator 실행과 git diff --exit-code, React, Vue, Svelte, HTML, Python, Rust의 form-render suite, tests/form-markup/naming.test.mts |
[o] |
| C14.32 | kit 0.0.46으로 옮긴다(kit K155, K157–K168). 게시된 파일의 integrity로 pin하고, make kit-sync가 .oxlintrc.json, .golangci.yml, bootstrap, 그리고 Composer monorepo의 root composer.json과 composer.lock을 허용하는 config/layout.kit.yml을 복사하므로 config/layout.yml의 vars.root_files에서 그 둘을 뺀다(K167). make dependency-review RECORD=1이 lock을 기록한다. |
— | make kit-check, make layout-check, make dependency-policy-check, make dependency-policy-mutation-check |
[o] |
| C14.33 | 폼 비교 PHPUnit suite의 php-ext leg를 실행한다(C14.30이 대기로 지명했다). local verification은 servers/php의 suite를 php-ext 모드로 한 번 더 실행하며, ordered_json.so와 crudui.so를 kit run-tests의 --php-extension(kit K165)으로 로드하고 PHP 라이브러리를 구성하는 group php는 뺀다. 그 PHPUnit process가 로드하는 tests/php의 child process helper PhpProcess는 Polyspec\Crudui\TestSupport\에서 php-ext 실행이 class를 internal로 요구하는 namespace 밖인 Polyspec\CruduiTestSupport\로 옮긴다. packages/php-ext의 suite는 이미 PHP_EXTENSION으로 child process에서 모듈을 로드하므로 leg가 필요 없다. Red: examples/form-comparison/check-verification.test.mjs가 없는 단계 php-ext-mode로 실패했고, php-ext 모드의 suite는 test 21개 중 8개가 실패했다. group php의 test 6개와, A CRUDUI class loaded from PHP source in the PHP extension process: Polyspec\Crudui\TestSupport\PhpProcess로 실패한 구성 2개다. |
— | node node_modules/@polyspec/kit/dist/run-tests.mjs node -- examples/form-comparison/check-verification.test.mjs, php와 php-ext 모드의 servers/php suite, packages/php-ext의 PHPUnit suite, make lint-php |
[o] |
| C14.34 | 폼 비교는 모든 언어에서 OrderedJSON 0.0.3 대신 게시된 릴리스 0.0.9를 쓴다. config/release-assets.json이 sha256으로 고정하는 release zip polyspec-ordered-json-php-0.0.9.zip의 Composer 패키지 polyspec/ordered-json 0.0.9(0.0.6부터 archive 이름에 언어가 들어간다), Go module github.com/polyspec/ordered-json/packages/go v0.0.9(go get. OrderedJSON의 T1.42부터 module 경로가 폴더를 이름 짓는다), Git tag v0.0.9의 Rust crate(cargo update), 그리고 패키지가 packages/에 있는 tag v0.0.9 체크아웃의 npm link packages/js와 확장 source packages/php-ext/src다. make dependency-review RECORD=1이 lock을 기록하며 더 새로운 안정 릴리스를 보고하지 않는다. Red: 0.0.9가 게시되어 있는데 make dependency-review가 OrderedJSON 0.0.3을 검토했고, 체크아웃을 옮긴 뒤 taggedNpmPackages가 v0.0.3을 이름 지어 tests/build/dependency-health.test.mts가 실패했다. |
— | node node_modules/@polyspec/kit/dist/run-tests.mjs node -- examples/form-comparison/src/ordered-json-source.test.mjs examples/form-comparison/check-verification.test.mjs examples/form-comparison/src/local-servers.test.mjs tests/build/dependency-health.test.mts, php와 php-ext 모드의 servers/php PHPUnit suite, Go 서버의 go test, Rust 서버의 cargo test --locked, make lint-go, make lint-php, make dependency-policy-check |
[o] |
| C14.35 | kit 0.0.47로 옮긴다(kit K156, K170). 게시된 파일의 integrity로 pin하고, make kit-sync가 non-null assertion을 실패시키고 type-aware rule을 TypeScript 파일에만 적용하는 .oxlintrc.json을 복사한다. make dependency-review RECORD=1이 lock을 기록하며 packages/svelte/tools의 @sveltejs/package 3.0.0 < 3.0.1을 보고하고, make dependency-review UPDATE=1이 그것을 받는다. |
— | make kit-check, make dependency-policy-check, make dependency-policy-mutation-check, packages/svelte의 build |
[o] |
| C14.36 | kit 0.0.48로 옮긴다(kit K169, K171). 게시된 파일의 integrity로 pin한다. Node.js release는 .node-version에서 mise.toml의 node = "26.12.0"으로 옮기고 make toolchain-lock이 그것을 lock하며, .node-version과 config/toolchain.json의 node는 지운다. 모든 package.json의 engines.node는 >=26.12.0이고, Makefile의 각 recipe는 checkout의 도구를 kit.mk의 변수나 절대 경로로 시작하며, CI의 setup-node는 mise.toml을 읽고, workflow의 action은 전체 SHA로 pin한다. make kit-sync가 bootstrap과 release.yml을 복사한다. |
— | make kit-check, make toolchain-check, make layout-check, make lint-ts, main의 ci-passed |
[o] |
| C14.37 | @polyspec/crudui-generator-core에서 컴파일된 폼 템플릿 JSON의 decoder를 공개해, fsm의 GUI 같은 client가 페이지의 템플릿을 cast 없이 읽게 한다. decodeFormTemplate(value)는 파싱한 값을 새 contracts/schema/form-template.schema.json에 대해 ajv의 compile<FormTemplate>로 검사하고, 첫 번째로 잘못된 멤버의 JSON Pointer와 함께 FormTemplateError(INVALID_FORM_INPUT)를 던진다. TSDoc, API report, contract manifest, docs/spec/form-runtime.md가 그것을 밝힌다. |
— | packages/generator/ts/src/decode-template.test.ts, 패키지의 API Extractor, npm run manifest:check |
[o] |
| C14.38 | decodeFormTemplate은 contracts/schema/form-template.schema.json의 ajv standalone 검증 코드로 검사한다. scripts/generate-form-template-validator.mts가 그것을 packages/generator/ts/src/form-template-validator.mjs에 쓰고(make form-template-validator) make build가 검사한다(make form-template-validator-check). template T25.6-2와 같다. @polyspec/crudui-generator-core는 실행 중에 ajv를 import하지 않고 코드를 컴파일하지 않으므로 unsafe-eval 없는 Content-Security-Policy가 그것을 허용하며, ajv는 패키지의 개발 의존성이다. pointer와 오류는 C14.37과 같다. |
— | packages/generator/ts/src/decode-template.test.ts, make form-template-validator-check |
[o] |
| C14.39 | packages/cli는 소스와 테스트를 덮는 엄격한 tsconfig(루트 tsconfig.json의 옵션에 noUnusedLocals, noUnusedParameters, noImplicitReturns를 더한 것)를 가지며, 그 typecheck script와 그래서 make typecheck가 그것을 실행한다. 코드는 cast 없이 그것을 통과한다. |
— | make typecheck, packages/cli의 vitest |
[o] |
| C14.40 | packages/generator/ts/src/form.test.ts의 테스트는 공유 시나리오 tests/fixtures/form-session/scenario.mts를 @ts-expect-error 없이 import한다. 소스와 테스트를 검사하는 패키지의 tsconfig는 packages/react의 tsconfig처럼 저장소 루트까지 닿고 .mts 확장자를 허용하며, tsconfig.build.json은 src를 선언의 루트로 유지한다. |
— | make typecheck, packages/generator/ts의 vitest와 build |
[o] |
| C14.41 | kit 0.0.51로 옮긴다(kit K172–K192, 0.0.49와 0.0.50 포함). 게시된 파일의 integrity로 pin한다. make kit-sync는 패키지 설치 전에 $(NODE)를 정의하는 kit-bootstrap.mk를 복사하므로(K185) crudui-install-ordered-json은 다시 $(NODE)로 node를 시작하고, ignoreBinaries 없는 knip.json을 복사하므로 config/knip-entries.json이 script와 테스트가 실행하는 binary go, php, make를 선언한다(K176). 생성된 폼 템플릿 validator는 oxlint-disable 머리말 대신 config/knip-entries.json의 ignore 항목으로 knip과 oxlint에서 빠진다(K172). make dependency-review UPDATE=1이 es-module-lexer 3.0.4를 받고 make dependency-review RECORD=1이 lock을 기록한다. |
— | make kit-check, make toolchain-check, make layout-check, make lint-ts, make dependency-policy-check, main의 ci-passed |
[o] |
| C14.42 | crudui describe는 docs/spec/expressions.md의 token table을 줄을 버리거나 자르지 않고 읽는다: 각 줄을 exec(line) ?? []로 맞추고 다른 모양의 줄을 건너뛰었으며, 탐욕적인 pattern cell이 LPAREN RPAREN 줄을 예시의 escape되지 않은 ||에서 잘랐고(GFM도 이를 cell로 나눈다), pattern은 ( ) | (.a |로 읽혔다. 이제 header와 separator 다음의 모든 줄은 cell이 |를 escape하는 token 줄이거나, describe가 그 줄을 지목하며 throw하고, 문서는 그 ||를 escape한다; precedence 목록은 번호 항목의 filter를 명시적으로 유지하고, 없는 section은 ''로 읽히는 대신 throw한다. toolchain test는 ''와 맞추는 대신(recorded.node ?? '') mise.toml이 선언하지 않은 도구를 지목한다. Red: describe가 LPAREN RPAREN에 pattern ( ) | (.a |를 주었다; green: ( )와 17개 token |
— | make crudui-test-cli, make docs-check, tests/build/runtime-version-policy.test.mts |
[o] |
| C14.43 | 릴리스 0.0.6: config/release.json이 적은 모든 manifest와 그 lock(package-lock.json, composer.lock 파일들, Cargo.lock 6개)의 version, packages/generator/python의 git pin, contracts/features.json의 version, packages/php-ext의 module version, 이를 읽는 검사와 문서를 0.0.6으로 정한다. CHANGELOG.md와 CHANGELOG.ko.md의 ## Unreleased를 ## 0.0.6으로 바꾸고 그 위에 빈 ## Unreleased를 새로 쓴다. dependency review를 기록하고, make release-assets와 make release-consumer-lock으로 tests/release-install의 사용자 project를 0.0.6 archive에 맞게 쓴다. 릴리스 commit에 로컬 tag v0.0.6, packages/validator/go/v0.0.6, packages/generator/go/v0.0.6을 만들고 maintainer가 그것을 push한다. Go workspace module은 이 릴리스에서 pseudo-version 요구를 유지한다 |
— | make release-versions TAG=v0.0.6, make release-consumer TAG=v0.0.6, make release-go-tags TAG=v0.0.6, make dependency-policy-check |
[o] |
| C14.43-1 | v0.0.6에는 게시된 release가 없다. 그 release proof(run 38699087245)는 generator의 Requires-Dist에 있는 sibling wheel의 release URL을 draft가 나열하는 URL과 비교하는 kit의 검사에서 실패했으며, GitHub는 release가 게시되기 전까지 그 URL을 untagged-…로 준다. kit K195가 그 검사를 고친다. draft release v0.0.6은 지우고 tag는 남기며, 0.0.7이 그것을 대신한다. |
— | gh release view v0.0.6이 release가 없다고 보고한다 |
[o] |
| C14.44 | 두 Python package를 wheel로 릴리스한다(kit K29). K29는 config/release.json이 아직 적은 proof.gitTag의 Python git-tag kind를 없앴으므로 make release-versions가 실패했다. polyspec-crudui-validator와 polyspec-crudui-generator는 pyproject.toml이 archive manifest인 wheel package이고, consumers.wheel이 각각을 import하며, config/toolchain.json의 pythonBackend가 setuptools==84.0.0을 그 wheel의 sha256으로 pin한다(K166). generator는 polyspec-crudui-validator==<version>을 요구하고 [tool.uv.sources]가 그것을 workspace의 validator로 풀며, scripts/release-pins.mts, make release-pins, tests/build/python-git-pins.test.mts는 지운다. |
— | make release-coverage, make kit-check, 설정을 읽는 make release-versions TAG=v0.0.5, make dependency-policy-check |
[o] |
| C14.45 | 모든 workflow의 PHP는 .php-version의 minor다. 새 파일은 crudui의 PHP minor(8.5, workflow가 inline으로 pin하던 config/toolchain.json의 minor)를 담고, workflow의 모든 setup-php step은 php-version-file: .php-version을 읽으므로 PHP API와 PHP validator job에는 PHP matrix가 없다. kit의 release.yml은 runner의 PHP 8.3을 유지하는 대신 release proof에 그 PHP를 설치한다(template v0.0.7). |
— | tests/build/runtime-version-policy.test.mts, tests/build/ci-local.test.mts, make toolchain-check |
[o] |
| C14.46 | cross-check console suite가 Vite SSR boot에서 멈추지 않는다. 로컬에서 npm test --prefix examples/cross-check-console/server의 3번 중 2번 실행에서 각 test 파일의 첫 4-renderer SSR case가 120초에서 timeout이었고, getEngine만의 boot는 rolldown의 dependency scan에서 실패했다. 원인을 찾아 이전에 실패하는 test와 함께 고친다. timeout을 늘리거나 재시도하지 않는다. |
— | npm test --prefix examples/cross-check-console/server |
[o] |
| C14.47 | tests/widget-script-runs.test.mts에 navigation race가 없다. 한 실행이 "Execution context was destroyed, most likely because of a navigation"(chromium vue client)와 "window.scriptRunsTest is undefined"(firefox html client)로 실패했고 다시 실행하면 통과했다. 원인을 찾아 이전에 실패하는 test와 함께 고친다. timeout을 늘리거나 재시도하지 않는다. |
— | tests/widget-script-runs.test.mts |
[o] |
| C14.48 | kit 0.0.53으로 옮긴다(kit K191, K193, K195, 0.0.52 포함). 게시된 파일의 integrity로 pin한다. make kit-sync는 어떤 release step보다 먼저 make kit-check를 실행하는 release workflow를 복사하며, kit-check는 crudui가 C14.45부터 선언하는 .php-version을 요구한다. release proof는 release가 각 sibling wheel을 나열하는지 확인한다(K195). make dependency-review RECORD=1이 lock을 기록한다. |
— | make kit-check, make toolchain-check, make layout-check, make lint-ts, make dependency-policy-check, main의 ci-passed |
[o] |
| C14.49 | 게시되지 않은 v0.0.6(C14.43-1)을 대신하는 릴리스 0.0.7: config/release.json이 적은 모든 manifest와 그 lock(package-lock.json, composer.lock, Cargo.lock 6개, uv.lock)의 version, Python generator가 요구하는 validator wheel의 sibling URL, contracts/features.json의 version, packages/php-ext의 module version, 이를 읽는 검사와 문서, npm 사용자 project의 version을 0.0.7로 정한다. CHANGELOG.md와 CHANGELOG.ko.md의 ## Unreleased를 ## 0.0.7로 바꾸고 그 위에 빈 ## Unreleased를 새로 쓴다. dependency review를 기록하고, make release-assets와 make release-consumer-lock으로 tests/release-install의 사용자 project를 0.0.7 archive에 맞게 쓴다. CI가 ci-passed success로 끝난 head commit에 로컬 annotated tag v0.0.7, packages/validator/go/v0.0.7, packages/generator/go/v0.0.7을 만들고 maintainer가 그것을 push한다. |
— | make release-versions TAG=v0.0.7, make release-consumer TAG=v0.0.7, make release-go-tags TAG=v0.0.7, make release-verify TAG=v0.0.7, make dependency-policy-check |
[o] |
| C14.50 | @polyspec/crudui-generator-core에서 render option의 decoder를 공개해, fsm의 GUI 같은 client가 페이지 state의 render 멤버를 cast 없이 읽게 한다. decodeFormRenderOptions(value)는 formRenderModel이 node에 묶기 전에 option에 실행하는 같은 검사를 두 번째 복사본 없이 실행하고, 그 값을 FormRenderOptions로 돌려주며, pointer가 잘못된 멤버의 JSON Pointer인 FormRenderOptionsError(INVALID_FORM_INPUT, form-runtime.md의 메시지)를 던진다. TSDoc, API report, contract manifest, docs/spec/form-runtime.md가 그것을 밝힌다. |
— | packages/generator/ts/src/form-render.test.ts, 패키지의 API Extractor, npm run manifest:check |
[o] |
| C14.51 | 0.0.x의 local link를 가리킨다(kit K198, kit의 docs/plans/local-links.md). AGENTS.md와 AGENTS.ko.md는 polyspec version이 0.0.x인 동안 개발 checkout이 그 문서대로 설치된 polyspec package를 source에 link하며 link는 commit하지 않는다고 한 문장으로 적는다 |
— | make docs-check |
[o] |
Wave 15 — 각 생태계의 표준 도구를 있는 그대로 사용하기
| ID | Task | Deliverables | Verification | State |
|---|---|---|---|---|
| C15.1 | 저장소가 기존 외부 도구나 라이브러리가 이미 하는 일을 다시 만들지 않는다는 규칙을 추가한다. AGENTS.md와 AGENTS.ko.md는 각 생태계의 표준 도구를 있는 그대로 사용한다고 적고(Python interpreter, environment, lock에는 uv, PHP 의존성에는 Composer), 그런 도구가 만드는 symbolic link를 받아들이며, 경로와 symbolic link 규칙을 이 저장소가 만드는 것에만 적용한다고 명시한다 |
— | make docs-check |
[o] |