Skip to content

Stop the repo-ownership audit from flagging projected rationale nodes - #521

Merged
zzet merged 1 commit into
mainfrom
fix/rationale-nodes-fail-repo-ownership-audit
Aug 9, 2026
Merged

Stop the repo-ownership audit from flagging projected rationale nodes#521
zzet merged 1 commit into
mainfrom
fix/rationale-nodes-fail-repo-ownership-audit

Conversation

@zzet

@zzet zzet commented Aug 9, 2026

Copy link
Copy Markdown
Owner

What

Exempt KindRationale from the repo-prefix ownership audit, in both the in-memory classifier and the SQLite predicate that transcribes it.

Why

Every daemon start on a workspace holding repo-scoped development memories logged this at Error:

daemon: repo ownership is inconsistent — repo-scoped reads will silently return a subset
of the graph; the store holds both prefixed and unprefixed copies of the same code, or a
node's stamped repo differs from its id
  owned_code_nodes: 644746
  unowned_code_nodes: 0
  misprefixed_nodes: 9
  misprefixed_samples: ["rationale::mem3539475d73f80040", "rationale::mem4209e14e240d8045", …]

The graph was healthy. owned=644746 / unowned=0 means not mixed — no ghost population, which is the failure the detector exists for. Only misprefixed was non-zero, and every node in it was a false positive.

ClassifyNodePrefix already exempts the identity namespaces whose IDs deliberately do not mirror a source path: contracts, contract bridges, topics. projectMemories mints nodes of exactly that shape:

id := "rationale::" + e.IDFilePath: rationaleVirtualFile,   // ".gortex/rationale"
   RepoPrefix: e.RepoPrefix,        // stamped from the memory entry

A repo is stamped, but the identity is namespaced and the path is virtual, so strings.HasPrefix(n.ID, repoPrefix+"/") can never hold. KindRationale was simply never added to the exempt switch — so one repo-scoped memory was enough to drive Clean() false and escalate the whole audit to Error, with copy describing a data-loss bug that was not occurring.

index_health reported the same false positive, via repo_ownership.consistent: false plus the "Graph holds inconsistent repository ownership … untrack and re-track the repo" remediation.

Changes

  • internal/graph/prefix_diagnostics.go — add KindRationale to the exempt switch in IsAuditableRepoSourceNode; explain why in the doc comment.
  • internal/graph/store_sqlite/prefix_diagnostics.go — mirror it in auditableRepoSourceNodePredicate (kind NOT IN (…, 'rationale')). The file's contract is that the SQL is a transcription of the Go classifier and the two change together.
  • internal/graph/prefix_diagnostics_test.go — classifier case for a projected rationale identity.
  • internal/graph/storetest/storetest.go — projected rationale node in the cross-backend conformance fixture.

Only the ownership audit is affected. buildIndexHealthPayloadCtx's path-liveness probe also calls IsAuditableRepoSourcePath, but it walks NodesByKind(KindFile) only, so rationale nodes never reach it.

Verification

Both fences fail without their corresponding fix — confirmed by reverting each and re-running:

--- FAIL: TestClassifyNodePrefix/projected_rationale_identity_is_not_audited
        ClassifyNodePrefix() = "misprefixed_identity", want ""
--- FAIL: TestSQLiteStoreConformance/PrefixDiagnostics
        MisprefixedNodes = 2, want 1 (owned=1 unowned=1 misprefixed=2)

With the fix:

  • go test -race ./internal/graph/ ./internal/graph/storetest/ — ok
  • go test -race ./internal/graph/store_sqlite/ — ok (full suite, 1438s)
  • golangci-lint run ./internal/graph/... — 0 issues

Every daemon start on a workspace holding repo-scoped development memories
logged the ownership audit at Error — "repo ownership is inconsistent,
repo-scoped reads will silently return a subset of the graph" — with a
handful of `rationale::mem…` IDs as the named offenders. The graph was fine:
owned=644746, unowned=0, no ghost population. Only the misprefixed count was
non-zero, and every node in it was a false positive.

The audit exempts the identity namespaces whose IDs deliberately do not
mirror a source path — contracts, contract bridges, topics. The memory
projection mints nodes of exactly that shape: `rationale::<memory-id>` over
the virtual .gortex/rationale path, with the owning repo still stamped on
RepoPrefix. Their identity can never start with "<repo>/", so the audit read
the stamped field and the minted identity as disagreeing, Clean() went false,
and the whole line escalated to Error with copy describing a data-loss bug
that was not happening. index_health reported the same thing through
repo_ownership.consistent and its remediation string.

Exempt KindRationale alongside the other identity namespaces, in the
in-memory classifier and in the SQLite predicate that transcribes it, so the
two backends stay in lockstep. Both paths are fenced: the classifier table
gains the rationale case and the cross-backend conformance fixture gains a
projected rationale node, each of which fails without the corresponding fix.
@zzet
zzet merged commit d13facb into main Aug 9, 2026
10 checks passed
@zzet
zzet deleted the fix/rationale-nodes-fail-repo-ownership-audit branch August 9, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant