fix: derive every repo's spelling join from the body map - #288
Merged
Conversation
The bare and prefixed spellings of seven repos normalised to two different keys, so a policy map filed under one was invisible to the other. `_target_sets` registered BOTH spellings of every `PyAuto*` repo as a known target while `repo_aliases` only joined them for the libraries. Reproduced on main: `@autobrain` reached a live known target with no witness row, so the refactor conductor advised "strengthen tests first" for the best-tested repo in the organism, and intake filed `Target: autobrain` — a folder that does not exist. Five organs, one project repo and the Hands were split this way. THE DECISION, taken before the edit as the prompt required: organs key on the PREFIXED form. That is not a new rule, it is #269's rule made executable — the canonical key is the package the repo SHIPS where it ships one, the repo name where it does not. Organs ship no package; Nerves is the one that does and keys bare already. Everything downstream was filed prefixed for organs, including the Mind's own target folders, so the alternative would have rekeyed five maps for no gain. THE CLASS, not the fourth instance. `repo_aliases` was hand-maintained while the known-target set was derived from the body map, so the two drifted silently. The join is now DERIVED (`_derived_aliases`), keyed off the body map's new `package:` field, and a hand row that contradicts the derivation raises instead of quietly winning. What remains hand-written is only what a body map cannot know: short forms and pre-rename spellings. Also here: - A repo whose NAME carries a `.` or `/` is now deliberately NOT registered. `normalise_repo` truncates at the first separator, so no mention could ever reach it; the tempting alias-the-head fix is worse than the gap, because where that head is the org's own name every org-qualified mention would resolve to that one repo. Derived from the names, not a hand-kept exclusion list. - `REPO_DISPLAY` derives from the body map too — it had the beginnings of the same drift, with reachable keys carrying no row, so a header came out as `Target: pyautohands`. Five guards, each mutation-tested: - no repo splits across two keys (removing the derived join names all seven) - no alias points at a key nothing is filed under - the body map's `package:` agrees with the witness map - unreachable repos are excluded AND their truncated head resolves to nothing - canonical keys survive a body map with no `package:` — the fallback that lets this half stand alone against a Mind checkout pinned to main Verified: 515 tests pass (510 before). `repos_sync --check` all 13 legs OK, tenant firewall included — it rejected two drafts of this prose for naming instance repos, which is why the comments read generically. An A/B of every spelling in the alias table and target sets shows exactly six resolutions changed, all of them the intended joins; libraries, short forms, renames, workspaces and org-qualified paths are byte-identical. Heart NOT EVALUATED — unreachable from a web-github session. Depends on PyAutoMind e160d40b (the `package:` field). The fallback above means this does not block on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nj7cTnL8Ke5WoNsdGVhCKn
CI failed on this PR: `test_body_map_package_agrees_with_the_witness_map` required `repos.yaml` to DECLARE `package:` for every witness row that names one, and tests.yml pins the sibling Mind checkout to `main`, where the field does not exist yet. The rest of the fix already tolerated that — `canonical_key` falls back to the hand table, with a test pinning it — but this guard did not, so the half that was designed to stand alone could not. Absence of the field is an older body map, not a contradiction: a map declaring NO package anywhere has nothing to corroborate, and the guard stands down. The moment it declares even one, every witness row that names a package must have one — so a PARTIALLY declared map still fails, which is what a new library added without its `package:` would look like. That is a stronger guard than the one it replaces, not a weakened one. Verified against CI's exact condition — this branch's Brain against a PyAutoMind checkout at origin/main, zero `package:` rows: 515 pass. Against the branch's Mind, with all seven declared: 515 pass. Mutation: declaring one package and not the rest fails the guard naming the other six. Also fixes the DeprecationWarning this PR introduced (`maxsplit` passed positionally in `unreachable_repos`). The identical warning from `normalise_repo` is pre-existing and left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nj7cTnL8Ke5WoNsdGVhCKn
This was referenced Aug 26, 2026
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 free
to 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.
Closes #287. Mind half: PyAutoLabs/PyAutoMind#333 (merge that first; this does not block on it — see the fallback below).
The defect, reproduced on main
The bare and prefixed spellings of seven repos normalised to two different keys, so a policy map filed under one was invisible to the other.
_target_setsregistered BOTH spellings of everyPyAuto*repo as a known target whilerepo_aliasesonly joined them for the libraries:@PyAutoBrainpyautobrain→PyAutoBrain/tests@autobrainautobrain→ no witness rowpyautobrain→PyAutoBrain/testsSo
pyauto-brain refactorreported[unwitnessed: autobrain]and advised "strengthen tests first" for the best-tested repo in the organism, andpyauto-brain intakeon the same mention filedTarget: autobrain— a folder that does not exist. Five organs, one project repo and the Hands were split this way. Nothing failed; the advice was just quietly wrong.The decision, taken before the edit
The prompt required answering the keying question first rather than sweeping aliases blind. Organs key on the PREFIXED form. That is not a new rule — it is #269's rule made executable: the canonical key is the package the repo SHIPS where it ships one, and the repo name where it does not. Organs ship no package; Nerves is the one that does and keys bare (
autonerves) already. Everything downstream was filed prefixed for organs —test_witness,target_signals,REPO_DISPLAY,target_default_wiki, and the Mind's own target folders — so keying bare would have rekeyed five maps for no gain.The class, not the fourth instance
repo_aliaseswas hand-maintained while the known-target set was derived from the body map, so the two drifted silently. The join is now derived (_derived_aliases), keyed off the body map's newpackage:field, and a hand row that contradicts the derivation raises instead of quietly winning. What stays hand-written is only what a body map cannot know: short forms (aa,af) and pre-rename spellings.Two things found that were not in the prompt
pyautolabs.github.iois now deliberately NOT registered.normalise_repotruncates at the first.//, so no mention could ever reach it. The tempting fix — alias the truncated head — is worse than the gap: that head is the org's own name, so@PyAutoLabs/PyAutoFitwould have started resolving to the static site instead of PyAutoFit. The acceptance criterion's other branch ("or is deliberately not registered") covers it, and the exclusion is derived from the names, not a hand-kept list. A guard pins both halves.REPO_DISPLAYalso derives from the body map now — it had the beginnings of the same drift, with reachable keys carrying no row, so a header came out asTarget: pyautohands.Guards
Five, each mutation-tested rather than assumed:
package:agrees with the witness mappackage:value raises at importpackage:That last one matters for merge order: CI checks the sibling Mind out at
main, pinned, so a Brain half that required the Mind half would sit red until Mind merged.canonical_keyfalls back to the hand table's library rows when nopackage:is declared, giving identical keys either way. Those rows become dead weight once PyAutoMind#333 lands and can be deleted then.Validation
repos_sync --checkall 13 legs OK. The tenant firewall rejected two drafts of this prose for naming instance repos, which is why the comments read generically — the same check that caught drafts at fix: retire PyAutoConf rename leftovers in Brain functional surfaces #267 and fix: witness map misses 5 repos with real test suites, splits 2 more across keys #269.pyauto-heartis unreachable from a web-github session, so leg 4 of the ship gate did not run. Recorded that way rather than claimed clean.Generated by Claude Code