feat(gate): docs-system input + docs-gate lane + fragment example cleanup#105
Conversation
New reusable boolean input docs-system (default false, same opt-in pattern as stack). When true, a docs-gate job runs on pull_request events after detect in the consumer checkout: npm run docs:render -- --check (rendered-block drift) and node scripts/doc-health/health.mjs --repo . --changed-from origin/<base> --json (blocking findings; the checkout uses fetch-depth: 0 to reach the merge base). The job conditions on the input directly, not on a detect output, so a caller whose workflow-library-ref lags this body cannot silently no-op the gate. decision aggregates it like every other optional lane — skipped is not failed for non-opted consumers, and the require_success call carries the same pull_request event guard as pr-contract so opted-in consumers stay green on push/merge_group runs. classify-pr.mjs threads docsSystem through to a run-docs-gate output and the lane summary (docs-gate rides along every PR lane incl. docs-only). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b2W87gyzLPaHBbpuU9Zxt
Drop examples/changelog-fragment.yml and examples/repo-update-log-fragment.yml so new-workflow guidance no longer points agents at fragment callers. Guidance cleanup only: the reusable workflow bodies stay because existing consumers still call them; the installed-baseline/registry migration belongs to the later S3/C1/T1 cleanup. README inventory rows keep the reusable bodies and mark the example cells retired; a shape test now locks in stubs-gone / bodies-present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b2W87gyzLPaHBbpuU9Zxt
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b2W87gyzLPaHBbpuU9Zxt
Adversarial review (mutation testing) found the one docs-gate wire with no failing test: deleting the detect job's `docsSystem: process.env.DOCS_SYSTEM === 'true'` classifier argument left all 179 tests green while run-docs-gate silently became 'false' for opted-in consumers, making the lane summary report docs-gate as skipped even though decision (which reads inputs.docs-system directly) still requires it. Pin the env -> classifyPR hand-off with a substring assertion; verified the mutation now fails 1/179 and the restored workflow passes 179/179. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b2W87gyzLPaHBbpuU9Zxt
Adversarial review dispositions (fixer/promoter pass)One finding adjudicated for this lane: 1. [P3] test-coverage — detect-job Independently re-verified before fixing: deleting Fix (test-first): added the pinning substring assertion to No other findings were raised; the review confirmed all six enforcement-path mutations (default-flip, decision ignoring the lane result, job-if/RUN-flag source flips, example resurrection, classifier lane wiring) are each caught by exactly one failing test, and the consumer commands match the real repo-template producers. Review threads checked via GraphQL before and after the push: none present. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ea0c30cd8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The docs-gate "Detect package manager" step fell through to `npm ci` whenever no pnpm/yarn lockfile was present. `npm ci` REQUIRES a package-lock.json and hard-fails without one, so an opted-in docs-system consumer that ships package.json + docs scripts but no committed lockfile would go red before either docs check ran. Add the same fallback the shared node-ci.yml already uses: `npm ci` only when package-lock.json exists, else `npm install --no-package-lock`. actionlint exit 0; vitest 179/179. Resolves the PR #105 review thread (no-lockfile docs consumers). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Eiuy6Jd6Pf372zhG2od66m
Summary
repo-required-gate.ymlgains an opt-indocs-systemboolean input (defaultfalse, same opt-in pattern asstack). When true, a newdocs-gatejob runs onpull_requestevents afterdetect, in the consumer checkout:npm run docs:render -- --check(fails on stale committed rendered blocks) andnode scripts/doc-health/health.mjs --repo . --changed-from origin/<base> --json(fails on blocking doc-health findings). The checkout usesfetch-depth: 0sohealth.mjscan resolve the merge base with the PR base branch; the base ref is passed through an env var, not shell interpolation.decisionaggregates the docs gate exactly like every other optional lane:RUN_DOCS_GATE: ${{ inputs.docs-system }}guarded by the sameEVENT_NAME = pull_requestcheck thatpr-contractuses. Skipped is NOT failed for non-opted consumers, and opted-in consumers stay green on push/merge_group runs where the PR-only job skips.if:conditions on the input directly, not on a detect output: a caller whoseworkflow-library-reflags this workflow body would import a classifier withoutrun-docs-gate, and the empty-string comparison would silently no-op the gate for an opted-in consumer.scripts/classify-pr.mjsstill threadsdocsSystemthrough to arun-docs-gatedetect output and thejobsRequired/jobsSkippedlane summary, so the Actions UI reports the gate honestly. It rides along every PR lane including docs-only — doc drift is precisely a docs-only concern — and stays decoupled frompr-contract(the archon#200 decoupling shape test now coversdocs-gatetoo).examples/changelog-fragment.ymlandexamples/repo-update-log-fragment.ymlare retired so new-workflow guidance no longer points agents at fragment callers. The reusable workflow bodies stay — existing consumers still call them; the installed-baseline/registry migration belongs to the later S3/C1/T1 cleanup. README inventory cells updated and a shape test locks in stubs-gone / bodies-present.Sequencing (issue #104 item 3 is explicitly OUT of this PR): the repo-template caller flip to
docs-system: trueand its live-red scratch-branch proof must wait until this PR merges AND the owner advances thev1tag — repo-template pins@v1, and the retag ships the gate fleet-wide, so it stays owner-deferred. Follow-up tracked in ArchonVII/repo-template#124 (P1 lane).Verification
TDD RED then GREEN on the two touched suites: the 12 new assertions (6 classifier, 6 workflow-shape) failed before implementation and passed after.
Full suite on the lane worktree, all files: 9 test files, 179/179 passing (origin/main baseline was 168; net +11 covering input default, docs-gate decision aggregation, opt-in condition, detect output, decoupling, classifier threading, and retired-example lock-in).
actionlint on the touched workflow YAML reported no findings.
Adversarial-review mutation gap closed (commit
3ea0c30): deleting the detect job'sdocsSystem: process.env.DOCS_SYSTEM === 'true'classifier argument previously left all 179 tests green whilerun-docs-gatesilently became'false'for opted-in consumers (lane summary would report the gate as skipped; enforcement unaffected since the jobif:and decision readinputs.docs-systemdirectly). A substring assertion in the docs-gate detect-output test now pins the env ->classifyPRhand-off.git diff --checkexited 0 before each commit (no whitespace errors introduced).Verification Notes
Consumer-side command contract was verified against repo-template
origin/main(57d6381): thedocs:rendernpm script exists (scripts/docs/render.mjs), andscripts/doc-health/health.mjsaccepts--repo/--changed-from/--json, setting exit code 1 on blocking findings (the L2 contract) and 0 on clean/warnings-only runs. The skipped-is-not-failed invariant is asserted by the new decision-aggregation shape test (RUN_DOCS_GATEenv plus theEVENT_NAME = pull_requestbash guard), and the lane-decoupling loop test now includesdocs-gateso it can never silently gain apr-contractdependency. This PR cannot exercise the new gate end-to-end in its own CI — this repo's PR CI is the vitest self-test, and the reusable body only runs in consumer context — so the end-to-end live-red proof is deliberately scheduled for the repo-template caller PR after the ownerv1retag, per the sequencing note in the Summary.Docs / Changelog
.changelog/unreleased/104-docs-gate.mdfragment added.docs/repo-update-log.mdentry appended (2026-07-04) with propagation recorded as pending: owner@v1retag, then the repo-template caller flip + live-red proof, then the archon-setup snapshot refresh that picks up the retired example stubs.examples/repo-required-gate.ymlnow documents thedocs-systemopt-in block.Plan/status artifacts: none were created or used by this lane; the living tracker is ArchonVII/repo-template#124 (P1 row), and issue #104's remaining acceptance boxes (rt live-red proof,
@v1retag) are owner/merge-gated follow-ups by design.Owner decisions this lane: none
Linked Issue
Closes #104
Refs ArchonVII/repo-template#124
Risks
docs-system: trueAND the owner retagsv1. The one load-bearing invariant — a skipped docs-gate must never fail non-opted consumers or opted-in push/merge_group runs — is locked by the new shape test.@v1retag; repo-template caller flip + live-red scratch-branch proof (Epic: self-maintaining docs system + closeout contract (supersedes the doc-policy approach in #223) repo-template#124 P1 / issue repo-required-gate: docs-gate lane (docs:render --check + doc-health blocking subset) + fragment example cleanup #104 item 3); archon-setup snapshot refresh ofexamples/; S3/C1/T1 registry migration for the locked fragment defaults.