feat(rollback): derive global-alias ownership from ledger (RAN-491)#13
Merged
Conversation
…CP duplication RAN-491: owning_package decides what rollback tombstones but sits outside compute_state_fingerprint, so corrupting it was invisible to drift-check while remaining load-bearing. Chosen fix (decision.s5d.ownership-derivation, TRIZ separation-on-condition): instead of widening the fingerprint -- which would invalidate every existing baseline in every repo -- rollback now derives the owner of each global alias from the append-only ledger plus stored packages at the point of use. The stored field degrades to a verified cache. Semantics pinned by tests: - derivation confirms or vetoes tombstoning, NEVER expands it: a wrong derivation can keep data alive but can never destroy an alias the stored field does not claim - mismatch (stored claims spec, ledger says other) = skip + loud warning, checked BEFORE the referenced-globals guard so corruption on shared globals is reported rather than silently shadowed by kept_referenced - ledger says spec but stored names another = suspected tamper, reported, no destructive action (dodge attempts surface instead of silently winning) - no ledger trace = stored-field fallback with warning, so legacy repos (pre-ledger aliases, manual edits) keep working First-import derivation is correct by construction: AliasTable::resolve is first-creator-wins, apply_renames preserves the owner, and no ownership transfer API exists. rollback_spec is extracted into the library next to execute_import; CLI run_rollback and MCP tool_s5d_rollback are thin callers now -- the previous copy-paste duplication is gone, so the cross-check exists exactly once. owning_package_corruption_is_invisible_to_drift_check flips from "known limitation" to "by design": the field is no longer load-bearing, so drift-check staying synced is intended; protection lives in rollback. Fingerprint formula untouched -- zero false drift on existing baselines, no migration day.
Tribunal round 1 (Codex, REJECT) found two real holes in the first derivation: 1. It applied CURRENT package files to HISTORICAL ledger entries, ignoring LedgerEntry.spec_sha256 -- a backdated package edit could fabricate a false owner and veto a legitimate rollback. 2. It ignored rollback epochs: AliasTable::resolve is first-creator-wins within the ACTIVE epoch (rollback tombstones, next import re-creates with a new owner), so "first ever importer" false-flagged the legitimate second-epoch owner on an untampered repo. Replay now: an import/reconcile entry contributes claims only when the current file sha matches the entry; a mismatching or missing file poisons every key unowned at that point (Unverifiable), cleared only by that package's own rollback entry; a rollback entry releases the package's ownerships. Unverifiable deliberately does NOT route to the no-trace fallback: the fallback tombstones, and a two-file tamper (alias field + any package file) could otherwise downgrade the veto into destruction. Inconclusive evidence keeps data and reports loudly; only zero-trace legacy aliases fall back to stored-field trust. Both counterexamples are pinned as tests: - rollback_after_reimport_does_not_false_flag_new_owner - rollback_skips_tombstoning_when_ownership_unverifiable Tribunal verdict + resolution recorded in .s5d/evidence/feat.s5d.ownership-derivation/tribunal-round1.md.
…g globals Tribunal round 2 (Codex, REJECT) caught two lifecycle holes in the round-1 poison model: 1. Normal evolution (import -> edit -> re-approve -> re-import) left the FIRST ledger entry with a stale sha, which poisoned the package's own keys and degraded its own rollback to an "unverifiable" skip. The read-rule now treats self-poison as harmless by construction: whichever of a package's imports actually created the key, the owner is that same package -- only a FOREIGN package in the poison set breaks certainty. 2. referenced_globals walked every package file regardless of record status, so a rolled-back spec left on disk pinned its globals alive forever. Pre-existing behavior, but inconsistent with the epoch semantics the derivation now implements: a Deprecated record's claim ended with its rollback. Such specs no longer count as referencing. New DerivedOwnership::UnverifiedCandidate distinguishes "only ledger candidate, unverified": consistent with the stored field -> fallback tombstone with warning; conflicting -> non-destructive skip. Pure multi-candidate ambiguity stays Unverifiable. Both lifecycle variants pinned: - rollback_after_edit_and_reimport_tombstones_own_globals - rollback_epoch_transition_works_with_old_spec_file_left_on_disk
random1st
added a commit
that referenced
this pull request
Jul 1, 2026
New tests: - approve_rejects_spec_modified_since_preview (#6) - import_rejects_spec_modified_since_approval (#6) - rollback_after_import_tombstones_aliases_and_removes_index (#10) - rollback_without_successful_import_fails (#10) - decide_rejects_winner_without_spec_ref (#15) - reconcile_restores_synced_status_after_drift (#9) Known bug (test ignored): - rollback_of_first_spec_does_not_break_second_spec_sharing_global_artifact (#13) — CONFIRMED: rollback tombstones global aliases by owning_package, breaking other specs that share the same global artifact. 84 tests pass, 1 ignored (known bug).
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.
RAN-491: owning_package corruption no longer load-bearing
owning_packageon global aliases decides what rollback tombstones, but it sits outsidecompute_state_fingerprint— corrupting it was invisible to drift-check. Widening the fingerprint would invalidate every existing baseline in every repo (mass false drift), so this PR implements the recorded decisiondecision.s5d.ownership-derivationinstead:Rollback derives the owner of each global alias from the append-only ledger + stored packages at the point of use. The stored field degrades to a verified cache.
Semantics (test-pinned)
First-import derivation is correct by construction:
AliasTable::resolveis first-creator-wins,apply_renamespreserves the owner, no transfer API exists.Also
rollback_specextracted into the library (next toexecute_import); CLIrun_rollbackand MCPtool_s5d_rollbackare thin callers — the copy-paste duplication is gone.owning_package_corruption_is_invisible_to_drift_checkflips from "known limitation" to "by design".Evidence
🤖 Generated with Claude Code