Key OPERATIONS.md to the need for it, not to the workflow model - #507
Conversation
…anup The previous entry said the initial commits belong on a feature branch and reach develop by squash. Correct, but it still leaves main and develop as things an agent might commit onto and then have to clean up. Blog tried exactly that and was blocked, which is the ruleset working. The better sequence removes the problem instead of managing it. Create main carrying nothing, create develop from main carrying nothing, create the first feature branch from develop and do the whole standup there, add the repo to GitHub and apply the repo config from that branch, then open a normal pull request to develop when ready. Two details the write-up needs and would otherwise get wrong. A git branch cannot exist without a commit, so "carrying nothing" means one signed empty root commit, and section 0's signing window applies to it. And squashing afterwards is not an escape, because non_fast_forward is set on both develop payloads, so the history cannot be rewritten without disabling the ruleset. STANDUP.md currently says nothing about how main and develop come into being, which is the gap that lets an agent commit onto whichever branch it finds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OPERATIONS.md was declared appliesTo: ["operational"], which binds a content need to a workflow model. Those are unrelated axes. The `operational` model describes where config lives and how a change reaches develop, not whether the repo has runbooks, so the selector read a sufficient condition as a necessary one. Blog is the proof. It moved from operational to release and its runbook needs did not change by one word: same host, same deploy, same staging. Only the commit-flow classification moved. The rationale in section-model.md already generalized. It mandated the file because the convention was emerging unevenly and the same material scattered across ad-hoc names, which has nothing to do with the workflow model. Every repo has operational surface: a package registry needs trusted-publisher setup, an image needs registry credentials, a site needs a deploy and a staging path. So OPERATIONS.md is now appliesTo: "*", and a repo with nothing to say carries a stub of the headings rather than a blank file, because a stub names the destination and its shape where a blank names only the destination. Also states the rule that decides what earns a declared destination at all: a content class is declared when it recurs across repos. Architecture recurs, so ARCHITECTURE.md stays declared, and advisory, since how much of it earns a separate document is contextual. A one-repo need such as a home-device inventory stays the repo's own file, neither declared nor mandated, because the scattering these destinations prevent has no force for content existing in exactly one repo. The hub carried no OPERATIONS.md, so it would have shipped a rule it broke. It now has a real one rather than a stub, covering the gates, the fleet audit, the configure.sh runbook, and the tool caveats this repo has already hit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the fleet documentation model so OPERATIONS.md is required based on the recurring need for operational/runbook content rather than being keyed to the operational workflow model, and adds an OPERATIONS.md to ProjectTemplate so the hub complies with the baseline it defines.
Changes:
- Make
OPERATIONS.mduniversally required by changingspec/files.jsonfromappliesTo: ["operational"]toappliesTo: "*". - Update
spec/section-model.mdandSTANDUP.mdto explicitly state that workflow model and operational documentation needs are orthogonal, and document the “declared destination” rule. - Add a new
OPERATIONS.mdrunbook for this repository and updateTODO.mdto track a related STANDUP improvement.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| TODO.md | Replaces the prior STANDUP-related backlog item with a more explicit branch-bootstrap step proposal. |
| STANDUP.md | Updates onboarding guidance to reflect OPERATIONS.md being required for all repos and clarifies the workflow-model independence. |
| spec/section-model.md | Updates the destination/requirement rationale for OPERATIONS.md and documents the rule for when destinations are declared. |
| spec/files.json | Makes OPERATIONS.md a universal baseline requirement (appliesTo: "*") instead of workflow-model-scoped. |
| OPERATIONS.md | Adds an operations/runbook document for ProjectTemplate covering gates, audit, repo-config application, and related tooling notes. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
CI's editorconfig-checker step failed because the new file was LF while `[*] end_of_line = crlf` applies. The tooling that created it writes LF, and nothing local caught that before the push. Converted to CRLF, and added the editorconfig-checker invocation to the gate list in the file itself, which is where its absence caused this. The runbook listed every other gate CI runs and omitted the one that failed. This is the same trap Blog reported from the other side: a carried file arrives with the hub's endings, so an LF repo fails its own check on the first run. Here a new file arrived with the tool's endings and failed the hub's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (2)
OPERATIONS.md:20
- The editorconfig-checker command here doesn’t match the CI invocation in .github/workflows/validate-task.yml (missing
--workdir /checkand adding an extraecargument). Since this section is explicitly “Run the gates the way CI runs them”, it should mirror the workflow command exactly to avoid local runs behaving differently.
docker run --rm --pull=always -v "$PWD:/check" mstruebing/editorconfig-checker:latest ec
spec/section-model.md:66
- This section says “Three topical docs take it” but the destination list omits
TODO.md(which STANDUP.md includes as a declared destination for repo-specific backlog content). This inconsistency can lead to agents scattering TODO/backlog content under other filenames.
A repo's own content is not carried and is not declared here, so extraction needs a predictable destination rather than a judgment call per repo. Three topical docs take it, chosen by what the content *is*:
- [`CODESTYLE.md`][codestyle]: a repo's language and formatting conventions beyond the carried rules.
- `ARCHITECTURE.md`: how a code repo is built, its module layout, data flow, and design decisions.
- `OPERATIONS.md`: how a repo is run, covering runbooks, backup, log and debug procedures, tool-usage notes, and config layout. It is the operational analogue of `ARCHITECTURE.md`, and it is where an `AGENTS.md` split puts the repo-specific half.
Four findings from the Copilot round, all real. "A stub of the headings above" was a dangling reference in both files, since neither named any headings. Both now name the five: Runbooks, Backup and Recovery, Logs and Debugging, Tool Usage, and Configuration Layout. They match this repo's own OPERATIONS.md, which both files now point at as the worked example. section-model.md said "Three topical docs take it" while STANDUP.md said four, the difference being TODO.md. section-model.md was the one missing it, so a reader of the spec had no declared destination for backlog content. It now lists four. The editorconfig-checker line did not match CI's invocation, in a section titled "Run the gates the way CI runs them". It now mirrors CI exactly. Both forms were verified to behave identically against a deliberate violation, so this removes a dependency on the image's default working directory rather than fixing a broken command. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
commented
Aug 1, 2026
Closing the loop on the two suppressed findings, both verified before accepting and both fixed in 1. The CI runs: docker run --rm --pull=always -v "$PWD":/check --workdir /check mstruebing/editorconfig-checker:latestI had written I checked whether my form was actually broken rather than assuming either way, by dropping a deliberate LF violation into the tree and running both: Identical. The image's default working directory is already 2. "Three topical docs" against STANDUP.md's four. Real, and
Both of these were pre-existing in one half and introduced by me in the other, so I have not tried to separate blame. They are fixed where they sit. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
spec/section-model.md:73
- This PR updates the rule so
OPERATIONS.mdis required for every repo, butreports/conformance-matrix.mdstill states the old behavior (it saysOPERATIONS.mdis required only forworkflowModel: operationalwithappliesTo: ["operational"]). That leaves the hub docs internally inconsistent and can mislead readers using the conformance matrix.
Please update the conformance-matrix row (currently reports/conformance-matrix.md:22) to reflect the new appliesTo: "*" requirement (and any similar references, if added later).
**`OPERATIONS.md` is required for every repo**, declared in [`files.json`][files] as `appliesTo: "*"` and checked for presence only, the same footing as `README.md` and `HISTORY.md`, so its content is entirely the repo's own. It is mandatory rather than advisory because the convention was already emerging unevenly: of the four operational-model repos, two wrote one unprompted and the others scattered the same material across ad-hoc names, which is the improvisation these destinations exist to prevent. That reasoning never depended on the workflow model. Every repo has operational surface, since publishing to a package registry needs trusted-publisher setup, shipping an image needs registry credentials, and serving a site needs a deploy path and a staging story. A repo with nothing to say still carries the file as a stub, meaning those five headings with no content under them, because a stub names the destination and its shape where a blank file names only the destination. This repo's own [`OPERATIONS.md`][operations] is the worked example.
**The workflow model and the need for this file are unrelated axes.** `operational` as a `workflowModel` describes where config lives and how a change reaches `develop`, not whether the repo has runbooks. Keying the file to that selector read a sufficient condition as a necessary one, since an operational-model repo certainly has runbooks while a release repo has them too. Reclassifying a repo between models does not change how much operational surface it has, which is the test that showed the selector was wrong.
**A destination is declared when its content class recurs across repos.** Software architecture recurs, because every code repo has one, so `ARCHITECTURE.md` is declared. A home-device inventory does not recur, so it stays the repo's own file, neither declared nor mandated. The scattering these destinations prevent is the same material landing under different names in different repos, and that has no force for content existing in exactly one repo. Declaring a destination for a one-repo need would grow this list without bound and still lag whatever the next repo invents.
The matrix still said OPERATIONS.md is required only for an operational repo, with appliesTo: ["operational"], which this PR makes false. Merging without this would have shipped the hub contradicting itself in the document agents read to learn what a shape requires. The operational row now describes what actually distinguishes that shape, that its develop ruleset carries no pull_request rule, so branch discipline rests on the instruction rather than the gate. The same row also still claimed Blog as the next operational standup. Blog is release plus source-only now, so it gets its own row rather than sitting under a model it no longer uses. The new row adds one warn-only dash finding, the `| - |` empty cell every other row also carries. The dash rule reads a table placeholder as prose. Keeping the table's own convention is worth one backlog entry, and the rule gap is the follow-up rather than a reason to write the row differently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
commented
Aug 1, 2026
Third round's suppressed finding was real and is fixed in
This PR makes that false. Merging without the fix would have shipped the hub contradicting itself in the document an agent reads to learn what a shape requires, which is worse than the original defect. The operational row now states what actually distinguishes that shape: its Checking that row also caught something Copilot did not flag. It still claimed Blog as the next operational standup, and Blog moved to One deliberate cost: the new row adds a single warn-only |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (2)
OPERATIONS.md:25
- This says the second prose invocation is "warn-only", but the command shown here will still exit non-zero locally on findings. In CI it’s warn-only because the workflow runs this step with
continue-on-error: true(see.github/workflows/validate-task.yml:76-78). Clarifying that distinction will avoid readers treating a failing local run as unexpected.
The first prose invocation gates. The second is warn-only and reports the backlog that is corrected as each file is next edited.
OPERATIONS.md:82
- These Docker commands differ from the documented canonical invocations (missing
--workdir /workdirand running cspell over all**/*.md). As written, they may not run from the mounted directory, and they don’t mirror CI’s cspell scope (README.md + HISTORY.md only). Aligning withGOVERNANCE.md:338-345keeps the runbook consistent and avoids extra non-gated spelling noise.
docker run --rm --pull=always -v "$PWD:/workdir" davidanson/markdownlint-cli2:latest "**/*.md"
docker run --rm --pull=always -v "$PWD:/workdir" ghcr.io/streetsidesoftware/cspell:latest --no-progress "**/*.md"
…s from Two suppressed findings, both real. The runbook called the second prose invocation warn-only without saying that the leniency is CI's, not the command's. The command exits non-zero locally whenever findings exist, and the workflow step sets continue-on-error. A reader following the runbook would have read the expected result as a failure. The markdownlint and cspell lines also drifted from the canonical invocations in GOVERNANCE.md: both omitted --workdir, and cspell read every markdown file rather than README and HISTORY. The narrower cspell scope is deliberate, so the file now carries that reason too, since a reader who does not know it will widen the scope back and get noise no gate acts on. Third round of real findings in this file. It was a lot of new prose written quickly, and the reviews have been earning their place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
commented
Aug 1, 2026
Fourth round's two suppressed findings, both real, both fixed in 1. "Warn-only" attributed the leniency to the wrong thing. The command exits non-zero locally whenever findings exist. It is warn-only in CI because the step sets 2. The Docker lines had drifted from the canonical invocations. Checked against The cspell scope is the more useful half of that. Reading every markdown file is not just non-canonical, it produces findings no gate acts on, since the CI cspell step covers those two files only. So the file now carries the reason as well as the command, because a reader who does not know why the scope is narrow will widen it back and generate noise. That reason is the same one the workflow comment gives: gating every markdown file would mean padding This is the third round to find real problems in |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (1)
OPERATIONS.md:33
prose_lint.pycan be invoked on an explicit file path (even if untracked), so saying it "reads only files git tracks" is only accurate for whole-tree discovery modes (e.g.,prose_lint.py ./--diff). As written, this can mislead a reader into thinking an untracked file can never be checked locally.
`prose_lint.py` reads only files git tracks, so a new file reports clean until it is staged. The first clean run on an unstaged file is vacuous.
The runbook said prose_lint.py reads only files git tracks. That holds for whole-tree discovery and for --diff, not for an explicit path, which is read whether or not git tracks it. Verified rather than reasoned: an untracked file named directly reports its finding and exits 1, while the same file is invisible to a whole-tree run. The wording mattered more than an ordinary inaccuracy, because it discouraged the one habit that catches a new file's problems before CI does. This PR hit exactly that: OPERATIONS.md was written with the wrong line endings and failed the first CI run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
commented
Aug 1, 2026
Fifth round's suppressed finding, real, fixed in Exactly as you said. This one mattered more than an ordinary inaccuracy. As written it told a reader that a new file cannot be checked before staging, which discourages the single habit that catches a new file's problems before CI does. This PR is the worked example: The line now separates the two modes and says to name a new file directly. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (3)
OPERATIONS.md:33
- Same issue later in this section:
prose_lint.py .isn’t a valid path/command in this repo; the actual script isscripts/prose_lint.py. This affects copy-pasteability of the guidance.
Whole-tree discovery reads only files git tracks, so `prose_lint.py .` and `--diff` do not see a new file until it is staged, and a clean whole-tree run proves nothing about an unstaged one. An explicit path is always read, tracked or not, so name a new file directly to check it before staging.
OPERATIONS.md:9
OPERATIONS.mdrefers to runningprose_lint.py <file>, but the script in this repo isscripts/prose_lint.py(there is no top-levelprose_lint.py). As written, the command name is misleading and may not work for readers.
This issue also appears on line 33 of the same file.
CI passes explicit `--check` lists, and a bare `prose_lint.py <file>` runs `DEFAULT_RULES`, which omits `comment-wrap`, `comment-case` and `sentence-split`. A bare run therefore under-reports and a clean result from it proves less than it appears to. Run the CI invocations:
spec/section-model.md:62
- This sentence says repo-specific content “is not declared here”, but this very section immediately declares the standard destinations (
CODESTYLE.md,ARCHITECTURE.md,OPERATIONS.md,TODO.md). Dropping that clause would avoid a self-contradiction for downstream readers.
A repo's own content is not carried and is not declared here, so extraction needs a predictable destination rather than a judgment call per repo. Four topical docs take it, chosen by what the content *is*:
Three review findings plus two my own re-read caught. The prose referred to `prose_lint.py` while the script is `scripts/prose_lint.py`, so the sentences did not match the commands beside them. Both now use the real path. section-model.md said repo content "is not declared here" and then declared four destinations. The intent was that the hub declares where content goes rather than what it says, so it now says that instead of reading as a contradiction. Two found by re-reading rather than by review. The gate list omitted the jq parse loop that CI runs before validate.py, so the section claiming to mirror CI did not. And the note that `gh pr checks --json` "does not exist before gh 2.50" asserted a version boundary that was never verified. It now states the verified fact, that the installed 2.46.0 has no such flag, and names the working alternative. The configure.sh section now warns that a bare invocation defaults to apply. That is not hypothetical: running the script with no arguments to check whether it was executable performed a live apply against this repo. It was a no-op because the repo was already conformant, confirmed by a clean check afterward, but the runbook should say so before someone else learns it the same way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (2)
OPERATIONS.md:19
- The JSON validity loop doesn’t include
repo-config/operational/develop.json, so it misses one of the repo-config JSON files that CI carries and validates elsewhere.
for f in registry/*.json spec/*.json repo-config/*.json; do jq empty "$f"; done
OPERATIONS.md:9
OPERATIONS.mdsays a bareprose_lint.pyrun under-reports becauseDEFAULT_RULESomitssentence-split, but CI also doesn’t runsentence-split(see.github/workflows/validate-task.yml). As written, this implies CI checkssentence-splitwhen it doesn’t, which is misleading.
CI passes explicit `--check` lists, and a bare `python3 scripts/prose_lint.py <file>` runs `DEFAULT_RULES`, which omits `comment-wrap`, `comment-case` and `sentence-split`. A bare run therefore under-reports and a clean result from it proves less than it appears to. Run the CI invocations:
The runbook said DEFAULT_RULES omits comment-wrap, comment-case and sentence-split, which reads as though CI covers all three. CI names sentence-split nowhere, so the sentence overstated the coverage it warned about. Two gaps are CI's rather than this runbook's, and are now recorded rather than silently mirrored. The jq glob covers repo-config/*.json and does not reach repo-config/operational/develop.json, so a malformed operational payload passes the JSON validity step, and validate.py does not parse it either. sentence-split is implemented and carries tests but is named by no invocation, so a rule the repo maintains never runs. Both are left as findings rather than fixed here, since changing what CI runs is a different change from declaring where operational content lives. Both are clean today, so enabling them costs nothing when it happens. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Records the README shape rework Pieter asked for, measured rather than described. Backlog only, no spec change here. ## What was compared `ptr727/PlexCleaner` `README.md` (hand-crafted, the wanted shape), this repo's `README.md`, and `spec/readme-structure.md`. ## Four concrete divergences | # | PlexCleaner | Spec / this repo | | --- | --- | --- | | 1 | `**Binary Releases**`, pre-compiled executables | `**Versioned Releases**`, fixed for every repo | | 2 | License shield at the **bottom**, in a closing `## License` section right before the link definitions | License shield in the **top** `### Build Status` block | | 3 | "See Release History for complete release notes and older versions." | "See Release History for the full history." | | 4 | Channel bullets and shields vary per deliverable | One fixed channel list | **1 and 4 are the same underlying point.** The label depends on what ships. PlexCleaner ships executables so the channel is Binary Releases, while this repo ships source archives so Versioned Releases is right *here*. That makes the label a **per-channel table** rather than one canonical string, which is also what the `readme-structure` audit dimension needs in order to check a repo against its own declared types (GitHub binaries, Docker Hub, NuGet, PyPI each with a distinct bullet label and shield set). **3 carries a caveat.** PlexCleaner writes it as an inline link: ```markdown See [Release History](./HISTORY.md) for complete release notes and older versions. ``` The reference-style rule in `GOVERNANCE.md` forbids inline targets outside the four agent-instruction files. So the entry says to adopt the **wording** and keep the **reference form**, rather than copying the line whole. ## The section order is recorded as a separate question Deliberately not folded into the item above, because it affects every repo and the audit rather than one file's contents. PlexCleaner places `## Questions or Issues` immediately after the Table of Contents, where the spec orders it **ninth**. It also carries sections the spec names nowhere: Performance Considerations, Runtime Metrics, Custom Plugins, Testing, Development Tooling, Feature Ideas, Sample Media Files. Under the recurrence rule landed in #507, **those unnamed sections are correctly repo-specific and stay undeclared**, so they are not evidence that the spec's list is short. The open question is only the *position* of the sections the spec already names, and that is Pieter's call rather than something to infer from one repo. ## Verification ``` prose_lint . --check charset --check dupword --check spelling clean prose_lint TODO.md --check dash --check semicolon --check charset-unknown --check sentence-split clean markdownlint-cli2 (docker, canonical invocation) 0 issues editorconfig-checker (docker, canonical) clean python3 spec/validate.py OK, 21 cataloged ``` The spelling gate caught `labelled` in the first draft of this entry and it was corrected to `labeled`. Worth noting because it is the gate doing its job on the same commit that documents gates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the governance question #508 raised: may a downstream repo open a pull request against the hub to write its own audit report? ## Why there was a vacuum `AUDIT.md` said the audit is read-only and produces a report under `reports/`, and `AUDIT.md` section 10 said "the hub audits and the agent applies the fixes via target PRs". Neither ever said **who writes the report**, so an agent filled the gap, reasonably. ## The answer: the hub authors it The maintainer's call, and the argument that settles it is **efficiency, not principle**: > consider you want it in develop, and you want to audit it anyway, you are going to run the same effort, and the result would be the same if you agree with their audit That is correct and it dismantles the case I had been making. I argued a submitted report preserves downstream context the hub would otherwise rebuild. But **verifying the judgment dimensions is the audit**: confirming a verdict like "analyzers enforced, `TreatWarningsAsErrors` present" means reading the same files the audit reads. Only the deterministic subset is cheap. So a submitted report saves the **writing up**, which is the trivial part once the audit is done. The independence argument survives as a second reason, since a repo grading itself produces a claim rather than evidence, but on its own it would only have justified verifying a submission rather than declining one. ## A third reason #508 demonstrated **A submitted report is stale by construction.** It is a snapshot of one hub revision arriving at a later one. #508 was authored against hub `6501479` and merged at `839ecde`, and before adopting it I had to reconcile two findings that did not exist when it was written: `OPERATIONS.md` became universal in #507, and the merge-bot workflow was declared in #512. That reconciliation is inherent to the workflow, not incidental. ## What downstream still does, and it matters Unchanged, and it is where downstream context is worth most: - **File findings about the hub as issues.** The opposite of self-certification, and it has surfaced several real hub defects today: the merge-bot workflow that no spec required while `WORKFLOW.md` D8.1 graded it, `copilot-instructions.md` describing a pre-split `AGENTS.md`, `CODESTYLE.md` contradicting the markdownlint config, and D2.2 wording that nearly caused a real regression. - **Apply fixes to its own repo** per section 10. ## Also names the base branch `#508` targeted **`main`**, and I merged it without checking `baseRefName`. That put content on `main` that `develop` lacked, breaking the forward-only invariant, and needed a forward-port in #513. The branching model was already stated in `GOVERNANCE.md`, but not in the place an agent opening a conformance pull request against the hub would be reading. It is now stated here too. ## Verification ``` prose_lint AUDIT.md --check charset --check dupword --check spelling --check charset-unknown clean markdownlint-cli2 (docker, canonical) 0 issues editorconfig-checker (docker, canonical) clean python3 spec/validate.py OK, 21 cataloged ``` Prose only, no engine change. `AUDIT.md` is carried at `intent`, so downstream repos pick this up on their next re-vendor. ## What this does not solve Thirteen of twenty-one cataloged repos still have no committed audit report, tracked in `TODO.md`. This decision means the hub writes them, which is a capacity question rather than a governance one. Accepting unverified submissions would have closed the gap by lowering the bar rather than by doing the audits. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records a decision that was carried only in conversation, at the maintainer's request. ## The gap `AUDIT.md` already states the **outcome**: the deterministic subset is "owner-initiated, run on demand when onboarding a repo, on suspected drift, or before fleet-wide changes." What it does not state is that **automation was explored and deliberately deferred**, or why. So the next person to notice that 13 of 21 repos have no committed report re-derives the whole analysis, and quite possibly reaches a different answer for reasons already rejected. ## What was considered, and what blocked it Three shapes: 1. A **scheduled hub-driven audit** publishing each report as a workflow artifact. 2. The same, **committing the report back**, the way the codegen bot updates its own files. 3. A **pull-request hook in each downstream repo**, auditing itself against the current hub. Three things blocked all three: - **Noise.** Until the fleet reaches stasis with every repo onboarded, a scheduled run reports mostly noise, because a repo mid-onboarding is *expected* to be non-conformant. Findings that are correct and not actionable train people to ignore the channel. - **Hub stability.** The hub has to be stable before downstreams can audit against it, since a hub change lands as fleet-wide findings the same day. Today is the worked example: `OPERATIONS.md` becoming universal in #507 created 17 findings, and declaring the merge-bot workflow in #512 created 6 more. - **A catch-22 on the downstream shape.** A self-auditing pull-request hook is CI instrumentation, and the repos that most need auditing are exactly the ones that do not carry it yet. The agreed outcome was the on-demand audit `AUDIT.md` describes today. ## Why the entry is actionable rather than a note It records the **preconditions** that would make reopening worthwhile (the fleet onboarded, and the hub going a stretch without carried-content changes) and **which shape to try first**: the artifact, because it produces evidence without committing anything, so it cannot generate review load while the noise level is still unknown. That makes the first experiment cheap and reversible. ## One correction to an existing entry The `reports/` entry read as a defect. It is not. Per the maintainer, the gap is **paced by capacity**, with repos brought up to spec as they are worked on, so the entry now says it records the outstanding set rather than a failure. This matters because #515 just put report authorship with the hub, and an entry that reads as an accusation invites re-opening a decision that was made deliberately. ## Verification ``` prose_lint TODO.md (dash, semicolon, charset, charset-unknown, dupword, spelling, sentence-split) clean markdownlint-cli2 (docker, canonical) 0 issues editorconfig-checker (docker, canonical) clean ``` Backlog only. The `[audit]` reference already pointed at `spec/audit.py`, so the new pointer to the document uses a separate `[audit-doc]` definition rather than overloading it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the carried-file corrections to main (#517) Promotes twelve commits to `main`. Three of them change **carried** files, so downstream repos read the stale text until this lands, which is what makes the promotion the delivery step rather than bookkeeping. ## What this delivers to the fleet **Carried-file corrections**, picked up by every repo on its next re-vendor: | PR | File | Correction | | --- | --- | --- | | #514 | `.github/copilot-instructions.md` | Described the pre-split `AGENTS.md`, wrong in both halves. `GOVERNANCE.md` holds the rule sections and the two `intent` ones; `AGENTS.md` carries two verbatim sections and none repo-specific | | #514 | `CODESTYLE.md` | Said MD033 flags HTML elements while the config allows `details` and `summary`; also gains the `HISTORY.md` mirror rule, moved from a hub-only spec file so a repo can read the rule it is graded against | | #514 | `WORKFLOW.md` | D2.2 "skipped on smoke" read as the job rather than the validation, and a review acting on it proposed a change that would have coupled the release to smoke | | #503 | `.editorconfig`, `.gitattributes` | 36 comment findings in the files a new repo copies first | | #501 (earlier) | `repo-config/configure.sh` | Already on `main` | **Spec and requirement changes**: - **#507** makes `OPERATIONS.md` `appliesTo: "*"`, keyed to the need rather than to `workflowModel`. Measured: 3 of 21 repos carry it, so **17 will report a new `LETTER file:` finding**. Intended, and a headed stub satisfies it. - **#512** declares `.github/workflows/merge-bot-pull-request.yml` at `interface` fidelity, closing a graded contract (`WORKFLOW.md` D8.1) that no spec required. 15 of 21 already carry it, so this ratifies a de facto standard; the other **6 report a new `DRIFT`**, and those six have Dependabot pull requests piling up with nothing to merge them. - **#505** stops the audit flagging the hub against itself and drops a premise that was false since this repo went public. - **#511** fixes the README shape across repository visibility. - **#515** puts audit-report authorship with the hub and names the base branch, after a downstream conformance PR targeted `main` and I merged it without checking. - **#513** forward-ports that PR's content, restoring the forward-only invariant. **Backlog and reports**: #504, #506, #510, #516 record the README shape rework, the branch-bootstrap step, the declared-description design, and why the audit is on demand. ## Fleet consequence, stated before it lands Two owed follow-ups this promotion creates or carries: 1. **Re-vendor `repo-config/configure.sh`**, already owed from #501, since it is `verbatim` with `appliesTo: "*"` and every repo holding a copy is byte-mismatched. 2. **The next fleet audit reads noisier**, by 17 `OPERATIONS.md` letters and 6 merge-bot drifts. Both are new requirements rather than repo regressions. ## Verification Run on `develop` at `e4c76bc`: ``` python3 scripts/test_prose_lint.py 144 tests, OK python3 scripts/test_repo_gate.py 23 tests, OK python3 scripts/test_pr_review.py 27 tests, OK python3 spec/audit.py --selftest SELFTEST PASS python3 scripts/repo_gate.py eol 0, sha-pin 0 python3 spec/validate.py OK, 21 cataloged prose_lint --check charset --check dupword --check spelling clean editorconfig-checker (docker, canonical) clean ``` Every one of the twelve was merged green with a Copilot review covering its head SHA. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes the
OPERATIONS.mdhalf of the topical-destination question Pieter raised, and states the rule that decides what earns a declared destination at all.The defect
OPERATIONS.mdwas declaredappliesTo: ["operational"], which binds a content need to a workflow model. Those are unrelated axes.operationaldescribes where config lives and how a change reachesdevelop. It says nothing about whether the repo has runbooks, so the selector read a sufficient condition as a necessary one.Blogis the proof. It was reclassified fromoperationaltoreleasetoday and its runbook needs did not change by one word: same host, same deploy, same staging, same rsync. Only the commit-flow classification moved. A doc requirement that appears and disappears on an unrelated axis is mis-keyed.The existing rationale already generalized.
spec/section-model.mdjustified mandating the file like this:Nothing there depends on the workflow model. Every repo has operational surface: publishing to a package registry needs trusted-publisher setup, shipping an image needs registry credentials, serving a site needs a deploy path and a staging story.
The change
OPERATIONS.mdisappliesTo: "*", required everywhere, still presence-checked only, so its content stays entirely the repo's own.spec/section-model.mdandSTANDUP.mdnow say explicitly that the workflow model and this file are unrelated, so nobody re-derives the link.The rule this also states
A destination is declared when its content class recurs across repos.
ARCHITECTURE.mdstays declared, and advisory, because how much of it earns a separate document is contextual.devices.md) does not recur, so it stays the repo's own file, neither declared nor mandated.The scattering these destinations prevent is the same material landing under different names in different repos. That has no force for content existing in exactly one repo, and declaring a destination for a one-repo need would grow the list without bound while still lagging whatever the next repo invents.
This also dissolves an asymmetry that had been accidental rather than decided:
OPERATIONS.mdmandatory andARCHITECTURE.mdadvisory are consistent once declared and required are separate questions. Both are declared. Only the universal one is required.The hub now complies with its own rule
ProjectTemplatecarried noOPERATIONS.md, so this PR would have shipped a rule the hub itself broke.It now has a real one, not a stub, covering what this repo has actually hit: running the gates the way CI runs them (including that a bare
prose_lint.pyrunsDEFAULT_RULESand under-reports), the fleet audit, theconfigure.shrunbook with its blast radius stated, branch recovery viagit push origin <sha>:refs/heads/<branch>, and the two liveghlimitations on this host.Fleet impact, stated rather than discovered
This makes the file required for 18 more repos. Measured live:
This PR fixes the hub, leaving 17 repos that will report a new
LETTER file: OPERATIONS.md absentfinding on the next audit. That is the intended consequence of widening the requirement, not a surprise, and a stub satisfies it. It is worth knowing before the next fleet audit reads noisier than the last one.Verification
cspellwas run and reports words inOPERATIONS.mdand pre-existing ones inSTANDUP.md. None are gated: the CI cspell step coversREADME.mdandHISTORY.mdonly, deliberately, because gating all markdown would mean endlessly paddingcspell.jsonwith technical terms. Nothing was added to the dictionary.Not in this PR
The advisory audit dimension for misplaced content in undeclared markdown, which is the other half of the discussion. It needs
spec/audit.pyto enumerate unmatched markdown andAUDIT.mdto carry the agent's classification step, and it is cleaner as its own change.🤖 Generated with Claude Code