Conversation
Generated output only, no hand edits. `reports/divergences.md` reads each repo's ground-truth `main`, so before #542 promoted it was measuring a hub `main` that predated this session and reporting the hub as not carrying a section it had just authored. ## What moved, and how each was established **Predicted and correct.** `AGENTS.md > Fleet Bootstrap` no longer lists ProjectTemplate. The hub's own `main` carries it now, so that row was pre-promotion state rather than fleet drift. **Predicted and wrong.** I expected `repo-config/configure.sh` to fall from 9 repos to 7. It fell to **8**. ProjectTemplate dropped off correctly, but PhotoCleaner is **genuinely stale** rather than an artifact: #540 moved the canonical, so a copy that was current yesterday is behind today. That is the intended consequence of changing a `verbatim` file, flagged in #540 itself and then forgotten when predicting here. **Not predicted, and checked rather than assumed.** PhotoCleaner dropped off five carried-section stale rows and off the `GOVERNANCE.md > Representative Data` not-carried row. The cause is external to this work: that repo merged its own promotion `c457ff3` earlier today and its `main` now carries the section. An unexplained improvement deserves the same scrutiny as an unexplained regression, so it was verified against that repo's commits rather than accepted. ## What the report now says about the fleet - **19 repos owe `AGENTS.md > Fleet Bootstrap`.** The bootstrap shipped and nothing downstream has it. That is the propagation job #536 exists to make possible, and it is the honest measure of where the fleet stands. - **Financial-Modeling is the only repo still stale on the carried governance sections**, at 18 rows. - **`.markdownlint-cli2.jsonc` still lists 16 repos**, because this session's fleet merges landed on each repo's `develop` and have not promoted to their own `main`. The ledger reads `main`, correctly, so these clear as those repos promote. - **`repo-config/configure.sh` at 8**, five of which additionally need the `ruleset-*.json` payload migration before they can take the canonical at all. ## Verification Regenerated by `python3 spec/fidelity_honesty.py --report` from a live fleet pass after the promotion merged. editorconfig clean, CRLF preserved, diff-scoped `prose_lint` clean. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Who may bypass a ruleset decides who the rules do not apply to. That is
a per-repository human decision, not a value the fleet config hands out.
All three payloads declared `bypass_actors: [{ actor_id: 5, actor_type:
RepositoryRole, bypass_mode: always }]`, so applying them granted
repository admins a standing exemption on `main` and `develop` in every
repo. Verified live: every fleet repo sampled carries exactly that entry
on both branches.
## It was a real grant, not a restatement
GitHub's documentation is explicit that a ruleset **applies to
administrators by default** and that the bypass list **starts empty**.
Admins are *eligible* for bypass and must be added. The maintainer's UI
screenshot confirms the same from the other side: the entry offers a
**Delete bypass** action, so it is a deliberate list member rather than
an inherent property of the role.
Whether these repos first got it from this config or from hand-made
rulesets that predate the tooling is unknown, and it does not matter.
The config should not be the thing maintaining it either way.
## Removing the declaration alone would have made it worse
`apply` sends the payload with `gh api --method PUT`, which **replaces
the whole document**. A payload with no `bypass_actors` would therefore
have cleared the live list on every run. That is code deleting a bypass,
which is the specific thing that must never happen.
So `apply` now reads the live list and writes it back unchanged, and
**aborts** rather than proceeding if that read fails, since applying
without it would silently clear what it was meant to preserve. On create
there is no live list and none is sent, so a new ruleset starts at
GitHub's own empty default and nothing is deleted, because nothing
existed.
## `check` reports and never asserts
No payload declares a bypass value, so there is nothing to compare
against, and inventing an expectation would put code back in charge of
the decision. `check` now prints the live list on every run, the way it
already prints the secrets manual-verify note:
```text
ruleset 'develop' bypass list: RepositoryRole 5 always (not managed by this script)
```
Visible on every run, judged by nothing.
## Verification
The preserve path was proven by composing the PUT body against this
repo's live ruleset **without sending it**:
```text
live bypass: [{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}]
payload declares: ABSENT
composed PUT body would send: [{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}]
```
`bash -n` clean, `prose_lint` clean, editorconfig clean, live read-only
`check` against this repo passes.
## What this does not do
It does not remove the bypass entries that already exist on the fleet's
rulesets. Removing those would itself be code deleting a bypass.
Clearing them is a deliberate per-repo act in the UI, and `check` now
makes each one visible so they can be reviewed rather than forgotten.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>Two backlog entries, both raised by the maintainer, both with worked examples from the session that produced them. ## An agent works in its own checkout Own directory, own feature branch, and never writes to a repository because it happened to be on disk. The reason care is not enough is that **the operations which cross the boundary are the ordinary ones**. A blanket `git add -A` sweeps another agent's uncommitted work into your commit. A `git reset --hard` deletes it. A branch switch carries it into a change it has nothing to do with. Each command is correct in isolation and wrong only because of who else is in the directory. This session produced the first of those. A blanket add swept a second agent's in-progress onboarding work into an unrelated commit and pushed it. It survived only because it was rescued and restored by hand afterwards, and the same directory had already seen a `reset --hard` and two branch switches that would have destroyed it outright rather than merely misfiling it. ## The same rule extends to reading, which is the commoner case A clone on disk is not the branch it names. It is whatever that clone was last fetched to. Reading it to answer *what does this repository carry* answers instead *what did this clone last see*, and **the failure is silent**: the read succeeds and returns a plausible answer. Two instances, both from this session, both wrong in a way nothing flagged: - A detection pass over local clones reported a repository as still drifted on a file whose fix had already merged, because that clone's refs predated the merge. The correction came from a `fetch`, not from the read. - A second pass reported a repository as missing a file it does carry, because the local checkout sat on a branch behind the one being measured. The entry states the practice alongside the rule: read the live ref through the API where a claim will be acted on, or fetch immediately before reading, and name the ref and commit in any finding derived from a local read so a reader can tell what was actually measured. It also keeps the honest exception, that a local clone is the right tool for anything needing history or a build, since an API read cannot give either. ## Every carried file is a copy that breaks when it is not current `repo-config/configure.sh` is the worked example, and it happened twice today. A defect in its check mode was fixed once in the hub and immediately became a re-vendor owed by every repository carrying it. Until each takes the fix they run different logic while reporting the same command, and a repository that misses the sweep **does not fail loudly** — it quietly audits itself with an older script. Hosting the tool in the hub and having downstream call it removes the stale copy rather than detecting it. This is the same argument as the existing reusable-workflow entry, so the entry says to settle them together rather than separately. Three things to decide are recorded: what genuinely needs vendoring against what only needs to be reachable, how a consumed tool is pinned, since calling hub code at run time is a floating dependency unless it names a commit, and what a repository does when the hub is unreachable, since a vendored copy still works offline and a called one does not. ## Verification `prose_lint` clean, `markdownlint-cli2` clean, CRLF preserved. One file in the commit, staged by explicit path rather than a blanket add, for the reason the first entry describes. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Promotes the current develop state to main, with the key behavior change being removal of the ruleset bypass_actors grant from the fleet-managed payloads while preserving any existing live bypass list during configure.sh apply.
Changes:
- Remove
bypass_actorsfrom the committed ruleset payload JSON files so the fleet config no longer grants admin bypass by default. - Update
repo-config/configure.sh applyto read the live ruleset’sbypass_actorsand write it back unchanged onPUT, aborting if the live list cannot be read. - Update
repo-config/configure.sh checkto report (but not assert) the live bypass list for visibility without managing it.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| TODO.md | Adds backlog entries capturing follow-up work around reducing vendored surface area and agent isolation practices. |
| reports/divergences.md | Regenerates the divergence ledger to reflect the post-promotion baseline. |
| repo-config/operational/develop.json | Removes bypass_actors from the operational develop ruleset payload. |
| repo-config/main.json | Removes bypass_actors from the main ruleset payload. |
| repo-config/develop.json | Removes bypass_actors from the release develop ruleset payload. |
| repo-config/configure.sh | Preserves live bypass_actors on update, omits it on create, and reports it in check mode without asserting. |
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three commits. Merged as a merge commit, never squashed, per the branching model.
e48d64dmain90e3255362aec8The one that matters
#545 is a behaviour change to
configure.sh, not a doc edit. All three ruleset payloads declaredbypass_actors: [{ actor_id: 5, actor_type: RepositoryRole, bypass_mode: always }], so applying them granted repository admins a standing exemption onmainanddevelopin every repo.GitHub's documentation is explicit that a ruleset applies to administrators by default and that the bypass list starts empty. Admins are eligible for bypass and must be added. So the declaration was a real grant rather than a restatement of something inherent, and the fleet config was the thing handing it out.
Removing the declaration alone would have been worse than leaving it.
applysends the payload as aPUT, which replaces the whole document, so a payload with nobypass_actorswould have cleared the live list on every run. That is code deleting a bypass.applynow reads the live list and writes it back unchanged, and aborts rather than proceeding if that read fails. On create it sends none, so a new ruleset starts at GitHub's own empty default and nothing is deleted, because nothing existed.checkreports the list on every run and asserts nothing, since no payload declares a value to compare against and inventing one would put code back in charge of a human decision.This does not remove the bypass entries already live on the fleet's rulesets. Removing those would itself be code deleting a bypass. They are now visible on every
checkrun so they can be cleared deliberately, per repo, in the UI.Expected consequence, stated rather than discovered
repo-config/configure.shis carriedverbatimwithappliesTo: "*", so this promotion makes every repo holding a copy stale against the hub. That is the third re-vendor this file has generated in one session, which is exactly the evidence the vendored-tooling entry added in #546 now records.The follow-up plan is to fold that re-vendor into the payload migration already owed by five repos, so each gets one visit that renames its
ruleset-*.jsonpayloads and takes the current script, rather than two.Verification
spec/validate.pyOK (21 cataloged), diff-scopedprose_lintclean,markdownlint-cli2clean, editorconfig clean, live read-onlycheckagainst this repo passes with the bypass list reported and unasserted. The preserve path was proven by composing thePUTbody against this repo's live ruleset without sending it: the payload declares nothing, the live list isRepositoryRole 5 always, and the composed body carries that same list back.