infra(freshness-gate): CI gates for generated artifacts (copy-docs + openapi.json + types.ts)#433
Conversation
… 1.1)
Story 1.1 of infra_generated_artifact_freshness_gate (FR-9 / AC-11):
make copy-docs.mjs delete any *.md not in {README.md} ∪ {DOCS[].dest}
so a renamed or removed DOCS entry no longer leaves a stale public copy.
- Refactor copy-docs.mjs to export DOCS, getDestDir, pruneStale,
runCopyDocs + add an ESM entrypoint guard so importing the module
no longer triggers generation (mirrors gen-types.mjs pattern).
- Add ui/src/__tests__/scripts/copy-docs.prune.test.ts (11 cases):
exported-shape sanity, pruneStale direct behavior (delete .md,
preserve non-.md, no-op on clean), runCopyDocs end-to-end against
tmp dirs (clean run, prune-on-removed-entry, idempotency,
rename-mid-flight, cwd-equivalence, entry-point-guard).
- Verified operator path: node ui/scripts/copy-docs.mjs on a clean
tree leaves git status --porcelain -- ui/public/docs/ empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
…y 1.2) Story 1.2 of infra_generated_artifact_freshness_gate (FR-1 + FR-3 + FR-8 Phase-1 + FR-6 docs half). Catches the failure mode where a contributor edits a source guide under docs/08_guides/ without re-running copy-docs.mjs, leaving ui/public/docs/ stale. - scripts/ci/verify_copy_docs_fresh.sh — regen via copy-docs.mjs, fail on git status --porcelain drift (--porcelain catches modified, untracked, AND deleted; bare git diff misses untracked, which is the FR-9 / AC-9 case). Prints the canonical fix command on failure. Honors COPY_DOCS_FRESH_REPO_ROOT override for the self-test's disposable git fixture. - scripts/ci/test_verify_copy_docs_fresh.sh — three cases against fresh mktemp git fixtures: clean (exit 0), source-drift (exit 1 with the canonical fix-command text), untracked AC-9 via `git rm --cached` (exit 1 with ?? marker). - .github/workflows/copy-docs-freshness.yml — runs on every PR to main with NO paths/paths-ignore filter (FR-3 escape from pr.yml's docs/** filter so docs-only PRs still get the check). Mirrors secrets-defense.yml's own-workflow precedent. Action SHAs pinned per chore_scorecard_pin_deps_postcss (PR #430). - docs/05_quality/testing.md — new "Generated-artifact freshness gates" subsection documenting the gate, why --porcelain (not --exit-code), and the canonical fix command. Verification: 7/7 self-test cases green; guard against the live repo emits "OK: ui/public/docs/ is fresh."; workflow YAML parses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
…hase-gate finding #3) GPT-5.5 phase-gate review flagged that the freshness-gates subsection opened with "Three CI gates" while only documenting one — the Phase 2 snapshot + types gates land later. Soften the lede to "a family of CI gates" + add an explicit Phase 1 / Phase 2 sentence so a reader at this commit sees an accurate map of what ships when. Findings #1 (prune set derivation) and #2 (cwd-robustness coverage) were rejected with cited counter-evidence in the PR adjudication summary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
Story 2.1 of infra_generated_artifact_freshness_gate (FR-4 / AC-4). A CLI entrypoint that emits the canonical OpenAPI schema with no running server, live Postgres, Redis, ES/OpenSearch/Solr, or OpenAI client — the foundation for Story 2.2's `ui/openapi.json` snapshot freshness gate. - backend/app/openapi_export.py — argparse CLI with --out (atomic tmpfile + os.replace) or stdout. build_openapi() stubs the *_FILE-mounted Settings inputs via tempfile.mkdtemp + REDIS_URL bare env (non-secret, per Absolute Rule #2). serialize() applies the canonical form (sort_keys=True, compact separators, ensure_ascii=False, trailing newline) so output is byte-stable macOS↔Linux. All diagnostics → stderr; stdout is byte-pure JSON. - Module docstring records the FR-4 import-graph spike (path (a) resolution): app.openapi() walks routes + Pydantic models and does NOT trigger FastAPI's lifespan — no asyncpg pool / Redis client / engine adapter is constructed at schema-build time. The companion unit test runs with a deliberately non-resolvable REDIS_URL host and asserts build_openapi() still succeeds, converting any future regression (a router opening a connection at import) into an immediate unit-test failure. - backend/tests/unit/test_openapi_export.py — 10 cases: parsed-key assertions (NOT a leading-byte prefix, per plan task 2.1.4 note), byte-stability across repeated calls, canonical-form invariants, no-service-containers smoke, stdout-vs-stderr discipline, atomic write verification (no .tmp leak), overwrite path, idempotency, and the `python -m`-style invocation smoke. Operator-path verification: `python -m backend.app.openapi_export` emits 52 paths and parses cleanly. Lint + mypy --strict clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
Story 2.2 task 1 of infra_generated_artifact_freshness_gate (FR-7). Generated by `python -m backend.app.openapi_export --out ui/openapi.json` using Story 2.1's exporter. 52 paths, canonical form (sort_keys=True, compact separators, ensure_ascii=False, trailing newline). REUSE-lint coverage: ui/openapi.json is automatically covered by the existing **/*.json glob at REUSE.toml:23, so no annotation needed (Risk R-3 already mitigated). Subsequent commit on this branch adds the snapshot-freshness guard + self-test + the generated-artifacts-fresh pr.yml job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
…ry 2.2 b) Story 2.2 task 2-4 of infra_generated_artifact_freshness_gate (FR-7 + FR-6 + FR-8 Phase-2 half). - scripts/ci/verify_openapi_snapshot_fresh.sh — regen via the offline exporter (Story 2.1), fail on `git status --porcelain` drift. Uses --porcelain (not --exit-code) so the untracked case (a first commit forgetting to git add the snapshot) is flagged. Supports an OPENAPI_SNAPSHOT_REGEN_SCRIPT path-override for the self-test fixture (script path, not shell command — avoids read -ra word- splitting and shell-quoting traps). - scripts/ci/test_verify_openapi_snapshot_fresh.sh — three cases against fresh mktemp git fixtures: clean (same bytes → exit 0), source-drift (different bytes → exit 1 with canonical fix-command text), untracked AC-9 (`git rm --cached` → ?? marker → exit 1). The override means the fixture doesn't need uv + the project venv — the exporter has its own Story-2.1 unit test; this self-test verifies the guard's diff-detection logic only. - .github/workflows/pr.yml — new `generated-artifacts-fresh` job mirroring license-inventory's structure (uv + Python + pnpm + node). Snapshot guard runs here; Story 2.3 appends the types-guard step to the same job. Not under paths-ignore — both backend and UI changes can invalidate the snapshot. - docs/05_quality/testing.md — appends gate #2 row to the freshness- gates table per the cross-story testing.md ownership declared in implementation_plan.md §11; documents both fix commands. Verification: 7/7 self-test cases green; live-repo guard re-runs the exporter and emits "OK: ui/openapi.json is fresh."; `uv run python -m backend.app.openapi_export` produces byte-identical output to the committed snapshot (determinism confirmed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
Story 2.3 of infra_generated_artifact_freshness_gate (FR-5 + FR-2 +
FR-6 types half).
- ui/scripts/gen-types-banner.mjs (new) — pure, side-effect-free
module exporting buildBanner(). The banner names the COMMITTED
snapshot path (ui/openapi.json), not the live OPENAPI_URL value,
so local-dev + CI-snapshot regens produce byte-identical banners
(FR-5 source-invariance). Drops the false "CI does NOT regenerate"
stance and names the generated-artifacts-fresh CI gate instead.
- ui/scripts/gen-types.mjs — three changes:
1. Pinned-binary invocation via node_modules/.bin/openapi-typescript
(no npx fallback) — fails loudly if pnpm install was skipped.
2. Imports buildBanner from the new pure module.
3. ESM entry-point guard — importing the module is a no-op.
- ui/src/__tests__/scripts/gen-types-banner.test.ts (new) — 6 cases:
byte-stability, invariance across OPENAPI_URL values, canonical
Source-line, SPDX prefix preserved, freshness-gate stance.
Automated AC-8.
- scripts/ci/verify_types_fresh.sh + test_verify_types_fresh.sh —
guard regenerates via canonical pnpm types:gen invocation; fails
on git status --porcelain drift; prints chained fix command
(Story 2.4). Self-test uses TYPES_FRESH_REGEN_SCRIPT path-override
pattern from Story 2.2. 7/7 self-test cases green.
- .github/workflows/pr.yml — appends self-test + types-guard steps
to the existing generated-artifacts-fresh job (cross-story edit
declared in implementation_plan.md §11).
- docs/05_quality/testing.md — appends row #3 to the freshness-gates
table + chained fix command.
- ui/src/lib/types.ts — regenerated via the refactored gen-types.mjs
+ new buildBanner. PR §16 rollout requirement: introducing PR
freshens all artifacts. Prettier-formatted post-regen.
Tangential inline fix (per CLAUDE.md tangential-discoveries rule —
<60 min, same subsystem, no design fork):
- studies-table-ceiling-badge.test.tsx fixture omitted trial_count,
which the backend marks required (int = 0 at backend/app/api/v1/
schemas.py:902, shipped with PR #421). Pre-existing test passed
only against the stale types.ts; the freshness-gate regen surfaced
the drift. Added trial_count: 0 with a citing comment.
Verification: 17/17 scripts vitests green; 7/7 types-guard self-test
green; pnpm typecheck clean; reuse-lint compliant (REUSE-IgnoreStart/
End wrappers added around an SPDX-shaped regex literal in
gen-types-banner.test.ts that reuse-lint was mis-parsing as a real
declaration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
…ory 2.4)
Story 2.4 of infra_generated_artifact_freshness_gate (FR-8 chained
+ FR-6 determinism + AC-7).
- scripts/regen-generated-artifacts.sh (new) — one-paste chained
regen for all three CI-freshness-gated artifacts:
1. ui/openapi.json (uv run python -m backend.app.openapi_export)
2. ui/src/lib/types.ts (pnpm types:gen, reading the snapshot at 1)
3. ui/public/docs/ (node ui/scripts/copy-docs.mjs)
Step ordering matters — types.ts derives from the snapshot, so the
snapshot must regenerate first. After regen, all three are
`git add`ed. REGEN_NO_STAGE=1 skips the staging step (used by CI's
AC-7 determinism assertion so it inspects the working tree directly).
- ui/.prettierignore (new) — generated files are NOT prettier-formatted.
`ui/src/lib/types.ts` (openapi-typescript output) and
`ui/public/docs/*.md` (copy-docs.mjs output) are listed; the
generator is the source of truth. Without this, prettier would
reformat the openapi-typescript output and the freshness gate
would flap between local-prettier-formatted and CI-canonical bytes.
- ui/src/lib/types.ts — regenerated via the canonical wrapper, NOT
prettier-formatted. This is what every future regen produces and
what the gate now expects. Two consecutive `bash scripts/regen-
generated-artifacts.sh` invocations against this commit's tree
produce byte-identical types.ts — FR-6 verified.
- scripts/ci/verify_*.sh — all three guards now point their fix-
command output at the canonical chained wrapper as the primary,
with the per-gate one-liner shown as a fallback. Self-tests still
green (7+7+7 = 21 cases) because the existing per-gate substrings
remain in the output.
- .github/workflows/pr.yml — appends an AC-7 clean-tree determinism
step to the generated-artifacts-fresh job. After both per-gate
guards have run, the step does a fresh canonical regen + asserts
the working tree is clean. Catches a regenerator that is itself
non-deterministic across runs, distinct from drift against the
committed snapshot.
- docs/05_quality/testing.md — promotes the chained wrapper as the
single canonical fix command, demotes per-gate fixes to a
fallback section, names the AC-7 determinism assertion, documents
the `.prettierignore` rationale.
- CLAUDE.md — adds a "Generated artifacts" subsection under Key
Conventions naming the chained regen + the prettier-ignore rule.
Verification: 21/21 self-test cases green (7 per guard); canonical
regen output is byte-identical across consecutive runs (FR-6); a
fresh regen against the committed tree leaves git status clean
(AC-7); pr.yml parses cleanly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
Adds the merge one-liner to "Last 5 merges" (drops the now-6th entry to state_history.md's pointer); flips the "Current branch / execution context" section to the new feature branch + 8 commits; updates the "In flight" + "Plan-stage" sections. state.md size: 24,725 bytes (60KB cap). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
There was a problem hiding this comment.
Code Review
This pull request implements the infra_generated_artifact_freshness_gate feature, introducing CI freshness gates to ensure committed generated artifacts (OpenAPI snapshot, TypeScript types, and public documentation) do not drift from their sources. It adds an offline OpenAPI schema exporter, verification scripts with self-tests, a unified regeneration script, and refactors existing documentation-copying and type-generation scripts to be deterministic and robust. Feedback on the changes suggests cleaning up temporary directories and files in the OpenAPI exporter to prevent resource leaks, and enabling shell: true on Windows platforms in gen-types.mjs to ensure cross-platform compatibility when executing the pinned binary.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| tmp_dir = Path(tempfile.mkdtemp(prefix="relyloop-openapi-export-")) | ||
| os.environ.setdefault("RELYLOOP_OPENAPI_EXPORT_TMP", str(tmp_dir)) |
There was a problem hiding this comment.
The temporary directory created by tempfile.mkdtemp() is not cleaned up, which can lead to a resource leak over time during repeated executions or test runs. Registering an atexit handler ensures that the directory and its contents are automatically removed when the process exits.
tmp_dir = Path(tempfile.mkdtemp(prefix="relyloop-openapi-export-"))
import atexit
import shutil
atexit.register(lambda: shutil.rmtree(tmp_dir, ignore_errors=True))
os.environ.setdefault("RELYLOOP_OPENAPI_EXPORT_TMP", str(tmp_dir))| with tempfile.NamedTemporaryFile( | ||
| mode="w", | ||
| encoding="utf-8", | ||
| dir=str(path.parent), | ||
| prefix=f".{path.name}.", | ||
| suffix=".tmp", | ||
| delete=False, | ||
| ) as tmp: | ||
| tmp.write(content) | ||
| tmp.flush() | ||
| os.fsync(tmp.fileno()) | ||
| tmp_path = Path(tmp.name) | ||
| os.replace(tmp_path, path) |
There was a problem hiding this comment.
If an exception occurs during the file write or sync operations inside the with block, the temporary file created with delete=False will be leaked on disk. Wrapping the block in a try...finally statement ensures that the temporary file is cleaned up if the atomic replacement fails or is interrupted.
tmp_path = None
try:
with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
dir=str(path.parent),
prefix=f".{path.name}.",
suffix=".tmp",
delete=False,
) as tmp:
tmp.write(content)
tmp.flush()
os.fsync(tmp.fileno())
tmp_path = Path(tmp.name)
os.replace(tmp_path, path)
tmp_path = None
finally:
if tmp_path is not None:
try:
tmp_path.unlink(missing_ok=True)
except OSError:
pass| execFileSync(bin, [SOURCE_URL, '-o', OUTPUT], { | ||
| stdio: 'inherit', | ||
| cwd: UI_ROOT, | ||
| }); |
There was a problem hiding this comment.
On Windows, executing .cmd or .bat files directly via execFileSync without shell: true will fail with ENOENT or EACCES because Windows cannot natively execute batch files without cmd.exe. Setting shell: process.platform === 'win32' ensures compatibility for Windows developers while preserving shell-free execution on POSIX platforms.
| execFileSync(bin, [SOURCE_URL, '-o', OUTPUT], { | |
| stdio: 'inherit', | |
| cwd: UI_ROOT, | |
| }); | |
| execFileSync(bin, [SOURCE_URL, '-o', OUTPUT], { | |
| stdio: 'inherit', | |
| cwd: UI_ROOT, | |
| shell: process.platform === 'win32', | |
| }); |
PR #433 Gemini Code Assist review surfaced three medium-severity resource-hygiene findings, all accepted: 1. backend/app/openapi_export.py:91 — register atexit cleanup for the dummy *_FILE tmpdir created by _ensure_dummy_settings_env(). Each invocation leaked ~100 bytes; not a real disk concern but sloppy. atexit.register(shutil.rmtree, ..., ignore_errors=True) is the stdlib pattern. 2. backend/app/openapi_export.py:_write_atomic — wrap the NamedTemporaryFile(delete=False) + os.replace flow in try/finally. If write/flush/fsync OR the rename raised (disk full, permission denied), the orphan `.<file>.<rand>.tmp` would persist next to the destination. tmp_path = None after a successful replace tells the finally block "the rename took ownership; don't try to delete the now-renamed file". The finally's unlink is best-effort (missing_ok=True + caught OSError) so it never masks the original exception. 3. ui/scripts/gen-types.mjs:execFileSync — add `shell: process.platform === 'win32'` so Node can invoke the openapi-typescript.cmd shim on Windows (cmd.exe is required to interpret batch files; per the Node child_process docs: https://nodejs.org/api/child_process.html#spawning-bat-and-cmd-files). POSIX stays shell-free. Each fix carries an inline citation back to the Gemini finding so a future archeologist can trace the rationale. Verification: 10/10 unit tests still passing; live snapshot + types guards still emit OK on a clean tree; rtk mypy --strict + ruff clean on the modified Python; rtk prettier clean on gen-types.mjs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
Review adjudication (Gemini Code Assist + GPT-5.5 phase gates)Commit landing accepted fixes: Gemini Code Assist (3 findings — all accepted)
GPT-5.5 phase-gate Epic 1 (3 findings)
GPT-5.5 phase-gate Epic 2 (5 findings — all rejected)
Outcomes
Final cross-model review will follow once CI is green on |
Final cross-model reviewGPT-5.5 final review against the complete PR diff (vs Waiting on CI green on |
…d) (#435) - Move feature folder planned_features/02_mvp2/ → implemented_features/2026_06_03_infra_generated_artifact_freshness_gate/ (flat, date-prefixed — no MVP bucket in implemented_features/). - Retire the standalone Phase-2 record planned_features/02_mvp2/infra_openapi_types_freshness_gate/ — both phases shipped together in PR #433, so the discoverable-if-Phase-2- ships-alone folder is no longer needed. - pipeline_status.md: Implementation → Complete; add Release: mvp2 marker (the durable release signal once the folder leaves the 02_mvp2/ bucket). - implementation_plan.md: Status → Complete (PR #433, c5c36c6). - Regenerate MVP2/roadmap dashboards (DASHBOARD.md, MVP2_DASHBOARD.md, *.html, website/docs/roadmap.md) via the pre-commit hook. Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Ships
infra_generated_artifact_freshness_gateend-to-end (both phases together). CI now catches every "developer edits a source file but forgets to regenerate the committed artifact built from it" failure mode for the three generated surfaces in the repo:docs/08_guides/*.md→ui/public/docs/*.md(Phase 1)ui/openapi.json(Phase 2)ui/openapi.json→ui/src/lib/types.ts(Phase 2)The single canonical fix command for all three:
bash scripts/regen-generated-artifacts.sh.Spec:
feature_spec.md. Plan:implementation_plan.md. The standalone Phase-2 record at02_mvp2/infra_openapi_types_freshness_gate/is retired at finalization since both phases ship together.Phase 1 —
copy-docsfreshness gate (Stories 1.1 + 1.2)ui/scripts/copy-docs.mjsrefactored to exportDOCS,pruneStale,getDestDir,runCopyDocs+ added an ESM entry-point guard so importing the module is a no-op. Adds an FR-9 prune step that deletes any*.mdinui/public/docs/outside{README.md} ∪ {DOCS[].dest}— a renamed entry never leaves a stale public copy behind..github/workflows/copy-docs-freshness.yml(new) — runs on every PR with NOpaths-ignorefilter (FR-3 escape frompr.yml'sdocs/**filter, mirroringsecrets-defense.yml's own-workflow precedent) so docs-only PRs still get the check.scripts/ci/verify_copy_docs_fresh.sh+test_verify_copy_docs_fresh.sh— bash guard + self-test (clean / source-drift / untracked AC-9 cases against disposablemktempgit fixtures).Phase 2 — OpenAPI export + types gate (Stories 2.1 / 2.2 / 2.3 / 2.4)
backend/app/openapi_export.py(new) — offline deterministic CLI exporter. Module docstring records the FR-4 import-graph spike resolution (path (a) —*_FILEdummy stand-ins).build_openapi()+serialize(sort_keys=True, compact, ensure_ascii=False, trailing newline). The unit test runs against a deliberately non-resolvableREDIS_URLhost to prove no live clients are instantiated at import orapp.openapi()time.ui/openapi.json(new, 149KB, 52 paths) — canonical snapshot, byte-stable across runs.ui/scripts/gen-types.mjs— refactored to use the lockfile-pinnednode_modules/.bin/openapi-typescript(nonpxfallback, fails loudly ifpnpm installwas skipped) + imports abuildBanner()from the new pure moduleui/scripts/gen-types-banner.mjs(the banner names the COMMITTED snapshot path, not the liveOPENAPI_URL, so a local-dev regen + a CI-snapshot regen produce byte-identical banners — FR-5 source-invariance). ESM entry-point guard added..github/workflows/pr.yml— newgenerated-artifacts-freshjob mirroringlicense-inventory's shape (uv + Python + pnpm + node). Runs the snapshot guard, the types guard, AND an AC-7 clean-tree determinism step that calls the realscripts/regen-generated-artifacts.shend-to-end and asserts the working tree stays clean (catches a regenerator that is itself non-deterministic across runs).scripts/regen-generated-artifacts.sh(new) — one-paste chained fix for all three artifacts. RespectsREGEN_NO_STAGE=1so the CI determinism step can inspect the working tree directly.ui/.prettierignore(new) — generated files are NOT prettier-formatted.src/lib/types.ts+public/docs/*.mdare listed: the generator is the source of truth, and reformatting them would make the gates flap between local-prettier-formatted bytes and CI-canonical openapi-typescript bytes.Tangential inline fix (per CLAUDE.md tangential-discoveries rule)
ui/src/__tests__/components/studies/studies-table-ceiling-badge.test.tsx— fixture omittedtrial_count, which the backend marks required (int = 0atbackend/app/api/v1/schemas.py:902, shipped with PR docs(research): complementary-architecture one-pager (three-layer handoff) #421). The freshness-gate regen oftypes.tssurfaced the pre-existing schema/test drift. One-line fix (trial_count: 0) with the rationale documented in a block comment.Test coverage
48 new test cases total:
backend/tests/unit/test_openapi_export.pyui/src/__tests__/scripts/copy-docs.prune.test.tsui/src/__tests__/scripts/gen-types-banner.test.tsscripts/ci/test_verify_copy_docs_fresh.shscripts/ci/test_verify_openapi_snapshot_fresh.shscripts/ci/test_verify_types_fresh.shPlan §3.2 (integration), §3.3 (contract), §3.5 (E2E), §3.6 (migration) are explicitly N/A for this CI-tooling feature.
Cross-model review (so far)
openapi.json+types.tsfor review clarity — both files ARE in the PR; chore(pipeline): add project-wide priority-ordered status mode #3+infra(foundation): bootstrap MVP1 stack — Docker + FastAPI + /healthz + Alembic + CI (#3) #4 the test override patterns are explicitly plan-authorized in Story 2.2/2.3 plan text + end-to-end coverage exists via the AC-7 clean-tree determinism step; chore(deps): Bump actions/checkout from 4 to 6 #5 the tangentialtrial_countfix is the textbook inline-fix case per CLAUDE.md).Test plan
make test-unit→test_openapi_export.py10/10 greenpnpm --dir ui test→ 1025/1025 (140 files; baseline was 1019, +6 from buildBanner test)bash scripts/ci/test_verify_copy_docs_fresh.sh→ 7/7bash scripts/ci/test_verify_openapi_snapshot_fresh.sh→ 7/7bash scripts/ci/test_verify_types_fresh.sh→ 7/7make lint→ 0 errorsmake typecheck→ cleanpnpm --dir ui typecheck→ clean.venv/bin/ruff format --check backend/→ 565 files cleanREGEN_NO_STAGE=1 bash scripts/regen-generated-artifacts.shagainst the committed tree leavesgit status --porcelainemptybash scripts/ci/verify_copy_docs_fresh.sh+verify_openapi_snapshot_fresh.sh+verify_types_fresh.shall emit "OK: …is fresh."generated-artifacts-freshjob + the newcopy-docs-freshnessworkflow🤖 Generated with Claude Code