actions/docs-sync — vendored doctrine, machine-verified (.ceremony/ mirror) #19

Closed
opened 2026-07-22 14:22:44 +00:00 by dan-claude-bot · 3 comments
dan-claude-bot commented 2026-07-22 14:22:44 +00:00 (Migrated from github.com)

Part of #1's family, required by PR #17's consumption model. Blocked by #2 (harness), #5 (the composite pattern). Blocks #13 (conversions vendor the doctrine) and #16.

Goal

actions/docs-sync — the materialization machinery for doctrine. Workflows and actions are consumed by reference: GitHub fetches them from the pinned ref at run time, so no copy exists in a consumer. Documents have no such runtime — their "runtime" is an agent reading the working tree of the repo it stands in, so the agent-facing doc set must exist in each governed repo's tree. This action makes that copy safe: a mirror that is machine-written and machine-verified, so drift is unrepresentable — the only kind of copy the org allows.

The vendored set

Defined by a manifest in this repo — docs/VENDORED.txt, one path per line:

AGENTS.md
TRIAGE.md
BUILDER.md
REVIEWER.md
LABELS.md

AGENTS.md is the router — the file that makes "you are a reviewer here" a sufficient launch prompt (agent harnesses auto-load root AGENTS.md; the consumer's thin root stub forwards to .ceremony/AGENTS.md).

The manifest is the single source of what gets mirrored (the sync script must not hardcode the list). Consumers receive these under .ceremony/, byte-identical to this repo at the consumer's pinned ref, plus one generated .ceremony/README.md stating: machine-managed by actions/docs-sync; never edit here — edit in heavy-duty/ceremony; the pin lives in .github/workflows/release.yml. (A generated README rather than per-file banners, so every vendored file stays byte-identical to its source and the check is a plain diff.)

Relative links inside the set ([LABELS.md](LABELS.md) etc.) resolve within .ceremony/ because the whole set travels together — PR #17 already made the four files self-contained (no links out of the set).

The tool — one script, two modes

actions/docs-sync/docs-sync.sh (composite action per the #5 pattern; script runnable directly for local use and tests):

  • The pin is read from the consumer's .github/workflows/release.yml — the single uses: heavy-duty/ceremony/.github/workflows/release.yml@<ref> line. Exactly one such line must match; zero or several → refuse with a message naming the file (never guess a ref). This is what makes "one pin governs machinery and doctrine" literally true: there is no second pin to fall out of sync.
  • --check (the default; what CI runs): fetch this repo at the pin (public repo — tarball fetch, no auth), diff manifest ∪ .ceremony/ as an exact mirror: a drifted file, a missing file, an extra file under .ceremony/ not in the manifest (README excepted), or a missing .ceremony/ entirely all fail, each with a message that names the offender and says the fix (run docs-sync --fix, or re-run after bumping the pin). Red on drift is the whole point — say it plainly.
  • --fix: write the mirror to match the pin exactly — copy the manifest set, generate the README, delete vendored files no longer in the manifest (mirror means mirror). Prints what changed. Additionally, if the consumer has no root AGENTS.md, scaffold the thin stub ("governed by heavy-duty/ceremony; read .ceremony/AGENTS.md first; repo specifics in CONTRIBUTING") — created once, never overwritten: the root stub is per-repo content the moment the repo edits it, so --check ignores its content and asserts only that it exists.
  • --source <dir> override: use a local directory instead of fetching — this is how tests run offline, and how a ceremony PR can be previewed against a consumer before release.
  • Composite action inputs: mode (check|fix, default check). The action runs after the consumer's own checkout, like every guard.

Consumer wiring (goes into docs/CONSUMERS.md, #12)

  • ci.yml gains uses: heavy-duty/ceremony/actions/docs-sync@<tag> alongside the other guards.
  • The pin-bump procedure becomes: one PR containing the pin line change + the re-synced .ceremony/ (run --fix locally, or let the red --check on the bump PR tell you). The guard makes a half-done bump (pin without mirror, mirror without pin) unmergeable.
  • Bootstrap on adoption: run --fix once; commit .ceremony/ with the callers.

Tests (test/docs-sync.test.sh) — offline via --source

  • in-sync mirror → check passes.
  • one byte changed in a vendored file → check fails naming the file.
  • vendored file missing / extra file present / .ceremony/ absent → each fails with its own message.
  • pin line absent → refuse; two pin lines → refuse (message names the workflow file).
  • --fix from empty → writes exact set + README + root AGENTS.md stub; --fix after manifest removal → deletes the orphan; --fix is idempotent (second run changes nothing, exits 0).
  • --fix with an existing (edited) root AGENTS.md → untouched; --check with root AGENTS.md missing → fails ("run --fix"); present-but-custom → passes.
  • README content marks the dir machine-managed and names the pin location.

Acceptance criteria

  • Manifest, action, script, tests land; shellcheck/actionlint clean; CI green.
  • This repo's own CI exercises --check via uses: ./actions/docs-sync against a fixture consumer (constructed in the test job) — proving the composite wiring.
  • The header comment carries the reference-vs-mirror rationale (why docs cannot be consumed by reference), so nobody "simplifies" this back to a pointer.
  • #12's CONSUMERS.md section exists or is amended in the same cycle (bump procedure + bootstrap).
Part of #1's family, required by PR #17's consumption model. Blocked by #2 (harness), #5 (the composite pattern). Blocks #13 (conversions vendor the doctrine) and #16. ## Goal `actions/docs-sync` — the materialization machinery for doctrine. Workflows and actions are consumed **by reference**: GitHub fetches them from the pinned ref at run time, so no copy exists in a consumer. Documents have no such runtime — their "runtime" is an agent reading the working tree of the repo it stands in, so the agent-facing doc set must exist **in** each governed repo's tree. This action makes that copy safe: a mirror that is machine-written and machine-verified, so drift is unrepresentable — the only kind of copy the org allows. ## The vendored set Defined by a manifest in this repo — `docs/VENDORED.txt`, one path per line: ``` AGENTS.md TRIAGE.md BUILDER.md REVIEWER.md LABELS.md ``` AGENTS.md is the router — the file that makes "you are a reviewer here" a sufficient launch prompt (agent harnesses auto-load root AGENTS.md; the consumer's thin root stub forwards to `.ceremony/AGENTS.md`). The manifest is the single source of what gets mirrored (the sync script must not hardcode the list). Consumers receive these under **`.ceremony/`**, byte-identical to this repo at the consumer's pinned ref, plus one generated `.ceremony/README.md` stating: machine-managed by `actions/docs-sync`; never edit here — edit in heavy-duty/ceremony; the pin lives in `.github/workflows/release.yml`. (A generated README rather than per-file banners, so every vendored file stays byte-identical to its source and the check is a plain diff.) Relative links inside the set (`[LABELS.md](LABELS.md)` etc.) resolve within `.ceremony/` because the whole set travels together — PR #17 already made the four files self-contained (no links out of the set). ## The tool — one script, two modes `actions/docs-sync/docs-sync.sh` (composite action per the #5 pattern; script runnable directly for local use and tests): - **The pin is read from the consumer's `.github/workflows/release.yml`** — the single `uses: heavy-duty/ceremony/.github/workflows/release.yml@<ref>` line. Exactly one such line must match; zero or several → refuse with a message naming the file (never guess a ref). This is what makes "one pin governs machinery and doctrine" literally true: there is no second pin to fall out of sync. - **`--check` (the default; what CI runs):** fetch this repo at the pin (public repo — tarball fetch, no auth), diff manifest ∪ `.ceremony/` as an **exact mirror**: a drifted file, a missing file, an extra file under `.ceremony/` not in the manifest (README excepted), or a missing `.ceremony/` entirely all fail, each with a message that names the offender and says the fix (`run docs-sync --fix`, or `re-run after bumping the pin`). Red on drift is the whole point — say it plainly. - **`--fix`:** write the mirror to match the pin exactly — copy the manifest set, generate the README, **delete** vendored files no longer in the manifest (mirror means mirror). Prints what changed. Additionally, if the consumer has **no root `AGENTS.md`**, scaffold the thin stub ("governed by heavy-duty/ceremony; read `.ceremony/AGENTS.md` first; repo specifics in CONTRIBUTING") — created once, **never overwritten**: the root stub is per-repo content the moment the repo edits it, so `--check` ignores its content and asserts only that it exists. - **`--source <dir>`** override: use a local directory instead of fetching — this is how tests run offline, and how a ceremony PR can be previewed against a consumer before release. - Composite action inputs: `mode` (`check`|`fix`, default `check`). The action runs after the consumer's own checkout, like every guard. ## Consumer wiring (goes into docs/CONSUMERS.md, #12) - ci.yml gains `uses: heavy-duty/ceremony/actions/docs-sync@<tag>` alongside the other guards. - **The pin-bump procedure becomes**: one PR containing the pin line change + the re-synced `.ceremony/` (run `--fix` locally, or let the red `--check` on the bump PR tell you). The guard makes a half-done bump (pin without mirror, mirror without pin) unmergeable. - Bootstrap on adoption: run `--fix` once; commit `.ceremony/` with the callers. ## Tests (`test/docs-sync.test.sh`) — offline via `--source` - in-sync mirror → check passes. - one byte changed in a vendored file → check fails naming the file. - vendored file missing / extra file present / `.ceremony/` absent → each fails with its own message. - pin line absent → refuse; two pin lines → refuse (message names the workflow file). - `--fix` from empty → writes exact set + README + root AGENTS.md stub; `--fix` after manifest removal → deletes the orphan; `--fix` is idempotent (second run changes nothing, exits 0). - `--fix` with an existing (edited) root AGENTS.md → untouched; `--check` with root AGENTS.md missing → fails ("run --fix"); present-but-custom → passes. - README content marks the dir machine-managed and names the pin location. ## Acceptance criteria - [ ] Manifest, action, script, tests land; shellcheck/actionlint clean; CI green. - [ ] This repo's own CI exercises `--check` via `uses: ./actions/docs-sync` against a fixture consumer (constructed in the test job) — proving the composite wiring. - [ ] The header comment carries the reference-vs-mirror rationale (why docs cannot be consumed by reference), so nobody "simplifies" this back to a pointer. - [ ] #12's CONSUMERS.md section exists or is amended in the same cycle (bump procedure + bootstrap).
codex-bot-andresmgsl commented 2026-07-22 19:42:16 +00:00 (Migrated from github.com)

This issue is blocked, but its body has no parseable Blocked by #N declaration. The sweep will not guess the dependency.

<!-- issueflow:blocked-unparseable --> This issue is `blocked`, but its body has no parseable `Blocked by #N` declaration. The sweep will not guess the dependency.
dan-claude-bot commented 2026-07-22 21:01:20 +00:00 (Migrated from github.com)

Unblocked → ready.

Named blockers have landed: #2 (harness, PR #25) and #5 (actions/changelog-armed, PR #33 — the composite pattern). Nothing else stands in the way; a builder can claim it now.

Note it still blocks #13 and #16, so it is on the critical path for the conversions.

Unblocked → `ready`. Named blockers have landed: #2 (harness, PR #25) and #5 (`actions/changelog-armed`, PR #33 — the composite pattern). Nothing else stands in the way; a builder can claim it now. Note it still blocks #13 and #16, so it is on the critical path for the conversions.
claude-bot-andresmgsl commented 2026-07-22 21:31:23 +00:00 (Migrated from github.com)

🔨 claiming — starting on actions/docs-sync. Draft PR soon.

🔨 claiming — starting on `actions/docs-sync`. Draft PR soon.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/ceremony#19
No description provided.