LearnNewsExamplesServices
Frontmatter
titlefix(memory-core): canonicalize stored agent identities (#15038)
authorneo-gpt
stateOpen
createdAt3:54 PM
updatedAt4:22 PM
closedAt
mergedAt
branchesdevcodex/15038-canonical-identity-endpoints
urlhttps://github.com/neomjs/neo/pull/15040
contentTrust
projected
quarantined0
signals[]
Open
neo-gpt
neo-gpt commented on 3:54 PM

Resolves #15038

This closes the terminal-state gap identified in Ada's review of PR #15032: direct mailbox and permission identities now converge on canonical @<identity> storage, and historical message-WAL projection can no longer recreate legacy spellings after migration. The read-path compatibility introduced by #15032 remains in place until each deployment produces its own clean migration census.

Evidence: L3 (live non-destructive CLI dry-run on the configured graph plus isolated executable SQLite/WAL probes) → L3 required (the shipped migration and guarded replay boundary are directly invocable without mutating the live graph). No residual close-target ACs; destructive deployment remains an explicit post-merge operator procedure.

Strategic Fit

Canonical-on-read is the safe compatibility increment; canonical-on-write plus a guarded, one-time storage migration is the stable architecture. This PR seals both writers that could perpetuate drift: ordinary APIs were already canonicalized by PR #15032, while historical WAL repair is now canonicalized before endpoint validation or any graph write. The migration then removes legacy direct-id forms without absorbing mailbox grammar into generic graph primitives.

Implementation

  • Adds ai/scripts/migrations/canonicalizeStoredAgentIdentities.mjs, an import-safe, dry-run-first CLI with atomic --apply, --db override, blocker/skip reporting, before/after census, collision handling, and idempotent reruns.
  • Converges mailbox edge endpoints, every PermissionService.validScopes identity endpoint, and mirrored MESSAGE.properties.from / to values only when the canonical destination exists as AgentIdentity.
  • Preserves AGENT:*, family aliases, roles, humans, unrelated work-topology BLOCKED_BY edges, RLS boundaries, delivery state, and the maximum stored edge weight.
  • Refuses ambiguous message routing, wrong-type endpoints, and node/edge user_id disagreements before mutation.
  • Canonicalizes accepted historical WAL routing before projection checks, endpoint restoration, message writes, or edge creation; route-wide endpoint validation completes before the first write.
  • Documents the deploy → quiesce → back up → dry-run → apply → restart → census sequence and makes a clean per-deployment census the retirement gate for getMailboxIdentityStorageVariants().

Source of Authority

  • ADR 0024 §2.3 owns SENT_BY, SENT_TO, and DELIVERED_TO mailbox edges.
  • PermissionService.validScopes is executable authority for permission-edge coverage.
  • normalizeAgentIdentityNodeId() owns direct AgentIdentity canonical spelling.
  • ADR 0019 is preserved: the CLI reads AiConfig.storagePaths.graph lazily at the entrypoint, while --help stays bootstrap-free.

Deltas from ticket

No scope expansion. Adversarial review added three fail-closed refinements within the ticket contract: distinguishing permission-shaped from work-topology BLOCKED_BY, blocking cross-tenant node/edge convergence, and preflighting every WAL endpoint before any partial projection write.

Test Evidence

  • npm run agent-preflight -- --no-fix <five changed files> — passed.
  • node --check on both product modules and both focused spec modules — passed.
  • git diff --check — passed.
  • npm run ai:structure-map — passed; the new runner maps under ai/scripts/migrations/.
  • npm run ai:lint-guides — passed with zero hard failures; existing repository warnings remain.
  • npm run ai:lint-mcp-test-locations — passed.
  • node ai/scripts/migrations/canonicalizeStoredAgentIdentities.mjs --help — passed without graph bootstrap.
  • Live read-only CLI dry-run on the configured graph — clean: true, zero blockers/skips, and zero legacy census across alias nodes, identity edge endpoints, and message properties.
  • Direct executable SQLite probes — dry-run/apply, all permission scopes, collision-state merge, idempotence, transaction rollback, message/edge disagreement, node/edge RLS disagreement, and permission-vs-work BLOCKED_BY discrimination passed.
  • Direct WAL probes — direct and broadcast legacy replay canonicalized idempotently; wrong-type direct/broadcast endpoints failed before mutation with zero message nodes and zero edges.
  • Focused Playwright invocations were attempted in the isolated worktree, but the local runner emitted no test-case output and was terminated rather than represented as a pass. Hosted CI remains the authoritative focused-spec run.
  • No full unit suite was run locally; repository CI owns the broad matrix.
  • Hosted exact-head CI at 7e67c68f8f — unit, integration-unified, CodeQL, and every repository lint/check gate passed.

Post-Merge Validation

  • Deploy the guarded projector to every graph-writing process before applying the migration.
  • Quiesce older writers and take a SQLite-safe backup.
  • Preserve and review the live dry-run output before --apply.
  • Apply atomically, restart only guarded builds, and preserve a post-restart clean: true census with zero blockers, skips, and legacy forms.
  • Keep getMailboxIdentityStorageVariants() until every deployment has produced that evidence; retire it only in a later change.

Authored by Euclid (GPT-5.6 Sol, Codex Desktop). Session de713f27-0e82-4960-b4c6-f281e0c36449.