Skip to content

feat(review): enumerate all invalid compact recovery edges in one pass (#1582)#1607

Merged
Alan-TheGentleman merged 13 commits into
Gentleman-Programming:mainfrom
ardelperal:feat/1582-inspect-authority
Jul 21, 2026
Merged

feat(review): enumerate all invalid compact recovery edges in one pass (#1582)#1607
Alan-TheGentleman merged 13 commits into
Gentleman-Programming:mainfrom
ardelperal:feat/1582-inspect-authority

Conversation

@ardelperal

@ardelperal ardelperal commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

🔗 Linked Issue

Closes #1582

This PR is the prerequisite for #1452 (atomic batch reconciliation). The contract decision recorded on #1452 — atomic batch over monotonic repair, with enumeration landing first — is what made #1582 the right shape. Without a read-only enumerator that exposes every invalid edge at once, the batch command on #1452 has nothing to declare against.


🏷️ PR Type

What kind of change does this PR introduce?

  • type:feature — New feature (non-breaking change that adds functionality)
  • type:bug — Bug fix (non-breaking change that fixes an issue)
  • type:docs — Documentation only
  • type:refactor — Code refactoring (no functional changes)
  • type:chore — Build, CI, or tooling changes
  • type:breaking-change — Breaking change (fix or feature that changes behavior)

📝 Summary

Adds gentle-ai review inspect-authority --cwd <repo>, a read-only walker that enumerates every recovery edge in the compact-v2 authority inventory and reports every anomaly instead of returning on the first one. Output is JSON with stable top-level key order (summary, edges, diagnostics).

  • The walker reuses validateCompactRecoveryEdge (the same source of truth used by compactAuthorityLeaves) so the two surfaces can never disagree on what counts as invalid.
  • Anomaly classification is driven by errors.Is against the two existing sentinels (errCompactRecoveryTargetUnchanged, errCompactRecoveryAuthorizationInexact); when both fire on the same edge, the entry uses the joined class string unchanged_target,malformed_recovery_authorization (the wire format introduced in fix(review): reconcile dual-anomaly recovery edge #1465).
  • The strict gate at internal/reviewtransaction/compact_reconcile.go:221 is byte-identical with origin/main. Verified locally with git diff origin/main -- internal/reviewtransaction/compact_reconcile.go → empty.
  • review status, review start, and negotiated review validate keep their fail-closed semantics. Nothing reachable from those code paths was modified.

This is the missing prerequisite for the batch reconciliation command that #1452 will add.


📂 Changes

File / Area What Changed
internal/reviewtransaction/compact_store.go Added InspectCompactAuthority walker; reuses validateCompactRecoveryEdge at line 318 and the two sentinels at lines 42 and 47.
internal/reviewtransaction/compact_inspect_test.go New test file covering S1–S9 (and S10–S11 from the spec). Per-scenario coverage instead of shared helpers so the failure messages stay close to the requirement.
internal/reviewtransaction/testdata/inspect_authority_determinism.golden Single-line golden snapshot for the determinism property.
internal/cli/review_inspect.go New CLI handler RunReviewInspectAuthority. Reuses newReviewFlagSet and encodeReviewJSON.
internal/cli/review_inspect_test.go CLI dispatch + --cwd resolution + JSON envelope shape.
internal/cli/review_facade.go 4 lines: registration alongside RunReviewReconcileAuthority at lines 320-321 and a one-line usage-list edit at line 229.
internal/app/app_test.go 2-line update to the review help substring assertion.
CHANGELOG.md Bullet referencing #1582.
openspec/changes/feat-1582-inspect-authority/* Full SDD change folder: exploration, proposal, spec, design, tasks, apply-progress, verify-report. Traceability artifacts so every review decision can be sourced back to the original contract reasoning.

Total diff: 1739 insertions / 2 deletions across 15 files.


🧪 Test Plan

Focused tests (the surface this PR adds)

go test -count=1 -v ./internal/reviewtransaction/... ./internal/cli/...

All 18 new tests pass: 10 walker scenarios in compact_inspect_test.go (S1–S11 mapped), 2 CLI scenarios in review_inspect_test.go, plus 6 smoke captures behind //go:build smoke / //go:build smokecapture (untracked, intentionally out of the diff).

Gate integrity (the most important check)

git diff origin/main -- internal/reviewtransaction/compact_reconcile.go

Empty output. The strict gate is untouched.

Static checks

gofmt -l .
go vet ./...

Both clean.

Full repo (go test ./...)

go test -count=1 ./... shows 6 pre-existing failures in internal/cli, internal/components/communitytool, internal/reviewtransaction (TestChainBundleImportRejectsTamperingTruncationAndWrongBindings), internal/sddstatus, internal/tui (TestPickerFlowSlice), and internal/update/upgrade. All 6 reproduce on origin/main before this PR; none reference the new code paths. Listed verbatim under Notes for Reviewers so the reviewer has the baseline.

  • Unit tests pass for the surface this PR adds
  • Go format passes
  • Go vet passes
  • Manual smoke against a synthetic inventory (5-edge fixture covering valid + each anomaly class + combined + unloadable) matches the spec's JSON shape byte-for-byte across two consecutive runs
  • Full go test ./... is green — fails on 6 pre-existing tests unrelated to this PR (see Notes)

📏 size:exception Request

Total diff is 1739 lines, ~1339 over the 400-line review budget. This PR explicitly requests maintainer-applied size:exception.

Breakdown:

  • Code + tests + CHANGELOG: 418 insertions / 2 deletions across 8 files. The walker is 102 lines, the CLI handler is 39 lines, the focused tests are 251 lines (197 walker + 54 CLI), the golden file is 1 line, the CHANGELOG entry is 21 lines, and review_facade.go plus app_test.go are 6 lines combined. This 418-line slice is itself 18 lines over the documented 400-line code budget — the test file carries per-scenario coverage rather than shared helpers, chosen for clarity of failure messages over line count.
  • SDD traceability artifacts: 1321 insertions across 7 files under openspec/changes/feat-1582-inspect-authority/. These are the team's planning artifacts (exploration, proposal, spec, design, tasks, apply-progress, verify-report) and follow the same convention as archived changes upstream (see commit f572288 chore(sdd): archive persona-canonical-channel change).

The combined size is the cost of the team's "no inline decisions" SDD process, not feature scope creep. If maintainers prefer a smaller diff, the SDD artifacts can be moved out of the PR and the diff drops to 418 lines (still 18 over budget — the per-scenario test choice is the actual code-side decision worth confirming).


🤖 Automated Checks

Check Status Description
Check PR Cognitive Load PR exceeds 400 changed lines — size:exception requested in Notes
Check Issue Reference Body contains Closes #1582
Check Issue Has status:approved #1582 has status:approved (verified 2026-07-21)
Check PR Has type:* Label Requesting type:feature (contributor lacks label permission)
Unit Tests (focused) All 18 new tests pass
Go Format gofmt -l empty
Go Vet go vet ./... clean
E2E Tests Not run (no Docker change in this PR)
Full go test ./... ⚠️ 6 pre-existing failures unrelated to this PR (see Notes)

✅ Contributor Checklist

  • PR is linked to an issue with status:approved (feat(review): enumerate all invalid compact recovery edges in one pass #1582)
  • PR exceeds 400 changed lines; size:exception requested with rationale documented in this body
  • Maintainer: apply type:feature label and, if accepted, size:exception (contributor lacks label permission)
  • Unit tests pass for the surface this PR adds (go test ./internal/reviewtransaction/... ./internal/cli/...)
  • Go format passes (gofmt -l empty)
  • Go vet passes (go vet ./... clean)
  • E2E tests pass — pre-existing failures only, not introduced by this PR
  • Documentation updated (CHANGELOG.md)
  • Commits follow Conventional Commits format
  • Commits do not include Co-Authored-By trailers

💬 Notes for Reviewers

Review focus order, by blast radius:

  1. internal/reviewtransaction/compact_store.go — the walker. Confirm it only reads, only classifies via errors.Is, never mutates.
  2. internal/cli/review_inspect.go — the CLI handler. Confirm it sits next to RunReviewReconcileAuthority and reuses newReviewFlagSet / encodeReviewJSON.
  3. internal/reviewtransaction/compact_inspect_test.go — the per-scenario tests. Confirm S1–S11 coverage maps back to the spec.
  4. internal/cli/review_facade.go (the 4-line diff) — confirm the registration touches the same sites the existing commands do.
  5. openspec/changes/feat-1582-inspect-authority/spec.md — the source of truth for what the walker is supposed to do.

Pre-existing test failures (baseline; present on origin/main before this PR):

  • internal/cli — timeout in TestReviewFacadeStartProvableShellAndModeRiskSelectsCanonical4R
  • internal/components/communitytoolTestPiCodeGraphProbeClassifiesStalledMCPResponsesAsDeadlineExceeded
  • internal/reviewtransactionTestChainBundleImportRejectsTamperingTruncationAndWrongBindings
  • internal/sddstatus — sddstatus drift tests
  • internal/tuiTestPickerFlowSlice (picker-flow drift)
  • internal/update/upgradeTestCheckSingleTool_EngramUsesBinaryReleaseChannel, TestInstallScriptBetaGoInstallBypassesPublicGoProxy

None of these reference the new code paths. They reproduce on origin/main with no changes applied. If any reviewer sees one of these fail on this PR, it is a baseline failure, not a regression from this change.

What this PR deliberately does NOT do (out of scope, per the contract recorded on #1452):

  • Cycle / fork detection in the inventory graph
  • Mutation of any authority record
  • Changes to the semantics of compactAuthorityLeaves (the fail-closed path)
  • Changes to the gate at compact_reconcile.go:221
  • Batch reconciliation (that lands as fix(review): reconcile multiple invalid recovery edges #1452 once this PR is merged)

Smoke test files behind //go:build smoke / //go:build smokecapture are intentionally left untracked in this PR per the verification report's design (they document the smoke methodology for future contributors but don't run on default go test).


Generated by SDD orchestration (feat-1582-inspect-authority). All planning artifacts are versioned under openspec/changes/feat-1582-inspect-authority/ for traceability.

Summary by CodeRabbit

  • New Features

    • Added review inspect-authority to generate a read-only JSON report of compact recovery edges and entry diagnostics.
    • Reports now identify invalid, incomplete, forked, cyclic, or otherwise anomalous recovery relationships.
    • Inspection output is deterministic and excludes sensitive authorization details.
  • Bug Fixes

    • Improved handling and classification of supported recovery-edge anomalies during reconciliation.
  • Tests

    • Added coverage for corrupted recovery states, cancellation, deterministic output, and protection against repository mutations.

Completes the change folder for feat-1582-inspect-authority so reviewers can trace every decision back to its source. The exploration.md maps the code surface (compact_store.go compactAuthorityLeaves at :474, validateCompactRecoveryEdge at :318, sentinel errors at :42 and :47). The proposal.md locks the four product decisions (load errors as diagnostics, joined combined anomaly class, valid edges as count only, per-edge scope only). The spec.md codifies R1-R9 with RFC 2119 keywords and S1-S11 in Given/When/Then form. The design.md fixes the architecture and exact path:line citations for the walker, CLI dispatch, and reuse of newReviewFlagSet / encodeReviewJSON. The verify-report.md captures focused test results, gate byte-identity check, JSON smoke evidence, and the documented size:exception.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bf358b77-5195-4fc0-949a-c8a5551ef626

📥 Commits

Reviewing files that changed from the base of the PR and between f6c3b31 and b5ad2b0.

📒 Files selected for processing (8)
  • internal/app/app_test.go
  • internal/cli/review_facade.go
  • internal/cli/review_inspect_authority.go
  • internal/cli/review_inspect_authority_test.go
  • internal/reviewtransaction/compact_inspect.go
  • internal/reviewtransaction/compact_inspect_test.go
  • internal/reviewtransaction/compact_reconcile.go
  • internal/reviewtransaction/compact_reconcile_test.go

📝 Walkthrough

Walkthrough

Adds read-only compact recovery inspection with anomaly classification, deterministic diagnostics, cancellation handling, and JSON output through the new review inspect-authority command. Reconciliation now reuses the shared classification helper.

Changes

Compact authority inspection

Layer / File(s) Summary
Recovery anomaly classification and reconciliation
internal/reviewtransaction/compact_reconcile.go, internal/reviewtransaction/compact_reconcile_test.go
Classifies unchanged-target and malformed-authorization anomalies, derives reconciliation proofs, records authorization digests, and rejects non-reconcilable corruption.
Inspection report and validation engine
internal/reviewtransaction/compact_inspect.go, internal/reviewtransaction/compact_inspect_test.go
Enumerates compact records, reports edge and entry diagnostics, detects missing predecessors, forks, and cycles, sorts results deterministically, and checks cancellation and read-only behavior.
Inspection command and dispatch
internal/cli/review_inspect_authority.go, internal/cli/review_facade.go, internal/app/app_test.go, internal/cli/review_inspect_authority_test.go
Adds review inspect-authority, resolves the canonical repository root, emits structured JSON, wires command dispatch and help text, and tests argument validation, determinism, mutation safety, and cancellation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: alan-thegentleman

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a command to enumerate invalid compact recovery edges in one pass.
Linked Issues check ✅ Passed The PR adds a read-only inspect-authority command that enumerates all recovery edges, reports valid and invalid ones, and outputs deterministic JSON as requested.
Out of Scope Changes check ✅ Passed The changes stay focused on the inspection command, shared anomaly classification, and supporting tests; no unrelated feature work stands out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 21: Add the required single trailing newline at the end of CHANGELOG.md,
without changing the existing changelog content.
- Line 14: Update the changelog wording for the edge-reporting command to state
that it emits all edges as JSON, with valid edges included and invalid edges
classified as anomalies. Preserve the existing sorting detail by successor
lineage and revision.

In `@internal/reviewtransaction/compact_inspect_test.go`:
- Around line 176-197: Add a sibling test to
TestInspectCompactAuthorityLoadError that constructs a valid successor whose
Recovery.PredecessorLineageID references a lineage absent from records, then
calls InspectCompactAuthority and asserts a missing_predecessor diagnostic is
emitted with a non-empty message. Keep the existing load_failure test unchanged
and verify the report’s diagnostic count and code.

In `@internal/reviewtransaction/compact_store.go`:
- Around line 592-616: Simplify compactInspectionAnomalyClass by removing the
repaired/provenance construction and duplicate validation calls; when unchanged
and successor.Recovery is non-nil, set authorization directly from whether
Recovery.MaintainerAuthorization differs from the exact binding and does not use
the recovery authorization schema prefix, preserving the existing switch
behavior.
- Around line 550-568: Update the inspection logic around the records-loading
loop and the second store loop to maintain a separate map keyed by each store’s
directory-derived lineageID for self-successor lookup, while retaining records
keyed by record.State.LineageID for predecessor references. Detect duplicate
content identities during loading, emit an appropriate diagnostic instead of
overwriting the existing record, and ensure stores with mismatched or missing
self identities are reported rather than silently skipped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e7baa628-64cc-4602-819d-0a0f63f21cc1

📥 Commits

Reviewing files that changed from the base of the PR and between 222949c and f6c3b31.

⛔ Files ignored due to path filters (7)
  • openspec/changes/feat-1582-inspect-authority/apply-progress.md is excluded by !openspec/**
  • openspec/changes/feat-1582-inspect-authority/design.md is excluded by !openspec/**
  • openspec/changes/feat-1582-inspect-authority/exploration.md is excluded by !openspec/**
  • openspec/changes/feat-1582-inspect-authority/proposal.md is excluded by !openspec/**
  • openspec/changes/feat-1582-inspect-authority/spec.md is excluded by !openspec/**
  • openspec/changes/feat-1582-inspect-authority/tasks.md is excluded by !openspec/**
  • openspec/changes/feat-1582-inspect-authority/verify-report.md is excluded by !openspec/**
📒 Files selected for processing (8)
  • CHANGELOG.md
  • internal/app/app_test.go
  • internal/cli/review_facade.go
  • internal/cli/review_inspect.go
  • internal/cli/review_inspect_test.go
  • internal/reviewtransaction/compact_inspect_test.go
  • internal/reviewtransaction/compact_store.go
  • internal/reviewtransaction/testdata/inspect_authority_determinism.golden

Comment thread CHANGELOG.md Outdated
- `gentle-ai review inspect-authority --cwd <repo>` (#1582): read-only
enumeration of every compact-v2 recovery edge. The walker classifies each
edge through the existing `validateCompactRecoveryEdge` source of truth,
emits invalid edges as JSON (sorted by successor lineage and revision) and

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document that valid edges are included too.

The command must report valid edges as well as invalid ones, but “emits invalid edges as JSON” can be read as an invalid-only output contract. Clarify that all edges are emitted and anomalies are classified within the report.

Proposed wording
-  emits invalid edges as JSON (sorted by successor lineage and revision) and
+  emits all edges as JSON, classifying invalid edges (sorted by successor
+  lineage and revision), and
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
emits invalid edges as JSON (sorted by successor lineage and revision) and
emits all edges as JSON, classifying invalid edges (sorted by successor
lineage and revision), and
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 14, Update the changelog wording for the edge-reporting
command to state that it emits all edges as JSON, with valid edges included and
invalid edges classified as anomalies. Preserve the existing sorting detail by
successor lineage and revision.

Comment thread CHANGELOG.md Outdated
strings are `unchanged_target`, `malformed_recovery_authorization`, and the
joined `unchanged_target,malformed_recovery_authorization` for edges that
fail both sentinels. This prerequisite surface unblocks the batch
reconciliation plan tracked under #1452. No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required trailing newline.

markdownlint-cli2 reports that this file does not end with a single newline.

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 21-21: Files should end with a single newline character

(MD047, single-trailing-newline)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 21, Add the required single trailing newline at the end
of CHANGELOG.md, without changing the existing changelog content.

Source: Linters/SAST tools

Comment on lines +176 to +197
func TestInspectCompactAuthorityLoadError(t *testing.T) {
repo := initSnapshotRepo(t)
validInspectRecoveryFixture(t, repo)
root, _, err := reviewAuthorityRoot(context.Background(), repo)
if err != nil {
t.Fatal(err)
}
bad := filepath.Join(root, "v2", "inspect-corrupt")
if err := os.MkdirAll(bad, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(bad, "review-state.json"), []byte("not json\n"), 0o644); err != nil {
t.Fatal(err)
}
report, err := InspectCompactAuthority(context.Background(), repo)
if err != nil {
t.Fatal(err)
}
if report.Summary.TotalEdges != 1 || len(report.Diagnostics) != 1 || report.Diagnostics[0].Code != "load_failure" || report.Diagnostics[0].Message == "" {
t.Fatalf("load-error inspection = %#v", report)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No dedicated test for the missing_predecessor diagnostic.

TestInspectCompactAuthorityLoadError covers load_failure, but there's no sibling test asserting the missing_predecessor diagnostic (compact_store.go's other diagnostic code, emitted when a successor's predecessor record isn't in records). Given this is one of only two diagnostic codes the walker emits, and diagnostics drive the CLI's error-exit behavior, it's worth a dedicated regression test (e.g., write a successor whose Recovery.PredecessorLineageID points at a lineage with no corresponding store).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/reviewtransaction/compact_inspect_test.go` around lines 176 - 197,
Add a sibling test to TestInspectCompactAuthorityLoadError that constructs a
valid successor whose Recovery.PredecessorLineageID references a lineage absent
from records, then calls InspectCompactAuthority and asserts a
missing_predecessor diagnostic is emitted with a non-empty message. Keep the
existing load_failure test unchanged and verify the report’s diagnostic count
and code.

Comment on lines +550 to +568
records := make(map[string]CompactRecord, len(stores))
for _, store := range stores {
record, loadErr := store.Load()
if loadErr != nil {
report.Diagnostics = append(report.Diagnostics, InspectionDiagnostic{Code: "load_failure", Path: store.Dir, Message: loadErr.Error()})
continue
}
records[record.State.LineageID] = record
}
for _, store := range stores {
successor, ok := records[store.lineageID]
if !ok || successor.State.Recovery == nil {
continue
}
predecessor, ok := records[successor.State.Recovery.PredecessorLineageID]
if !ok {
report.Diagnostics = append(report.Diagnostics, InspectionDiagnostic{Code: "missing_predecessor", Path: store.Dir, Message: "recovery predecessor is unavailable"})
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Successor self-lookup can silently drop or misroute records under corrupted inventories.

records is populated keyed by the loaded record's own claimed identity (record.State.LineageID, line 557), but in the second loop each store looks itself up via records[store.lineageID] (line 560) — the directory-derived identity. This is safe only if store.lineageID == record.State.LineageID always holds. If a compact store's on-disk content has a lineage_id field that doesn't match its own directory name (a plausible corruption/tamper scenario, not just JSON syntax errors), this lookup silently misses (ok == false), and the store is dropped from the report entirely with no diagnostic — violating the stated goal of being "safe against corrupt inventories" and inspecting every edge. Worse, if the mismatched LineageID happens to collide with another store's real identity, records[record.State.LineageID] = record (line 557) silently overwrites that other store's entry, so a legitimate successor could be validated against the wrong data.

Keep records keyed by content identity for predecessor cross-references (line 564 needs that), but resolve each store's own successor via a store-identity-keyed map populated at load time, and diagnose duplicate content-identity collisions instead of silently overwriting.

🛡️ Proposed fix
 	records := make(map[string]CompactRecord, len(stores))
+	byStore := make(map[string]CompactRecord, len(stores))
 	for _, store := range stores {
 		record, loadErr := store.Load()
 		if loadErr != nil {
 			report.Diagnostics = append(report.Diagnostics, InspectionDiagnostic{Code: "load_failure", Path: store.Dir, Message: loadErr.Error()})
 			continue
 		}
-		records[record.State.LineageID] = record
+		byStore[store.lineageID] = record
+		if _, dup := records[record.State.LineageID]; dup {
+			report.Diagnostics = append(report.Diagnostics, InspectionDiagnostic{Code: "duplicate_lineage_id", Path: store.Dir, Message: "lineage_id collides with another compact store"})
+		}
+		records[record.State.LineageID] = record
 	}
 	for _, store := range stores {
-		successor, ok := records[store.lineageID]
+		successor, ok := byStore[store.lineageID]
 		if !ok || successor.State.Recovery == nil {
 			continue
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
records := make(map[string]CompactRecord, len(stores))
for _, store := range stores {
record, loadErr := store.Load()
if loadErr != nil {
report.Diagnostics = append(report.Diagnostics, InspectionDiagnostic{Code: "load_failure", Path: store.Dir, Message: loadErr.Error()})
continue
}
records[record.State.LineageID] = record
}
for _, store := range stores {
successor, ok := records[store.lineageID]
if !ok || successor.State.Recovery == nil {
continue
}
predecessor, ok := records[successor.State.Recovery.PredecessorLineageID]
if !ok {
report.Diagnostics = append(report.Diagnostics, InspectionDiagnostic{Code: "missing_predecessor", Path: store.Dir, Message: "recovery predecessor is unavailable"})
continue
}
records := make(map[string]CompactRecord, len(stores))
byStore := make(map[string]CompactRecord, len(stores))
for _, store := range stores {
record, loadErr := store.Load()
if loadErr != nil {
report.Diagnostics = append(report.Diagnostics, InspectionDiagnostic{Code: "load_failure", Path: store.Dir, Message: loadErr.Error()})
continue
}
byStore[store.lineageID] = record
if _, dup := records[record.State.LineageID]; dup {
report.Diagnostics = append(report.Diagnostics, InspectionDiagnostic{Code: "duplicate_lineage_id", Path: store.Dir, Message: "lineage_id collides with another compact store"})
}
records[record.State.LineageID] = record
}
for _, store := range stores {
successor, ok := byStore[store.lineageID]
if !ok || successor.State.Recovery == nil {
continue
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/reviewtransaction/compact_store.go` around lines 550 - 568, Update
the inspection logic around the records-loading loop and the second store loop
to maintain a separate map keyed by each store’s directory-derived lineageID for
self-successor lookup, while retaining records keyed by record.State.LineageID
for predecessor references. Detect duplicate content identities during loading,
emit an appropriate diagnostic instead of overwriting the existing record, and
ensure stores with mismatched or missing self identities are reported rather
than silently skipped.

Comment thread internal/reviewtransaction/compact_store.go Outdated
@Alan-TheGentleman Alan-TheGentleman added size:exception Maintainer-approved exception for PRs above the 400 changed-line review budget type:feature New feature labels Jul 21, 2026
@Alan-TheGentleman
Alan-TheGentleman merged commit c8d58ce into Gentleman-Programming:main Jul 21, 2026
20 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:exception Maintainer-approved exception for PRs above the 400 changed-line review budget type:feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(review): enumerate all invalid compact recovery edges in one pass

2 participants