Phase GFG-D2-FIX — muse+git-mirror D2 ID-space (Thinking freeze)
Status: Reviewed → pass (D2F-r2). GFG-D2-FIX-a is spec-only and now frozen; no product
code lands in this Thinking close. GFG-D2-FIX-b (Auto) is cleared to build mechanically against
this contract.
phase: GFG-D2-FIX
outputs:
- id: gfg-d2-muse-id-space
path: docs/archive/phases/PHASE-GFG-D2-MUSE-ID-SPACE.md
frozen: true
frozen_inputs:
- id: gfg-freshness-gate
path: docs/archive/phases/PHASE-GFG-GOVERNANCE-FRESHNESS-GATE.md
- id: hygiene-outline-d2
path: docs/archive/phases/PHASE-9A-5-GOVERNANCE-HYGIENE-AGENT-OUTLINE.md
- id: drift-detect-d2
path: tools/governance_hygiene/drift.py
- id: muse-git-mirror-adapter
path: adapters/muse_git_mirror/adapter.py
- id: adapter-bridge-helpers
path: adapters/base.py
- id: realign-guard
path: tools/governance_hygiene/realign.py
- id: verified-reads
path: tools/governance_hygiene/reads.py
review_stamp:
reviewed_at: '2026-07-28T15:50:11Z'
verdict: pass
reviewer_mode: agent
reviewer_model: thinking-high
reviewer_provider: local
kit_version: 0.1.0
artifact_digest: sha256:3148c577ef724f456d1b9da88564a301f4280783f76040c0fae7c94e4eaf7cac
Downstream edge: GFG-D2-FIX-b treats this document as ground truth. It amends the R2 ID space
used by D2 (and realign verify) under Muse 0.2.x content-addressed tips. It does not reopen GFG
fail-closed wiring, marker rules, Automation templates, or exit-code precedence. It does not
make git-import into Muse main the default dogfood recovery path.
Review record (§6.2): every freeze-review finding MUST cite file+line per SPEC §6; uncited
findings are invalid and are discarded. Fixes during the loop are Tier 1 (feature branch); merge to
main is Tier 3 and is never part of this loop.
| Round | Reviewer | Verdict | Resolution |
|---|---|---|---|
| D2F-r1 | Freeze-review loop (checklist + thinking, thinking-high) |
findings | R1-M1 §D2F.4.2 mirror-ref fallback was underspecified (could still return a git tip compared to sha256: R3). Tightened: if [last_export] exists without muse_commit_id → ReadError; mirror-ref fallback only when [last_export] section absent. R1-M2 §D2F.5 must cite plan_realign using reads.r2_anchor_sha for ancestry (realign.py lines 25–29) so Auto replaces that call site with R2_git. Fixed. |
| D2F-r2 | Freeze-review loop (checklist + thinking, thinking-high) |
pass | Checklist gate clean (0 findings, dry-run). Semantic re-read: R1-M1/R1-M2 RESOLVED; §D2F.3 ID-space rule matches live Scooling bridge evidence; realign ancestry/verify split precise; non-goals hold (no freshness default-off, no default git-import dogfood); no security/irreversible/real_money/gates_tier3 escalation. Stamp written by ok review --freeze. |
§D2F.0 — Simple summary
Overseer checks whether the Muse↔Git bridge still matches Muse’s main tip. That check was comparing
the Git export SHA (a 40-character hex string) to Muse’s tip (a sha256:… content hash). Those
are different ID systems, so a healthy bridge always looked “drifted,” and the tool offered to import
GitHub history into Muse — the wrong default on a working consumer.
This phase freezes the correct comparison: under muse+git-mirror, D2 is aligned when the
bridge’s last_export.muse_commit_id equals Muse main tip (same ID space). The Git export SHA
stays for realign ancestry / from_ref only. Realign verification must use the Muse ID, not
git_sha == muse tip.
Technical summary: amend read_canonical_anchor (and D2/realign consumers of R2) so R2 for
muse+git-mirror is last_export.muse_commit_id from .muse/git-bridge.toml. Keep
read_bridge_git_sha for realign() from_ref and the Git ancestry/superset precondition. Fail
closed when muse_commit_id is absent. Do not silent-default-off governance_freshness.
§D2F.1 — Verified incident (do not redesign around folklore)
Live Scooling (2026-07-28, post GFG ok sync):
| Field | Value |
|---|---|
last_export.git_sha |
1e734a922a8de5dcac248007b8dfb706c4a0f84e (40-hex) |
last_export.muse_commit_id |
sha256:67001f71f4481906b1bad7a9f46ccf61f9113c44a6cf64473416c4c77a8b6116 |
muse rev-parse main |
sha256:67001f71f4481906b1bad7a9f46ccf61f9113c44a6cf64473416c4c77a8b6116 |
| D1 | aligned (handover claim fixed) |
| D2 (broken) | drifted permanently — compares git_sha to Muse tip |
| Bridge health | muse_commit_id == muse tip (healthy export) |
| Dry-run realign | offered muse bridge git-import with would_import=47 — correctly withheld on consumer (Tier 3 / SD-14) |
Root cause (code):
adapters/muse_git_mirror/adapter.pyread_canonical_anchor(lines 63–66) returnsread_bridge_git_sha(..., "last_export")— Git ID space.tools/governance_hygiene/drift.py_detect_d2(lines 55–60) equatesr2_anchor_shator3_canonical_main_sha(Muse tip,sha256:…).tools/governance_hygiene/realign.pyverify (lines 85–86) demandsanchor.anchor_sha == muse tip— same cross-ID false fail after a real import.adapters/base.pyread_bridge_git_sha(lines 164–183) only parsesgit_sha; nomuse_commit_idreader exists.
GFG itself is correct to fail-closed on D2 drift. The D2 predicate is wrong under Muse 0.2.x.
§D2F.2 — Scope
In scope (freeze only — this Thinking phase writes no product code):
- Correct D2 alignment predicate for
muse+git-mirrorunder Muse content-hash tips (§D2F.3). - Bridge field roles:
muse_commit_idvsgit_sha(§D2F.4). - Realign plan/verify amendments so healthy bridges do not plan
git-import, and verify does not demandgit_sha == muse tip(§D2F.5). - Fail-closed rules when
muse_commit_idis missing (§D2F.6). - Auto deliverables + seven-tier matrix (§D2F.8–§D2F.9).
- Explicit non-goals (§D2F.7).
Out of scope:
| Non-goal | Why rejected |
|---|---|
Silent default-off for governance_freshness |
Recreates the GFG incident floor hole (§GFG.8.1). |
Making git-import into Muse main the default dogfood “fix” |
SD-14 / Tier 3; healthy bridges must not look drifted. |
| Redefining D1, D3, marker format, Automation templates, exit codes | GFG freeze stays; this is an R2 ID-space amendment only. |
Equating git_sha to Muse tip via hash translation tables |
No stable bijection; inventing one is dishonest. |
Consumer posture/env flips; kit main merge |
Tier 3 / out of Auto. |
| Rewriting Muse bridge export tooling | Kit consumes .muse/git-bridge.toml; does not own Muse. |
§D2F.3 — Frozen D2 rule (muse+git-mirror)
Let:
- R3 = Muse main tip from
adapter.read_head("muse:<muse.main_branch>")(today:sha256:…under Muse 0.2.x). - R2_muse =
.muse/git-bridge.toml→[last_export].muse_commit_id(string, case-normalized for comparison). - R2_git =
.muse/git-bridge.toml→[last_export].git_sha(40-hex), used only for realignfrom_ref/ Git ancestry — never for D2 equality against R3.
D2 resolution for muse+git-mirror (frozen):
- If R3 is unreadable → D2
unreadable. - If
muse_commit_idis missing/empty in[last_export]→ D2unreadable(fail-closed). Do not fall back to comparinggit_shato R3. - Else if
normalize(R2_muse) == normalize(R3)→ D2aligned. - Else → D2
drifted.
Regression locked: when muse_commit_id == R3 and git_sha ≠ R3 (the permanent healthy-bridge
case under Muse content hashes), D2 MUST be aligned. That inequality alone is not drift.
Other regimes (unchanged):
| Regime | D2 rule |
|---|---|
git-only |
Existing: R2 (git tip / origin/main) vs R3 (same ID space). |
muse-only |
Existing: R2 is Muse tip via adapter; equals R3 when readable. |
§D2F.4 — Frozen adapter / read surface
§D2F.4.1 — read_bridge_muse_commit_id
Add adapters/base.py helper (sibling to read_bridge_git_sha):
- Read
.muse/git-bridge.tomlsectionlast_exportorlast_importas requested. - Parse
muse_commit_id = "…"(TOML string; acceptsha256:…and any future Muse id form that is a non-empty quoted string). - Return
Nonewhen file/section/key absent.
§D2F.4.2 — read_canonical_anchor (muse+git-mirror)
Amend MuseGitMirrorAdapter.read_canonical_anchor (adapters/muse_git_mirror/adapter.py
lines 63–79):
- If
.muse/git-bridge.tomlhas a[last_export]section:- Read
muse_commit_idviaread_bridge_muse_commit_id(repo, "last_export"). - If present and non-empty →
AnchorResult(anchor_sha=<muse_commit_id>, source=".muse/git-bridge.toml:last_export.muse_commit_id"). - If missing/empty →
ReadError("read_canonical_anchor", "missing last_export.muse_commit_id")— fail-closed. Do not returngit_sha.
- Read
- Else (no
[last_export]section): keep today’s mirror-ref fallback ({remote}/{mirror_branch}git tip) when readable; else the existing “no bridge anchor…”ReadError. - Never return
git_shaasanchor_shawhen[last_export]exists.
Frozen intent: after Auto, R2 flowing into VerifiedReads.r2_anchor_sha for
muse+git-mirror with a live [last_export] bridge is the Muse ID (muse_commit_id), so
existing _detect_d2 equality against R3 (drift.py lines 55–60) becomes correct. Auto may
instead keep a dual-field design if and only if it still satisfies §D2F.3 bit-for-bit.
§D2F.4.3 — git_sha retention
read_bridge_git_sharemains;realign()continues to use it forfrom_ref.plan_realign/_github_superset_of_anchorMUST use R2_git (git_sha), never the Muse content-hash R2, forgit merge-base --is-ancestor.
§D2F.5 — Frozen realign plan / verify
Amend tools/governance_hygiene/realign.py:
- Plan trigger: still “only when D2 drifted” (9A-5 §5;
plan_realignlines 19–20). After §D2F.3, a healthy bridge (muse_commit_id == muse tip) yields D2 aligned → skip realign (nogit-importplan). - Superset precondition: today
plan_realignpassesreads.r2_anchor_shainto_github_superset_of_anchor(lines 25–29). After §D2F.4.2 that field is a Muse ID and must not be fed togit merge-base --is-ancestor. Auto MUST pass R2_git (read_bridge_git_sha(..., "last_export")or equivalent) as the ancestry argument. If R2_git is missing when D2 is drifted → withhold realign with a clear reason (operator required); do not invent a Muse-ID ancestry check. - Verify after apply: today lines 85–86 compare
anchor.anchor_shato Muse tip. After §D2F.4.2 that comparison is Muse-ID equality (correct). Forbidden: reintroducinggit_sha == muse tipas the verify predicate.
Hard stop retained: Auto/apply realign into Muse main remains operator-gated on consumers (Tier 3 / SD-14). This phase stops the false plan on healthy bridges; it does not auto-apply imports.
§D2F.6 — Fail-closed honesty
| Condition | Result |
|---|---|
muse_commit_id missing under muse+git-mirror with bridge present |
D2 / anchor read unreadable — not optimistic aligned |
muse_commit_id ≠ Muse tip |
D2 drifted — genuine bridge lag / inversion signal |
muse_commit_id == Muse tip, git_sha ≠ Muse tip |
D2 aligned |
| Unreadable Muse tip | D2 unreadable |
| Turning off governance freshness to green status | Forbidden |
§D2F.7 — Boundary / rejection table
| Scenario | After this fix |
|---|---|
Healthy Scooling bridge (muse_commit_id == tip) |
D2 aligned; dry-run may stamp marker; status --exit-code → 0 when D1+marker ok |
False realign plan (would_import=N) on healthy bridge |
Must not appear solely because git_sha ≠ tip |
True Muse tip ahead of last export’s muse_commit_id |
D2 drifted (real) |
| GitHub-only history ahead; operator wants Muse catch-up via import | Still Tier 3 / SD-14; realign path remains available when D2 truly drifted + superset holds |
| Equate git SHA to sha256 tip | Rejected |
§D2F.8 — Auto deliverables (exact)
read_bridge_muse_commit_idinadapters/base.py.- Amend
MuseGitMirrorAdapter.read_canonical_anchorper §D2F.4.2. - Amend
plan_realign/execute_realign_guardper §D2F.5 (git_sha for ancestry; Muse ID for verify). - Ensure
_detect_d2+ freshness probe inherit correct R2 (via anchor read and/or explicit muse+git-mirror branch that still matches §D2F.3). - Update fixtures that assumed
git_sha-as-anchor for muse+git-mirror to includemuse_commit_idmatching the fixture Muse tip where D2 aligned is expected. - Seven-tier tests per §D2F.9 (unit/integration/e2e minimum named below).
/build-verification-review→passbefore ROADMAP → DONE.- Update
docs/ROADMAP.md+docs/OVERSEER-HANDOVER.mdtogether; feature-branch commit. NEXT after land = consumer re-stamp (Scooling first):ok sync→ok governance-sync --dry-run→ok status --exit-code→0.
§D2F.9 — Seven-tier test matrix (Auto must satisfy)
| Tier | Proves |
|---|---|
| unit | D2 aligned when muse_commit_id == sha256:… tip and git_sha is a different 40-hex; D2 drifted when muse_commit_id ≠ tip; D2 unreadable when muse_commit_id missing; read_canonical_anchor returns muse id + source suffix muse_commit_id; read_bridge_muse_commit_id parses live-shaped TOML. |
| integration | plan_realign returns skip when D2 aligned under content-hash tips even if git_sha ≠ tip; realign verify success path compares Muse IDs (mock); does not require git_sha == tip. |
| e2e | Fixture muse+git-mirror: healthy bridge (muse_commit_id == tip, mismatched git_sha) → freshness/D2 aligned → dry-run stamps marker path available without planning git-import as the repair for that mismatch. |
| stress | Bridge TOML with large unrelated sections — parsers still isolate [last_export] keys. |
| data-integrity | Round-trip: muse_commit_id string preserved (including sha256: prefix); no mutation of git_sha when reading Muse id. |
| performance | Bridge parse is file-local; no network; no gh. |
| security | Fail-closed on missing muse id (no optimistic aligned); remediation strings non-executed; no secrets from bridge file; no silent freshness disable. |
§D2F.10 — Hard stops
- No kit
mainmerge without Tier 3. - No consumer posture/env flips.
- No “fix” that is default
git-importinto Muse main on healthy bridges. - No silent default-off for
governance_freshness.
§D2F.11 — Acceptance (post Auto + consumer re-stamp)
On a muse+git-mirror consumer where last_export.muse_commit_id == muse tip and D1 is aligned:
ok governance-sync --dry-runstamps.overseer/last_governance_sync(D1/D2 aligned path).ok status --exit-code→0withgovernance_freshness.oktrue.
Cross-references
docs/archive/phases/PHASE-GFG-GOVERNANCE-FRESHNESS-GATE.md§GFG.4 / §GFG.8.1 — freshness still fail-closed on D2.docs/archive/phases/PHASE-9A-5-GOVERNANCE-HYGIENE-AGENT-OUTLINE.md§3 D2 / §5 realign — amended ID space only.- Live bridge shape:
.muse/git-bridge.toml[last_export]keysmuse_commit_id+git_sha.