LearnNewsExamplesServices
Frontmatter
id13578
titleCanonicalize user_id at the graph write boundary (stop @-form stamping in upsertNode/linkNodes)
stateClosed
labels
enhancementairefactoringarchitecturemodel-experience
assigneesneo-opus-ada
createdAtJun 20, 2026, 2:42 AM
updatedAtJun 20, 2026, 5:13 AM
githubUrlhttps://github.com/neomjs/neo/issues/13578
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 20, 2026, 5:13 AM

Canonicalize user_id at the graph write boundary (stop @-form stamping in upsertNode/linkNodes)

Closed v13.1.0/archive-v13-1-0-chunk-4 enhancementairefactoringarchitecturemodel-experience
neo-opus-ada
neo-opus-ada commented on Jun 20, 2026, 2:42 AM

Context

Residual cleanup carved from #13571 / PR #13572 (per @neo-gpt's close-target reconciliation on the #13572 review). The read boundaries now tolerate both stored user_id forms (@-prefixed and normalized), so reads are correct regardless of the stored form. This ticket eliminates the inconsistency at its source: the write path still stamps the @-prefixed getAgentIdentityNodeId() form into the user_id column, while MemoryService (and others) write the normalized (no-@) form — the mixed-form column the read-side now has to defensively tolerate.

Problem

GraphService.upsertNode / linkNodes stamp currentUserId from getAgentIdentityNodeId() — the @-prefixed node id, explicitly documented as NOT the isolation key in RequestContextService — into the user_id isolation column. Writers therefore disagree on the stored form, producing the mixed-form column that forced the both-form read tolerance in PR #13572.

Fix

  • Stamp the canonical isolation key (normalizeUserId(getUserId() ?? getAgentIdentityNodeId()) — the same canonicalization PR #13572 applied on the read side) on the write path (upsertNode / linkNodes), so all new rows store one normalized form.
  • Once writers are consistent, evaluate whether to (a) simplify the read-side both-form tolerance back to a single predicate, and (b) run a one-time migration to normalize existing @-form rows — optional, since reads already tolerate both forms (not correctness-critical).

Acceptance Criteria

  • upsertNode / linkNodes stamp the normalized canonical key into user_id (no leading @).
  • A unit test seeds via the write path and asserts the stored user_id is normalized.
  • No isolation regression: cross-tenant reads stay null; the both-form read tolerance from PR #13572 remains until a deliberate, separately-reviewed simplification.

Out of Scope

  • The read-side fix (delivered in PR #13572).
  • Forced migration of existing rows (separate decision; reads already tolerate both forms).

Related

Source bug: #13571 (the @-form/mixed-form inconsistency). Read-side mitigation: PR #13572. Isolation-key contract: getUserId() is the isolation key; getAgentIdentityNodeId() is not.