From 1d4e20ab06f8859cbf55dc94e9ed45bbd247aa09 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 1 Aug 2026 11:35:02 -0700 Subject: [PATCH 1/3] Answer the four spec questions the PhotoCleaner audit raised All four verified against the current tree before changing anything. The carried copilot-instructions section still described the pre-split AGENTS.md, saying most of it is fleet law and two of its sections are repo-specific. After the split GOVERNANCE.md holds the rule sections and the two intent ones, and AGENTS.md carries exactly two verbatim sections and no repo-specific ones. Every repo carrying the section inherited the wrong description, and a reviewer following it looked for byte-locked rule text in the wrong file. CODESTYLE.md said MD033 flags HTML elements while .markdownlint-cli2.jsonc allows details and summary. The config's own comment documents the exception the prose denied. The HISTORY.md mirror rule lived only in spec/readme-structure.md, which is hub-only and carried by nobody, so a repo could not read the rule it was graded against and PhotoCleaner wrote a local copy for want of a destination. It moves into CODESTYLE.md, which every repo carries, and the spec now states only what the audit does with it rather than restating the rule. WORKFLOW.md D2.2 said the gate "is skipped on smoke", which names the validation, and a review read it as the job status and proposed a job-level if:. That would have been a real regression, since github-release has validate-release in its needs and a skipped need skips the dependent. The wording now says the check exits early while the job still reports success, and says why the distinction matters. Reported by the PhotoCleaner agent on #509. Finding 3 landed separately in #511. Co-Authored-By: Claude Opus 5 (1M context) --- .github/copilot-instructions.md | 2 +- CODESTYLE.md | 3 ++- WORKFLOW.md | 2 +- spec/readme-structure.md | 6 +++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f7bfe16f..18848560 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -18,7 +18,7 @@ Summarized for VS Code's generators. The full rules, rationale, and examples are ## Reviewing Carried Fleet Content -Several of this repository's governance files are carried from a shared template and kept in sync across a fleet of sibling repositories, among them `AGENTS.md`, `CODESTYLE.md`, `WORKFLOW.md`, this file, and the `repo-config/` rulesets. Most of `AGENTS.md` is universal fleet law: every section that states a rule, as opposed to the two that describe this repository's own directory tree and devcontainer, is byte-locked and verified by an automated byte-for-byte match against the template canonical, not by line-by-line review. +Several of this repository's governance files are carried from a shared template and kept in sync across a fleet of sibling repositories, among them `AGENTS.md`, `CODESTYLE.md`, `WORKFLOW.md`, this file, and the `repo-config/` rulesets. Most of `GOVERNANCE.md` is universal fleet law: every section that states a rule, as opposed to the two that describe this repository's own directory tree and devcontainer, is byte-locked and verified by an automated byte-for-byte match against the template canonical, not by line-by-line review. `AGENTS.md` is the thin router and carries two byte-locked sections of its own, with no repository-specific ones. Two constraints follow when reviewing that content. diff --git a/CODESTYLE.md b/CODESTYLE.md index fd8642d8..dfdfaa9f 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -33,9 +33,10 @@ Each language defines a **clean-compile** verification: the combination of build These apply repo-wide, in every directory: -1. **Markdown linting**: All `.md` files must be lint-clean (error and warning free) via the VS Code `markdownlint` extension. [`.markdownlint-cli2.jsonc`][markdownlint-cli2] at the repo root is the single source of truth, and the davidanson `markdownlint` extension and a command-line `markdownlint-cli2` run both read it, so the IDE and CLI stay in lock-step. Rules it deliberately disables (e.g. `MD013` line-length) are **intentional**, so do not "fix" them. `MD033` inline HTML stays **enabled**: HTML comments are permitted (markdownlint does not flag them), HTML elements are flagged, and anything with a native markdown equivalent uses the markdown. Fix violations at the source rather than disabling rules. +1. **Markdown linting**: All `.md` files must be lint-clean (error and warning free) via the VS Code `markdownlint` extension. [`.markdownlint-cli2.jsonc`][markdownlint-cli2] at the repo root is the single source of truth, and the davidanson `markdownlint` extension and a command-line `markdownlint-cli2` run both read it, so the IDE and CLI stay in lock-step. Rules it deliberately disables (e.g. `MD013` line-length) are **intentional**, so do not "fix" them. `MD033` inline HTML stays **enabled**: HTML comments are permitted (markdownlint does not flag them), `details` and `summary` are allowed because a GitHub collapsible has no markdown equivalent, every other element is flagged, and anything with a native markdown equivalent uses the markdown. Fix violations at the source rather than disabling rules. 2. **Spelling**: All spelling must be clean via the CSpell VS Code integration, and words must be correctly spelled in **US English** (the repo-wide convention, per [GOVERNANCE.md][governance]). The shared `cspell.json` sets `"language": "en-US"` so British spellings are flagged, where a bare `"en"` accepts both US and British and silently passes the wrong spelling. Project-specific terms go in the shared `cspell.json` `words` list, the single source of truth the extension, CLI, and CI all read. The `.code-workspace` must **not** carry its own `cspell.words`/`cSpell.words` block, and when externalizing words into `cspell.json`, delete any word list left in the workspace (a leftover one duplicates the list and silently drifts). 3. **Spelling CI scope**: The enforced CI spell-check gate covers **`README.md` and `HISTORY.md` only**, because these are the files every repo visitor sees, so they must be clean. It is deliberately **not** all `**/*.md`: repos carry many markdown files full of technical terms, and gating every one of them would mean endlessly padding `cspell.json` just to keep CI green. Broad, live spell-checking across any file (source, markdown, text) is the **cspell editor extension's** job, so typos still surface to whoever is editing. A repo owner **may** widen their own CI file list, but README + HISTORY are the default; keep the CI workflow, the `Lint: Spelling` VS Code task, and the GOVERNANCE.md cspell one-liner on the same file list. The list is explicit (not a glob), so a repo that ships no `HISTORY.md` (e.g. one with no changelog) must drop it from all three surfaces and gate on `README.md` alone, since cspell errors on a listed file that does not exist. Markdown *linting* (item 1) stays repo-wide `**/*.md`, which does not choke on technical terms. +4. **`HISTORY.md` mirrors the README opening**: `HISTORY.md` is the maintainer-curated changelog and opens as the README's twin, carrying the same `# ` (without the README's ToC-omit comment) and the same intro paragraph copied verbatim, then a `## Release History` section. The mirrored opening keeps the project identity consistent for a reader who lands on the changelog directly. The audit checks that the title and intro match the README, with HTML comments stripped. ## .NET diff --git a/WORKFLOW.md b/WORKFLOW.md index 10f22837..56d5cbea 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -150,7 +150,7 @@ The required behaviors, organized by domain. Each is a **MUST**, stated as input ### D2 - Input/State Validation at Entry - **D2.1 Validate before expensive work.** Output: a dedicated entry job/step asserts each cross-input/derived-state invariant and fails fast before builds. Downstream jobs `needs:` it. -- **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and it is **skipped on smoke** (a detached PR head always versions as prerelease). *Prevents: a non-default leg published as stable; a build-metadata false-positive; the gate blocking every default-base promotion PR.* +- **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and on a smoke build the **check exits early while the job still reports success** (a detached PR head always versions as prerelease). Read that as the validation being skipped rather than the job, because a job-level `if:` would skip the job itself, and everything carrying it in `needs:` skips with it. *Prevents: a non-default leg published as stable; a build-metadata false-positive; the gate blocking every default-base promotion PR.* - **D2.3 Publish only from main or develop.** Input: a dispatch publish. Output: a dispatch from any ref other than `main` or `develop` fails fast. *Prevents: cutting a release from an unintended branch.* - **D2.4 Mutually-exclusive / paired inputs are validated.** Input: a workflow with either/or or must-pair inputs (e.g. the docker-readme task's `repositories` XOR `manifest`+`manifest-jq`). Output: a half-filled or conflicting combination fails fast. *Prevents: a silent fall-through.* diff --git a/spec/readme-structure.md b/spec/readme-structure.md index 32c417d4..9b78432e 100644 --- a/spec/readme-structure.md +++ b/spec/readme-structure.md @@ -44,8 +44,12 @@ The file is the declared destination rather than a required file, the same footi ## HISTORY.md -`HISTORY.md` is the maintainer-curated changelog and opens as the README's twin: the same `# <Title>` (without the README's ToC-omit comment) and the same intro paragraph, copied verbatim, then a `## Release History` section. The mirrored opening keeps the project identity consistent for a reader who lands on the changelog directly, and the audit checks that the title and intro match the README (HTML comments stripped). +The rule lives in [`CODESTYLE.md`][codestyle] "Markdown and Spelling", which every repo carries, so a repo can read the rule it is measured against. This file states only what the audit does with it: the `readme-structure` dimension checks that the `HISTORY.md` title and intro match the README's, with HTML comments stripped. ## Docker Hub README Docker Hub has two text fields: a **short description** (the tagline, capped near 100 characters) that mirrors the README intro line (item 1), and the longer **overview**. A repo that publishes a Docker image keeps a **separate** `Docker/README.md` for the overview: Docker Hub's description has a much smaller size limit than a project README, so it carries a trimmed overview, not the full README. It is published by the docker-readme workflow task, not copied from the root README. + +<!-- Internal --> + +[codestyle]: ../CODESTYLE.md From 8a73dacb43c3dab3e6bf0ba7cac627a343cda33b Mon Sep 17 00:00:00 2001 From: Pieter Viljoen <ptr727@users.noreply.github.com> Date: Sat, 1 Aug 2026 11:50:58 -0700 Subject: [PATCH 2/3] Say when a dependent actually skips with its need The D2.2 clarification claimed everything carrying a skipped job in needs: skips with it. That is not true of a dependent that opts out, and this repo carries the counter-example: check-workflow-status has needs: [validate] with if: always(), so it runs and reads needs.validate.result explicitly. The warning still holds where it matters, and now says so precisely. github-release carries validate-release in needs: with if: inputs.github && !inputs.smoke and no always(), so a job-level skip there would couple the release to smoke through a second path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --- WORKFLOW.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WORKFLOW.md b/WORKFLOW.md index 56d5cbea..df2ffff3 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -150,7 +150,7 @@ The required behaviors, organized by domain. Each is a **MUST**, stated as input ### D2 - Input/State Validation at Entry - **D2.1 Validate before expensive work.** Output: a dedicated entry job/step asserts each cross-input/derived-state invariant and fails fast before builds. Downstream jobs `needs:` it. -- **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and on a smoke build the **check exits early while the job still reports success** (a detached PR head always versions as prerelease). Read that as the validation being skipped rather than the job, because a job-level `if:` would skip the job itself, and everything carrying it in `needs:` skips with it. *Prevents: a non-default leg published as stable; a build-metadata false-positive; the gate blocking every default-base promotion PR.* +- **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and on a smoke build the **check exits early while the job still reports success** (a detached PR head always versions as prerelease). Read that as the validation being skipped rather than the job, because a job-level `if:` would skip the job itself, and a dependent skips with it unless that dependent opts out with `if: always()` and reads the result explicitly, the way the PR aggregator does. `github-release` carries `validate-release` in `needs:` and does **not** opt out, so a job-level skip there would couple the release to smoke through a second path on top of the `if:` it already carries. *Prevents: a non-default leg published as stable; a build-metadata false-positive; the gate blocking every default-base promotion PR.* - **D2.3 Publish only from main or develop.** Input: a dispatch publish. Output: a dispatch from any ref other than `main` or `develop` fails fast. *Prevents: cutting a release from an unintended branch.* - **D2.4 Mutually-exclusive / paired inputs are validated.** Input: a workflow with either/or or must-pair inputs (e.g. the docker-readme task's `repositories` XOR `manifest`+`manifest-jq`). Output: a half-filled or conflicting combination fails fast. *Prevents: a silent fall-through.* From 6f54f63ae987c2492cc9535327c252bba207a52a Mon Sep 17 00:00:00 2001 From: Pieter Viljoen <ptr727@users.noreply.github.com> Date: Sat, 1 Aug 2026 11:59:50 -0700 Subject: [PATCH 3/3] Use a documented link-group header, not a new one The new CODESTYLE pointer was filed under `<!-- Internal -->`, copied from spec/section-model.md. This file enumerates the group headers two sections above, as Shields, Workflow, Repo and External, so it was inventing a fifth name in the file that states the four. `[codestyle]` is a local repository path, which is what Repo names, so the block now uses it. Found as a suppressed finding in the Copilot round. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --- spec/readme-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/readme-structure.md b/spec/readme-structure.md index 9b78432e..3cc7f214 100644 --- a/spec/readme-structure.md +++ b/spec/readme-structure.md @@ -50,6 +50,6 @@ The rule lives in [`CODESTYLE.md`][codestyle] "Markdown and Spelling", which eve Docker Hub has two text fields: a **short description** (the tagline, capped near 100 characters) that mirrors the README intro line (item 1), and the longer **overview**. A repo that publishes a Docker image keeps a **separate** `Docker/README.md` for the overview: Docker Hub's description has a much smaller size limit than a project README, so it carries a trimmed overview, not the full README. It is published by the docker-readme workflow task, not copied from the root README. -<!-- Internal --> +<!-- Repo --> [codestyle]: ../CODESTYLE.md