Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/atlas/assets/seed/catalog.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"citations":[{"path":"tests/producer-fence-attacks/read-only-route-successors.json","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"8622a59921c275b0d98bcffdc98b2a7775f72df5dd533ca70ba8ad02d23ba310"},{"path":"tests/producer-fence-attacks/run.py","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"67b3d1bcf6cd0f1777fbce3daea8c58e86568e6858fc00f46caa3f44b9ec71d6"},{"path":".github/workflows/kit-materialize.yml","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"57a126319b824d27e9b34146b594ecbf2ff3aa5215cfa9f91c81009cc6edb1fe"}],"conflictsWith":[],"id":"github/kit-materialize-edit-rerecords-producer-fence-successor","kind":"lesson","recordedAt":"2026-11-02T07:11:21Z","repository":"FS-GG/.github","schema":"fsgg.atlas/1","status":"recorded","summary":"tests/producer-fence-attacks/run.py byte-pins .github/workflows/kit-materialize.yml: validate_kit_read_only_successor refuses with 'Kit successor does not bind the current route bytes' unless the sha256 in tests/producer-fence-attacks/read-only-route-successors.json equals the file's current bytes (no generator exists; use sha256sum). It also parses the file's job identities and every uses: line against a reviewed list, so a changed action pin changes that list too. Observed twice: .github#4965 added the closing-keywords job and main went red at the next unrelated push (re-recorded by #4972, which also widened the coord-github path filter so edits to the pinned files now run the edge job); .github#5196 pinned the closing-keywords checkout to the v7 commit, which changed the bytes and broke the uses: regex on the trailing comment, caught on the PR's own exact head (re-recorded in the same PR, and the selftest now covers the pinned form and a trailing comment). Scope: edits to kit-materialize.yml and to tests/producer-fence-attacks/*; this is an observation of the validator, not a rule about other workflows. The v1-writer-census row for the same file is a second, separate byte pin that also needs rehashing.","supersedes":[],"tags":["kit-materialize","producer-fence","successor","sha256","coord-github","edge","closing-keywords"],"title":"Any edit to kit-materialize.yml must re-record its producer-fence successor hash in the same PR"}
2 changes: 1 addition & 1 deletion .claude/skills/atlas/assets/seed/catalog.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"citations":[{"path":"tests/producer-fence-attacks/read-only-route-successors.json","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"8622a59921c275b0d98bcffdc98b2a7775f72df5dd533ca70ba8ad02d23ba310"},{"path":"tests/producer-fence-attacks/run.py","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"67b3d1bcf6cd0f1777fbce3daea8c58e86568e6858fc00f46caa3f44b9ec71d6"},{"path":".github/workflows/kit-materialize.yml","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"57a126319b824d27e9b34146b594ecbf2ff3aa5215cfa9f91c81009cc6edb1fe"}],"conflictsWith":[],"id":"github/kit-materialize-edit-rerecords-producer-fence-successor","kind":"lesson","recordedAt":"2026-11-02T07:11:21Z","repository":"FS-GG/.github","schema":"fsgg.atlas/1","status":"recorded","summary":"tests/producer-fence-attacks/run.py byte-pins .github/workflows/kit-materialize.yml: validate_kit_read_only_successor refuses with 'Kit successor does not bind the current route bytes' unless the sha256 in tests/producer-fence-attacks/read-only-route-successors.json equals the file's current bytes (no generator exists; use sha256sum). It also parses the file's job identities and every uses: line against a reviewed list, so a changed action pin changes that list too. Observed twice: .github#4965 added the closing-keywords job and main went red at the next unrelated push (re-recorded by #4972, which also widened the coord-github path filter so edits to the pinned files now run the edge job); .github#5196 pinned the closing-keywords checkout to the v7 commit, which changed the bytes and broke the uses: regex on the trailing comment, caught on the PR's own exact head (re-recorded in the same PR, and the selftest now covers the pinned form and a trailing comment). Scope: edits to kit-materialize.yml and to tests/producer-fence-attacks/*; this is an observation of the validator, not a rule about other workflows. The v1-writer-census row for the same file is a second, separate byte pin that also needs rehashing.","supersedes":[],"tags":["kit-materialize","producer-fence","successor","sha256","coord-github","edge","closing-keywords"],"title":"Any edit to kit-materialize.yml must re-record its producer-fence successor hash in the same PR"}
2 changes: 1 addition & 1 deletion docs/atlas/catalog.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"citations":[{"path":"tests/producer-fence-attacks/read-only-route-successors.json","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"8622a59921c275b0d98bcffdc98b2a7775f72df5dd533ca70ba8ad02d23ba310"},{"path":"tests/producer-fence-attacks/run.py","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"67b3d1bcf6cd0f1777fbce3daea8c58e86568e6858fc00f46caa3f44b9ec71d6"},{"path":".github/workflows/kit-materialize.yml","repository":"FS-GG/.github","revision":"31632a60a2053c181a43eab22044ef8c7157c65d","sha256":"57a126319b824d27e9b34146b594ecbf2ff3aa5215cfa9f91c81009cc6edb1fe"}],"conflictsWith":[],"id":"github/kit-materialize-edit-rerecords-producer-fence-successor","kind":"lesson","recordedAt":"2026-11-02T07:11:21Z","repository":"FS-GG/.github","schema":"fsgg.atlas/1","status":"recorded","summary":"tests/producer-fence-attacks/run.py byte-pins .github/workflows/kit-materialize.yml: validate_kit_read_only_successor refuses with 'Kit successor does not bind the current route bytes' unless the sha256 in tests/producer-fence-attacks/read-only-route-successors.json equals the file's current bytes (no generator exists; use sha256sum). It also parses the file's job identities and every uses: line against a reviewed list, so a changed action pin changes that list too. Observed twice: .github#4965 added the closing-keywords job and main went red at the next unrelated push (re-recorded by #4972, which also widened the coord-github path filter so edits to the pinned files now run the edge job); .github#5196 pinned the closing-keywords checkout to the v7 commit, which changed the bytes and broke the uses: regex on the trailing comment, caught on the PR's own exact head (re-recorded in the same PR, and the selftest now covers the pinned form and a trailing comment). Scope: edits to kit-materialize.yml and to tests/producer-fence-attacks/*; this is an observation of the validator, not a rule about other workflows. The v1-writer-census row for the same file is a second, separate byte pin that also needs rehashing.","supersedes":[],"tags":["kit-materialize","producer-fence","successor","sha256","coord-github","edge","closing-keywords"],"title":"Any edit to kit-materialize.yml must re-record its producer-fence successor hash in the same PR"}
61 changes: 54 additions & 7 deletions docs/coordination/reusable-workflow-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -747,14 +747,14 @@ Any one of these, and the decision is re-opened rather than eroded:

The PR-body closing-keyword lint ([#4730](https://github.com/FS-GG/.github/issues/4730)) is the same
route again: a job in `kit-materialize.yml`, so every receiver that calls that file at `@main` reports it
with no receiver-side edit. That is six of the seven: FS.GG.Templates calls it at the pinned commit
`941a82c0e06c9afe9db4c88fc29997d7627a5895`, which predates the job, so Templates does not report it until
its pin moves. GitHub binds a closing keyword next to an issue reference and ignores the
with no receiver-side edit. That is all seven kit receivers: six call it at `@main`, and FS.GG.Templates
calls it at a pinned commit, which until FS.GG.Templates#835 (merged 2026-11-02) was `941a82c0…`, before
the job existed, and now is `f1ac83ac4f5c2c60251dd6e5f07ffe4f745de5eb`, past it. GitHub binds a closing keyword next to an issue reference and ignores the
words around it, so a body saying a PR does *not* close an issue closes it; FS.GG.Game#731 did exactly
that to FS.GG.Game#730.

**The context string is `materialize / closing-keywords`**, and like `kit-bump-shape` it is required in
zero repositories: a red is visible in the check list and blocks nothing. Arm it the way this page
zero repositories (the per-repo inventory below, measured 2026-11-02): a red is visible in the check list and blocks nothing. Arm it the way this page
arms any context (dry run, watch it report, then `repos.sh require-context`), never first.

| | |
Expand All @@ -763,20 +763,27 @@ arms any context (dry run, watch it report, then `repos.sh require-context`), ne
| Subject | the PR body as raw text, and the PR **title** (plain text, no opt-in: under `COMMIT_OR_PR_TITLE` it becomes the squash commit subject, as .github#4963 showed). Live read first, event payload as fallback, so a re-run after an edit or retitle judges the edit. Commit subjects are read through the API and only **warn** |
| Fails | any of the nine GitHub keywords next to `#N`, `GH-N`, `owner/repo#N` or an issue URL, negated or not, in prose, a code span, a fence or an indented block |
| Passes | `Refs #N`; a keyword with no reference after it; `resolving`/`closing`/`fixing` (not GitHub keywords); the **opt-in** declaration line, `Closes #N.` alone on a line |
| Reach | six of seven on opened, synchronize and reopened: SDD, Rendering, Governance, Game, Audio and Net. **Templates is excluded** (pinned to `941a82c0…`, before the job existed). A caller re-checks an edit or retitle only if it lists `edited`: SDD and Rendering did already; Governance, Game, Audio and Net were adopted by one PR each adding `edited` to the `pull_request` types |
| Reach | all seven kit receivers on opened, synchronize and reopened: SDD, Rendering, Governance, Game, Audio, Net and, since FS.GG.Templates#835, Templates (`materialize / closing-keywords` reported on that PR's own head). A caller re-checks an edit or retitle only if it lists `edited`: SDD and Rendering did already; Governance, Game, Audio and Net were adopted by one PR each adding `edited` to the `pull_request` types |
| Proof | [`tests/closing-keywords/product-lint.sh`](../../tests/closing-keywords/product-lint.sh): the keyword matrix, the code-span and fence decision, and the real job step run against a stub `gh` |

**Before arming `materialize / closing-keywords` as required**, all of these must hold:

1. **Move the Templates pin** past a commit that carries the job. Until then the context never reports on
Templates, and requiring it holds every Templates pull request at "Expected" (#1508).
1. ~~Move the Templates pin~~ Done: FS.GG.Templates#835 merged 2026-11-02 (`5dfa33f4`) and moved the pin to
`f1ac83ac…`, past the job; the context reported on that PR. Requiring a context that never reports holds
every pull request at "Expected" (#1508), which is why this came first.
2. ~~Merge the four `edited` adoption PRs~~ Done: measured 2026-11-01, the `kit-materialize.yml` callers of
SDD, Rendering, Governance, Game, Audio and Net all list `edited`.
3. **Resolve the Renovate pattern.** Some Renovate bodies close an issue in prose, for example
"Coordinated migration: Closes #254; parent ..." (FS.GG.Audio#251, FS.GG.Net#69). The rule flags it,
correctly, because it will close the issue. Either the coordinated-migration template declares the close
on a line of its own, or an explicit exemption is decided. Otherwise a required context reds those
bot pull requests. A 696-pull-request backtest (2026-10-30) found 5 findings, 2 of them Renovate.
**Still open.** Re-measured 2026-11-02 over the `app/renovate` pull requests of the seven kit receivers
(554 scanned, `gh pr list --author app/renovate`, case-insensitive keyword-plus-reference match): 6
matched, one each in Audio, Net, Game, SDD, Rendering and Templates, all created 2026-08-11 to
2026-08-14 (the coordinated-migration wave); none since, and Governance has none. Audio#251 and Net#69
are merged. Neither a template change nor an exemption is decided, so a recurrence would red a required
context.
4. The usual dry run and a period of observed real reports.

Not covered, by design: whole commit messages beyond their subject line (the hub's own check reads them;
Expand All @@ -799,3 +806,43 @@ the two differ). A caller is one thin file that names it at `@main`, as every ot
| Reach | one caller per repository; `edited` in its `pull_request` types re-checks a retitle or body edit |
| Pin | `@main`, except where the repository's Actions policy requires full-SHA pins (`sha_pinning_required`), where the caller pins a commit of the receiver workflow and rebinds it when the rule changes |
| Arming | not armed anywhere by the change that adds a caller. A private repository on a plan without branch protection (GitHub answers 403 "Upgrade to GitHub Pro or make this repository public") cannot arm it; there the check is advisory |

### Per-repo inventory of the gate and its required-context state (.github#5195)

Measured 2026-11-02 UTC; refreshed after the receiver caller merges by reading each default branch's branch protection and rulesets
(`gh api repos/FS-GG/<repo>/branches/<default>/protection/required_status_checks` and `.../rules/branches/<default>`; HighBarV3 and RecoilEngine use `master`)
and its workflows. The route is how the repository gets the lint: **kit** (`materialize / closing-keywords`,
through `kit-materialize.yml`), **receiver** (a thin caller of `closing-keywords-receiver.yml`, context
`closing-keywords / title-and-body`) or **standalone** (a copy of the job that fetches the rule at a pinned hub
commit, context `closing-keywords`). A private repository on this plan cannot be protected at all (GitHub answers
403 "Upgrade to GitHub Pro or make this repository public"); the plan is fixed, so there the check can report but
can never be required.

| Repository | Visibility | Route | Required today | Arming possible |
| --- | --- | --- | --- | --- |
| .github | public | its own `closing-keywords` workflow (the hub's check, with the commit messages) | no | yes, by protection |
| FS.GG.Game, SDD, Governance, Audio, Rendering, Net | public | kit, at `@main` | no | yes, by protection (Governance also has a ruleset) |
| FS.GG.Templates | public | kit, pinned to `f1ac83ac…` | no | yes, by protection |
| FS.GG.Algorithms | public | standalone | **yes**, `closing-keywords` in its `main-protection` ruleset | already armed |
| FS.GG.Coordination | public | receiver pending: caller FS.GG.Coordination#1118 remains open (pinned to the receiver's merge commit: its execution-pin validator refuses `@main`); no caller on the default branch | no | yes, by its ruleset |
| FsQuint, FSBarV2 | public | receiver on the default branch, callers FsQuint#51 and FSBarV2#54 merged; both caller heads reported the check successfully | no | only after the repository gets a protection or a ruleset: it has neither |
| FS.GG.RuneScape | private | standalone | no | no, advisory only |
| FS.GG.FourD, SC2.Client, Unity.Client, Native.Qualification | private | receiver on the default branch; the four caller PRs are merged and successful native Actions jobs now observed (Native.Qualification pins the merge commit: it enforces full-SHA pins) | no | no, advisory only |
| HighBarV3, RecoilEngine | public | absent on the default branch; minimal read-only receiver callers prepared locally under #5195, not yet pushed or installed at measurement | no | only after a protection or ruleset is added; neither currently has one |

The public receiver caller heads reported `closing-keywords / title-and-body` successfully, including the
still-open Coordination caller. The earlier four-private-repository reporting Unknown is resolved by
fresh authenticated Actions run/job metadata: each has a completed successful `title-and-body` job,
matching run/head/attempt and its native `check_run_url`. This is execution/reporting evidence, not a
claim that the previously denied check-runs-list endpoint became readable. Private raw evidence is
retained by the delivery owner, not copied into this document.

HighBarV3 and RecoilEngine now reference Project3 Deliveries, so the original repository set is no
longer complete: HighBarV3#18 and RecoilEngine#3 are concrete new examples. Both default branches lack
the lint. Their prepared callers reuse the maintained read-only hub workflow; they are neither installed
nor required at measurement. This is an explicit #5195 remainder, not evidence of fleet completion.
The table is a dated observation; refresh native source/reporting before declaring the rollout complete.
No branch protection or ruleset was changed by this inventory. Not in the inventory because no pull request
there references a Project 3 Delivery at the original measurement: FS.GG.Coordination.Authority and the two Sandbox repositories.
Algorithms is the only repository where the lint is already a merge gate, and its standalone job is the reason
the receiver exists: that copy carries its own pin to move by hand.
9 changes: 7 additions & 2 deletions registry/driver-skill-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"id": "atlas",
"scope": "driver",
"sha256": "a1383a9a8029f91930798e7624f5de87db68b0b6f575ea4d899761449e9fa423",
"tree-sha256": "72e552f306d6d8a76fba09628508e0a392f13b493769bb699baef34b31832055",
"tree-sha256": "9e4164d923735d548f8b1a0bce64c3d727055bc8717216c92eee27d1fd851bda",
"files": [
{
"path": "SKILL.md",
Expand All @@ -24,7 +24,7 @@
},
{
"path": "assets/seed/catalog.json",
"sha256": "690979f4fabb9a12f880031f71dc0fbc382ed69d8d1243ff22ee7092ef8f8e8b",
"sha256": "8e7b8251c098af979ce097bf2329bbb792ae722510ed0fa1a4321da8759a428a",
"executable": false
},
{
Expand Down Expand Up @@ -317,6 +317,11 @@
"sha256": "23af79bd1e7995fc8162ddc4d830318acb054bbfcc9c7d5cb141ec306da67386",
"executable": false
},
{
"path": "assets/seed/records/24278a8f6b5925968be17b45f7db4c08169ddd5c92cbc103a166a690c02dc351.json",
"sha256": "24278a8f6b5925968be17b45f7db4c08169ddd5c92cbc103a166a690c02dc351",
"executable": false
},
{
"path": "assets/seed/records/24373b643450d9dd3b8a5b0e74a5fec2844cd8cbca7d8a57dbc8b9ad9eade9dc.json",
"sha256": "24373b643450d9dd3b8a5b0e74a5fec2844cd8cbca7d8a57dbc8b9ad9eade9dc",
Expand Down
Loading
Loading