realign.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
| 1 | """Muse realignment guard (§5 / §D2F.5).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from adapters.base import VcsAdapter, read_bridge_git_sha |
| 6 | from adapters.config import OverseerConfig |
| 7 | from adapters.errors import ReadError |
| 8 | from adapters.runner import CommandRunner, quote_arg |
| 9 | from tools.governance_hygiene.types import DriftReport, VerifiedReads |
| 10 | |
| 11 | |
| 12 | def plan_realign( |
| 13 | config: OverseerConfig, |
| 14 | adapter: VcsAdapter, |
| 15 | reads: VerifiedReads, |
| 16 | drift: DriftReport, |
| 17 | ) -> tuple[bool, str]: |
| 18 | """Return whether realign would run and a human-readable reason.""" |
| 19 | if drift.d2_anchor_vs_canonical != "drifted": |
| 20 | return False, "D2 aligned — skip realign" |
| 21 | if config.vcs.regime != "muse+git-mirror": |
| 22 | return False, f"{config.vcs.regime}: realign no-op" |
| 23 | if reads.r1_github_main_sha is None: |
| 24 | return False, "missing R1 github main sha" |
| 25 | # Ancestry uses Git ID space (git_sha), never muse_commit_id / R2 Muse tip (§D2F.5). |
| 26 | git_anchor = read_bridge_git_sha(adapter.repo_root, "last_export") |
| 27 | if not git_anchor: |
| 28 | git_anchor = read_bridge_git_sha(adapter.repo_root, "last_import") |
| 29 | if not git_anchor: |
| 30 | return False, "missing bridge git_sha for realign ancestry — operator required" |
| 31 | if not _github_superset_of_anchor( |
| 32 | adapter, |
| 33 | git_anchor, |
| 34 | reads.r1_github_main_sha, |
| 35 | ): |
| 36 | return False, "R1 is not a content superset of anchor — operator required" |
| 37 | return True, "D2 drift + superset precondition met" |
| 38 | |
| 39 | |
| 40 | def execute_realign_guard( |
| 41 | config: OverseerConfig, |
| 42 | adapter: VcsAdapter, |
| 43 | reads: VerifiedReads, |
| 44 | drift: DriftReport, |
| 45 | *, |
| 46 | dry_run: bool, |
| 47 | ) -> tuple[str | None, str | None]: |
| 48 | """ |
| 49 | Run §5 guard sequence. |
| 50 | |
| 51 | Returns ``(summary, error_command)`` where error_command is set on verification failure. |
| 52 | """ |
| 53 | should_run, reason = plan_realign(config, adapter, reads, drift) |
| 54 | if not should_run: |
| 55 | return reason, None |
| 56 | |
| 57 | max_commits = config.thresholds.realign_max_commits |
| 58 | preview = adapter.realign(dry_run=True, max_commits=max_commits) |
| 59 | if isinstance(preview, ReadError): |
| 60 | return None, preview.command |
| 61 | |
| 62 | if preview.would_import > max_commits: |
| 63 | return ( |
| 64 | f"realign withheld: would_import={preview.would_import} > max={max_commits}", |
| 65 | None, |
| 66 | ) |
| 67 | |
| 68 | if dry_run: |
| 69 | return ( |
| 70 | f"realign planned: would_import={preview.would_import} " |
| 71 | f"from {preview.from_ref} to {preview.to_ref}", |
| 72 | None, |
| 73 | ) |
| 74 | |
| 75 | applied = adapter.realign(dry_run=False, max_commits=max_commits) |
| 76 | if isinstance(applied, ReadError): |
| 77 | return None, applied.command |
| 78 | |
| 79 | if not applied.applied: |
| 80 | return f"realign not applied: {applied.reason}", None |
| 81 | |
| 82 | anchor = adapter.read_canonical_anchor() |
| 83 | if isinstance(anchor, ReadError): |
| 84 | return None, anchor.command |
| 85 | |
| 86 | muse_main = config.vcs.muse.main_branch |
| 87 | head = adapter.read_head(f"muse:{muse_main}") |
| 88 | if isinstance(head, ReadError): |
| 89 | return None, head.command |
| 90 | |
| 91 | # Verify in Muse ID space (muse_commit_id vs muse tip) — never git_sha == tip (§D2F.5). |
| 92 | if anchor.anchor_sha.lower() != head.sha.lower(): |
| 93 | return None, "realign verification failed: muse_commit_id != muse main" |
| 94 | |
| 95 | return ( |
| 96 | f"realign applied: imported {applied.would_import} " |
| 97 | f"from {applied.from_ref} to {applied.to_ref}", |
| 98 | None, |
| 99 | ) |
| 100 | |
| 101 | |
| 102 | def _github_superset_of_anchor( |
| 103 | adapter: VcsAdapter, |
| 104 | anchor_sha: str, |
| 105 | main_sha: str, |
| 106 | ) -> bool: |
| 107 | """True when ``main_sha`` contains ``anchor_sha`` (recovery precondition).""" |
| 108 | if anchor_sha.lower() == main_sha.lower(): |
| 109 | return True |
| 110 | runner = adapter.runner |
| 111 | repo_root = str(adapter.repo_root) |
| 112 | cmd = ( |
| 113 | "git merge-base --is-ancestor " |
| 114 | + quote_arg(anchor_sha) |
| 115 | + " " |
| 116 | + quote_arg(main_sha) |
| 117 | ) |
| 118 | result = runner.run(cmd, cwd=repo_root) |
| 119 | return result.ok |
File History
2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
53 days ago