This repository is a template for verification-first development. The goal is to make acceptance checks repeatable, inspectable, and portable by always producing a proof packet (logs plus a machine-readable result).
The mental model:
- Proposal: any human or agent can propose code changes quickly.
- Certification: a deterministic runner evaluates those changes and emits evidence.
- Evidence: build_reports plus result.json, uploaded by CI as an artifact, so you can debug from facts instead of opinions.
- A SPEC-driven acceptance contract in SPEC.md
- A deterministic acceptance runner (tools/ai/run_acceptance.sh)
- A proof packet directory (build_reports/<RUN_ID>/...) containing step logs and result.json
- A GitHub Actions workflow that always uploads proof-packet and writes a job summary
SPEC.md contains a section named:
Inside that section is a fenced bash block. Each line in the fenced block should be a runnable command.
Rules:
- Only runnable commands inside the fenced code block.
- Comments are allowed if they start with #.
- Avoid prose inside the fenced block.
- Do not call ./tools/ai/run_acceptance.sh inside the acceptance fence. The runner already runs the spec.
tools/ai/run_acceptance.sh:
- reads SPEC.md
- extracts the commands under Acceptance Checks
- runs them step by step
- saves stdout and stderr per step
- writes build_reports/latest_run
- writes build_reports/<RUN_ID>/result.json
.github/workflows/acceptance.yml:
- creates build_reports/
- runs spec lint and acceptance in non-blocking mode (always exits 0)
- writes a GitHub job summary from result.json when available
- uploads build_reports/ as an artifact named proof-packet even if acceptance fails
Why the workflow can show Success while result.json shows FAIL:
- The job is designed to never stop producing evidence.
- The real pass/fail status lives in result.json and the job summary.
- This avoids losing proof-packet artifacts when acceptance fails.
bash tools/ai/spec_lint.sh SPEC.mdrm -rf build_reports && mkdir -p build_reports
./tools/ai/run_acceptance.sh --report-dir build_reports SPEC.md || trueRID="$(cat build_reports/latest_run)"
cat "build_reports/$RID/result.json"
find "build_reports/$RID" -maxdepth 3 -type f -printbuild_reports/
├── latest_run
├── ci_note.txt (CI metadata, in GitHub Actions)
├── spec_lint.stdout.log and spec_lint.stderr.log (in CI)
├── acceptance.stdout.log and acceptance.stderr.log (in CI)
└── <RUN_ID>/
├── result.json
└── steps/
├── 001.cmd.txt
├── 001.stdout.log
├── 001.stderr.log
└── ...
result.json fields (typical):
- status: PASS or FAIL
- failing_step: step number when failing, otherwise null
- failing_command: command string when failing, otherwise null
- PASS means failing_step and failing_command are null.
- FAIL means failing_command matches a real acceptance command from SPEC.md.
- CI still uploads the proof packet regardless of PASS or FAIL.
YAML is indentation-sensitive. Heredocs and python blocks must be indented under run: | exactly.
This is expected when the workflow exits 0 on purpose to always upload proof-packet. Check:
- GitHub job summary
- proof-packet artifact
- build_reports/<RUN_ID>/result.json
This often means a command could not be executed in the environment. In CI, this can happen if:
- the command is blocked by policy
- the command is not installed
- the spec includes a line that is not a runnable command
Fix by keeping Acceptance Checks CI-safe and runnable-only.