From 23b10b67f4b350bb0ed41596db277eab73d974ba Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 19 Sep 2026 20:01:15 -0700 Subject: [PATCH 1/3] Reflow the Runbook's Coverage Paragraph to One Width The paragraph ran from 87 to 124 columns and broke "Re-run the loop after every fix push" across two lines, leaving "push." opening a line under an eight-column "finding." The split predates the change that rewrote this paragraph, and that change rewrapped every line around it and left it, which is what made it read as an accidental hard wrap rather than as the original spacing. Reflowed to the 87 to 96 columns the neighbouring paragraphs use. The word sequence is unchanged, asserted rather than eyeballed: the file's `split()` is identical before and after and the character count is the same, one newline having become a space. Co-Authored-By: Claude Opus 5 (1M context) --- .github/copilot-instructions.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 66091cd7..d6e2cb1b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -55,16 +55,15 @@ conversation. Its status gate verifies the current head, diff coverage, output s threads, body-only findings, and required checks. A formal review with no findings is complete only when it covers the current head and full diff -coverage is stated for the change set that head has. The round covering the head states it, or the -newest round that states it at all does and the pull request changes the same set of files at -both commits, which is the only condition under which a statement carries forward. Only that -newest round is consulted, so an older round whose change set does match carries nothing. A round reporting partial coverage -of the diff blocks the merge, and so does a refusal, a coverage statement that does not reach -this head, meaning absent from every round or carried by none because the change set moved or -could not be compared, an unrecognized output shape, an unresolved thread, or a body-only -finding. -Re-run the loop after every fix -push. Never infer review completion from `mergeStateStatus: CLEAN`. +coverage is stated for the change set that head has. The round covering the head states it, or +the newest round that states it at all does and the pull request changes the same set of files +at both commits, which is the only condition under which a statement carries forward. Only that +newest round is consulted, so an older round whose change set does match carries nothing. A +round reporting partial coverage of the diff blocks the merge, and so does a refusal, a coverage +statement that does not reach this head, meaning absent from every round or carried by none +because the change set moved or could not be compared, an unrecognized output shape, an +unresolved thread, or a body-only finding. Re-run the loop after every fix push. Never infer +review completion from `mergeStateStatus: CLEAN`. Review effort is user-controlled. The automation observes `Lite`, `Balanced`, or `Max`, including an inherited `Default ()`, and never selects or changes the setting. Effort does not determine coverage or completion. A request can complete without a `copilot_work_started` event, so absence of that event is not a stalled-review verdict. When `wait` returns `PENDING` with `requested=yes`, report the state and rerun `wait` for another bounded interval by default. Do not clear the request automatically because it may be active. If the maintainer directs a retry, remove Copilot in the pull request UI, add it again, and rerun `wait`. This recovery replaces only the review request and never changes the effort setting. From 7cac9f1e15f10fab0d2806691ca56195ff0a28a3 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 20 Sep 2026 06:53:02 -0700 Subject: [PATCH 2/3] Unwrap the Runbook to One Paragraph Per Line The reflow this replaces made the file tidier while keeping it non-conformant. `comment-and-doc-style` states the rule plainly, one logical paragraph per line with no hard-wrap line-length limit, and `GOVERNANCE.md`, `AGENTS.md`, `CODESTYLE.md`, `README.md` and `WORKFLOW.md` all follow it, running to between 980 and 4096 columns. This file was the only one hard-wrapped, and the orphaned break that raised the finding was a symptom of that rather than the defect. Fixed as the class rather than the paragraph, since fifteen blocks were wrapped and leaving fourteen of them means the next edit to any one meets this again. The file goes from 113 lines to 77, and from 756 columns to 989, which sits inside the band the sibling documents occupy. The word sequence is unchanged, asserted rather than eyeballed, and asserted twice: the file's `split()` is identical before and after, and the same holds per paragraph, so no word crossed a blank-line boundary either. Joining lines cannot split a substring, so the raw-text assertions recorded separately are unaffected. Co-Authored-By: Claude Opus 5 (1M context) --- .github/copilot-instructions.md | 96 +++++++++++---------------------- 1 file changed, 30 insertions(+), 66 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d6e2cb1b..14784475 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,39 +2,19 @@ Repository-wide instructions for GitHub Copilot. -Read [AGENTS.md](../AGENTS.md) first. It routes every standing repository rule to its canonical -document. When performing code review, load and follow the `code-review` skill in -`.github/skills/code-review/SKILL.md`, then load every language, documentation, or workflow skill -that it selects for the changed files. GitHub Copilot reads these files from the pull request's -head branch, so review the instructions in that tree. +Read [AGENTS.md](../AGENTS.md) first. It routes every standing repository rule to its canonical document. When performing code review, load and follow the `code-review` skill in `.github/skills/code-review/SKILL.md`, then load every language, documentation, or workflow skill that it selects for the changed files. GitHub Copilot reads these files from the pull request's head branch, so review the instructions in that tree. -Do not duplicate rules from `AGENTS.md`, `GOVERNANCE.md`, `CODESTYLE.md`, or `WORKFLOW.md` here. -This file contains only Copilot-specific bootstrap and output requirements. +Do not duplicate rules from `AGENTS.md`, `GOVERNANCE.md`, `CODESTYLE.md`, or `WORKFLOW.md` here. This file contains only Copilot-specific bootstrap and output requirements. ## Commit Messages and Pull Request Titles -Use an imperative subject of at most 72 characters with no trailing period. Use US English and -title case with lowercase short bind words. Do not add `Co-Authored-By:` unless requested. Do not -put a release-bump magnitude in the title. The full contract is in -[GOVERNANCE.md "Pull Request Title and Commit Message Conventions"](../GOVERNANCE.md#pull-request-title-and-commit-message-conventions). +Use an imperative subject of at most 72 characters with no trailing period. Use US English and title case with lowercase short bind words. Do not add `Co-Authored-By:` unless requested. Do not put a release-bump magnitude in the title. The full contract is in [GOVERNANCE.md "Pull Request Title and Commit Message Conventions"](../GOVERNANCE.md#pull-request-title-and-commit-message-conventions). ## Reviewing Carried Fleet Content -Follow the fidelity declared for the file. A byte-locked reference to shared infrastructure that -this repository does not carry is intentional, not a broken link. Raise substantive defects in -canonical content, but locate the fix at its canonical source instead of proposing a local edit. - -`.github/skills/`, and in the hub `.claude-plugin/fleet-skills/`, are generated by the hub's -`scripts/build_dist.py` from its `.agents/skills/`, so a defect in either is fixed in the source or -the generator and never in the copy. A defect inside an include region, the text between the -marker lines `` and `` that every copy -carries as its authored source does, is fixed in the hub under the heading that key names, since -the region is generated from that heading's body and the key's path resolves against the hub's -root rather than this repository's copy of the same file. Where that heading's body is itself a -region, the fix sits one hop further, under the heading its own key names. Post no review comment -on a file under `.github/skills/` or, in the hub, `.claude-plugin/fleet-skills/`. When the pull -request changes the file the fix belongs in, comment on that file instead, and otherwise state -the finding in the review summary. +Follow the fidelity declared for the file. A byte-locked reference to shared infrastructure that this repository does not carry is intentional, not a broken link. Raise substantive defects in canonical content, but locate the fix at its canonical source instead of proposing a local edit. + +`.github/skills/`, and in the hub `.claude-plugin/fleet-skills/`, are generated by the hub's `scripts/build_dist.py` from its `.agents/skills/`, so a defect in either is fixed in the source or the generator and never in the copy. A defect inside an include region, the text between the marker lines `` and `` that every copy carries as its authored source does, is fixed in the hub under the heading that key names, since the region is generated from that heading's body and the key's path resolves against the hub's root rather than this repository's copy of the same file. Where that heading's body is itself a region, the fix sits one hop further, under the heading its own key names. Post no review comment on a file under `.github/skills/` or, in the hub, `.claude-plugin/fleet-skills/`. When the pull request changes the file the fix belongs in, comment on that file instead, and otherwise state the finding in the review summary. ## GitHub Copilot Review Runbook @@ -42,28 +22,13 @@ For every review: 1. Read the full pull request diff and count its changed files. 2. Follow `.github/skills/code-review/SKILL.md` and every skill it selects. -3. Publish every supported finding. Never suppress a finding or place it in a low-confidence or - hidden findings block. -4. Use an inline comment when a changed line can anchor the finding. Use the review body only when - no valid inline anchor exists. +3. Publish every supported finding. Never suppress a finding or place it in a low-confidence or hidden findings block. +4. Use an inline comment when a changed line can anchor the finding. Use the review body only when no valid inline anchor exists. 5. End the review body with the exact machine-readable marker required by the `code-review` skill. -The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`, -`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying -review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request -conversation. Its status gate verifies the current head, diff coverage, output shape, inline -threads, body-only findings, and required checks. - -A formal review with no findings is complete only when it covers the current head and full diff -coverage is stated for the change set that head has. The round covering the head states it, or -the newest round that states it at all does and the pull request changes the same set of files -at both commits, which is the only condition under which a statement carries forward. Only that -newest round is consulted, so an older round whose change set does match carries nothing. A -round reporting partial coverage of the diff blocks the merge, and so does a refusal, a coverage -statement that does not reach this head, meaning absent from every round or carried by none -because the change set moved or could not be compared, an unrecognized output shape, an -unresolved thread, or a body-only finding. Re-run the loop after every fix push. Never infer -review completion from `mergeStateStatus: CLEAN`. +The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`, `comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request conversation. Its status gate verifies the current head, diff coverage, output shape, inline threads, body-only findings, and required checks. + +A formal review with no findings is complete only when it covers the current head and full diff coverage is stated for the change set that head has. The round covering the head states it, or the newest round that states it at all does and the pull request changes the same set of files at both commits, which is the only condition under which a statement carries forward. Only that newest round is consulted, so an older round whose change set does match carries nothing. A round reporting partial coverage of the diff blocks the merge, and so does a refusal, a coverage statement that does not reach this head, meaning absent from every round or carried by none because the change set moved or could not be compared, an unrecognized output shape, an unresolved thread, or a body-only finding. Re-run the loop after every fix push. Never infer review completion from `mergeStateStatus: CLEAN`. Review effort is user-controlled. The automation observes `Lite`, `Balanced`, or `Max`, including an inherited `Default ()`, and never selects or changes the setting. Effort does not determine coverage or completion. A request can complete without a `copilot_work_started` event, so absence of that event is not a stalled-review verdict. When `wait` returns `PENDING` with `requested=yes`, report the state and rerun `wait` for another bounded interval by default. Do not clear the request automatically because it may be active. If the maintainer directs a retry, remove Copilot in the pull request UI, add it again, and rerun `wait`. This recovery replaces only the review request and never changes the effort setting. @@ -78,36 +43,35 @@ Review effort is user-controlled. The automation observes `Lite`, `Balanced`, or **The entries are this repository's own.** Each names a file and a revision, so a repository holding a copy of this file carries the shape and the rules above rather than these findings, deletes an entry whose subject it does not carry, and records what it has proved itself. - **`keys_unsorted` requires jq 1.6, so the ruleset normalizer in `repo-config/configure.sh` fails to compile on jq 1.5.** Raised as a suppressed finding, by analogy to the `walk/1` call the same filter was rewritten to avoid. - - **Disproved by** - running both builtins on `jq-1.5-1-a5b5cbe`, the build that reproduces the `walk/1` failure. `keys_unsorted` evaluates there and the whole normalizer returns the sorted document, while `walk(.)` on that binary answers `jq: 1 compile error`. The two builtins are not in the same position, and the analogy is the whole of what carried the finding. - - **Proved against** - the `norm` filter in `repo-config/configure.sh` on `develop` at `756a53e`. - - **Delete when** - the filter stops calling `keys_unsorted`, or nothing this check runs on carries a jq older than 1.6. +- **Disproved by** - running both builtins on `jq-1.5-1-a5b5cbe`, the build that reproduces the `walk/1` failure. `keys_unsorted` evaluates there and the whole normalizer returns the sorted document, while `walk(.)` on that binary answers `jq: 1 compile error`. The two builtins are not in the same position, and the analogy is the whole of what carried the finding. +- **Proved against** - the `norm` filter in `repo-config/configure.sh` on `develop` at `756a53e`. +- **Delete when** - the filter stops calling `keys_unsorted`, or nothing this check runs on carries a jq older than 1.6. - **A description's stale commit claims are found by extracting the bare SHAs it quotes.** Not a reviewer's finding but the method this repository's own backlog specified for the `claims` check in `scripts/pr_review.py`, recorded here because a rejected method costs the same to re-propose as a declined finding costs to re-derive, and because a backlog has a place for a claim the tree contradicts and none for a method a measurement rejects. - - **Disproved by** - running it over the 25 most recent merged pull requests, where it raised four references and all four were correct prose: a `develop` commit named as history, a SHA inside a pasted digest, and two commits in another repository written without a URL. Nothing in the shape of a bare SHA separates those from a claim, and separating them by meaning is the similarity heuristic [GOVERNANCE.md "Documentation Style Conventions"](../GOVERNANCE.md#documentation-style-conventions) rules out. A path arm measured on the same corpus is worse, flagging 54 of 215 backticked candidates, nearly all of them bare basenames and other repositories. - - **Proved against** - the 25 most recent merged pull requests as of `develop` at `756a53e`, the corpus on which the anchored verb form that ships instead raises one reference, and that one true. - - **Delete when** - `claims` stops reading a description for commit references. +- **Disproved by** - running it over the 25 most recent merged pull requests, where it raised four references and all four were correct prose: a `develop` commit named as history, a SHA inside a pasted digest, and two commits in another repository written without a URL. Nothing in the shape of a bare SHA separates those from a claim, and separating them by meaning is the similarity heuristic [GOVERNANCE.md "Documentation Style Conventions"](../GOVERNANCE.md#documentation-style-conventions) rules out. A path arm measured on the same corpus is worse, flagging 54 of 215 backticked candidates, nearly all of them bare basenames and other repositories. +- **Proved against** - the 25 most recent merged pull requests as of `develop` at `756a53e`, the corpus on which the anchored verb form that ships instead raises one reference, and that one true. +- **Delete when** - `claims` stops reading a description for commit references. - **The GraphQL `pullRequests` connection defaults to `states: [OPEN]`, so the bot node id query in "Triggering and Polling" returns nothing in a repository whose Copilot-reviewed pull requests have all merged.** Raised against the repo-wide read, on the ground that a cold start is exactly the case where no open pull request carries a review. - - **Disproved by** - running the connection both ways against this repository while exactly one pull request was open. With `states` omitted, `pullRequests(first: 5, orderBy: { field: CREATED_AT, direction: DESC })` answers `628 OPEN`, `627 MERGED`, `626 MERGED`, `625 MERGED` and `624 MERGED`, so the omitted default is every state rather than `OPEN`. The same call with `states: [OPEN]` answers `628 OPEN` alone, which is the behavior the finding predicts for the first form and is what distinguishes them. The read was first run when this repository had no open pull request at all, and it returned the id from merged ones. - - **Proved against** - the `BOT_ID` query in "Triggering and Polling" in this file, run against this repository's pull request list on 2026-08-08. - - **Delete when** - that query names `states` explicitly, or stops reading pull requests to find the id. +- **Disproved by** - running the connection both ways against this repository while exactly one pull request was open. With `states` omitted, `pullRequests(first: 5, orderBy: { field: CREATED_AT, direction: DESC })` answers `628 OPEN`, `627 MERGED`, `626 MERGED`, `625 MERGED` and `624 MERGED`, so the omitted default is every state rather than `OPEN`. The same call with `states: [OPEN]` answers `628 OPEN` alone, which is the behavior the finding predicts for the first form and is what distinguishes them. The read was first run when this repository had no open pull request at all, and it returned the id from merged ones. +- **Proved against** - the `BOT_ID` query in "Triggering and Polling" in this file, run against this repository's pull request list on 2026-08-08. +- **Delete when** - that query names `states` explicitly, or stops reading pull requests to find the id. - **"The agent check branches" in `STANDUP.md` section 0 is a subject-verb disagreement, and should read "The agent checks branches".** Raised as a suppressed finding against a line the change under review only touched as diff context. - - **Disproved by** - reading the sentence against the snippet it describes. The subject is the noun phrase "the agent check", meaning the check for the signing agent, and "branches" is its verb, which is what the `if [ ... = ssh ]; then ssh-add -L; else gpg --list-secret-keys; fi` line does. The proposed reading needs "branches" as a plural noun, and the paragraph is section 0, before a repository exists, where the alternatives it names are the SSH and GPG forms rather than refs. - - **Proved against** - the paragraph following the agent snippet in `STANDUP.md` section 0 on `develop` at `676a2bd`, unchanged since `77be3a3`. - - **Delete when** - the sentence is reworded for any reason, since the entry is about this phrasing rather than about the rule it states. +- **Disproved by** - reading the sentence against the snippet it describes. The subject is the noun phrase "the agent check", meaning the check for the signing agent, and "branches" is its verb, which is what the `if [ ... = ssh ]; then ssh-add -L; else gpg --list-secret-keys; fi` line does. The proposed reading needs "branches" as a plural noun, and the paragraph is section 0, before a repository exists, where the alternatives it names are the SSH and GPG forms rather than refs. +- **Proved against** - the paragraph following the agent snippet in `STANDUP.md` section 0 on `develop` at `676a2bd`, unchanged since `77be3a3`. +- **Delete when** - the sentence is reworded for any reason, since the entry is about this phrasing rather than about the rule it states. - **A hyphenated key such as `inputs.app-login` or `inputs.delete-branch` cannot be read with dot notation in a GitHub Actions expression, since `-` parses as subtraction, so the merge-bot task's `if:` conditions never match and its `env:` values never resolve.** Raised as four suppressed findings against `.github/workflows/merge-bot-task.yml`, each a variant of the one claim. - - **Disproved by** - reading the expressions reference, which states that property dereference syntax needs a name that starts with a letter or `_` and contains only alphanumeric characters, `-`, or `_`, so a hyphen is inside the allowed set and index syntax is required only for a name outside it. And by the same file, whose every job reads `steps.app-token.outputs.token` with dot notation on the same hyphenated shape, the expression the fleet's merge-bot has resolved on every Dependabot merge it has performed. `actionlint` parses the file without a finding. - - **Proved against** - `.github/workflows/merge-bot-task.yml` on `feature/reusable-workflows` at `210d88f`, and the "About contexts" property dereference rule in the GitHub Actions contexts reference read on 2026-08-15. - - **Delete when** - the task stops declaring hyphenated inputs, or the expressions reference changes the allowed set. +- **Disproved by** - reading the expressions reference, which states that property dereference syntax needs a name that starts with a letter or `_` and contains only alphanumeric characters, `-`, or `_`, so a hyphen is inside the allowed set and index syntax is required only for a name outside it. And by the same file, whose every job reads `steps.app-token.outputs.token` with dot notation on the same hyphenated shape, the expression the fleet's merge-bot has resolved on every Dependabot merge it has performed. `actionlint` parses the file without a finding. +- **Proved against** - `.github/workflows/merge-bot-task.yml` on `feature/reusable-workflows` at `210d88f`, and the "About contexts" property dereference rule in the GitHub Actions contexts reference read on 2026-08-15. +- **Delete when** - the task stops declaring hyphenated inputs, or the expressions reference changes the allowed set. - **`sudo_implementations()` in `host-setup/linux/install-tools.sh` never matches, because `update-alternatives --query sudo` prints a slave as `visudo:` with a trailing colon, so a host whose active `visudo` rejects the drop-in always reaches the "No sudo on this host parses timestamp_type" refusal even where an alternative would parse it.** Raised as a suppressed finding against the sudo timestamp action. - - **Disproved by** - running the subcommand the function actually calls. `update-alternatives --query sudo` prints each slave as two space-separated fields under `Slaves:`, one space-indented line reading `visudo /usr/sbin/visudo.ws`, and the awk over that output prints `/usr/bin/sudo.ws /usr/sbin/visudo.ws` and `/usr/lib/cargo/bin/sudo /usr/lib/cargo/bin/visudo`. The colon form the finding describes belongs to `update-alternatives --display sudo`, a different subcommand, which prints `slave visudo: /usr/sbin/visudo.ws`. The path the finding says is unreachable was also driven end to end in an `ubuntu:25.10` container, where the run found `/usr/bin/sudo.ws`, switched the alternative to it, and wrote the drop-in. - - **Proved against** - `sudo_implementations` in `host-setup/linux/install-tools.sh` on `feature/sudo-timestamp-global` at `13e689f`, against `update-alternatives` 1.22.x on Ubuntu 25.10. - - **Delete when** - the function stops reading `update-alternatives --query`, or that subcommand changes its slave format. +- **Disproved by** - running the subcommand the function actually calls. `update-alternatives --query sudo` prints each slave as two space-separated fields under `Slaves:`, one space-indented line reading `visudo /usr/sbin/visudo.ws`, and the awk over that output prints `/usr/bin/sudo.ws /usr/sbin/visudo.ws` and `/usr/lib/cargo/bin/sudo /usr/lib/cargo/bin/visudo`. The colon form the finding describes belongs to `update-alternatives --display sudo`, a different subcommand, which prints `slave visudo: /usr/sbin/visudo.ws`. The path the finding says is unreachable was also driven end to end in an `ubuntu:25.10` container, where the run found `/usr/bin/sudo.ws`, switched the alternative to it, and wrote the drop-in. +- **Proved against** - `sudo_implementations` in `host-setup/linux/install-tools.sh` on `feature/sudo-timestamp-global` at `13e689f`, against `update-alternatives` 1.22.x on Ubuntu 25.10. +- **Delete when** - the function stops reading `update-alternatives --query`, or that subcommand changes its slave format. ## When in Doubt -Stop and report the uncertainty. Do not guess at an instruction, suppress a possible finding, or -claim coverage that the review did not perform. +Stop and report the uncertainty. Do not guess at an instruction, suppress a possible finding, or claim coverage that the review did not perform. From eb16d9f4f2f9fea5f97a797ffffb4c134031a859 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 20 Sep 2026 06:58:35 -0700 Subject: [PATCH 3/3] Keep a List Item's Indent, Which Is Structure Rather Than Wrapping The unwrap stripped the leading whitespace from the first line of every block, so the eighteen `Disproved by`, `Proved against` and `Delete when` sub-bullets lost their two-space indent and six three-item sub-lists became siblings of the claim they belong to. That is a rendering change rather than a whitespace one, and the rule being applied asks only that a line not be hard-wrapped. The sibling documents show the two coexisting, `CODESTYLE.md` and `README.md` each keeping a nested item on one long line at indent two. It matters past rendering here, because the `copilot-instructions-keeper` Skill carries this section's shape while leaving its entries to each repository, so the nesting is part of what a downstream copy reproduces. Flattened, a claim and its three proof lines read as peers in the raw text the reviewer reads. The indent of an item's own first line is kept and only continuation lines are folded. Asserted by a third postcondition beside the two the previous commit carried: every list item, with its indent and its folded text, compares equal to the merge base. That assertion is what the previous commit lacked, and the two it had could not see indentation at all. Co-Authored-By: Claude Opus 5 (1M context) --- .github/copilot-instructions.md | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 14784475..d4b93b32 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -43,34 +43,34 @@ Review effort is user-controlled. The automation observes `Lite`, `Balanced`, or **The entries are this repository's own.** Each names a file and a revision, so a repository holding a copy of this file carries the shape and the rules above rather than these findings, deletes an entry whose subject it does not carry, and records what it has proved itself. - **`keys_unsorted` requires jq 1.6, so the ruleset normalizer in `repo-config/configure.sh` fails to compile on jq 1.5.** Raised as a suppressed finding, by analogy to the `walk/1` call the same filter was rewritten to avoid. -- **Disproved by** - running both builtins on `jq-1.5-1-a5b5cbe`, the build that reproduces the `walk/1` failure. `keys_unsorted` evaluates there and the whole normalizer returns the sorted document, while `walk(.)` on that binary answers `jq: 1 compile error`. The two builtins are not in the same position, and the analogy is the whole of what carried the finding. -- **Proved against** - the `norm` filter in `repo-config/configure.sh` on `develop` at `756a53e`. -- **Delete when** - the filter stops calling `keys_unsorted`, or nothing this check runs on carries a jq older than 1.6. + - **Disproved by** - running both builtins on `jq-1.5-1-a5b5cbe`, the build that reproduces the `walk/1` failure. `keys_unsorted` evaluates there and the whole normalizer returns the sorted document, while `walk(.)` on that binary answers `jq: 1 compile error`. The two builtins are not in the same position, and the analogy is the whole of what carried the finding. + - **Proved against** - the `norm` filter in `repo-config/configure.sh` on `develop` at `756a53e`. + - **Delete when** - the filter stops calling `keys_unsorted`, or nothing this check runs on carries a jq older than 1.6. - **A description's stale commit claims are found by extracting the bare SHAs it quotes.** Not a reviewer's finding but the method this repository's own backlog specified for the `claims` check in `scripts/pr_review.py`, recorded here because a rejected method costs the same to re-propose as a declined finding costs to re-derive, and because a backlog has a place for a claim the tree contradicts and none for a method a measurement rejects. -- **Disproved by** - running it over the 25 most recent merged pull requests, where it raised four references and all four were correct prose: a `develop` commit named as history, a SHA inside a pasted digest, and two commits in another repository written without a URL. Nothing in the shape of a bare SHA separates those from a claim, and separating them by meaning is the similarity heuristic [GOVERNANCE.md "Documentation Style Conventions"](../GOVERNANCE.md#documentation-style-conventions) rules out. A path arm measured on the same corpus is worse, flagging 54 of 215 backticked candidates, nearly all of them bare basenames and other repositories. -- **Proved against** - the 25 most recent merged pull requests as of `develop` at `756a53e`, the corpus on which the anchored verb form that ships instead raises one reference, and that one true. -- **Delete when** - `claims` stops reading a description for commit references. + - **Disproved by** - running it over the 25 most recent merged pull requests, where it raised four references and all four were correct prose: a `develop` commit named as history, a SHA inside a pasted digest, and two commits in another repository written without a URL. Nothing in the shape of a bare SHA separates those from a claim, and separating them by meaning is the similarity heuristic [GOVERNANCE.md "Documentation Style Conventions"](../GOVERNANCE.md#documentation-style-conventions) rules out. A path arm measured on the same corpus is worse, flagging 54 of 215 backticked candidates, nearly all of them bare basenames and other repositories. + - **Proved against** - the 25 most recent merged pull requests as of `develop` at `756a53e`, the corpus on which the anchored verb form that ships instead raises one reference, and that one true. + - **Delete when** - `claims` stops reading a description for commit references. - **The GraphQL `pullRequests` connection defaults to `states: [OPEN]`, so the bot node id query in "Triggering and Polling" returns nothing in a repository whose Copilot-reviewed pull requests have all merged.** Raised against the repo-wide read, on the ground that a cold start is exactly the case where no open pull request carries a review. -- **Disproved by** - running the connection both ways against this repository while exactly one pull request was open. With `states` omitted, `pullRequests(first: 5, orderBy: { field: CREATED_AT, direction: DESC })` answers `628 OPEN`, `627 MERGED`, `626 MERGED`, `625 MERGED` and `624 MERGED`, so the omitted default is every state rather than `OPEN`. The same call with `states: [OPEN]` answers `628 OPEN` alone, which is the behavior the finding predicts for the first form and is what distinguishes them. The read was first run when this repository had no open pull request at all, and it returned the id from merged ones. -- **Proved against** - the `BOT_ID` query in "Triggering and Polling" in this file, run against this repository's pull request list on 2026-08-08. -- **Delete when** - that query names `states` explicitly, or stops reading pull requests to find the id. + - **Disproved by** - running the connection both ways against this repository while exactly one pull request was open. With `states` omitted, `pullRequests(first: 5, orderBy: { field: CREATED_AT, direction: DESC })` answers `628 OPEN`, `627 MERGED`, `626 MERGED`, `625 MERGED` and `624 MERGED`, so the omitted default is every state rather than `OPEN`. The same call with `states: [OPEN]` answers `628 OPEN` alone, which is the behavior the finding predicts for the first form and is what distinguishes them. The read was first run when this repository had no open pull request at all, and it returned the id from merged ones. + - **Proved against** - the `BOT_ID` query in "Triggering and Polling" in this file, run against this repository's pull request list on 2026-08-08. + - **Delete when** - that query names `states` explicitly, or stops reading pull requests to find the id. - **"The agent check branches" in `STANDUP.md` section 0 is a subject-verb disagreement, and should read "The agent checks branches".** Raised as a suppressed finding against a line the change under review only touched as diff context. -- **Disproved by** - reading the sentence against the snippet it describes. The subject is the noun phrase "the agent check", meaning the check for the signing agent, and "branches" is its verb, which is what the `if [ ... = ssh ]; then ssh-add -L; else gpg --list-secret-keys; fi` line does. The proposed reading needs "branches" as a plural noun, and the paragraph is section 0, before a repository exists, where the alternatives it names are the SSH and GPG forms rather than refs. -- **Proved against** - the paragraph following the agent snippet in `STANDUP.md` section 0 on `develop` at `676a2bd`, unchanged since `77be3a3`. -- **Delete when** - the sentence is reworded for any reason, since the entry is about this phrasing rather than about the rule it states. + - **Disproved by** - reading the sentence against the snippet it describes. The subject is the noun phrase "the agent check", meaning the check for the signing agent, and "branches" is its verb, which is what the `if [ ... = ssh ]; then ssh-add -L; else gpg --list-secret-keys; fi` line does. The proposed reading needs "branches" as a plural noun, and the paragraph is section 0, before a repository exists, where the alternatives it names are the SSH and GPG forms rather than refs. + - **Proved against** - the paragraph following the agent snippet in `STANDUP.md` section 0 on `develop` at `676a2bd`, unchanged since `77be3a3`. + - **Delete when** - the sentence is reworded for any reason, since the entry is about this phrasing rather than about the rule it states. - **A hyphenated key such as `inputs.app-login` or `inputs.delete-branch` cannot be read with dot notation in a GitHub Actions expression, since `-` parses as subtraction, so the merge-bot task's `if:` conditions never match and its `env:` values never resolve.** Raised as four suppressed findings against `.github/workflows/merge-bot-task.yml`, each a variant of the one claim. -- **Disproved by** - reading the expressions reference, which states that property dereference syntax needs a name that starts with a letter or `_` and contains only alphanumeric characters, `-`, or `_`, so a hyphen is inside the allowed set and index syntax is required only for a name outside it. And by the same file, whose every job reads `steps.app-token.outputs.token` with dot notation on the same hyphenated shape, the expression the fleet's merge-bot has resolved on every Dependabot merge it has performed. `actionlint` parses the file without a finding. -- **Proved against** - `.github/workflows/merge-bot-task.yml` on `feature/reusable-workflows` at `210d88f`, and the "About contexts" property dereference rule in the GitHub Actions contexts reference read on 2026-08-15. -- **Delete when** - the task stops declaring hyphenated inputs, or the expressions reference changes the allowed set. + - **Disproved by** - reading the expressions reference, which states that property dereference syntax needs a name that starts with a letter or `_` and contains only alphanumeric characters, `-`, or `_`, so a hyphen is inside the allowed set and index syntax is required only for a name outside it. And by the same file, whose every job reads `steps.app-token.outputs.token` with dot notation on the same hyphenated shape, the expression the fleet's merge-bot has resolved on every Dependabot merge it has performed. `actionlint` parses the file without a finding. + - **Proved against** - `.github/workflows/merge-bot-task.yml` on `feature/reusable-workflows` at `210d88f`, and the "About contexts" property dereference rule in the GitHub Actions contexts reference read on 2026-08-15. + - **Delete when** - the task stops declaring hyphenated inputs, or the expressions reference changes the allowed set. - **`sudo_implementations()` in `host-setup/linux/install-tools.sh` never matches, because `update-alternatives --query sudo` prints a slave as `visudo:` with a trailing colon, so a host whose active `visudo` rejects the drop-in always reaches the "No sudo on this host parses timestamp_type" refusal even where an alternative would parse it.** Raised as a suppressed finding against the sudo timestamp action. -- **Disproved by** - running the subcommand the function actually calls. `update-alternatives --query sudo` prints each slave as two space-separated fields under `Slaves:`, one space-indented line reading `visudo /usr/sbin/visudo.ws`, and the awk over that output prints `/usr/bin/sudo.ws /usr/sbin/visudo.ws` and `/usr/lib/cargo/bin/sudo /usr/lib/cargo/bin/visudo`. The colon form the finding describes belongs to `update-alternatives --display sudo`, a different subcommand, which prints `slave visudo: /usr/sbin/visudo.ws`. The path the finding says is unreachable was also driven end to end in an `ubuntu:25.10` container, where the run found `/usr/bin/sudo.ws`, switched the alternative to it, and wrote the drop-in. -- **Proved against** - `sudo_implementations` in `host-setup/linux/install-tools.sh` on `feature/sudo-timestamp-global` at `13e689f`, against `update-alternatives` 1.22.x on Ubuntu 25.10. -- **Delete when** - the function stops reading `update-alternatives --query`, or that subcommand changes its slave format. + - **Disproved by** - running the subcommand the function actually calls. `update-alternatives --query sudo` prints each slave as two space-separated fields under `Slaves:`, one space-indented line reading `visudo /usr/sbin/visudo.ws`, and the awk over that output prints `/usr/bin/sudo.ws /usr/sbin/visudo.ws` and `/usr/lib/cargo/bin/sudo /usr/lib/cargo/bin/visudo`. The colon form the finding describes belongs to `update-alternatives --display sudo`, a different subcommand, which prints `slave visudo: /usr/sbin/visudo.ws`. The path the finding says is unreachable was also driven end to end in an `ubuntu:25.10` container, where the run found `/usr/bin/sudo.ws`, switched the alternative to it, and wrote the drop-in. + - **Proved against** - `sudo_implementations` in `host-setup/linux/install-tools.sh` on `feature/sudo-timestamp-global` at `13e689f`, against `update-alternatives` 1.22.x on Ubuntu 25.10. + - **Delete when** - the function stops reading `update-alternatives --query`, or that subcommand changes its slave format. ## When in Doubt