@graphorin/memory 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -1
- package/README.md +31 -31
- package/dist/conflict/index.d.ts +3 -1
- package/dist/conflict/index.js +1 -1
- package/dist/conflict/locale-packs/en.d.ts +13 -0
- package/dist/conflict/locale-packs/en.d.ts.map +1 -0
- package/dist/conflict/locale-packs/en.js +1 -1
- package/dist/conflict/locale-packs/en.js.map +1 -1
- package/dist/conflict/pipeline.d.ts +2 -2
- package/dist/conflict/pipeline.js +3 -3
- package/dist/conflict/pipeline.js.map +1 -1
- package/dist/conflict/stages/helpers.js +1 -1
- package/dist/conflict/stages/helpers.js.map +1 -1
- package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -1
- package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
- package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -1
- package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -1
- package/dist/conflict/types.d.ts +7 -7
- package/dist/conflict/types.js +1 -1
- package/dist/conflict/types.js.map +1 -1
- package/dist/consolidator/budget.js +16 -3
- package/dist/consolidator/budget.js.map +1 -1
- package/dist/consolidator/decay.d.ts +13 -2
- package/dist/consolidator/decay.js +17 -5
- package/dist/consolidator/decay.js.map +1 -1
- package/dist/consolidator/dlq.js +1 -1
- package/dist/consolidator/dlq.js.map +1 -1
- package/dist/consolidator/errors.js +2 -2
- package/dist/consolidator/errors.js.map +1 -1
- package/dist/consolidator/index.js +8 -6
- package/dist/consolidator/index.js.map +1 -1
- package/dist/consolidator/lock.js +1 -1
- package/dist/consolidator/lock.js.map +1 -1
- package/dist/consolidator/noise-filter.js.map +1 -1
- package/dist/consolidator/phases/deep.js +6 -2
- package/dist/consolidator/phases/deep.js.map +1 -1
- package/dist/consolidator/phases/induce.d.ts +10 -10
- package/dist/consolidator/phases/induce.js +6 -6
- package/dist/consolidator/phases/induce.js.map +1 -1
- package/dist/consolidator/phases/learned-context.js +129 -0
- package/dist/consolidator/phases/learned-context.js.map +1 -0
- package/dist/consolidator/phases/light.js +2 -1
- package/dist/consolidator/phases/light.js.map +1 -1
- package/dist/consolidator/phases/reflect.js +5 -4
- package/dist/consolidator/phases/reflect.js.map +1 -1
- package/dist/consolidator/phases/standard.js +37 -8
- package/dist/consolidator/phases/standard.js.map +1 -1
- package/dist/consolidator/reconcile.js +1 -1
- package/dist/consolidator/reconcile.js.map +1 -1
- package/dist/consolidator/runtime.d.ts +4 -4
- package/dist/consolidator/runtime.d.ts.map +1 -1
- package/dist/consolidator/runtime.js +73 -16
- package/dist/consolidator/runtime.js.map +1 -1
- package/dist/consolidator/scheduler.d.ts +5 -5
- package/dist/consolidator/scheduler.js +1 -1
- package/dist/consolidator/scheduler.js.map +1 -1
- package/dist/consolidator/triggers.js +7 -7
- package/dist/consolidator/triggers.js.map +1 -1
- package/dist/consolidator/types.d.ts +68 -20
- package/dist/consolidator/types.d.ts.map +1 -1
- package/dist/consolidator/types.js +16 -6
- package/dist/consolidator/types.js.map +1 -1
- package/dist/context-engine/annotations.d.ts +9 -9
- package/dist/context-engine/annotations.js +3 -3
- package/dist/context-engine/annotations.js.map +1 -1
- package/dist/context-engine/auto-recall.js.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.d.ts +18 -3
- package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js +26 -5
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
- package/dist/context-engine/compaction/compactor.js +19 -6
- package/dist/context-engine/compaction/compactor.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +4 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +2 -1
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.d.ts +28 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.js +71 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.js.map +1 -0
- package/dist/context-engine/compaction/hooks/types.d.ts +15 -3
- package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -1
- package/dist/context-engine/compaction/index.d.ts +1 -0
- package/dist/context-engine/compaction/index.js +2 -1
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +10 -3
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.js +42 -5
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
- package/dist/context-engine/compaction/thresholds.js.map +1 -1
- package/dist/context-engine/compaction/types.d.ts +47 -7
- package/dist/context-engine/compaction/types.d.ts.map +1 -1
- package/dist/context-engine/engine.d.ts +23 -4
- package/dist/context-engine/engine.d.ts.map +1 -1
- package/dist/context-engine/engine.js +69 -15
- package/dist/context-engine/engine.js.map +1 -1
- package/dist/context-engine/index.d.ts +1 -0
- package/dist/context-engine/index.d.ts.map +1 -1
- package/dist/context-engine/index.js +2 -1
- package/dist/context-engine/locale-packs/en.js +14 -13
- package/dist/context-engine/locale-packs/en.js.map +1 -1
- package/dist/context-engine/locale-packs/types.d.ts +10 -10
- package/dist/context-engine/locale-packs/types.d.ts.map +1 -1
- package/dist/context-engine/locale-packs/types.js.map +1 -1
- package/dist/context-engine/metadata.d.ts +1 -1
- package/dist/context-engine/metadata.js +2 -2
- package/dist/context-engine/metadata.js.map +1 -1
- package/dist/context-engine/preambles/inbound-en.d.ts +1 -1
- package/dist/context-engine/preambles/inbound-en.js +1 -1
- package/dist/context-engine/preambles/inbound-en.js.map +1 -1
- package/dist/context-engine/privacy-filter.d.ts +2 -2
- package/dist/context-engine/privacy-filter.js +1 -1
- package/dist/context-engine/privacy-filter.js.map +1 -1
- package/dist/context-engine/templates/base-en.d.ts +1 -1
- package/dist/context-engine/templates/base-en.js +1 -1
- package/dist/context-engine/templates/base-en.js.map +1 -1
- package/dist/context-engine/templates/composer.d.ts +1 -1
- package/dist/context-engine/templates/composer.js.map +1 -1
- package/dist/context-engine/token-budget.d.ts +1 -1
- package/dist/context-engine/token-budget.js +1 -1
- package/dist/context-engine/token-budget.js.map +1 -1
- package/dist/context-engine/token-counter.d.ts +16 -7
- package/dist/context-engine/token-counter.d.ts.map +1 -1
- package/dist/context-engine/token-counter.js +31 -9
- package/dist/context-engine/token-counter.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +4 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/facade.d.ts +44 -15
- package/dist/facade.d.ts.map +1 -1
- package/dist/facade.js +16 -8
- package/dist/facade.js.map +1 -1
- package/dist/graph/entity-resolver.d.ts +2 -2
- package/dist/graph/entity-resolver.js +2 -2
- package/dist/graph/entity-resolver.js.map +1 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/contextualize.js +2 -2
- package/dist/internal/contextualize.js.map +1 -1
- package/dist/internal/embedder-binding.js +1 -1
- package/dist/internal/embedder-binding.js.map +1 -1
- package/dist/internal/id.js +1 -1
- package/dist/internal/id.js.map +1 -1
- package/dist/internal/injection-heuristics.js +25 -2
- package/dist/internal/injection-heuristics.js.map +1 -1
- package/dist/internal/spans.js +1 -1
- package/dist/internal/spans.js.map +1 -1
- package/dist/internal/storage-adapter.d.ts +71 -22
- package/dist/internal/storage-adapter.d.ts.map +1 -1
- package/dist/migration/embedder-migration.d.ts +7 -7
- package/dist/migration/embedder-migration.js.map +1 -1
- package/dist/search/explain.d.ts +7 -4
- package/dist/search/explain.d.ts.map +1 -1
- package/dist/search/explain.js +1 -1
- package/dist/search/explain.js.map +1 -1
- package/dist/search/fit-weights.d.ts +48 -0
- package/dist/search/fit-weights.d.ts.map +1 -0
- package/dist/search/fit-weights.js +70 -0
- package/dist/search/fit-weights.js.map +1 -0
- package/dist/search/graph-ppr.d.ts +36 -0
- package/dist/search/graph-ppr.d.ts.map +1 -0
- package/dist/search/graph-ppr.js +38 -0
- package/dist/search/graph-ppr.js.map +1 -0
- package/dist/search/index.d.ts +4 -1
- package/dist/search/index.js +4 -1
- package/dist/search/iterative.d.ts +15 -8
- package/dist/search/iterative.d.ts.map +1 -1
- package/dist/search/iterative.js +10 -9
- package/dist/search/iterative.js.map +1 -1
- package/dist/search/query-transform.d.ts +6 -6
- package/dist/search/query-transform.js +6 -6
- package/dist/search/query-transform.js.map +1 -1
- package/dist/search/rrf.d.ts +4 -4
- package/dist/search/rrf.js +4 -4
- package/dist/search/rrf.js.map +1 -1
- package/dist/search/trust.d.ts +30 -0
- package/dist/search/trust.d.ts.map +1 -0
- package/dist/search/trust.js +50 -0
- package/dist/search/trust.js.map +1 -0
- package/dist/search/types.d.ts +1 -1
- package/dist/tiers/episodic-memory.d.ts +6 -4
- package/dist/tiers/episodic-memory.d.ts.map +1 -1
- package/dist/tiers/episodic-memory.js +3 -2
- package/dist/tiers/episodic-memory.js.map +1 -1
- package/dist/tiers/index.d.ts +1 -1
- package/dist/tiers/index.js +1 -1
- package/dist/tiers/insight-memory.d.ts +4 -4
- package/dist/tiers/insight-memory.js +3 -3
- package/dist/tiers/insight-memory.js.map +1 -1
- package/dist/tiers/procedural-memory.d.ts +29 -7
- package/dist/tiers/procedural-memory.d.ts.map +1 -1
- package/dist/tiers/procedural-memory.js +66 -6
- package/dist/tiers/procedural-memory.js.map +1 -1
- package/dist/tiers/semantic-memory.d.ts +111 -30
- package/dist/tiers/semantic-memory.d.ts.map +1 -1
- package/dist/tiers/semantic-memory.js +120 -27
- package/dist/tiers/semantic-memory.js.map +1 -1
- package/dist/tiers/session-memory.d.ts +7 -7
- package/dist/tiers/session-memory.js +7 -7
- package/dist/tiers/session-memory.js.map +1 -1
- package/dist/tiers/shared-memory.d.ts +1 -1
- package/dist/tiers/shared-memory.js +1 -1
- package/dist/tiers/shared-memory.js.map +1 -1
- package/dist/tiers/working-memory.d.ts +3 -3
- package/dist/tiers/working-memory.js +2 -2
- package/dist/tiers/working-memory.js.map +1 -1
- package/dist/tools/block-tools.d.ts +9 -9
- package/dist/tools/block-tools.d.ts.map +1 -1
- package/dist/tools/block-tools.js +3 -3
- package/dist/tools/block-tools.js.map +1 -1
- package/dist/tools/fact-tools.d.ts +48 -31
- package/dist/tools/fact-tools.d.ts.map +1 -1
- package/dist/tools/fact-tools.js +30 -18
- package/dist/tools/fact-tools.js.map +1 -1
- package/dist/tools/index.d.ts +9 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/recall-tools.d.ts +40 -20
- package/dist/tools/recall-tools.d.ts.map +1 -1
- package/dist/tools/recall-tools.js +29 -10
- package/dist/tools/recall-tools.js.map +1 -1
- package/dist/tools/runbook-tools.d.ts +76 -0
- package/dist/tools/runbook-tools.d.ts.map +1 -0
- package/dist/tools/runbook-tools.js +53 -0
- package/dist/tools/runbook-tools.js.map +1 -0
- package/dist/tools/taint.js +33 -0
- package/dist/tools/taint.js.map +1 -0
- package/dist/tools/types.d.ts +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,99 @@
|
|
|
1
1
|
# @graphorin/memory
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Context-engineering adoptions (audit 2026-07-04 Wave C, cluster C4).
|
|
8
|
+
|
|
9
|
+
- Compaction failure hardening: one retry with backoff on summarizer failure; an auto-trigger pass that does not shrink the buffer FAILS (compression-loop class); after 3 consecutive failures the auto trigger disables itself until a later successful pass re-arms it.
|
|
10
|
+
- Recent user messages survive compaction verbatim (`preserveUserMessages`, default 2) - only assistant/tool content is summarized away.
|
|
11
|
+
- Summary prompt upgrades: handoff-to-another-LLM framing, quote-identifiers-VERBATIM rule, new "Constraints and non-negotiables" section (12 sections, v1.3); template id renamed to `summary-sections` (context-engine-14).
|
|
12
|
+
- Clearing-tier parity with `clear_tool_uses_20250919`: `clearToolInputs` blanks paired assistant tool-call args; `readResultToolName: null` keeps the externalized-handle placeholder honest when `read_result` is not registered (context-engine-11).
|
|
13
|
+
- New `reanchorRecentResults` post-compaction hook (+ `ctx.droppedMessages` on the hook context): re-injects the result handles a compaction just dropped, with bounded previews via an optional `readPreview` resolver.
|
|
14
|
+
|
|
15
|
+
- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Retrieval trust and quality (audit 2026-07-04 Wave C, cluster C5).
|
|
16
|
+
|
|
17
|
+
- Trust-aware retrieval ranking (MINJA defense): search multiplies each fused score by a rank-time trust factor reusing the eviction path's `SalienceWeights` - quarantined-but-included rows x0.3, foreign provenance (tool/imported/reflection/induction) x0.8, first-party UNTOUCHED. Surfaced as the `trust` signal on hits / `explainRecall`; per-call `trustWeighting: 'off'` escape hatch; weights follow `consolidator.salienceWeights`.
|
|
18
|
+
- Offline fusion-weight fitting: new pure `fitFusionWeights(cases, { grid, k })` + `ndcgAtK` in `@graphorin/memory/search` grid-search `FusionWeights` against labelled queries and report the plain-RRF baseline alongside.
|
|
19
|
+
- Extraction decontextualization (memory-consolidation-08): each extracted fact must now be a self-contained proposition (pronouns/ellipses resolved, entities inlined).
|
|
20
|
+
- rerankers.md rewritten: warns against installing a positional `WeightedRRFReranker` as process default (mis-assigns weights under fan-out - memory-retrieval-04), promotes the local cross-encoder as the first learned reranker to reach for, documents the fitting routine and the trust discount.
|
|
21
|
+
|
|
22
|
+
- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Deterministic security adoptions (audit 2026-07-04 Wave C, cluster C6).
|
|
23
|
+
|
|
24
|
+
- Derived-taint propagation: opt-in `dataFlowPolicy.derivedTaint: 'strict'` fires the paraphrase-robust `derived-untrusted-to-sink` flow for every model-driven sink call once untrusted content entered the run (CaMeL-style control-flow integrity); the agent also records each tainted step's assistant text as `llm-derived` spans so model-echoed phrasing trips the verbatim probe.
|
|
25
|
+
- Taint into memory (cross-session MINJA leg): `ToolReturn` gains a widen-only `taint` override honoured through the executor record path; `fact_search` / `deep_recall` / `recall_episodes` attach it when any returned item is quarantined or foreign-provenance, re-arming the ledger at recall. `RunState.taintSummary` additionally carries one-way FNV-1a span-tile hashes (no plaintext), so a resumed run re-detects pre-suspend verbatim copies.
|
|
26
|
+
- MCP pinning completed: `toTools({ pinStore })` records fingerprints on first use and REJECTS drift by default when a store is present (rug-pull defense; `onPinMismatch: 'warn'` downgrades); tool-description injection hits at registration are counted (`mcp.tool-description.injection-flagged.total`).
|
|
27
|
+
- Signal-only heuristics + Unicode pre-pass: shared `normalizeForMatching` (NFKC + zero-width strip) applied in the guardrails injection catalogue and the memory quarantine heuristics; security.md repositions all pattern catalogues as best-effort signal, never a sole gate. `TaintLabel.sourceKind` widened to `string` for the new descriptive kinds.
|
|
28
|
+
|
|
29
|
+
- [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Memory architecture evolution (audit 2026-07-04 Wave D, cluster D3) - opt-in; defaults byte-identical.
|
|
30
|
+
|
|
31
|
+
- Learned-context digest block (Letta sleep-time): a deep-phase pass rewrites the reserved `learned_context` working block from recent evidence via one budgeted LLM call (`consolidator.learnedContext`, off at every tier).
|
|
32
|
+
- Principal/owner dimension (`MemoryOwner` on facts/episodes/rules/insights, migration 026) stamped `'agent'` on synthesized writes, with a retrieval-time `owner` filter (`FactSearchOptions.owner`); default reads apply no filter.
|
|
33
|
+
- Retrieval-frequency reinforcement: `facts.access_count` (migration 027) + opt-in `SalienceWeights.accessReinforcement` (default 0 = inert).
|
|
34
|
+
- Runbook memory: `rules_fts` (migration 028) + `ProceduralMemory.search` returns whole validated procedures, with a gated `runbook_search` tool (`createMemory({ runbookSearch: true })`).
|
|
35
|
+
|
|
36
|
+
- [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Retrieval SOTA graph wins (audit 2026-07-04 Wave D, cluster D5) - offline, opt-in; the billed/migration-heavy legs stay eval-gated.
|
|
37
|
+
|
|
38
|
+
- PPR-lite graded expansion (HippoRAG-style damped spreading activation): `SqliteGraphStore.expandActivation` + `FactSearchOptions.expandHops: 0|1|2` + `graphScoring: 'ppr'` score neighbours by `damping^hopDepth` instead of a flat 1.
|
|
39
|
+
- Graph + entity as first-class tunable fusion weights (`FusionWeights.graph` / `.entity`, were hardcoded neutral).
|
|
40
|
+
- Exact entity-match retriever: `SqliteGraphStore.factsForEntityName` + `FactSearchOptions.entityMatch` add a precise "facts about <entity>" candidate leg.
|
|
41
|
+
- `longmemeval` harness gains `--retrieval ppr` / `--retrieval entity`. Bitemporal event time, Matryoshka truncation, and cascade LLM reranking remain eval-gated.
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- [#134](https://github.com/o-stepper/graphorin/pull/134) [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6) Thanks [@o-stepper](https://github.com/o-stepper)! - Fix three context-assembly defects (context-engine-02/03/04). The default post-compaction hooks (persona block, project rules, pinned facts) now apply the same D2 privacy decision as `assemble()`, so `sensitivity:'secret'` content the assembly withholds can no longer leak to the provider via the post-compaction splice. `renderMessageText` renders assistant tool-call arguments and `adaptTokenCounter` preserves the native `count(messages)` path, so tool-call args (file writes, code_execute scripts) finally contribute to trigger/before/after token arithmetic. And the trigger, SOTA-4 reclaim floor, and anti-thrash guard now share one full-buffer basis: `shouldCompact` takes `compactableFromIndex` (the pinned prefix is no longer counted as reclaimable) and `compactNow` takes `prefixMessages` so the guard arms against prefix + body + essentials instead of the sliced body - previously any real system prompt defeated the guard and a summarizer LLM call re-fired every step at the context edge.
|
|
46
|
+
|
|
47
|
+
- [#134](https://github.com/o-stepper/graphorin/pull/134) [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6) Thanks [@o-stepper](https://github.com/o-stepper)! - Enforce transcript well-formedness so no path emits provider-rejected message sequences (agent-01, tools-07, context-engine-01). The durable-HITL pre-screen now collects EVERY approval-gated call in a step (not just the first) and executes the non-gated remainder before suspending, so the persisted transcript never carries dangling `tool_use` ids; `executeBatch` synthesizes an `execution_failed` outcome instead of silently dropping a slot whose `executeOne` rejected (e.g. a throwing tracer); summarize-compaction snaps its boundary backward so the preserved window never starts with an orphan `tool` message. The agent mock-provider harness now asserts transcript well-formedness on every request by default.
|
|
48
|
+
|
|
49
|
+
- [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Retrieval and consolidation now honour the trust contracts they document (audit 2026-07-04 Wave B, cluster B3).
|
|
50
|
+
|
|
51
|
+
- memory-retrieval-01: default fact reads (FTS / vector / graph) behave as `asOf = now`, so superseded and validity-expired facts no longer surface as current - exactly what the `fact_supersede` tool promises. New `includeSuperseded: true` escape hatch (core `MemorySearchOptions`, memory `FactSearchOptions`, graph expand options) restores the full history for inspector / audit paths; `fact_search` and `deep_recall` outputs expose `validTo` / `supersededBy`.
|
|
52
|
+
- memory-retrieval-02: `deep_recall` passes `forceHard: true` (choosing the tool IS the hardness signal; the local heuristic gate rejected the tool's own documented examples and is English-only). Iterative results carry a new `graded` flag so ungraded single-shot passes stop claiming sufficiency as a verdict.
|
|
53
|
+
- memory-retrieval-03: a tagged search widens the fusion pool the same way decay does - the record-level tags filter runs after the topK cut, so tagged searches no longer silently return fewer than topK hits.
|
|
54
|
+
- memory-consolidation-01: the deep-phase dedup verdict now soft-forgets (replayable tombstone) instead of preferring the GDPR hard-delete `purge`, and a vanished conflicting fact skips the judge entirely (admit, no provider call) - a model verdict can no longer hard-delete the only surviving copy.
|
|
55
|
+
- memory-consolidation-07: the standard phase gains an embedder-independent exact-text duplicate guard (FTS + string equality, quarantine-aware), so replaying a partially-committed slice (DLQ / cursor retry) without an embedder no longer duplicates already-committed facts.
|
|
56
|
+
|
|
57
|
+
- [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Compaction-seam hardening (audit 2026-07-04 Wave B, cluster B4).
|
|
58
|
+
|
|
59
|
+
- context-engine-05: the agent's pinned-prefix scan stops at the `<graphorin_compaction_summary>` marker, so a compaction summary resumed from a suspended run stays compactable instead of being absorbed into the uncompactable prefix (one extra pinned summary per compact-then-resume cycle).
|
|
60
|
+
- context-engine-06: hard context overflow gets a last-resort tier - on a `context-length` provider error the agent forces ONE aggressive compaction (`preserveRecentTurns: 2`) and retries the same provider before walking the fallback chain or failing. Thrown `ProviderHttpError`s now classify by their canonical `errorKind`, so a thrown 429 / overflow is treated like the structured-event equivalent.
|
|
61
|
+
- context-engine-07: the summarizer prompt's older-messages dump is capped (default 96k chars, `summarizerInputCharBudget` strategy option; oldest lines elided with a marker) so pointing `summarizerModel` at a smaller model no longer overflows its window and silently disables compaction.
|
|
62
|
+
- context-engine-09: `<<<older_messages>>>` / `<<</older_messages>>>` sequences inside message text are neutralized before entering the summarizer prompt's data-only envelope - a tool result carrying the closing marker can no longer inject instructions into the summarizer.
|
|
63
|
+
- context-engine-10: new integration test drives the REAL context engine through the agent loop with tool loops crossing the threshold; the mock provider's transcript invariant validates every post-compaction request.
|
|
64
|
+
|
|
65
|
+
- [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Consolidator operational promises now hold (audit 2026-07-04 Wave B, cluster B5).
|
|
66
|
+
|
|
67
|
+
- memory-consolidation-02: `onExceed: 'log'` (the shipped standard/full presets) finally WARNs - once per resource per budget window, via a pluggable `BudgetTrackerOptions.logger`. New `priceUsage` hook on `CreateConsolidatorOptions` / `createMemory({ consolidator })` threads a USD pricer into every phase, so `maxCostPerDay` can actually accumulate spend (previously every call priced at $0 and the USD ceiling was inert at every tier).
|
|
68
|
+
- memory-consolidation-03: `drainDlq` has a production caller - every `trigger(...)` dispatch first replays ready (backoff-gated) dead-letter batches, so failed slices no longer accumulate forever. A separate-process `graphorin consolidator drain` CLI was deliberately NOT added: replays require the provider, which lives in the server process (same IP-4 boundary the CLI already documents for set-tier/stop).
|
|
69
|
+
- memory-consolidation-04: at tiers without a deep phase (free/cheap defaults) pending CONFLICT-CHECK rows older than 7 days are expired as `admit` (the safe direction - the candidate stays live), so the queue cannot grow monotonically.
|
|
70
|
+
- memory-consolidation-05/06: the guide's phase table now describes what the phases actually do (light = zero-LLM forgetting; standard = extraction + reconcile + episodes; deep = conflict judge + reflection; no procedural extraction, no shared-tier promotion) plus a new "Making it run" subsection documenting that the library-mode consolidator is dormant until `start()` + trigger wiring.
|
|
71
|
+
- memory-consolidation-08: extraction is temporally anchored - each transcript line carries its ISO timestamp and the prompt states today's date with an instruction to resolve relative dates into absolute ones.
|
|
72
|
+
|
|
73
|
+
- [#138](https://github.com/o-stepper/graphorin/pull/138) [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a) Thanks [@o-stepper](https://github.com/o-stepper)! - Documentation-truth sweep (audit 2026-07-04 Wave E, cluster E10): stale npm package descriptions rewritten (cli "Phase 14a three commands", mcp "upcoming auth CLI", store-sqlite's nonexistent WorkerPool), the store-sqlite WorkerPool TSDoc and the cipher-pragma ordering comment corrected, the executor timeout-precedence JSDoc fixed to the real `inlineToolTimeoutMs > tier timeoutMs > default` order, the `rrf.<label>` explain signals documented, and the skills spec-snapshot wording no longer claims a CI cron refreshes it (the diff is a manual `--upstream` pass; the release gate only parses the bundled snapshot).
|
|
74
|
+
|
|
75
|
+
- [#138](https://github.com/o-stepper/graphorin/pull/138) [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a) Thanks [@o-stepper](https://github.com/o-stepper)! - Release-pipeline and tarball-surface fixes (audit 2026-07-04 Wave E, cluster E2). `@graphorin/memory`'s `./conflict` subpath was runtime-EMPTY on npm 0.5.0 (`export { };` - preserveModules emitted the module without an explicit tsdown entry); it now ships all 8 runtime exports. `@graphorin/server`'s internal `workspace:*` peer dependencies are ranged (`workspace:>=0.5.0 <1.0.0`) so changesets stops escalating every sibling bump into a bogus MAJOR for the whole fixed group (the 1.0.0 landmine on the release bot's branch). `@graphorin/eslint-plugin` gains the `./package.json` self-export. All 27 per-package CHANGELOGs gain the 0.5.0 section (they were frozen at 0.1.0 inside every published tarball), and the `mvp-readiness` release gate now rejects a stale-CHANGELOG or unresolvable-exports release.
|
|
76
|
+
|
|
77
|
+
- Updated dependencies [[`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a), [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a)]:
|
|
78
|
+
- @graphorin/tools@0.6.0
|
|
79
|
+
- @graphorin/core@0.6.0
|
|
80
|
+
- @graphorin/security@0.6.0
|
|
81
|
+
- @graphorin/observability@0.6.0
|
|
82
|
+
|
|
83
|
+
## 0.5.0
|
|
84
|
+
|
|
85
|
+
First version published to the npm registry (with Sigstore build
|
|
86
|
+
provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
|
|
87
|
+
milestones and were never published. All `@graphorin/*` packages release
|
|
88
|
+
lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
|
|
89
|
+
in the repository-level
|
|
90
|
+
[CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).
|
|
91
|
+
|
|
3
92
|
## 0.1.0
|
|
4
93
|
|
|
5
94
|
### Minor Changes
|
|
6
95
|
|
|
7
|
-
- Phase 10a
|
|
96
|
+
- Phase 10a - initial release of `@graphorin/memory`. Ships the
|
|
8
97
|
`createMemory()` facade with six tier sub-modules (working,
|
|
9
98
|
session, episodic, semantic, procedural, shared), nine memory
|
|
10
99
|
tools registered through `@graphorin/tools`, the built-in
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @graphorin/memory
|
|
2
2
|
|
|
3
|
-
> Six-tier memory system for the Graphorin framework
|
|
3
|
+
> Six-tier memory system for the Graphorin framework - the heart of any
|
|
4
4
|
> long-living personal AI assistant built on top of Graphorin.
|
|
5
5
|
|
|
6
6
|
`@graphorin/memory` ships the `createMemory()` facade, the six tier
|
|
@@ -13,15 +13,15 @@ engine.
|
|
|
13
13
|
|
|
14
14
|
The package depends on:
|
|
15
15
|
|
|
16
|
-
- `@graphorin/core`
|
|
16
|
+
- `@graphorin/core` - typed contracts (`MemoryStore`, `EmbedderProvider`,
|
|
17
17
|
`Tool`, `Tracer`, `Sensitivity`, …).
|
|
18
|
-
- `@graphorin/observability`
|
|
18
|
+
- `@graphorin/observability` - the `Tracer` used to emit one `AISpan`
|
|
19
19
|
per memory operation.
|
|
20
|
-
- `@graphorin/security`
|
|
20
|
+
- `@graphorin/security` - the memory-modification guard (`MemoryGuardTier`)
|
|
21
21
|
every memory tool is wired against.
|
|
22
|
-
- `@graphorin/tools`
|
|
22
|
+
- `@graphorin/tools` - the `tool({...})` builder used to declare the
|
|
23
23
|
ten memory tools.
|
|
24
|
-
- `zod` (peer)
|
|
24
|
+
- `zod` (peer) - schema typing for the memory tools.
|
|
25
25
|
|
|
26
26
|
Storage is provided by any `MemoryStore` implementation; the default
|
|
27
27
|
production adapter is `@graphorin/store-sqlite` (built on
|
|
@@ -44,7 +44,7 @@ any `EmbedderProvider`; the default is
|
|
|
44
44
|
- **Eleven memory tools.** `block_append`, `block_replace`,
|
|
45
45
|
`block_rethink`, `fact_remember`, `fact_search`, `fact_supersede`,
|
|
46
46
|
`fact_forget`, `recall_episodes`, `conversation_search`,
|
|
47
|
-
`fact_history`, `fact_validate`
|
|
47
|
+
`fact_history`, `fact_validate` - every tool is a typed `Tool` with
|
|
48
48
|
`inputSchema` + `outputSchema`, the appropriate `memoryGuardTier`, and
|
|
49
49
|
the right `sideEffectClass`. `fact_search` accepts an `asOf` instant
|
|
50
50
|
for point-in-time reads; `fact_history` returns a fact's bi-temporal
|
|
@@ -57,7 +57,7 @@ any `EmbedderProvider`; the default is
|
|
|
57
57
|
role-markup smuggling, secrecy / exfiltration directives) land
|
|
58
58
|
`status: 'quarantined'` and are **excluded from default recall** until
|
|
59
59
|
a human promotes them via `fact_validate`. Quarantine is a retrieval
|
|
60
|
-
gate, never a delete
|
|
60
|
+
gate, never a delete - quarantined rows stay fully auditable, and the
|
|
61
61
|
`includeQuarantined` search option surfaces them for the validation
|
|
62
62
|
UI. This is the precondition for safely shipping synthesized memory
|
|
63
63
|
(reflection / reconciliation / induction) against memory-poisoning
|
|
@@ -83,10 +83,10 @@ any `EmbedderProvider`; the default is
|
|
|
83
83
|
`extraction` provenance gate.
|
|
84
84
|
- **Auto-importance + episode formation** (consolidator standard phase).
|
|
85
85
|
Each processed slice is summarized into one episode via a single budgeted
|
|
86
|
-
LLM call that also rates its importance `1
|
|
86
|
+
LLM call that also rates its importance `1-10` (normalized to `[0, 1]`),
|
|
87
87
|
so episodic triple-signal retrieval (recency × relevance × importance) finally
|
|
88
88
|
runs on all three signals. Auto-formed episodes carry `provenance: 'extraction'`
|
|
89
|
-
+ `status: 'quarantined'` (P1-4)
|
|
89
|
+
+ `status: 'quarantined'` (P1-4) - surfaced for review via
|
|
90
90
|
`episodic.search(..., { includeQuarantined: true })`. Importance is a *soft*
|
|
91
91
|
signal (never a retention gate). Controlled by the per-tier `formEpisodes` /
|
|
92
92
|
`importanceScoring` flags (on at `standard` / `full`); budget-aware
|
|
@@ -107,7 +107,7 @@ any `EmbedderProvider`; the default is
|
|
|
107
107
|
topics, Anthropic's Contextual Retrieval) so a terse fact stays findable; the
|
|
108
108
|
canonical `text` is preserved. The offline default `'late-chunk'`
|
|
109
109
|
(`createMemory({ contextualRetrieval })`) derives the context deterministically
|
|
110
|
-
from the fact's own structured signals with **no extra LLM call**
|
|
110
|
+
from the fact's own structured signals with **no extra LLM call** - a no-op for
|
|
111
111
|
plain-text writes. An opt-in, **consolidator-only** `'llm'` mode
|
|
112
112
|
(`consolidator: { contextualRetrieval: 'llm' }`) spends one budgeted cheap-model
|
|
113
113
|
call per write to author the prefix, degrading to late-chunk on any failure.
|
|
@@ -121,48 +121,48 @@ any `EmbedderProvider`; the default is
|
|
|
121
121
|
(supersede chain / quarantine / conflicts / citing insights) and
|
|
122
122
|
`graphorin memory activity` (consolidator / reflection activity).
|
|
123
123
|
- **Multi-signal forgetting** (cost / staleness control, *not* an accuracy
|
|
124
|
-
lever). The light phase scores each fact with `salience(...)`
|
|
124
|
+
lever). The light phase scores each fact with `salience(...)` - the Ebbinghaus
|
|
125
125
|
`retention` curve (recency + access frequency) combined with the `importance`
|
|
126
126
|
hint and a security-risk negative term (a quarantined fact is evicted first, a
|
|
127
127
|
foreign-provenance one slightly sooner). With neutral importance + an active,
|
|
128
128
|
first-party fact, `salience === retention`. Setting `decayCapacity`
|
|
129
129
|
(`createMemory({ consolidator: { decayCapacity } })`, default unbounded)
|
|
130
|
-
bounds storage: the lowest-salience facts are **soft-archived** (recoverable
|
|
130
|
+
bounds storage: the lowest-salience facts are **soft-archived** (recoverable -
|
|
131
131
|
`archived = 1`, never deleted) until the window fits.
|
|
132
132
|
- **Query transformation** (read path; multi-query / RAG-Fusion + opt-in HyDE).
|
|
133
133
|
`search(scope, query, { multiQuery: N })` fans the query into up to `N - 1`
|
|
134
134
|
reworded variants via one cheap LLM call, retrieves each, and fuses every list
|
|
135
|
-
through the same RRF reranker
|
|
135
|
+
through the same RRF reranker - recovering memories whose stored wording
|
|
136
136
|
differs from the question. `{ hyde: true }` additionally embeds a hypothetical
|
|
137
137
|
answer (arXiv:2212.10496) and fuses its neighbours. Both are **opt-in**, wired
|
|
138
138
|
by `createMemory({ queryTransform: { provider } })`; with no transformer (the
|
|
139
139
|
default) search stays **offline + single-shot** and these options are silent
|
|
140
|
-
no-ops. Reserve it for retrieval-heavy recall
|
|
140
|
+
no-ops. Reserve it for retrieval-heavy recall - it adds provider latency.
|
|
141
141
|
- **Weighted fusion + reranker guidance** (search quality, opt-in). RRF is a
|
|
142
142
|
good zero-tuning default, but once you have labels (the `@graphorin/evals`
|
|
143
143
|
harness) a calibrated weight can do better. `search(scope, query, { fusion: {
|
|
144
144
|
strategy: 'weighted', weights: { vector: 3, fts: 1 } } })` fuses through
|
|
145
145
|
`WeightedRRFReranker`, scaling each candidate list's reciprocal-rank
|
|
146
146
|
contribution by its retriever *kind* (FTS vs vector; the HyDE list counts as
|
|
147
|
-
vector). **RRF stays the default**
|
|
147
|
+
vector). **RRF stays the default** - omit `fusion` (or pass `{ strategy:
|
|
148
148
|
'rrf' }`) and behaviour is unchanged; equal weights reproduce RRF exactly. The
|
|
149
149
|
pure `fuseWeighted(lists, weights, k)` is exported for offline weight tuning
|
|
150
150
|
against the eval harness. For a further precision lift, install a cross-encoder
|
|
151
151
|
reranker (`@graphorin/reranker-transformersjs` / `-llm`) via
|
|
152
|
-
`createMemory({ reranker })` or `setReranker(...)` and raise `candidateTopK`
|
|
152
|
+
`createMemory({ reranker })` or `setReranker(...)` and raise `candidateTopK` -
|
|
153
153
|
reranking pays off **only on an already-high-recall candidate set**, so fix
|
|
154
154
|
recall first (contextual retrieval / multi-query) before reaching for it.
|
|
155
155
|
- **Relation graph + one-hop expansion** (new capability, opt-in). Activates the
|
|
156
156
|
dormant `(subject, predicate, object)` substrate: facts now carry s/p/o, and an
|
|
157
157
|
**entity resolver** folds the subject/object strings into canonical entities
|
|
158
|
-
("Anna", "Anna S.", "my sister" → one entity) via lexical + embedding dedup
|
|
158
|
+
("Anna", "Anna S.", "my sister" → one entity) via lexical + embedding dedup -
|
|
159
159
|
with **auditable, reversible merges** (an append-only ledger; `merged_into` is
|
|
160
160
|
single-level). At read time `search(scope, query, { expandHops: 1 })` seeds on
|
|
161
161
|
the candidates and fuses in facts sharing an entity through a recursive CTE,
|
|
162
162
|
answering "what did the person I met in Tbilisi recommend?" without leaving
|
|
163
163
|
SQLite. Enable linking with `createMemory({ graph: { entityResolution: true } })`
|
|
164
164
|
(offline; lexical + embedding). The ambiguous-similarity band mints a *new*
|
|
165
|
-
entity by default
|
|
165
|
+
entity by default - it never auto-merges on weak evidence; opt into LLM
|
|
166
166
|
adjudication (`graph: { llmAdjudication: true, provider }`) to resolve that band.
|
|
167
167
|
Omit it all and the default path is unchanged + fully offline (`expandHops`
|
|
168
168
|
defaults to `0`).
|
|
@@ -172,7 +172,7 @@ any `EmbedderProvider`; the default is
|
|
|
172
172
|
(`assessQueryDifficulty`) keeps simple lookups single-shot; only a query judged
|
|
173
173
|
hard *and* with a grader configured (`createMemory({ iterativeRetrieval: {
|
|
174
174
|
provider } })`) is graded for sufficiency and, when weak, reformulated +
|
|
175
|
-
retrieved again
|
|
175
|
+
retrieved again - **widening to one-hop graph expansion** on each reformulation -
|
|
176
176
|
up to a hard-capped `maxIterations` (≤ 5), then **abstaining**
|
|
177
177
|
(`result.abstained === true`) rather than confabulating. Exposed as
|
|
178
178
|
`SemanticMemory.searchIterative(...)` and the gated **`deep_recall`** tool (a
|
|
@@ -181,17 +181,17 @@ any `EmbedderProvider`; the default is
|
|
|
181
181
|
call**; the tool surface stays at the canonical eleven.
|
|
182
182
|
- **Procedural memory extraction** (new capability, gated + opt-in). AWM-style
|
|
183
183
|
workflow induction: from a **successful** agent trajectory,
|
|
184
|
-
`ProceduralMemory.induce(scope, trajectory)` distils a reusable procedure
|
|
184
|
+
`ProceduralMemory.induce(scope, trajectory)` distils a reusable procedure -
|
|
185
185
|
goal + value-abstracted steps (`"search for {product}"`) + the variable names
|
|
186
186
|
+ Voyager-style success criteria for self-verification on reuse
|
|
187
187
|
(`checkSuccessCriteria`). Induction fires on **success only** (a failed run
|
|
188
188
|
never calls the inducer), and the result lands **quarantined** +
|
|
189
|
-
`provenance: 'induction'` (P1-4)
|
|
189
|
+
`provenance: 'induction'` (P1-4) - procedures drive *actions*, so this is the
|
|
190
190
|
highest-poisoning-risk write and is excluded from `activate()` until validated.
|
|
191
191
|
`trajectoryFromRunState(runState)` distils the agent's already-emitted run
|
|
192
192
|
state, so capture needs **no agent change**. Wire it with
|
|
193
193
|
`createMemory({ procedureInduction: { provider } })`; omit it and `induce(...)`
|
|
194
|
-
throws `ProcedureInductionNotConfiguredError`
|
|
194
|
+
throws `ProcedureInductionNotConfiguredError` - the procedural tier stays pure
|
|
195
195
|
offline CRUD with no provider call.
|
|
196
196
|
- **Per-record `embedder_id` enforced.** Every embedded write registers
|
|
197
197
|
the embedder via the storage layer's `EmbeddingMetaRepository` and
|
|
@@ -215,7 +215,7 @@ any `EmbedderProvider`; the default is
|
|
|
215
215
|
(post-MVP).
|
|
216
216
|
- **`memory.compile(scope)` + `memory.metadata(scope)`.** The
|
|
217
217
|
interface used by the context engine (Phase 10d) to assemble the
|
|
218
|
-
six-layer memory-aware system prompt; `@graphorin/memory@0.
|
|
218
|
+
six-layer memory-aware system prompt; `@graphorin/memory@0.6.0`
|
|
219
219
|
ships the deterministic minimum-viable rendering so the agent
|
|
220
220
|
runtime in Phase 12 can already exercise the surface.
|
|
221
221
|
- **Memory-modification guard wiring.** Every tool's
|
|
@@ -354,22 +354,22 @@ console.log(outcome.decision.kind); // 'supersede' | 'dedup' | 'pending' | 'admi
|
|
|
354
354
|
|
|
355
355
|
The pipeline visits five stages in order:
|
|
356
356
|
|
|
357
|
-
1. **Exact dedup**
|
|
357
|
+
1. **Exact dedup** - MD5 hash on the canonical (lowercase + collapsed-
|
|
358
358
|
whitespace + trimmed) candidate body short-circuits on a hit.
|
|
359
|
-
2. **Embedding three-zone**
|
|
359
|
+
2. **Embedding three-zone** - top-K neighbours from `searchVector`
|
|
360
360
|
classify the candidate into HOT (`>= 0.95`), NEAR-DUP
|
|
361
361
|
(`>= 0.85`), CONFLICT-CHECK (`> 0.4`), or COLD. HOT zone always
|
|
362
362
|
dedups (semantic identity outranks every other signal).
|
|
363
|
-
3. **Heuristic regex**
|
|
363
|
+
3. **Heuristic regex** - the active locale pack's supersede +
|
|
364
364
|
negation markers fire when the candidate has an explicit
|
|
365
365
|
change signal (`moved to`, `no longer`, `got promoted`, …) and
|
|
366
366
|
there is at least one existing candidate.
|
|
367
|
-
4. **Subject / predicate**
|
|
367
|
+
4. **Subject / predicate** - naive `(subject, predicate, object)`
|
|
368
368
|
split using the locale pack's predicate normalisers; matching
|
|
369
369
|
subject + predicate with a different object is a strong supersede
|
|
370
370
|
signal.
|
|
371
|
-
5. **Defer to deep LLM judge**
|
|
372
|
-
the candidate sits in CONFLICT-CHECK zone
|
|
371
|
+
5. **Defer to deep LLM judge** - Stages 1-4 yielded no decision but
|
|
372
|
+
the candidate sits in CONFLICT-CHECK zone - the row is admitted
|
|
373
373
|
`pending` and queued in `conflict_check_pending` for the
|
|
374
374
|
consolidator's deep phase (Phase 10c).
|
|
375
375
|
|
|
@@ -398,4 +398,4 @@ MIT © 2026 Oleksiy Stepurenko.
|
|
|
398
398
|
|
|
399
399
|
---
|
|
400
400
|
|
|
401
|
-
**Project Graphorin** · v0.
|
|
401
|
+
**Project Graphorin** · v0.6.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
|
package/dist/conflict/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { LocaleMatch, LocalePack, LocalePatternEntry, LocaleSupersedeKind, defineLocalePack, evaluateMarkers } from "./locale-packs/types.js";
|
|
2
|
+
import { enLocalePack } from "./locale-packs/en.js";
|
|
2
3
|
import { ConflictDecision, ConflictPipeline, ConflictPipelineDeps, ConflictPipelineOptions, ConflictStage, ConflictThresholds, DEFAULT_CONFLICT_THRESHOLDS, PipelineStage, ReconcileAction, ReconcileDecision, StageContext, StageOutcome, reconcileToConflictDecision } from "./types.js";
|
|
3
|
-
import { _resetBypassWarningForTesting, createConflictPipeline, runConflictPipeline } from "./pipeline.js";
|
|
4
|
+
import { _resetBypassWarningForTesting, createConflictPipeline, runConflictPipeline } from "./pipeline.js";
|
|
5
|
+
export { type ConflictDecision, type ConflictPipeline, type ConflictPipelineDeps, type ConflictPipelineOptions, type ConflictStage, type ConflictThresholds, DEFAULT_CONFLICT_THRESHOLDS, LocaleMatch, LocalePack, LocalePatternEntry, LocaleSupersedeKind, type PipelineStage, type ReconcileAction, type ReconcileDecision, type StageContext, type StageOutcome, _resetBypassWarningForTesting, createConflictPipeline, defineLocalePack, enLocalePack, evaluateMarkers, reconcileToConflictDecision, runConflictPipeline };
|
package/dist/conflict/index.js
CHANGED
|
@@ -4,4 +4,4 @@ import "./locale-packs/index.js";
|
|
|
4
4
|
import { DEFAULT_CONFLICT_THRESHOLDS, reconcileToConflictDecision } from "./types.js";
|
|
5
5
|
import { _resetBypassWarningForTesting, createConflictPipeline, runConflictPipeline } from "./pipeline.js";
|
|
6
6
|
|
|
7
|
-
export {
|
|
7
|
+
export { DEFAULT_CONFLICT_THRESHOLDS, _resetBypassWarningForTesting, createConflictPipeline, defineLocalePack, enLocalePack, evaluateMarkers, reconcileToConflictDecision, runConflictPipeline };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LocalePack } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/conflict/locale-packs/en.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The default English `LocalePack`. Frozen - share the singleton.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
declare const enLocalePack: LocalePack;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { enLocalePack };
|
|
13
|
+
//# sourceMappingURL=en.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"en.d.ts","names":[],"sources":["../../../src/conflict/locale-packs/en.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cAqBa,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.js","names":["enLocalePack: LocalePack"],"sources":["../../../src/conflict/locale-packs/en.ts"],"sourcesContent":["/**\n * Bundled English locale pack used by the multi-stage conflict\n * resolution pipeline (DEC-117 / ADR-018 ext / RB-02). The patterns\n * cover the common change signals personal-assistant scenarios surface\n * on a daily basis: relocation, job change, preference flips,\n * relationship transitions, and health updates.\n *\n * Operators supplying a different locale register their own pack via\n * {@link defineLocalePack}. The framework ships English by default so\n * the pipeline always has a working baseline.\n *\n * @packageDocumentation\n */\n\nimport { defineLocalePack, type LocalePack } from './types.js';\n\n/**\n * The default English `LocalePack`. Frozen
|
|
1
|
+
{"version":3,"file":"en.js","names":["enLocalePack: LocalePack"],"sources":["../../../src/conflict/locale-packs/en.ts"],"sourcesContent":["/**\n * Bundled English locale pack used by the multi-stage conflict\n * resolution pipeline (DEC-117 / ADR-018 ext / RB-02). The patterns\n * cover the common change signals personal-assistant scenarios surface\n * on a daily basis: relocation, job change, preference flips,\n * relationship transitions, and health updates.\n *\n * Operators supplying a different locale register their own pack via\n * {@link defineLocalePack}. The framework ships English by default so\n * the pipeline always has a working baseline.\n *\n * @packageDocumentation\n */\n\nimport { defineLocalePack, type LocalePack } from './types.js';\n\n/**\n * The default English `LocalePack`. Frozen - share the singleton.\n *\n * @stable\n */\nexport const enLocalePack: LocalePack = defineLocalePack({\n id: 'en',\n supersedeMarkers: [\n // Location.\n { regex: /\\bmoved (?:to|from)\\b/i, kind: 'location', confidence: 0.9 },\n {\n regex: /\\bnow (?:live|living|based|residing) (?:in|at)\\b/i,\n kind: 'location',\n confidence: 0.9,\n },\n { regex: /\\brelocated\\b/i, kind: 'location', confidence: 0.85 },\n { regex: /\\bjust moved\\b/i, kind: 'location', confidence: 0.85 },\n { regex: /\\bI live in\\b/i, kind: 'location', confidence: 0.7 },\n\n // Job.\n { regex: /\\b(?:got|started|landed)(?: a)?(?: new)? job\\b/i, kind: 'job', confidence: 0.9 },\n { regex: /\\bgot promoted\\b/i, kind: 'job', confidence: 0.9 },\n { regex: /\\bgot fired\\b/i, kind: 'job', confidence: 0.9 },\n { regex: /\\b(?:I )?quit (?:my )?job\\b/i, kind: 'job', confidence: 0.9 },\n { regex: /\\bnew employer\\b/i, kind: 'job', confidence: 0.85 },\n { regex: /\\bnew job\\b/i, kind: 'job', confidence: 0.8 },\n { regex: /\\bswitched (?:companies|jobs|employers)\\b/i, kind: 'job', confidence: 0.9 },\n { regex: /\\bnow work(?:s|ing)? (?:at|for)\\b/i, kind: 'job', confidence: 0.85 },\n\n // Preference.\n { regex: /\\bswitched (?:from|to)\\b/i, kind: 'preference', confidence: 0.85 },\n { regex: /\\bchanged my (?:mind|preference|opinion)\\b/i, kind: 'preference', confidence: 0.85 },\n {\n regex: /\\bused to (?:like|love|prefer|drink|eat|use)\\b/i,\n kind: 'preference',\n confidence: 0.85,\n },\n { regex: /\\bused to (?:live|work|study|play|travel)\\b/i, kind: 'generic', confidence: 0.85 },\n { regex: /\\bI prefer\\b/i, kind: 'preference', confidence: 0.6 },\n\n // Relationship.\n { regex: /\\bgot (?:married|divorced|engaged)\\b/i, kind: 'relationship', confidence: 0.9 },\n { regex: /\\bbroke up\\b/i, kind: 'relationship', confidence: 0.85 },\n\n // Health.\n { regex: /\\bdiagnosed (?:with|as)\\b/i, kind: 'health', confidence: 0.85 },\n { regex: /\\brecovered from\\b/i, kind: 'health', confidence: 0.85 },\n\n // Generic.\n { regex: /\\bformerly\\b/i, kind: 'generic', confidence: 0.7 },\n { regex: /\\bpreviously\\b/i, kind: 'generic', confidence: 0.7 },\n { regex: /\\bused to be\\b/i, kind: 'generic', confidence: 0.7 },\n { regex: /\\bis now\\b/i, kind: 'generic', confidence: 0.7 },\n ],\n negationMarkers: [\n { regex: /\\bno longer\\b/i, kind: 'preference', confidence: 0.85 },\n { regex: /\\bdoesn(?:'|\\u2019)?t\\b/i, confidence: 0.6 },\n {\n regex:\n /\\b(?:don(?:'|\\u2019)?t|do not) (?:like|prefer|use|drink|eat|live|work|play|go|want)\\b/i,\n kind: 'preference',\n confidence: 0.85,\n },\n { regex: /\\bnever\\b/i, confidence: 0.6 },\n { regex: /\\bnot anymore\\b/i, kind: 'preference', confidence: 0.85 },\n { regex: /\\bisn(?:'|\\u2019)?t\\b/i, confidence: 0.6 },\n {\n regex: /\\bnot (?:a fan|interested|willing|going|planning|in|into|looking) /i,\n kind: 'preference',\n confidence: 0.7,\n },\n { regex: /\\b(?:is|are|am|was|were) not\\b/i, confidence: 0.5 },\n ],\n predicateNormalisers: [\n 'is',\n 'are',\n 'am',\n 'was',\n 'were',\n 'be',\n 'been',\n 'being',\n 'has',\n 'have',\n 'had',\n 'do',\n 'does',\n 'did',\n 'lives',\n 'live',\n 'lived',\n 'living',\n 'works',\n 'work',\n 'worked',\n 'working',\n 'plays',\n 'play',\n 'played',\n 'playing',\n 'likes',\n 'like',\n 'liked',\n 'loves',\n 'love',\n 'loved',\n 'prefers',\n 'prefer',\n 'preferred',\n 'drinks',\n 'drink',\n 'drank',\n 'drunk',\n 'eats',\n 'eat',\n 'ate',\n 'eaten',\n 'in',\n 'at',\n 'on',\n 'with',\n 'for',\n 'to',\n ],\n subjectStopWords: ['a', 'an', 'the', 'my', 'your', 'his', 'her', 'their', 'our', 'this', 'that'],\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,MAAaA,eAA2B,iBAAiB;CACvD,IAAI;CACJ,kBAAkB;EAEhB;GAAE,OAAO;GAA0B,MAAM;GAAY,YAAY;GAAK;EACtE;GACE,OAAO;GACP,MAAM;GACN,YAAY;GACb;EACD;GAAE,OAAO;GAAkB,MAAM;GAAY,YAAY;GAAM;EAC/D;GAAE,OAAO;GAAmB,MAAM;GAAY,YAAY;GAAM;EAChE;GAAE,OAAO;GAAkB,MAAM;GAAY,YAAY;GAAK;EAG9D;GAAE,OAAO;GAAmD,MAAM;GAAO,YAAY;GAAK;EAC1F;GAAE,OAAO;GAAqB,MAAM;GAAO,YAAY;GAAK;EAC5D;GAAE,OAAO;GAAkB,MAAM;GAAO,YAAY;GAAK;EACzD;GAAE,OAAO;GAAgC,MAAM;GAAO,YAAY;GAAK;EACvE;GAAE,OAAO;GAAqB,MAAM;GAAO,YAAY;GAAM;EAC7D;GAAE,OAAO;GAAgB,MAAM;GAAO,YAAY;GAAK;EACvD;GAAE,OAAO;GAA8C,MAAM;GAAO,YAAY;GAAK;EACrF;GAAE,OAAO;GAAsC,MAAM;GAAO,YAAY;GAAM;EAG9E;GAAE,OAAO;GAA6B,MAAM;GAAc,YAAY;GAAM;EAC5E;GAAE,OAAO;GAA+C,MAAM;GAAc,YAAY;GAAM;EAC9F;GACE,OAAO;GACP,MAAM;GACN,YAAY;GACb;EACD;GAAE,OAAO;GAAgD,MAAM;GAAW,YAAY;GAAM;EAC5F;GAAE,OAAO;GAAiB,MAAM;GAAc,YAAY;GAAK;EAG/D;GAAE,OAAO;GAAyC,MAAM;GAAgB,YAAY;GAAK;EACzF;GAAE,OAAO;GAAiB,MAAM;GAAgB,YAAY;GAAM;EAGlE;GAAE,OAAO;GAA8B,MAAM;GAAU,YAAY;GAAM;EACzE;GAAE,OAAO;GAAuB,MAAM;GAAU,YAAY;GAAM;EAGlE;GAAE,OAAO;GAAiB,MAAM;GAAW,YAAY;GAAK;EAC5D;GAAE,OAAO;GAAmB,MAAM;GAAW,YAAY;GAAK;EAC9D;GAAE,OAAO;GAAmB,MAAM;GAAW,YAAY;GAAK;EAC9D;GAAE,OAAO;GAAe,MAAM;GAAW,YAAY;GAAK;EAC3D;CACD,iBAAiB;EACf;GAAE,OAAO;GAAkB,MAAM;GAAc,YAAY;GAAM;EACjE;GAAE,OAAO;GAA4B,YAAY;GAAK;EACtD;GACE,OACE;GACF,MAAM;GACN,YAAY;GACb;EACD;GAAE,OAAO;GAAc,YAAY;GAAK;EACxC;GAAE,OAAO;GAAoB,MAAM;GAAc,YAAY;GAAM;EACnE;GAAE,OAAO;GAA0B,YAAY;GAAK;EACpD;GACE,OAAO;GACP,MAAM;GACN,YAAY;GACb;EACD;GAAE,OAAO;GAAmC,YAAY;GAAK;EAC9D;CACD,sBAAsB;EACpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;CACD,kBAAkB;EAAC;EAAK;EAAM;EAAO;EAAM;EAAQ;EAAO;EAAO;EAAS;EAAO;EAAQ;EAAO;CACjG,CAAC"}
|
|
@@ -4,7 +4,7 @@ import { Fact } from "@graphorin/core";
|
|
|
4
4
|
//#region src/conflict/pipeline.d.ts
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Reset the one-shot bypass-warning flag. Test-only helper
|
|
7
|
+
* Reset the one-shot bypass-warning flag. Test-only helper - production
|
|
8
8
|
* callers never invoke this.
|
|
9
9
|
*
|
|
10
10
|
* @internal
|
|
@@ -12,7 +12,7 @@ import { Fact } from "@graphorin/core";
|
|
|
12
12
|
declare function _resetBypassWarningForTesting(): void;
|
|
13
13
|
/**
|
|
14
14
|
* One-shot helper that mirrors RB-02 §8.1's `runConflictPipeline({...})`
|
|
15
|
-
* spec
|
|
15
|
+
* spec - convenient for callers that do not need to pre-build + cache
|
|
16
16
|
* the pipeline. Production wiring should still go through
|
|
17
17
|
* {@link createConflictPipeline} (`SemanticMemory` re-uses the cached
|
|
18
18
|
* instance per `Memory`).
|
|
@@ -11,7 +11,7 @@ import { DEFAULT_CONFLICT_THRESHOLDS } from "./types.js";
|
|
|
11
11
|
//#region src/conflict/pipeline.ts
|
|
12
12
|
let bypassWarnedOnce = false;
|
|
13
13
|
/**
|
|
14
|
-
* Reset the one-shot bypass-warning flag. Test-only helper
|
|
14
|
+
* Reset the one-shot bypass-warning flag. Test-only helper - production
|
|
15
15
|
* callers never invoke this.
|
|
16
16
|
*
|
|
17
17
|
* @internal
|
|
@@ -21,7 +21,7 @@ function _resetBypassWarningForTesting() {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* One-shot helper that mirrors RB-02 §8.1's `runConflictPipeline({...})`
|
|
24
|
-
* spec
|
|
24
|
+
* spec - convenient for callers that do not need to pre-build + cache
|
|
25
25
|
* the pipeline. Production wiring should still go through
|
|
26
26
|
* {@link createConflictPipeline} (`SemanticMemory` re-uses the cached
|
|
27
27
|
* instance per `Memory`).
|
|
@@ -249,7 +249,7 @@ function checkAbort(signal) {
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
function warnBypass() {
|
|
252
|
-
console.warn("[graphorin/memory] conflict pipeline disabled (mode: \"off\"). Semantic writes fall back to a straight-through path
|
|
252
|
+
console.warn("[graphorin/memory] conflict pipeline disabled (mode: \"off\"). Semantic writes fall back to a straight-through path - bi-temporal supersede / dedup will not fire until you re-enable the pipeline.");
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.js","names":["mode: 'on' | 'off'","localePack: LocalePack","thresholds: ConflictThresholds","auditInput: Parameters<NonNullable<typeof conflictStore>['recordDecision']>[0]","hits: ReadonlyArray<MemoryHit<Fact>>"],"sources":["../../src/conflict/pipeline.ts"],"sourcesContent":["/**\n * `runConflictPipeline` — orchestrator for the multi-stage conflict\n * resolution pipeline shipped in `@graphorin/memory` Phase 10b\n * (DEC-117 / ADR-018 ext / RB-02).\n *\n * @packageDocumentation\n */\n\nimport type { Fact, MemoryHit } from '@graphorin/core';\nimport { withMemorySpan } from '../internal/spans.js';\nimport { enLocalePack, type LocalePack } from './locale-packs/index.js';\nimport { stage1ExactDedup } from './stages/stage1-exact-dedup.js';\nimport { stage2EmbeddingThreeZone } from './stages/stage2-embedding-three-zone.js';\nimport { stage3HeuristicRegex } from './stages/stage3-heuristic-regex.js';\nimport { stage4SubjectPredicate } from './stages/stage4-subject-predicate.js';\nimport { stage5DeferToDeep } from './stages/stage5-defer-to-deep.js';\nimport {\n type ConflictDecision,\n type ConflictPipeline,\n type ConflictPipelineDeps,\n type ConflictPipelineOptions,\n type ConflictStage,\n type ConflictThresholds,\n DEFAULT_CONFLICT_THRESHOLDS,\n type StageContext,\n type StageOutcome,\n} from './types.js';\n\nlet bypassWarnedOnce = false;\n\n/**\n * Reset the one-shot bypass-warning flag. Test-only helper — production\n * callers never invoke this.\n *\n * @internal\n */\nexport function _resetBypassWarningForTesting(): void {\n bypassWarnedOnce = false;\n}\n\n/**\n * One-shot helper that mirrors RB-02 §8.1's `runConflictPipeline({...})`\n * spec — convenient for callers that do not need to pre-build + cache\n * the pipeline. Production wiring should still go through\n * {@link createConflictPipeline} (`SemanticMemory` re-uses the cached\n * instance per `Memory`).\n *\n * @stable\n */\nexport async function runConflictPipeline(args: {\n readonly candidate: Fact;\n readonly deps: ConflictPipelineDeps;\n readonly options?: ConflictPipelineOptions;\n}): Promise<ConflictDecision> {\n const pipeline = createConflictPipeline(args.options ?? {});\n return pipeline.run(args.deps, args.candidate);\n}\n\n/**\n * Returns a frozen pipeline handle wired with the supplied options.\n *\n * @stable\n */\nexport function createConflictPipeline(options: ConflictPipelineOptions = {}): ConflictPipeline {\n const mode: 'on' | 'off' = options.mode ?? 'on';\n const localePack: LocalePack = options.localePack ?? enLocalePack;\n const thresholds: ConflictThresholds = Object.freeze({\n hot: options.thresholds?.hot ?? DEFAULT_CONFLICT_THRESHOLDS.hot,\n nearDup: options.thresholds?.nearDup ?? DEFAULT_CONFLICT_THRESHOLDS.nearDup,\n cold: options.thresholds?.cold ?? DEFAULT_CONFLICT_THRESHOLDS.cold,\n });\n validateThresholds(thresholds);\n const stage2TopK = options.stage2TopK ?? 5;\n const now = options.now ?? ((): string => new Date().toISOString());\n\n async function run(deps: ConflictPipelineDeps, candidate: Fact): Promise<ConflictDecision> {\n if (mode === 'off') {\n if (!bypassWarnedOnce) {\n bypassWarnedOnce = true;\n warnBypass();\n }\n return { kind: 'admit', stage: 'exact-dedup', reason: 'pipeline-mode-off' };\n }\n\n checkAbort(deps.signal);\n const conflictStore = options.conflictStore ?? deps.store.conflicts;\n\n return withMemorySpan(\n deps.tracer,\n 'memory.conflict',\n {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n },\n {\n 'memory.conflict.candidate_id': candidate.id,\n 'memory.conflict.locale_pack': localePack.id,\n },\n async (span) => {\n const existing = await collectVectorCandidates(deps, candidate, stage2TopK);\n const ctx: StageContext = {\n candidate,\n existing,\n localePack,\n thresholds,\n };\n const decision = await orchestrate(ctx);\n await persistDecision(decision, candidate, conflictStore, now);\n span.setAttributes({\n 'memory.conflict.stage': decision.stage,\n 'memory.conflict.decision': decision.kind,\n 'memory.conflict.candidate_count': existing.length,\n ...(decision.kind === 'dedup' && decision.similarity !== undefined\n ? { 'memory.conflict.similarity': decision.similarity }\n : {}),\n ...(decision.kind === 'pending' && decision.similarity !== undefined\n ? { 'memory.conflict.similarity': decision.similarity }\n : {}),\n ...(decision.kind !== 'admit'\n ? { 'memory.conflict.reason': decision.reason ?? 'unspecified' }\n : decision.reason !== undefined\n ? { 'memory.conflict.reason': decision.reason }\n : {}),\n });\n return decision;\n },\n );\n }\n\n /**\n * Visit the five stages in the order documented by RB-02 §8.1 and\n * the conflict-resolution policy (DEC-117). Stage 1 short-circuits\n * exact duplicates. Stage 2 always computes the zone but the\n * orchestrator splits its decision in two halves: the HOT zone is\n * the only zone that overrides Stage 3 / Stage 4 (semantic identity\n * outranks any explicit user marker); every other zone runs Stages\n * 3 and 4 first to give the heuristic markers a chance to supersede\n * before falling back to Stage 2's default decision.\n *\n * Stage 3 is declared \"orthogonal to Stage 2\", which is why the\n * heuristic stages run regardless of Stage 2's COLD / NEAR-DUP /\n * CONFLICT-CHECK assignment as long as Stage 2 surfaced at least\n * one candidate. The HOT-zone short-circuit preserves the dedup\n * invariant for semantic identity.\n */\n async function orchestrate(ctx: StageContext): Promise<ConflictDecision> {\n const stage1 = await stage1ExactDedup.evaluate(ctx);\n if (stage1.kind !== 'continue') {\n return mapOutcomeToDecision('exact-dedup', stage1, ctx.candidate);\n }\n\n const stage2 = await stage2EmbeddingThreeZone.evaluate(ctx);\n if (stage2.kind === 'dedup' && stage2.reason === 'embedding-hot-zone') {\n return mapOutcomeToDecision('embedding-three-zone', stage2, ctx.candidate);\n }\n\n if (ctx.existing.length > 0) {\n const stage3 = await stage3HeuristicRegex.evaluate(ctx);\n if (stage3.kind !== 'continue') {\n return mapOutcomeToDecision('heuristic-regex', stage3, ctx.candidate);\n }\n const stage4 = await stage4SubjectPredicate.evaluate(ctx);\n if (stage4.kind !== 'continue') {\n return mapOutcomeToDecision('subject-predicate', stage4, ctx.candidate);\n }\n }\n\n if (stage2.kind === 'dedup' || stage2.kind === 'admit') {\n return mapOutcomeToDecision('embedding-three-zone', stage2, ctx.candidate);\n }\n\n const stage5 = await stage5DeferToDeep.evaluate(ctx);\n return mapOutcomeToDecision('defer-to-deep', stage5, ctx.candidate);\n }\n\n return Object.freeze({\n mode,\n localePack,\n thresholds,\n run,\n }) satisfies ConflictPipeline;\n}\n\nfunction mapOutcomeToDecision(\n stage: ConflictStage,\n outcome: StageOutcome,\n candidate: Fact,\n): ConflictDecision {\n switch (outcome.kind) {\n case 'continue':\n return { kind: 'admit', stage, reason: 'continue-fallthrough' };\n case 'admit':\n return {\n kind: 'admit',\n stage,\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n case 'dedup':\n return {\n kind: 'dedup',\n stage,\n existingId: outcome.existingId,\n ...(outcome.similarity !== undefined ? { similarity: outcome.similarity } : {}),\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n case 'supersede':\n return {\n kind: 'supersede',\n stage,\n existingId: outcome.existingId,\n reason: outcome.reason,\n };\n case 'pending':\n return {\n kind: 'pending',\n stage,\n candidateId: candidate.id,\n conflictingIds: outcome.conflictingIds,\n ...(outcome.similarity !== undefined ? { similarity: outcome.similarity } : {}),\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n }\n}\n\nasync function persistDecision(\n decision: ConflictDecision,\n candidate: Fact,\n conflictStore: ConflictPipelineDeps['store']['conflicts'],\n now: () => string,\n): Promise<void> {\n if (conflictStore === undefined) return;\n const scope = {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n };\n const detectionZone = decisionToZone(decision);\n const auditInput: Parameters<NonNullable<typeof conflictStore>['recordDecision']>[0] = {\n scope,\n candidateId: candidate.id,\n decision: decision.kind,\n stage: decision.stage,\n ...(detectionZone !== undefined ? { detectionZone } : {}),\n ...(decision.kind === 'dedup' && decision.existingId !== undefined\n ? { existingId: decision.existingId }\n : {}),\n ...(decision.kind === 'supersede' ? { existingId: decision.existingId } : {}),\n ...(decision.kind === 'dedup' && decision.similarity !== undefined\n ? { similarity: decision.similarity }\n : {}),\n ...(decision.kind === 'pending' && decision.similarity !== undefined\n ? { similarity: decision.similarity }\n : {}),\n ...(decision.kind !== 'admit' && decision.reason !== undefined\n ? { reason: decision.reason }\n : {}),\n ...(decision.kind === 'admit' && decision.reason !== undefined\n ? { reason: decision.reason }\n : {}),\n };\n await conflictStore.recordDecision(auditInput);\n if (decision.kind === 'pending') {\n await conflictStore.enqueuePending({\n scope,\n factId: candidate.id,\n candidateText: candidate.text,\n stage: decision.stage,\n conflictingIds: decision.conflictingIds,\n ...(decision.reason !== undefined ? { reason: decision.reason } : {}),\n });\n }\n void now;\n}\n\nfunction decisionToZone(decision: ConflictDecision): string | undefined {\n switch (decision.kind) {\n case 'admit':\n if (decision.reason === 'embedding-cold-zone') return 'cold';\n return undefined;\n case 'dedup':\n if (decision.reason === 'embedding-hot-zone') return 'hot';\n if (decision.reason === 'embedding-near-dup-zone') return 'near-dup';\n if (decision.reason === 'exact-hash-match') return 'exact';\n if (decision.reason === 'subject-predicate-match-same-object') return 'subject-predicate';\n return undefined;\n case 'supersede':\n if (decision.reason.startsWith('regex-supersede-marker')) return 'heuristic';\n if (decision.reason.startsWith('regex-negation')) return 'heuristic';\n if (decision.reason.startsWith('subject-predicate-match')) return 'subject-predicate';\n return undefined;\n case 'pending':\n return 'conflict-check';\n }\n}\n\nasync function collectVectorCandidates(\n deps: ConflictPipelineDeps,\n candidate: Fact,\n topK: number,\n): Promise<ReadonlyArray<MemoryHit<Fact>>> {\n if (deps.embedder === null || deps.embedderId === null) return [];\n const adapter = deps.store.semantic;\n if (typeof adapter.searchVector !== 'function') return [];\n checkAbort(deps.signal);\n // PS-10: the candidate is compared against stored facts, which are embedded\n // as `passage` — keep it in the same space for the asymmetric (E5) embedder.\n const [vector] = await deps.embedder.embed([candidate.text], { taskType: 'passage' });\n if (vector === undefined) return [];\n checkAbort(deps.signal);\n const scope = {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n };\n // Vector search may throw when the storage layer's vector extension\n // is missing or mis-configured (e.g. `sqlite-vec` not loaded). Fall\n // back to \"no candidates\" so the conflict pipeline degrades to the\n // 10a straight-through write rather than blocking the agent loop.\n // AbortError is re-raised so cancellation propagates up.\n let hits: ReadonlyArray<MemoryHit<Fact>>;\n try {\n hits = await adapter.searchVector(scope, vector, deps.embedderId, topK);\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') throw err;\n return [];\n }\n return [...hits].filter((hit) => hit.record.id !== candidate.id);\n}\n\nfunction validateThresholds(thresholds: ConflictThresholds): void {\n if (\n !(\n thresholds.hot >= thresholds.nearDup &&\n thresholds.nearDup >= thresholds.cold &&\n thresholds.hot <= 1 &&\n thresholds.cold >= 0\n )\n ) {\n throw new TypeError(\n `[graphorin/memory] invalid conflict thresholds: expected 0 <= cold <= nearDup <= hot <= 1, got ${JSON.stringify(thresholds)}.`,\n );\n }\n}\n\n/**\n * Throw the canonical `AbortError` when the supplied signal is\n * already aborted. Safe to call with `undefined`.\n *\n * @internal\n */\nfunction checkAbort(signal: AbortSignal | undefined): void {\n if (signal === undefined) return;\n if (signal.aborted) {\n if (typeof signal.throwIfAborted === 'function') {\n signal.throwIfAborted();\n }\n throw new DOMException('The operation was aborted.', 'AbortError');\n }\n}\n\nfunction warnBypass(): void {\n // Use the structured logger once it lands (Phase 04 follow-up). For\n // now, surface a one-shot console.warn so operators notice the\n // regression risk per the Phase 10b spec (\"WARN-once per process\").\n console.warn(\n '[graphorin/memory] conflict pipeline disabled (mode: \"off\"). ' +\n 'Semantic writes fall back to a straight-through path — bi-temporal supersede / dedup ' +\n 'will not fire until you re-enable the pipeline.',\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pipeline.js","names":["mode: 'on' | 'off'","localePack: LocalePack","thresholds: ConflictThresholds","auditInput: Parameters<NonNullable<typeof conflictStore>['recordDecision']>[0]","hits: ReadonlyArray<MemoryHit<Fact>>"],"sources":["../../src/conflict/pipeline.ts"],"sourcesContent":["/**\n * `runConflictPipeline` - orchestrator for the multi-stage conflict\n * resolution pipeline shipped in `@graphorin/memory` Phase 10b\n * (DEC-117 / ADR-018 ext / RB-02).\n *\n * @packageDocumentation\n */\n\nimport type { Fact, MemoryHit } from '@graphorin/core';\nimport { withMemorySpan } from '../internal/spans.js';\nimport { enLocalePack, type LocalePack } from './locale-packs/index.js';\nimport { stage1ExactDedup } from './stages/stage1-exact-dedup.js';\nimport { stage2EmbeddingThreeZone } from './stages/stage2-embedding-three-zone.js';\nimport { stage3HeuristicRegex } from './stages/stage3-heuristic-regex.js';\nimport { stage4SubjectPredicate } from './stages/stage4-subject-predicate.js';\nimport { stage5DeferToDeep } from './stages/stage5-defer-to-deep.js';\nimport {\n type ConflictDecision,\n type ConflictPipeline,\n type ConflictPipelineDeps,\n type ConflictPipelineOptions,\n type ConflictStage,\n type ConflictThresholds,\n DEFAULT_CONFLICT_THRESHOLDS,\n type StageContext,\n type StageOutcome,\n} from './types.js';\n\nlet bypassWarnedOnce = false;\n\n/**\n * Reset the one-shot bypass-warning flag. Test-only helper - production\n * callers never invoke this.\n *\n * @internal\n */\nexport function _resetBypassWarningForTesting(): void {\n bypassWarnedOnce = false;\n}\n\n/**\n * One-shot helper that mirrors RB-02 §8.1's `runConflictPipeline({...})`\n * spec - convenient for callers that do not need to pre-build + cache\n * the pipeline. Production wiring should still go through\n * {@link createConflictPipeline} (`SemanticMemory` re-uses the cached\n * instance per `Memory`).\n *\n * @stable\n */\nexport async function runConflictPipeline(args: {\n readonly candidate: Fact;\n readonly deps: ConflictPipelineDeps;\n readonly options?: ConflictPipelineOptions;\n}): Promise<ConflictDecision> {\n const pipeline = createConflictPipeline(args.options ?? {});\n return pipeline.run(args.deps, args.candidate);\n}\n\n/**\n * Returns a frozen pipeline handle wired with the supplied options.\n *\n * @stable\n */\nexport function createConflictPipeline(options: ConflictPipelineOptions = {}): ConflictPipeline {\n const mode: 'on' | 'off' = options.mode ?? 'on';\n const localePack: LocalePack = options.localePack ?? enLocalePack;\n const thresholds: ConflictThresholds = Object.freeze({\n hot: options.thresholds?.hot ?? DEFAULT_CONFLICT_THRESHOLDS.hot,\n nearDup: options.thresholds?.nearDup ?? DEFAULT_CONFLICT_THRESHOLDS.nearDup,\n cold: options.thresholds?.cold ?? DEFAULT_CONFLICT_THRESHOLDS.cold,\n });\n validateThresholds(thresholds);\n const stage2TopK = options.stage2TopK ?? 5;\n const now = options.now ?? ((): string => new Date().toISOString());\n\n async function run(deps: ConflictPipelineDeps, candidate: Fact): Promise<ConflictDecision> {\n if (mode === 'off') {\n if (!bypassWarnedOnce) {\n bypassWarnedOnce = true;\n warnBypass();\n }\n return { kind: 'admit', stage: 'exact-dedup', reason: 'pipeline-mode-off' };\n }\n\n checkAbort(deps.signal);\n const conflictStore = options.conflictStore ?? deps.store.conflicts;\n\n return withMemorySpan(\n deps.tracer,\n 'memory.conflict',\n {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n },\n {\n 'memory.conflict.candidate_id': candidate.id,\n 'memory.conflict.locale_pack': localePack.id,\n },\n async (span) => {\n const existing = await collectVectorCandidates(deps, candidate, stage2TopK);\n const ctx: StageContext = {\n candidate,\n existing,\n localePack,\n thresholds,\n };\n const decision = await orchestrate(ctx);\n await persistDecision(decision, candidate, conflictStore, now);\n span.setAttributes({\n 'memory.conflict.stage': decision.stage,\n 'memory.conflict.decision': decision.kind,\n 'memory.conflict.candidate_count': existing.length,\n ...(decision.kind === 'dedup' && decision.similarity !== undefined\n ? { 'memory.conflict.similarity': decision.similarity }\n : {}),\n ...(decision.kind === 'pending' && decision.similarity !== undefined\n ? { 'memory.conflict.similarity': decision.similarity }\n : {}),\n ...(decision.kind !== 'admit'\n ? { 'memory.conflict.reason': decision.reason ?? 'unspecified' }\n : decision.reason !== undefined\n ? { 'memory.conflict.reason': decision.reason }\n : {}),\n });\n return decision;\n },\n );\n }\n\n /**\n * Visit the five stages in the order documented by RB-02 §8.1 and\n * the conflict-resolution policy (DEC-117). Stage 1 short-circuits\n * exact duplicates. Stage 2 always computes the zone but the\n * orchestrator splits its decision in two halves: the HOT zone is\n * the only zone that overrides Stage 3 / Stage 4 (semantic identity\n * outranks any explicit user marker); every other zone runs Stages\n * 3 and 4 first to give the heuristic markers a chance to supersede\n * before falling back to Stage 2's default decision.\n *\n * Stage 3 is declared \"orthogonal to Stage 2\", which is why the\n * heuristic stages run regardless of Stage 2's COLD / NEAR-DUP /\n * CONFLICT-CHECK assignment as long as Stage 2 surfaced at least\n * one candidate. The HOT-zone short-circuit preserves the dedup\n * invariant for semantic identity.\n */\n async function orchestrate(ctx: StageContext): Promise<ConflictDecision> {\n const stage1 = await stage1ExactDedup.evaluate(ctx);\n if (stage1.kind !== 'continue') {\n return mapOutcomeToDecision('exact-dedup', stage1, ctx.candidate);\n }\n\n const stage2 = await stage2EmbeddingThreeZone.evaluate(ctx);\n if (stage2.kind === 'dedup' && stage2.reason === 'embedding-hot-zone') {\n return mapOutcomeToDecision('embedding-three-zone', stage2, ctx.candidate);\n }\n\n if (ctx.existing.length > 0) {\n const stage3 = await stage3HeuristicRegex.evaluate(ctx);\n if (stage3.kind !== 'continue') {\n return mapOutcomeToDecision('heuristic-regex', stage3, ctx.candidate);\n }\n const stage4 = await stage4SubjectPredicate.evaluate(ctx);\n if (stage4.kind !== 'continue') {\n return mapOutcomeToDecision('subject-predicate', stage4, ctx.candidate);\n }\n }\n\n if (stage2.kind === 'dedup' || stage2.kind === 'admit') {\n return mapOutcomeToDecision('embedding-three-zone', stage2, ctx.candidate);\n }\n\n const stage5 = await stage5DeferToDeep.evaluate(ctx);\n return mapOutcomeToDecision('defer-to-deep', stage5, ctx.candidate);\n }\n\n return Object.freeze({\n mode,\n localePack,\n thresholds,\n run,\n }) satisfies ConflictPipeline;\n}\n\nfunction mapOutcomeToDecision(\n stage: ConflictStage,\n outcome: StageOutcome,\n candidate: Fact,\n): ConflictDecision {\n switch (outcome.kind) {\n case 'continue':\n return { kind: 'admit', stage, reason: 'continue-fallthrough' };\n case 'admit':\n return {\n kind: 'admit',\n stage,\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n case 'dedup':\n return {\n kind: 'dedup',\n stage,\n existingId: outcome.existingId,\n ...(outcome.similarity !== undefined ? { similarity: outcome.similarity } : {}),\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n case 'supersede':\n return {\n kind: 'supersede',\n stage,\n existingId: outcome.existingId,\n reason: outcome.reason,\n };\n case 'pending':\n return {\n kind: 'pending',\n stage,\n candidateId: candidate.id,\n conflictingIds: outcome.conflictingIds,\n ...(outcome.similarity !== undefined ? { similarity: outcome.similarity } : {}),\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n }\n}\n\nasync function persistDecision(\n decision: ConflictDecision,\n candidate: Fact,\n conflictStore: ConflictPipelineDeps['store']['conflicts'],\n now: () => string,\n): Promise<void> {\n if (conflictStore === undefined) return;\n const scope = {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n };\n const detectionZone = decisionToZone(decision);\n const auditInput: Parameters<NonNullable<typeof conflictStore>['recordDecision']>[0] = {\n scope,\n candidateId: candidate.id,\n decision: decision.kind,\n stage: decision.stage,\n ...(detectionZone !== undefined ? { detectionZone } : {}),\n ...(decision.kind === 'dedup' && decision.existingId !== undefined\n ? { existingId: decision.existingId }\n : {}),\n ...(decision.kind === 'supersede' ? { existingId: decision.existingId } : {}),\n ...(decision.kind === 'dedup' && decision.similarity !== undefined\n ? { similarity: decision.similarity }\n : {}),\n ...(decision.kind === 'pending' && decision.similarity !== undefined\n ? { similarity: decision.similarity }\n : {}),\n ...(decision.kind !== 'admit' && decision.reason !== undefined\n ? { reason: decision.reason }\n : {}),\n ...(decision.kind === 'admit' && decision.reason !== undefined\n ? { reason: decision.reason }\n : {}),\n };\n await conflictStore.recordDecision(auditInput);\n if (decision.kind === 'pending') {\n await conflictStore.enqueuePending({\n scope,\n factId: candidate.id,\n candidateText: candidate.text,\n stage: decision.stage,\n conflictingIds: decision.conflictingIds,\n ...(decision.reason !== undefined ? { reason: decision.reason } : {}),\n });\n }\n void now;\n}\n\nfunction decisionToZone(decision: ConflictDecision): string | undefined {\n switch (decision.kind) {\n case 'admit':\n if (decision.reason === 'embedding-cold-zone') return 'cold';\n return undefined;\n case 'dedup':\n if (decision.reason === 'embedding-hot-zone') return 'hot';\n if (decision.reason === 'embedding-near-dup-zone') return 'near-dup';\n if (decision.reason === 'exact-hash-match') return 'exact';\n if (decision.reason === 'subject-predicate-match-same-object') return 'subject-predicate';\n return undefined;\n case 'supersede':\n if (decision.reason.startsWith('regex-supersede-marker')) return 'heuristic';\n if (decision.reason.startsWith('regex-negation')) return 'heuristic';\n if (decision.reason.startsWith('subject-predicate-match')) return 'subject-predicate';\n return undefined;\n case 'pending':\n return 'conflict-check';\n }\n}\n\nasync function collectVectorCandidates(\n deps: ConflictPipelineDeps,\n candidate: Fact,\n topK: number,\n): Promise<ReadonlyArray<MemoryHit<Fact>>> {\n if (deps.embedder === null || deps.embedderId === null) return [];\n const adapter = deps.store.semantic;\n if (typeof adapter.searchVector !== 'function') return [];\n checkAbort(deps.signal);\n // PS-10: the candidate is compared against stored facts, which are embedded\n // as `passage` - keep it in the same space for the asymmetric (E5) embedder.\n const [vector] = await deps.embedder.embed([candidate.text], { taskType: 'passage' });\n if (vector === undefined) return [];\n checkAbort(deps.signal);\n const scope = {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n };\n // Vector search may throw when the storage layer's vector extension\n // is missing or mis-configured (e.g. `sqlite-vec` not loaded). Fall\n // back to \"no candidates\" so the conflict pipeline degrades to the\n // 10a straight-through write rather than blocking the agent loop.\n // AbortError is re-raised so cancellation propagates up.\n let hits: ReadonlyArray<MemoryHit<Fact>>;\n try {\n hits = await adapter.searchVector(scope, vector, deps.embedderId, topK);\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') throw err;\n return [];\n }\n return [...hits].filter((hit) => hit.record.id !== candidate.id);\n}\n\nfunction validateThresholds(thresholds: ConflictThresholds): void {\n if (\n !(\n thresholds.hot >= thresholds.nearDup &&\n thresholds.nearDup >= thresholds.cold &&\n thresholds.hot <= 1 &&\n thresholds.cold >= 0\n )\n ) {\n throw new TypeError(\n `[graphorin/memory] invalid conflict thresholds: expected 0 <= cold <= nearDup <= hot <= 1, got ${JSON.stringify(thresholds)}.`,\n );\n }\n}\n\n/**\n * Throw the canonical `AbortError` when the supplied signal is\n * already aborted. Safe to call with `undefined`.\n *\n * @internal\n */\nfunction checkAbort(signal: AbortSignal | undefined): void {\n if (signal === undefined) return;\n if (signal.aborted) {\n if (typeof signal.throwIfAborted === 'function') {\n signal.throwIfAborted();\n }\n throw new DOMException('The operation was aborted.', 'AbortError');\n }\n}\n\nfunction warnBypass(): void {\n // Use the structured logger once it lands (Phase 04 follow-up). For\n // now, surface a one-shot console.warn so operators notice the\n // regression risk per the Phase 10b spec (\"WARN-once per process\").\n console.warn(\n '[graphorin/memory] conflict pipeline disabled (mode: \"off\"). ' +\n 'Semantic writes fall back to a straight-through path - bi-temporal supersede / dedup ' +\n 'will not fire until you re-enable the pipeline.',\n );\n}\n"],"mappings":"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"}
|
|
@@ -31,7 +31,7 @@ function normaliseSubject(input, pack) {
|
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* Naive subject / predicate / object split. The locale pack supplies
|
|
34
|
-
* the verb tokens used as predicate normalisers
|
|
34
|
+
* the verb tokens used as predicate normalisers - the first matching
|
|
35
35
|
* verb in the sentence becomes the predicate and splits the rest
|
|
36
36
|
* into subject (left side) and object (right side). The split is
|
|
37
37
|
* intentionally simple: anything more clever lives behind the entity-
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","names":[],"sources":["../../../src/conflict/stages/helpers.ts"],"sourcesContent":["/**\n * Internal helpers shared across the five pipeline stages.\n *\n * @packageDocumentation\n * @internal\n */\n\nimport { md5 } from '@graphorin/core';\nimport type { LocalePack } from '../locale-packs/index.js';\n\n/**\n * Lowercase + collapse whitespace + trim. Used by Stage 1 (exact\n * dedup) to compute the canonical fact body that feeds the MD5\n * comparison.\n */\nexport function canonicaliseFactBody(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().toLowerCase();\n}\n\n/**\n * Stable hash for a canonical fact body. We re-use the framework's\n * MD5 helper to stay consistent with the storage layer's existing\n * dedup hash column (see `facts.hash`).\n */\nexport function fastFactHash(text: string): string {\n return md5(text);\n}\n\n/** Strip leading articles + trim a candidate subject token. */\nexport function normaliseSubject(input: string, pack: LocalePack): string {\n const tokens = canonicaliseFactBody(input).split(' ');\n const stop = new Set(pack.subjectStopWords.map((s) => s.toLowerCase()));\n return tokens.filter((token) => token.length > 0 && !stop.has(token)).join(' ');\n}\n\n/**\n * Naive subject / predicate / object split. The locale pack supplies\n * the verb tokens used as predicate normalisers
|
|
1
|
+
{"version":3,"file":"helpers.js","names":[],"sources":["../../../src/conflict/stages/helpers.ts"],"sourcesContent":["/**\n * Internal helpers shared across the five pipeline stages.\n *\n * @packageDocumentation\n * @internal\n */\n\nimport { md5 } from '@graphorin/core';\nimport type { LocalePack } from '../locale-packs/index.js';\n\n/**\n * Lowercase + collapse whitespace + trim. Used by Stage 1 (exact\n * dedup) to compute the canonical fact body that feeds the MD5\n * comparison.\n */\nexport function canonicaliseFactBody(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().toLowerCase();\n}\n\n/**\n * Stable hash for a canonical fact body. We re-use the framework's\n * MD5 helper to stay consistent with the storage layer's existing\n * dedup hash column (see `facts.hash`).\n */\nexport function fastFactHash(text: string): string {\n return md5(text);\n}\n\n/** Strip leading articles + trim a candidate subject token. */\nexport function normaliseSubject(input: string, pack: LocalePack): string {\n const tokens = canonicaliseFactBody(input).split(' ');\n const stop = new Set(pack.subjectStopWords.map((s) => s.toLowerCase()));\n return tokens.filter((token) => token.length > 0 && !stop.has(token)).join(' ');\n}\n\n/**\n * Naive subject / predicate / object split. The locale pack supplies\n * the verb tokens used as predicate normalisers - the first matching\n * verb in the sentence becomes the predicate and splits the rest\n * into subject (left side) and object (right side). The split is\n * intentionally simple: anything more clever lives behind the entity-\n * linking layer (post-MVP).\n */\nexport function splitSubjectPredicateObject(\n text: string,\n pack: LocalePack,\n): { readonly subject: string; readonly predicate: string; readonly object: string } | null {\n const canonical = canonicaliseFactBody(text);\n if (canonical.length === 0) return null;\n const tokens = canonical.split(' ');\n const verbs = new Set(pack.predicateNormalisers.map((v) => v.toLowerCase()));\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === undefined) continue;\n const stripped = token.replace(/[.,!?;:]/g, '');\n if (verbs.has(stripped)) {\n const subject = normaliseSubject(tokens.slice(0, i).join(' '), pack);\n const predicate = stripped;\n const object = canonicaliseFactBody(tokens.slice(i + 1).join(' '));\n if (subject.length === 0 || object.length === 0) continue;\n return { subject, predicate, object };\n }\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,SAAgB,qBAAqB,MAAsB;AACzD,QAAO,KAAK,QAAQ,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa;;;;;;;AAQvD,SAAgB,aAAa,MAAsB;AACjD,QAAO,IAAI,KAAK;;;AAIlB,SAAgB,iBAAiB,OAAe,MAA0B;CACxE,MAAM,SAAS,qBAAqB,MAAM,CAAC,MAAM,IAAI;CACrD,MAAM,OAAO,IAAI,IAAI,KAAK,iBAAiB,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC;AACvE,QAAO,OAAO,QAAQ,UAAU,MAAM,SAAS,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC,KAAK,IAAI;;;;;;;;;;AAWjF,SAAgB,4BACd,MACA,MAC0F;CAC1F,MAAM,YAAY,qBAAqB,KAAK;AAC5C,KAAI,UAAU,WAAW,EAAG,QAAO;CACnC,MAAM,SAAS,UAAU,MAAM,IAAI;CACnC,MAAM,QAAQ,IAAI,IAAI,KAAK,qBAAqB,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC;AAC5E,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,QAAQ,OAAO;AACrB,MAAI,UAAU,OAAW;EACzB,MAAM,WAAW,MAAM,QAAQ,aAAa,GAAG;AAC/C,MAAI,MAAM,IAAI,SAAS,EAAE;GACvB,MAAM,UAAU,iBAAiB,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,KAAK;GACpE,MAAM,YAAY;GAClB,MAAM,SAAS,qBAAqB,OAAO,MAAM,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAClE,OAAI,QAAQ,WAAW,KAAK,OAAO,WAAW,EAAG;AACjD,UAAO;IAAE;IAAS;IAAW;IAAQ;;;AAGzC,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stage1-exact-dedup.js","names":["stage1ExactDedup: PipelineStage"],"sources":["../../../src/conflict/stages/stage1-exact-dedup.ts"],"sourcesContent":["/**\n * Stage 1
|
|
1
|
+
{"version":3,"file":"stage1-exact-dedup.js","names":["stage1ExactDedup: PipelineStage"],"sources":["../../../src/conflict/stages/stage1-exact-dedup.ts"],"sourcesContent":["/**\n * Stage 1 - Exact dedup. Computes an MD5 hash on the canonical fact\n * body (lowercase + trimmed + collapsed whitespace) and short-circuits\n * to `dedup` when an existing fact in the supplied vector hit list\n * shares the same canonical form.\n *\n * Stage 1 only inspects `existing` when Stage 2 already populated it\n * for the orchestrator (re-using the embedding round-trip). When the\n * orchestrator calls Stage 1 with an empty list the stage falls\n * through to `'continue'` - Stage 2 will gather candidates and Stage 1\n * is folded into Stage 2's HOT zone via the same canonical-form\n * comparison.\n *\n * @packageDocumentation\n */\n\nimport type { PipelineStage } from '../types.js';\nimport { canonicaliseFactBody, fastFactHash } from './helpers.js';\n\nexport const stage1ExactDedup: PipelineStage = {\n id: 'exact-dedup',\n async evaluate(ctx) {\n const candidateHash = fastFactHash(canonicaliseFactBody(ctx.candidate.text));\n if (ctx.existing.length === 0) {\n return { kind: 'continue' };\n }\n for (const hit of ctx.existing) {\n const existingHash = fastFactHash(canonicaliseFactBody(hit.record.text));\n if (existingHash === candidateHash) {\n return {\n kind: 'dedup',\n existingId: hit.record.id,\n similarity: 1,\n reason: 'exact-hash-match',\n };\n }\n }\n return { kind: 'continue' };\n },\n};\n"],"mappings":";;;AAmBA,MAAaA,mBAAkC;CAC7C,IAAI;CACJ,MAAM,SAAS,KAAK;EAClB,MAAM,gBAAgB,aAAa,qBAAqB,IAAI,UAAU,KAAK,CAAC;AAC5E,MAAI,IAAI,SAAS,WAAW,EAC1B,QAAO,EAAE,MAAM,YAAY;AAE7B,OAAK,MAAM,OAAO,IAAI,SAEpB,KADqB,aAAa,qBAAqB,IAAI,OAAO,KAAK,CAAC,KACnD,cACnB,QAAO;GACL,MAAM;GACN,YAAY,IAAI,OAAO;GACvB,YAAY;GACZ,QAAQ;GACT;AAGL,SAAO,EAAE,MAAM,YAAY;;CAE9B"}
|