LearnNewsExamplesServices
Frontmatter
id14540
titleConcept-id SSOT consumer sweep: AdrIngestor + discovery dedupe
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-ada
createdAtJul 3, 2026, 10:28 AM
updatedAt5:25 PM
githubUrlhttps://github.com/neomjs/neo/issues/14540
authorneo-opus-vega
commentsCount0
parentIssue14472
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAt5:25 PM

Concept-id SSOT consumer sweep: AdrIngestor + discovery dedupe

Closed Backlog/active-chunk-2 enhancementaiarchitecture
neo-opus-vega
neo-opus-vega commented on Jul 3, 2026, 10:28 AM

Context

Follow-up leaf of epic #14472, spun out of the PR #14528 review cycle (concept-spine canonicalization, Resolves #14502). During that review @neo-opus-ada grepped every (CONCEPT|CLASS|PROCESS): construction/match site in ai/ and found two concept-id sites NOT routed through the new SSOT (ai/services/graph/conceptSpineCanonicalization.mjs); I verified both on the PR head and dispositioned them out of #14528's AC scope (its Contract Ledger names exactly two mint sites). Pre-existing fragmentation, not a regression — but left un-routed, ADR→concept edges stay fragmented after the #14502 live merge runs, directly undermining its post-merge goal ("single-neighborhood resolution over Golden Path").

Live latest-open sweep: checked latest 20 open issues at 2026-07-03T08:29Z; no equivalent found. A2A in-flight claim sweep: no overlapping lane-claim in the herd window.

The Problem

The canonicalization pass is only as complete as its consumer sweep. #14528 routes the two LLM/extractor mint sites (SemanticGraphExtractor, MailboxService), but two deterministic sites still mint/key concept refs on divergent forms:

  1. ai/services/ingestion/AdrIngestor.mjs:182addEdge(adrId, match[1], ADR_EDGE_TYPES.CODIFIES_CONCEPT) links the RAW captured ref (e.g. GoldenPath, PascalCase, un-normalized). The canonically-minted node is golden-path, so ADR-codifies edges point at a different node than session/mailbox mints — the ADR→concept axis of the graph stays split even after the #14502 merge executor tombstones the alias clusters.
  2. ai/services/ingestion/ConceptDiscoveryService.mjs:97normalizeConceptNameForDedupe produces a different form than the SSOT (goldenpath, dash-less, vs golden-path). Low functional impact — it keys internal mining candidates, never graph ids (authorial context: I built this service under #10036/PR #10105; the dedupe key was always internal-only) — but it is the exact "one vocabulary per contract; re-derived local copies are defects" concern (ADR 0031 invariant 8).

The Architectural Reality

  • The SSOT now exists: conceptSpineCanonicalization.mjs exports canonicalizeConceptId / normalizeConceptKey; the #14528 pattern is import-and-delegate (see the probe's conceptClusterKey rewire — one-line body).
  • AdrIngestor edge targets feed the Native Edge Graph's ADR→concept axis, which the #14502 post-merge probe re-run (its AC5) measures over Golden Path — the exact neighborhood this fragmentation splits.
  • The merge executor (#14528) is re-runnable by design (plan/apply/execute seam): routing AdrIngestor then re-running ingestion + the merge plan converges historical ADR edges onto canonical nodes.
  • Non-blocking nits from the #14528 review ride here IF the author does not fold them there: one-line cache-warm comment on MailboxService.ensureTaggedConceptNode's getAdjacentNodes call (idiom documented at GraphService.mjs:257-260), and two L3-checklist additions (intra-cluster tombstone-to-tombstone edge residue count; canonical-filter-matches-legacy-message verification via the edge-reading path MailboxService.mjs:1385).

Structure map: ai/services/ingestion/ (owning folder of both files) + ai/services/graph/ (SSOT) — cited from the live run this session; no new files, no placement decision.

The Fix

  1. AdrIngestor.mjs:182: route match[1] through canonicalizeConceptId before addEdge (import the SSOT; keep non-spine refs untouched — the captured group is a concept ref by construction of the regex, so plain delegation is correct). Unit spec pinning GoldenPath → edge target golden-path.
  2. ConceptDiscoveryService.mjs:97: EITHER delegate normalizeConceptNameForDedupe to normalizeConceptKey (preferred if candidate-dedupe behavior is preserved — verify the dash-difference doesn't split existing dedupe groups) OR add a docblock line stating why the internal dedupe key intentionally differs from the graph vocabulary. Decide IN the PR from the code evidence.
  3. Fold the #14528 review nits above if still open at implementation time.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
CODIFIES_CONCEPT edge targets ADR 0031 invariant 8 + conceptSpineCanonicalization.mjs policy docblock Canonical bare-kebab concept ids Empty canonicalization → keep raw ref (never drop the edge; matches the #14502 never-block row) JSDoc at the link site Unit spec + post-ingestion probe check
normalizeConceptNameForDedupe This leaf (decide in PR) Delegate to SSOT or document intentional divergence Behavior-preserving: existing dedupe groups must not split silently Docblock Unit spec on representative candidates

Decision Record impact

aligned-with ADR 0024 (taxonomy) + ADR 0031 (invariant 8 — one vocabulary per contract).

Acceptance Criteria

  • AdrIngestor CODIFIES_CONCEPT targets canonicalize through the SSOT (unit spec: PascalCase ref → kebab canonical target)
  • ConceptDiscoveryService dedupe key either delegates to the SSOT or carries an explicit intentional-divergence docblock (decided in-PR from evidence)
  • No re-derived normalizer remains: grep over ai/ for concept-id kebab/prefix-strip logic outside conceptSpineCanonicalization.mjs comes back empty (or each hit carries the divergence docblock)
  • #14528 review nits folded here IF not already landed there (cache-warm comment; two L3-checklist lines)
  • PR Resolves this leaf, Refs #14472

Out of Scope

  • Re-running the live merge / ingestion (that is #14502's L3 post-merge checklist; this leaf only makes the re-run converge further).
  • Any change to the SSOT policy itself.

Avoided Traps

  • Forcing the dedupe-key unification without behavior verification — the dash-difference (goldenpath vs golden-path) could silently split or join existing mining-candidate groups; the AC requires deciding from evidence, not aesthetics.
  • Dropping ADR edges on canonicalization failure — the edge is provenance; keep raw on empty canonicalization (mirrors the #14502 ledger's never-block row).

Related

Parent: #14472 · Sibling: #14502 (PR #14528, the SSOT + mint guards) · Probe: #14474 · Origin review: PR #14528 review (Ada's Depth-Floor grep + my ticket-author disposition)

Origin Session ID: 8cf234b7-e698-47ca-99e2-bf865196b6aa

Retrieval Hint: concept-id SSOT consumer sweep AdrIngestor CODIFIES_CONCEPT canonicalize dedupe

tobiu referenced in commit ba22294 - "fix(ingestion): route AdrIngestor CODIFIES_CONCEPT through the concept-spine SSOT + document the dedupe divergence (#14540) (#14787) on 5:25 PM
tobiu closed this issue on 5:25 PM