diff --git a/.agents/skills/audit-a-repo/SKILL.md b/.agents/skills/audit-a-repo/SKILL.md index b13106ab..5cd0c133 100644 --- a/.agents/skills/audit-a-repo/SKILL.md +++ b/.agents/skills/audit-a-repo/SKILL.md @@ -22,7 +22,7 @@ The audit is the fleet's measurement procedure, and the two failure shapes it gu - **Know what the runner does and does not prove.** `spec/audit.py` mechanizes the deterministic subset only: settings, rulesets, secret names, file and section presence, verbatim hashing, interface wiring, Dependabot coverage, branch facts. It evaluates no check under a type in `spec/project-types.json`, so every per-type check is judged by hand, and a clean run is no evidence for them (`AUDIT.md` section 4). Silence from a tool that was never looking reads exactly like a pass. - **Judge letter and intent per check** and keep the vocabulary: letter miss with intent satisfied is a drift finding, both missing is a defect, and operational is binary over the applicable set (`AUDIT.md` sections 4 and 7). Do not invent a parallel scheme. - **Assert the Actions implement `WORKFLOW.md`** by outcome, not by matching catalog snippets byte for byte: the 5A static audit with a `file:line` citation per applicable guarantee, then the 5B trace scenarios (`AUDIT.md` section 5). The `workflow-ci-contract` skill summarizes that contract. -- **Check live settings, rulesets, and secrets from a hub checkout at `main`** with `AUDIT.md` section 6. Run `repo-config/configure.sh check` with the target repository and model rather than constructing a local comparison. The hub payloads are the only repository-configuration source. +- **Check live settings, rulesets, and secrets from a hub checkout at `main`** with `AUDIT.md` section 6. Run `repo-config/configure.sh check` with the target repository and model for settings and rulesets, and `spec/audit.py [RepoName]` for secrets, rather than constructing a local comparison. The hub payloads are the only repository-configuration source. ## Reporting diff --git a/.agents/skills/resync-a-repo/SKILL.md b/.agents/skills/resync-a-repo/SKILL.md index 019e45de..50930639 100644 --- a/.agents/skills/resync-a-repo/SKILL.md +++ b/.agents/skills/resync-a-repo/SKILL.md @@ -69,7 +69,7 @@ Preserve the evidence RESYNC.md section 2 requires, and do not leave the finding 5. **Settings, rulesets, and secrets.** Run `repo-config/configure.sh check "/" release` (substitute `operational` for an operational repo) from the hub at `main`, then `apply` for what it reports, never from a - carried copy. + carried copy. Run `spec/audit.py [RepoName]` from the same checkout for secrets. 6. **Intent files last, and by hand,** since nothing mechanical judges these. Reconcile the registry entry (`status`, `types`, `releaseTrigger`, `workflowModel`, diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index ce2732c5..3053de42 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -5ab0e6a26d537def +1e87239173caee9a diff --git a/.claude-plugin/fleet-skills/skills/audit-a-repo/SKILL.md b/.claude-plugin/fleet-skills/skills/audit-a-repo/SKILL.md index b13106ab..5cd0c133 100644 --- a/.claude-plugin/fleet-skills/skills/audit-a-repo/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/audit-a-repo/SKILL.md @@ -22,7 +22,7 @@ The audit is the fleet's measurement procedure, and the two failure shapes it gu - **Know what the runner does and does not prove.** `spec/audit.py` mechanizes the deterministic subset only: settings, rulesets, secret names, file and section presence, verbatim hashing, interface wiring, Dependabot coverage, branch facts. It evaluates no check under a type in `spec/project-types.json`, so every per-type check is judged by hand, and a clean run is no evidence for them (`AUDIT.md` section 4). Silence from a tool that was never looking reads exactly like a pass. - **Judge letter and intent per check** and keep the vocabulary: letter miss with intent satisfied is a drift finding, both missing is a defect, and operational is binary over the applicable set (`AUDIT.md` sections 4 and 7). Do not invent a parallel scheme. - **Assert the Actions implement `WORKFLOW.md`** by outcome, not by matching catalog snippets byte for byte: the 5A static audit with a `file:line` citation per applicable guarantee, then the 5B trace scenarios (`AUDIT.md` section 5). The `workflow-ci-contract` skill summarizes that contract. -- **Check live settings, rulesets, and secrets from a hub checkout at `main`** with `AUDIT.md` section 6. Run `repo-config/configure.sh check` with the target repository and model rather than constructing a local comparison. The hub payloads are the only repository-configuration source. +- **Check live settings, rulesets, and secrets from a hub checkout at `main`** with `AUDIT.md` section 6. Run `repo-config/configure.sh check` with the target repository and model for settings and rulesets, and `spec/audit.py [RepoName]` for secrets, rather than constructing a local comparison. The hub payloads are the only repository-configuration source. ## Reporting diff --git a/.claude-plugin/fleet-skills/skills/resync-a-repo/SKILL.md b/.claude-plugin/fleet-skills/skills/resync-a-repo/SKILL.md index 019e45de..50930639 100644 --- a/.claude-plugin/fleet-skills/skills/resync-a-repo/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/resync-a-repo/SKILL.md @@ -69,7 +69,7 @@ Preserve the evidence RESYNC.md section 2 requires, and do not leave the finding 5. **Settings, rulesets, and secrets.** Run `repo-config/configure.sh check "/" release` (substitute `operational` for an operational repo) from the hub at `main`, then `apply` for what it reports, never from a - carried copy. + carried copy. Run `spec/audit.py [RepoName]` from the same checkout for secrets. 6. **Intent files last, and by hand,** since nothing mechanical judges these. Reconcile the registry entry (`status`, `types`, `releaseTrigger`, `workflowModel`, diff --git a/.github/actions/prose-gate/prose_lint.py b/.github/actions/prose-gate/prose_lint.py index fedd9f6d..a7a62be4 100755 --- a/.github/actions/prose-gate/prose_lint.py +++ b/.github/actions/prose-gate/prose_lint.py @@ -353,6 +353,7 @@ def path_candidate(token: str, in_span: bool = True) -> str | None: "repo-config/main.json", "repo-config/README.md", "repo-config/settings.json", + "spec/secrets.json", ".github/workflows/get-version-task.yml", ".github/workflows/publish-plan-task.yml", ".github/workflows/build-release-task.yml", diff --git a/.github/skills/audit-a-repo/SKILL.md b/.github/skills/audit-a-repo/SKILL.md index b13106ab..5cd0c133 100644 --- a/.github/skills/audit-a-repo/SKILL.md +++ b/.github/skills/audit-a-repo/SKILL.md @@ -22,7 +22,7 @@ The audit is the fleet's measurement procedure, and the two failure shapes it gu - **Know what the runner does and does not prove.** `spec/audit.py` mechanizes the deterministic subset only: settings, rulesets, secret names, file and section presence, verbatim hashing, interface wiring, Dependabot coverage, branch facts. It evaluates no check under a type in `spec/project-types.json`, so every per-type check is judged by hand, and a clean run is no evidence for them (`AUDIT.md` section 4). Silence from a tool that was never looking reads exactly like a pass. - **Judge letter and intent per check** and keep the vocabulary: letter miss with intent satisfied is a drift finding, both missing is a defect, and operational is binary over the applicable set (`AUDIT.md` sections 4 and 7). Do not invent a parallel scheme. - **Assert the Actions implement `WORKFLOW.md`** by outcome, not by matching catalog snippets byte for byte: the 5A static audit with a `file:line` citation per applicable guarantee, then the 5B trace scenarios (`AUDIT.md` section 5). The `workflow-ci-contract` skill summarizes that contract. -- **Check live settings, rulesets, and secrets from a hub checkout at `main`** with `AUDIT.md` section 6. Run `repo-config/configure.sh check` with the target repository and model rather than constructing a local comparison. The hub payloads are the only repository-configuration source. +- **Check live settings, rulesets, and secrets from a hub checkout at `main`** with `AUDIT.md` section 6. Run `repo-config/configure.sh check` with the target repository and model for settings and rulesets, and `spec/audit.py [RepoName]` for secrets, rather than constructing a local comparison. The hub payloads are the only repository-configuration source. ## Reporting diff --git a/.github/skills/resync-a-repo/SKILL.md b/.github/skills/resync-a-repo/SKILL.md index 019e45de..50930639 100644 --- a/.github/skills/resync-a-repo/SKILL.md +++ b/.github/skills/resync-a-repo/SKILL.md @@ -69,7 +69,7 @@ Preserve the evidence RESYNC.md section 2 requires, and do not leave the finding 5. **Settings, rulesets, and secrets.** Run `repo-config/configure.sh check "/" release` (substitute `operational` for an operational repo) from the hub at `main`, then `apply` for what it reports, never from a - carried copy. + carried copy. Run `spec/audit.py [RepoName]` from the same checkout for secrets. 6. **Intent files last, and by hand,** since nothing mechanical judges these. Reconcile the registry entry (`status`, `types`, `releaseTrigger`, `workflowModel`, diff --git a/AUDIT.md b/AUDIT.md index 2e467be1..e89035f8 100644 --- a/AUDIT.md +++ b/AUDIT.md @@ -99,7 +99,7 @@ Run [`WORKFLOW.md`][workflow]'s methodology against the repo's **own** Actions: - **General settings and rulesets** - fetch the hub and check out `main`. Run `repo-config/configure.sh check / release|operational` from that checkout. Pass the target repository and its registry `workflowModel` explicitly. The command checks the shared settings, state-dependent settings, Dependabot security features, and both rulesets against the hub payloads. It preserves and reports `bypass_actors` without asserting them because bypass authority is a per-repository human decision. -- **Secrets** - confirm each required secret exists (name only, not the values). Check the Actions store and, where the mechanism needs it (Docker Hub, codegen App), the Dependabot store too. +- **Secrets** - from the same hub checkout, run [`spec/audit.py`][audit-runner] `[repo]` and read its Secrets section. It resolves the required set from the hub's own [`spec/secrets.json`][secrets] plus the registry entry's `publish[]`/`types[]`/`requiredSecrets[]`, confirming each required name exists (name only, not the values) in the Actions store and, where the mechanism needs it (Docker Hub, codegen App), the Dependabot store too. - **Dependabot ecosystem coverage** - for each ecosystem the repo's tree implies, `.github/dependabot.yml` must declare it: `github-actions` when `.github/workflows/` is present (its workflows reference actions, and otherwise those versions go stale and a stood-up merge-bot has no action-update PRs to auto-merge), and `devcontainers` when a `.devcontainer` is present. The mechanical check (`spec/audit.py`) asserts each implied ecosystem's **presence**. A tree-implied ecosystem declared nowhere is a **drift finding** (the file exists, so its absence would instead be a file-presence letter). Then confirm **by inspection** that each declared ecosystem **dual-targets `main` + `develop`** per the [Branching Model][governance-branching-model], since the regex below cannot pair an ecosystem with its `target-branch`. Language ecosystems (`nuget`/`uv`/`npm`) are directory-scoped and audited by inspection too. diff --git a/STANDUP.md b/STANDUP.md index 6af8e687..81f3bf9c 100644 --- a/STANDUP.md +++ b/STANDUP.md @@ -166,7 +166,7 @@ Carry `AGENTS.md`'s skill-dependency pointer paragraph, the one naming `scripts/ **`host-tools.json` is carried at the repo's root, and it is not the fleet declaration.** [`spec/host-tools.json`][host-tools] states what every repo's procedures need and is the hub's to change. The carried root file states what this repo needs **beyond** that, so it is where a tool only this repo uses, or a floor only this repo requires, is declared. [`scripts/host_gate.py`][host-gate] layers the root file over the fleet one, tighten-only: an entry may add a tool, raise a floor, or turn an optional tool required, and may not lower a floor or turn a required tool optional, since that retires a fleet check from inside the repo it protects. A rejected relaxation is reported rather than dropped. A repo with nothing to add carries the stub with an empty `tools` list, the same footing as `OPERATIONS.md`, so the declaration is somewhere a reader finds rather than somewhere they must know to look. -**The carried copy drops the `$schema` pointer, and that is not an oversight to correct.** The schemas are hub-only and no selector carries one, so a relative pointer copied downstream resolves to a path that repo does not have, and a schema-aware editor then reports the file invalid for a reason the repo cannot fix. This is settled fleet practice rather than a new rule: the hub's `spec/secrets.json` carries `./secrets.schema.json` and every downstream copy of that file omits the key. Copy the structure and leave the pointer behind. +**The carried copy drops the `$schema` pointer, and that is not an oversight to correct.** The schemas are hub-only and no selector carries one, so a relative pointer copied downstream resolves to a path that repo does not have, and a schema-aware editor then reports the file invalid for a reason the repo cannot fix. This is settled fleet practice rather than a new rule: this repo's own root `host-tools.json` carries `./spec/host-tools-local.schema.json` and every downstream copy of that carried file omits the key. Copy the structure and leave the pointer behind. **Repo-specific content has a declared destination, not a judgment call.** The baseline is what a repo *carries*. Anything the repo knows that the fleet does not needs somewhere to live, and improvising a location per repo is what the destinations in [`spec/section-model.md`][section-model] exist to prevent. Four topical docs take it, chosen by what the content **is**: diff --git a/TODO.md b/TODO.md index 61d3a208..ee31d2cf 100644 --- a/TODO.md +++ b/TODO.md @@ -39,15 +39,16 @@ One pull request pointing a hub `uses:` at a hub-owned action, so that the resol One pull request measuring the remaining carried surface against the carry-versus-reach test and moving whatever qualifies, now that the model is settled rather than open. -**State** `decision`. **Touches** [`AUDIT.md`][audit-doc], [`spec/secrets.json`][secrets], and [`spec/files.json`][files]. **Cost** one hub edit plus a retirement per repo on its next visit. The workflow half of this cluster, replacing copy-pasted workflow content with cross-repo reuse, is measured and answered under "Hub-Hosted Reusable Workflows" below. +**State** `decision`. **Touches** [`AUDIT.md`][audit-doc] and [`spec/files.json`][files]. **Cost** one hub edit plus a retirement per repo on its next visit. The workflow half of this cluster, replacing copy-pasted workflow content with cross-repo reuse, is measured and answered under "Hub-Hosted Reusable Workflows" below. -- **Measure carried [`AUDIT.md`][audit-doc] and [`spec/secrets.json`][secrets] against the test.** Each is adapted per repo today and the question is how much of each is genuinely per-repo. +- **Measure carried [`AUDIT.md`][audit-doc] against the test.** It is adapted per repo today and the question is how much of it is genuinely per-repo. - **Blocked by** - Nothing. - **Issue** - None filed, and [#305][issue-305] covers the propagation half from the other direction. - - **Checked** - `develop` at `3d1a0b1` on 2026-08-06, where [`spec/files.json`][files] declares both at `intent` and no longer declares `repo-config/configure.sh` at all. - - **Open** - Which of the two moves, if either. + - **Checked** - `develop` at `743fc81` on 2026-08-26, where [`spec/files.json`][files] declares it at `intent`. + - **Open** - Whether it moves. - **Settled** - The test is stated: a repository carries the content it is audited against and the configuration that describes it, and it reaches machinery whose content is identical in every repository. - **Settled** - `repo-config/configure.sh` is the first file moved across, carrying the ledger's only `retire` disposition and naming six repos, NxWitness, aiopurpleair, homeassistant-purpleair, ESPHome-NonRoot, VSCode-Server-DotNetCore and LanguageTags. + - **Settled** - [`spec/secrets.json`][secrets]'s adapted `baseline`/`mechanisms` carry is the second, retired outright rather than moved (ptr727/ProjectTemplate#993): `baseline` applies to every fleet repo by definition and `mechanisms`/`targetMechanisms`/`typeMechanisms` resolve centrally, `spec/audit.py` reading the hub's own file plus `registry/repos.json`, so no downstream repo needs a replacement local copy. - **Settled** - An unreachable hub means the tool did not run, reported as not run rather than worked around, since a hand-rolled substitute is the duplicated effort the model exists to end. ### The README Structure Rework diff --git a/WORKFLOW.md b/WORKFLOW.md index 5941ac40..1ebe89e8 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -149,7 +149,7 @@ The required behaviors, organized by domain. Each is a **MUST**, stated as input - **D1.3 Smoke never publishes and never uploads.** Input: `smoke: true`. Output: full compile/lint/test, but no registry/image push, no release, and **no** artifact uploads (every `upload-artifact`, including any aggregation job, is gated `!smoke`). *Prevents: a PR publishing, and orphaned artifacts churning the storage quota.* - **D1.4 Workflow-file changes are not smoke-built.** Input: a PR changing only `.github/workflows/**`. Output: the paths-filter excludes workflow files, so smoke-build skips. *Implication: a workflow-only change is not smoke-built, but actionlint still validates it in CI.* - **D1.5 One required aggregator gates merge.** Input: any PR. Output: a single aggregator job must **succeed**, `needs:` the changes job and the validation job, treat a **skipped** smoke build as pass, and **block** on `failure`/`cancelled`. Its name is ruleset-bound: the job `name:` and the ruleset `context:` are the same string and MUST be renamed together, never independently. *Prevents: a paths-filter error letting a target-changing PR merge unbuilt.* -- **D1.6 Coverage is reported to Codecov (C# and Python).** Input: a C# or Python repo's validation/test job. Output: tests run with coverage collection (`dotnet test --collect:"XPlat Code Coverage"` or `pytest --cov-report=xml`) and a `codecov/codecov-action` step uploads it, **best-effort** (`continue-on-error` and/or `fail_ci_if_error: false`, so a Codecov outage or an absent token never reds the gate). `CODECOV_TOKEN` lives in the repo's **actions** secret store and reaches the reusable validator via `secrets: inherit`. Required for **every** C# and Python repo that has tests. Where this guarantee does not apply, a repo's own `spec/secrets.json` may carry no `typeMechanisms` entry, and that absence is not drift. The repo also ships a **`codecov.yml`** that sets the project and patch statuses to **`informational: true`** so a coverage delta never gates a PR (a distinct knob from `fail_ci_if_error`, which only guards the upload step), and excludes intentionally-untested, non-shipped code (an example/demo or benchmark project) from the coverage denominator via `ignore`. A repo may override this to enforce a coverage threshold where its quality bar requires it. Coverage output is a build artifact, so `.gitignore` excludes it (e.g. `coverage/` and `*.cobertura.xml`, with `.gitignore` the full source of truth) so a blanket `git add -A` won't stage the untracked output. *Prevents: coverage silently going unreported; a stale, unused token; a coverage regression blocking an unrelated PR; a coverage artifact committed by a blanket add.* +- **D1.6 Coverage is reported to Codecov (C# and Python).** Input: a C# or Python repo's validation/test job. Output: tests run with coverage collection (`dotnet test --collect:"XPlat Code Coverage"` or `pytest --cov-report=xml`) and a `codecov/codecov-action` step uploads it, **best-effort** (`continue-on-error` and/or `fail_ci_if_error: false`, so a Codecov outage or an absent token never reds the gate). `CODECOV_TOKEN` lives in the repo's **actions** secret store and reaches the reusable validator via `secrets: inherit`. Required for **every** C# and Python repo that has tests. Where this guarantee does not apply (a `lint-only` profile for that type, per `registry/repos.json`), the hub's `spec/secrets.json` `typeMechanisms` mapping is not claimed for that repo, and the absence is not drift. The repo also ships a **`codecov.yml`** that sets the project and patch statuses to **`informational: true`** so a coverage delta never gates a PR (a distinct knob from `fail_ci_if_error`, which only guards the upload step), and excludes intentionally-untested, non-shipped code (an example/demo or benchmark project) from the coverage denominator via `ignore`. A repo may override this to enforce a coverage threshold where its quality bar requires it. Coverage output is a build artifact, so `.gitignore` excludes it (e.g. `coverage/` and `*.cobertura.xml`, with `.gitignore` the full source of truth) so a blanket `git add -A` won't stage the untracked output. *Prevents: coverage silently going unreported; a stale, unused token; a coverage regression blocking an unrelated PR; a coverage artifact committed by a blanket add.* ### D2 - Input/State Validation at Entry diff --git a/docs/repo-config.md b/docs/repo-config.md index f8c07ff1..8c7ffcee 100644 --- a/docs/repo-config.md +++ b/docs/repo-config.md @@ -18,7 +18,7 @@ The carried `AUDIT.md` reaches the hub at `main` for its configuration check. Th ## Per-Repository Secrets -- **Adapted `spec/secrets.json` shape.** The repo-scoped adaptation carries `baseline` (the App pair, which every fleet repo needs for the merge-bot) plus a `mechanisms` entry for each publish mechanism the repo actually uses, and the `targetMechanisms` routing entries for those mechanisms. **A source-only repo whose publish targets all map to a null mechanism (nothing to route) carries just `baseline` (plus a `note`)**, omitting `targetMechanisms` and `mechanisms` entirely, because a lone `targetMechanisms` map with no `mechanisms` reads as a schema bug (the audit enumerates `baseline` + `mechanisms`, never `targetMechanisms`, so an all-null routing map is dead weight). A `release` repo that uses a real mechanism (e.g. `nuget-oidc`, `docker-hub`, `codecov`) carries that `mechanisms` entry **and** its `targetMechanisms`/`typeMechanisms` routing, which the audit then picks up. +Downstream repositories carry no copy of `spec/secrets.json`. `baseline` applies to every fleet repo by definition, and `mechanisms`/`targetMechanisms`/`typeMechanisms` resolve from the registry entry (a repo's `publish[]` targets and `types[]`) rather than from anything repo-specific, so a per-repo copy could only restate the hub's own computation or drift from it between resyncs. `spec/audit.py [RepoName]`, run from a hub checkout, resolves the required set the same way the mechanized `repo-setup` check does: `targetMechanisms`/`typeMechanisms` selects each applicable mechanism from the registry entry, `baseline` plus the selected `mechanisms` plus the registry entry's own `requiredSecrets[]` (the repo's domain-specific additions) gives the required and forbidden names, and the result is cross-checked against the repo's live Actions and Dependabot secret stores. ## Applying the Config diff --git a/host-tools.json b/host-tools.json index 9cea46f6..a0f41fbc 100644 --- a/host-tools.json +++ b/host-tools.json @@ -1,5 +1,5 @@ { "$schema": "./spec/host-tools-local.schema.json", - "note": "This repository's own host-tool declaration, layered over the fleet declaration in spec/host-tools.json by scripts/host_gate.py. The two are not the same file, do not hold the same thing, and do not share a schema: an overlay allows an empty tools list and a partial entry, and the fleet declaration requires at least one entry and every field of each. The fleet declaration states what every repository's procedures need and is the hub's to change. This one states what this repository needs beyond that, so it is where a tool only this repository uses, or a floor only this repository requires, is declared. Layering is tighten-only: an entry here may add a tool, raise a floor, or turn an optional tool required, and may not lower a floor or turn a required tool optional, because that would retire a fleet check from inside the repository it protects. A rejected relaxation is reported rather than dropped. The tools list is empty because this repository needs no tool the fleet declaration does not already carry, and the file is still present rather than absent, on the same footing as OPERATIONS.md: a repository with nothing to add carries the stub, so the declaration is somewhere a reader can find rather than somewhere they have to know to look. A repository copying this file leaves the $schema pointer behind, because the schemas are hub-only and no selector carries one, so a relative pointer resolves to a path that repository does not have and a schema-aware editor reports the file invalid for a reason nobody there can fix. The carried spec/secrets.json already works that way, holding the pointer here and omitting it in every downstream copy.", + "note": "This repository's own host-tool declaration, layered over the fleet declaration in spec/host-tools.json by scripts/host_gate.py. The two are not the same file, do not hold the same thing, and do not share a schema: an overlay allows an empty tools list and a partial entry, and the fleet declaration requires at least one entry and every field of each. The fleet declaration states what every repository's procedures need and is the hub's to change. This one states what this repository needs beyond that, so it is where a tool only this repository uses, or a floor only this repository requires, is declared. Layering is tighten-only: an entry here may add a tool, raise a floor, or turn an optional tool required, and may not lower a floor or turn a required tool optional, because that would retire a fleet check from inside the repository it protects. A rejected relaxation is reported rather than dropped. The tools list is empty because this repository needs no tool the fleet declaration does not already carry, and the file is still present rather than absent, on the same footing as OPERATIONS.md: a repository with nothing to add carries the stub, so the declaration is somewhere a reader can find rather than somewhere they have to know to look. A repository copying this file leaves the $schema pointer behind, because the schemas are hub-only and no selector carries one, so a relative pointer resolves to a path that repository does not have and a schema-aware editor reports the file invalid for a reason nobody there can fix.", "tools": [] } diff --git a/repo-config/configure.sh b/repo-config/configure.sh index d8ec85e6..ea4dcdd4 100755 --- a/repo-config/configure.sh +++ b/repo-config/configure.sh @@ -25,7 +25,8 @@ # The derived settings apply computes are asserted by name rather than from a payload, meaning has_discussions and default_branch. # The two Dependabot security features are asserted the same way, since apply enables them and no payload declares them. # What is unaudited is a static setting absent from settings.json, since only that group is payload-driven. -# Secrets are per-repo (see spec/secrets.json) and not checkable from a standalone carry, so they are a manual-verify note. +# Secret names are checked separately, by spec/audit.py from a hub checkout. +# This script leaves them a manual-verify note for values, which are never readable via the API. set -Eeuo pipefail # ----- Command + target + model ----- @@ -382,9 +383,9 @@ cmd_check() { check_ruleset "$main_ruleset" check_settings check_security - # Secrets are per-repo (spec/secrets.json) and not readable by value. - # A standalone carry has no registry to derive the required set from, so they are verified by hand rather than asserted here. - note "verify manually: the repo's required secrets (see spec/secrets.json) are present with valid values" + # Secret names are asserted by spec/audit.py, not here. + # Values are never readable via the API regardless. + note "run spec/audit.py [RepoName] (the registry name, not owner/repo) for required secret names, then verify by hand that their values are valid" if [ "$FAILED" -ne 0 ]; then echo "Configuration drift detected on $repo." exit 1 diff --git a/spec/divergences.json b/spec/divergences.json index 5a16e7fa..a283cd1d 100644 --- a/spec/divergences.json +++ b/spec/divergences.json @@ -12,6 +12,7 @@ { "path": "repo-config/main.json", "disposition": "retire", "reason": "The hub hosts the shared main ruleset payload. A downstream repository is checked against the hub's main payload.", "tracking": null }, { "path": "repo-config/settings.json", "disposition": "retire", "reason": "The hub hosts the shared repository settings payload. A downstream repository is checked against the hub's main payload.", "tracking": null }, { "path": "repo-config/README.md", "disposition": "retire", "reason": "The hub hosts the repository-configuration reference beside the payloads and script it documents.", "tracking": null }, + { "path": "spec/secrets.json", "disposition": "retire", "reason": "The adapted baseline/mechanisms carry never varied per repo: baseline applies to every fleet repo by definition, and mechanisms/targetMechanisms/typeMechanisms are computed centrally by spec/audit.py from the hub's own spec/secrets.json plus registry/repos.json, which a downstream copy could only restate or let drift. A downstream repository is checked against the hub's copy by running spec/audit.py from a hub checkout instead (ptr727/ProjectTemplate#993).", "tracking": null }, { "path": ".github/workflows/build-release-task.yml", "disposition": "retire", "reason": "The release chain is hub-hosted as a workflow_call task, per docs/reusable-workflows.md \"Stage 4: The Release Chain and the Docker Core\", so a downstream copy of this filename is retired rather than re-vendored: the caller stub a repo carries after adoption is publish-release.yml and test-pull-request.yml calling the hub task by pin, and no adopting repo carries a same-named local file. The ten carriers measured on develop at hub 7c67328 are PhotoCleaner, PlexCleaner, LanguageTags, MediaTools, Utilities, aiopurpleair, ESPHome-NonRoot, VSCode-Server-DotNetCore, KiCadLibrary, and homeassistant-purpleair. Delete the copy as each repo adopts the hub task; adoption is a separate, later change per repo (docs/reusable-workflows.md \"Rollout\" Stage 4).", "tracking": null }, { "path": ".github/workflows/build-docker-task.yml", "disposition": "retire", "reason": "The Docker core is hub-hosted as its own workflow_call task alongside build-release-task.yml, per docs/reusable-workflows.md \"Stage 4: The Release Chain and the Docker Core\", so a downstream copy of this filename is retired rather than re-vendored the same way. The five carriers measured on develop at hub 7c67328 are ESPHome-NonRoot, NxWitness, PhotoCleaner, PlexCleaner, and VSCode-Server-DotNetCore. Delete the copy as each repo adopts the hub task; adoption is a separate, later change per repo (docs/reusable-workflows.md \"Rollout\" Stage 4).", "tracking": null }, { "path": "LICENSE", "disposition": "accepted", "reason": "Each repo owns its license file. The hub does not standardize license text, so it is intentionally outside the manifest.", "tracking": null }, diff --git a/spec/files.json b/spec/files.json index 9a822d80..7703dc8c 100644 --- a/spec/files.json +++ b/spec/files.json @@ -23,7 +23,6 @@ { "path": "host-tools.json", "appliesTo": "*" }, { "path": "version.json", "fidelity": "intent", "intentRef": "WORKFLOW.md#d3---versioning-and-classification", "appliesTo": "*" }, { "path": "AUDIT.md", "fidelity": "intent", "intentRef": "docs/repo-config.md", "appliesTo": "*" }, - { "path": "spec/secrets.json", "fidelity": "intent", "intentRef": "docs/repo-config.md", "appliesTo": "*" }, { "path": ".github/dependabot.yml", "appliesTo": "*" }, { "path": ".github/workflows/test-pull-request.yml", "fidelity": "interface", "contract": { "requiredJobKeys": ["check-workflow-status", "validate"], "requiredCheckName": "Check pull request workflow status job", "requireTokensInJob": { "validate": ["validate-task.yml"] } }, "intentRef": "GOVERNANCE.md#workflow-yaml-conventions", "appliesTo": "*" }, { "path": ".github/workflows/publish-release.yml", "fidelity": "interface", "contract": { "requiredJobKeys": ["plan", "validate", "publish"], "requireTokensInJob": { "plan": ["publish-plan-task.yml"], "validate": ["validate-task.yml"], "publish": ["build-release-task.yml", "needs.validate.result == 'success'"] } }, "intentRef": "WORKFLOW.md#d4---release--publish", "appliesTo": ["two-phase", "dispatch-only", "publish-on-merge"] }, diff --git a/spec/project-types.json b/spec/project-types.json index 7db32de5..3d89fe61 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -179,7 +179,7 @@ { "id": "recurring.charset", "verdict": "letter", "assert": "ASCII only in agent-authored text: no em-dash (use a spaced hyphen), no smart quotes, no stray non-ASCII.", "intentRef": "GOVERNANCE.md#character-set" }, { "id": "recurring.spelling", "verdict": "letter", "assert": "US English spelling; the shared cspell.json sets language en-US (a bare en accepts British spellings too).", "intentRef": "CODESTYLE.md#markdown-and-spelling" }, { "id": "recurring.eol", "verdict": "letter", "assert": "Line endings follow paired repository-wide defaults: .editorconfig carries [*] end_of_line, and .gitattributes carries * text=auto eol=. Both set *.bat/*.cmd to CRLF. A missing or mismatched global default is a drift finding. The global default is LF for release repos and Linux-native operational repos. A Windows-native app that uses CRLF requires CRLF in both defaults. Do not add per-language or per-file LF pins where the global default already applies. Edits preserve the file's endings.", "intentRef": "GOVERNANCE.md#line-endings" }, - { "id": "recurring.norepoxref", "verdict": "intent", "assert": "A carried file (AGENTS.md, GOVERNANCE.md, CODESTYLE.md, WORKFLOW.md, .github/copilot-instructions.md, spec/secrets.json, the carried AUDIT.md) carries no coordination reference: no reference to the template repo in prose or link (the coordination flow is machinery a consumer should not see - state the behavior, not the destination), and no sibling fleet repo named as an illustrative example of a rule or adoption. A contextually relevant link to a related project is NOT a coordination reference and is expected (the image that consumes this config, a library this depends on) - the test is whether the link serves a reader of this repo's content. The rule governs carried template content. A repo's own README.md and topical docs are its own content. Two bounded exceptions serve carried behavior: AGENTS.md 'Fleet Bootstrap' must name the hub and its bytes are fixed fleet-wide, and the carried AUDIT.md links hub-hosted audit inputs and procedures to their absolute hub URLs because those files are deliberately not carried. The AGENTS.md exception stops at the verbatim region boundary, and the same file's own prose is judged normally. spec/audit.py mechanically checks AGENTS.md, GOVERNANCE.md and .github/copilot-instructions.md for the template name outside their verbatim sections, and skips the hub itself, whose copies are the source.", "intentRef": "GOVERNANCE.md#documentation-style-conventions" } + { "id": "recurring.norepoxref", "verdict": "intent", "assert": "A carried file (AGENTS.md, GOVERNANCE.md, CODESTYLE.md, WORKFLOW.md, .github/copilot-instructions.md, the carried AUDIT.md) carries no coordination reference: no reference to the template repo in prose or link (the coordination flow is machinery a consumer should not see - state the behavior, not the destination), and no sibling fleet repo named as an illustrative example of a rule or adoption. A contextually relevant link to a related project is NOT a coordination reference and is expected (the image that consumes this config, a library this depends on) - the test is whether the link serves a reader of this repo's content. The rule governs carried template content. A repo's own README.md and topical docs are its own content. Two bounded exceptions serve carried behavior: AGENTS.md 'Fleet Bootstrap' must name the hub and its bytes are fixed fleet-wide, and the carried AUDIT.md links hub-hosted audit inputs and procedures to their absolute hub URLs because those files are deliberately not carried. The AGENTS.md exception stops at the verbatim region boundary, and the same file's own prose is judged normally. spec/audit.py mechanically checks AGENTS.md, GOVERNANCE.md and .github/copilot-instructions.md for the template name outside their verbatim sections, and skips the hub itself, whose copies are the source.", "intentRef": "GOVERNANCE.md#documentation-style-conventions" } ] }, "readme-structure": {