@graphorin/memory 0.5.0 → 0.6.1
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 +102 -1
- package/README.md +54 -37
- 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 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -5
- 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/package.js +6 -0
- package/dist/package.js.map +1 -0
- 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 +3 -3
- 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,111 @@
|
|
|
1
1
|
# @graphorin/memory
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`826ee59`](https://github.com/o-stepper/graphorin/commit/826ee5933ad38693b0dd2f20a110abfecba7d23d), [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430)]:
|
|
10
|
+
- @graphorin/observability@0.6.1
|
|
11
|
+
- @graphorin/core@0.6.1
|
|
12
|
+
- @graphorin/security@0.6.1
|
|
13
|
+
- @graphorin/tools@0.6.1
|
|
14
|
+
|
|
15
|
+
## 0.6.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- [#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).
|
|
20
|
+
|
|
21
|
+
- 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.
|
|
22
|
+
- Recent user messages survive compaction verbatim (`preserveUserMessages`, default 2) - only assistant/tool content is summarized away.
|
|
23
|
+
- 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).
|
|
24
|
+
- 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).
|
|
25
|
+
- 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.
|
|
26
|
+
|
|
27
|
+
- [#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).
|
|
28
|
+
|
|
29
|
+
- 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`.
|
|
30
|
+
- 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.
|
|
31
|
+
- Extraction decontextualization (memory-consolidation-08): each extracted fact must now be a self-contained proposition (pronouns/ellipses resolved, entities inlined).
|
|
32
|
+
- 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.
|
|
33
|
+
|
|
34
|
+
- [#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).
|
|
35
|
+
|
|
36
|
+
- 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.
|
|
37
|
+
- 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.
|
|
38
|
+
- 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`).
|
|
39
|
+
- 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.
|
|
40
|
+
|
|
41
|
+
- [#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.
|
|
42
|
+
|
|
43
|
+
- 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).
|
|
44
|
+
- 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.
|
|
45
|
+
- Retrieval-frequency reinforcement: `facts.access_count` (migration 027) + opt-in `SalienceWeights.accessReinforcement` (default 0 = inert).
|
|
46
|
+
- Runbook memory: `rules_fts` (migration 028) + `ProceduralMemory.search` returns whole validated procedures, with a gated `runbook_search` tool (`createMemory({ runbookSearch: true })`).
|
|
47
|
+
|
|
48
|
+
- [#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.
|
|
49
|
+
|
|
50
|
+
- 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.
|
|
51
|
+
- Graph + entity as first-class tunable fusion weights (`FusionWeights.graph` / `.entity`, were hardcoded neutral).
|
|
52
|
+
- Exact entity-match retriever: `SqliteGraphStore.factsForEntityName` + `FactSearchOptions.entityMatch` add a precise "facts about <entity>" candidate leg.
|
|
53
|
+
- `longmemeval` harness gains `--retrieval ppr` / `--retrieval entity`. Bitemporal event time, Matryoshka truncation, and cascade LLM reranking remain eval-gated.
|
|
54
|
+
|
|
55
|
+
### Patch Changes
|
|
56
|
+
|
|
57
|
+
- [#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.
|
|
58
|
+
|
|
59
|
+
- [#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.
|
|
60
|
+
|
|
61
|
+
- [#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).
|
|
62
|
+
|
|
63
|
+
- 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`.
|
|
64
|
+
- 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.
|
|
65
|
+
- 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.
|
|
66
|
+
- 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.
|
|
67
|
+
- 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.
|
|
68
|
+
|
|
69
|
+
- [#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).
|
|
70
|
+
|
|
71
|
+
- 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).
|
|
72
|
+
- 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.
|
|
73
|
+
- 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.
|
|
74
|
+
- 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.
|
|
75
|
+
- 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.
|
|
76
|
+
|
|
77
|
+
- [#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).
|
|
78
|
+
|
|
79
|
+
- 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).
|
|
80
|
+
- 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).
|
|
81
|
+
- 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.
|
|
82
|
+
- 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.
|
|
83
|
+
- 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.
|
|
84
|
+
|
|
85
|
+
- [#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).
|
|
86
|
+
|
|
87
|
+
- [#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.
|
|
88
|
+
|
|
89
|
+
- 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)]:
|
|
90
|
+
- @graphorin/tools@0.6.0
|
|
91
|
+
- @graphorin/core@0.6.0
|
|
92
|
+
- @graphorin/security@0.6.0
|
|
93
|
+
- @graphorin/observability@0.6.0
|
|
94
|
+
|
|
95
|
+
## 0.5.0
|
|
96
|
+
|
|
97
|
+
First version published to the npm registry (with Sigstore build
|
|
98
|
+
provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
|
|
99
|
+
milestones and were never published. All `@graphorin/*` packages release
|
|
100
|
+
lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
|
|
101
|
+
in the repository-level
|
|
102
|
+
[CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).
|
|
103
|
+
|
|
3
104
|
## 0.1.0
|
|
4
105
|
|
|
5
106
|
### Minor Changes
|
|
6
107
|
|
|
7
|
-
- Phase 10a
|
|
108
|
+
- Phase 10a - initial release of `@graphorin/memory`. Ships the
|
|
8
109
|
`createMemory()` facade with six tier sub-modules (working,
|
|
9
110
|
session, episodic, semantic, procedural, shared), nine memory
|
|
10
111
|
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`
|
|
23
|
-
|
|
24
|
-
- `zod` (peer)
|
|
22
|
+
- `@graphorin/tools` - the `tool({...})` builder used to declare the
|
|
23
|
+
eleven memory tools.
|
|
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,50 +121,55 @@ 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(...)`
|
|
125
|
-
`retention` curve (recency + access frequency
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
lever). The light phase scores each fact with `salience(...)` - the Ebbinghaus
|
|
125
|
+
`retention` curve (recency + access frequency, including an opt-in
|
|
126
|
+
`accessReinforcement` term fed by per-fact recall counters) combined with the
|
|
127
|
+
`importance` hint and a security-risk negative term (a quarantined fact is
|
|
128
|
+
evicted first, a foreign-provenance one slightly sooner). With neutral importance + an active,
|
|
128
129
|
first-party fact, `salience === retention`. Setting `decayCapacity`
|
|
129
130
|
(`createMemory({ consolidator: { decayCapacity } })`, default unbounded)
|
|
130
|
-
bounds storage: the lowest-salience facts are **soft-archived** (recoverable
|
|
131
|
+
bounds storage: the lowest-salience facts are **soft-archived** (recoverable -
|
|
131
132
|
`archived = 1`, never deleted) until the window fits.
|
|
132
133
|
- **Query transformation** (read path; multi-query / RAG-Fusion + opt-in HyDE).
|
|
133
134
|
`search(scope, query, { multiQuery: N })` fans the query into up to `N - 1`
|
|
134
135
|
reworded variants via one cheap LLM call, retrieves each, and fuses every list
|
|
135
|
-
through the same RRF reranker
|
|
136
|
+
through the same RRF reranker - recovering memories whose stored wording
|
|
136
137
|
differs from the question. `{ hyde: true }` additionally embeds a hypothetical
|
|
137
138
|
answer (arXiv:2212.10496) and fuses its neighbours. Both are **opt-in**, wired
|
|
138
139
|
by `createMemory({ queryTransform: { provider } })`; with no transformer (the
|
|
139
140
|
default) search stays **offline + single-shot** and these options are silent
|
|
140
|
-
no-ops. Reserve it for retrieval-heavy recall
|
|
141
|
+
no-ops. Reserve it for retrieval-heavy recall - it adds provider latency.
|
|
141
142
|
- **Weighted fusion + reranker guidance** (search quality, opt-in). RRF is a
|
|
142
143
|
good zero-tuning default, but once you have labels (the `@graphorin/evals`
|
|
143
144
|
harness) a calibrated weight can do better. `search(scope, query, { fusion: {
|
|
144
145
|
strategy: 'weighted', weights: { vector: 3, fts: 1 } } })` fuses through
|
|
145
146
|
`WeightedRRFReranker`, scaling each candidate list's reciprocal-rank
|
|
146
147
|
contribution by its retriever *kind* (FTS vs vector; the HyDE list counts as
|
|
147
|
-
vector). **RRF stays the default**
|
|
148
|
+
vector). **RRF stays the default** - omit `fusion` (or pass `{ strategy:
|
|
148
149
|
'rrf' }`) and behaviour is unchanged; equal weights reproduce RRF exactly. The
|
|
149
150
|
pure `fuseWeighted(lists, weights, k)` is exported for offline weight tuning
|
|
150
151
|
against the eval harness. For a further precision lift, install a cross-encoder
|
|
151
152
|
reranker (`@graphorin/reranker-transformersjs` / `-llm`) via
|
|
152
|
-
`createMemory({ reranker })` or `setReranker(...)` and raise `candidateTopK`
|
|
153
|
+
`createMemory({ reranker })` or `setReranker(...)` and raise `candidateTopK` -
|
|
153
154
|
reranking pays off **only on an already-high-recall candidate set**, so fix
|
|
154
155
|
recall first (contextual retrieval / multi-query) before reaching for it.
|
|
155
156
|
- **Relation graph + one-hop expansion** (new capability, opt-in). Activates the
|
|
156
157
|
dormant `(subject, predicate, object)` substrate: facts now carry s/p/o, and an
|
|
157
158
|
**entity resolver** folds the subject/object strings into canonical entities
|
|
158
|
-
("Anna", "Anna S.", "my sister" → one entity) via lexical + embedding dedup
|
|
159
|
+
("Anna", "Anna S.", "my sister" → one entity) via lexical + embedding dedup -
|
|
159
160
|
with **auditable, reversible merges** (an append-only ledger; `merged_into` is
|
|
160
161
|
single-level). At read time `search(scope, query, { expandHops: 1 })` seeds on
|
|
161
162
|
the candidates and fuses in facts sharing an entity through a recursive CTE,
|
|
162
163
|
answering "what did the person I met in Tbilisi recommend?" without leaving
|
|
163
164
|
SQLite. Enable linking with `createMemory({ graph: { entityResolution: true } })`
|
|
164
165
|
(offline; lexical + embedding). The ambiguous-similarity band mints a *new*
|
|
165
|
-
entity by default
|
|
166
|
+
entity by default - it never auto-merges on weak evidence; opt into LLM
|
|
166
167
|
adjudication (`graph: { llmAdjudication: true, provider }`) to resolve that band.
|
|
167
|
-
|
|
168
|
+
On top of hop expansion: `graphScoring: 'ppr'` weights neighbours by a
|
|
169
|
+
PPR-lite random-walk score instead of flat inclusion, `entityMatch: true`
|
|
170
|
+
adds exact entity-name hits as a candidate list, and `expandHops: 2` is
|
|
171
|
+
available for offline / analysis flows (expensive under dense hubs). Omit it
|
|
172
|
+
all and the default path is unchanged + fully offline (`expandHops`
|
|
168
173
|
defaults to `0`).
|
|
169
174
|
- **Agentic / iterative retrieval** (search quality, gated + opt-in). A
|
|
170
175
|
CRAG/Self-RAG-style grade-then-reformulate loop for hard multi-hop / temporal
|
|
@@ -172,7 +177,7 @@ any `EmbedderProvider`; the default is
|
|
|
172
177
|
(`assessQueryDifficulty`) keeps simple lookups single-shot; only a query judged
|
|
173
178
|
hard *and* with a grader configured (`createMemory({ iterativeRetrieval: {
|
|
174
179
|
provider } })`) is graded for sufficiency and, when weak, reformulated +
|
|
175
|
-
retrieved again
|
|
180
|
+
retrieved again - **widening to one-hop graph expansion** on each reformulation -
|
|
176
181
|
up to a hard-capped `maxIterations` (≤ 5), then **abstaining**
|
|
177
182
|
(`result.abstained === true`) rather than confabulating. Exposed as
|
|
178
183
|
`SemanticMemory.searchIterative(...)` and the gated **`deep_recall`** tool (a
|
|
@@ -181,18 +186,25 @@ any `EmbedderProvider`; the default is
|
|
|
181
186
|
call**; the tool surface stays at the canonical eleven.
|
|
182
187
|
- **Procedural memory extraction** (new capability, gated + opt-in). AWM-style
|
|
183
188
|
workflow induction: from a **successful** agent trajectory,
|
|
184
|
-
`ProceduralMemory.induce(scope, trajectory)` distils a reusable procedure
|
|
189
|
+
`ProceduralMemory.induce(scope, trajectory)` distils a reusable procedure -
|
|
185
190
|
goal + value-abstracted steps (`"search for {product}"`) + the variable names
|
|
186
191
|
+ Voyager-style success criteria for self-verification on reuse
|
|
187
192
|
(`checkSuccessCriteria`). Induction fires on **success only** (a failed run
|
|
188
193
|
never calls the inducer), and the result lands **quarantined** +
|
|
189
|
-
`provenance: 'induction'` (P1-4)
|
|
194
|
+
`provenance: 'induction'` (P1-4) - procedures drive *actions*, so this is the
|
|
190
195
|
highest-poisoning-risk write and is excluded from `activate()` until validated.
|
|
191
196
|
`trajectoryFromRunState(runState)` distils the agent's already-emitted run
|
|
192
197
|
state, so capture needs **no agent change**. Wire it with
|
|
193
198
|
`createMemory({ procedureInduction: { provider } })`; omit it and `induce(...)`
|
|
194
|
-
throws `ProcedureInductionNotConfiguredError`
|
|
199
|
+
throws `ProcedureInductionNotConfiguredError` - the procedural tier stays pure
|
|
195
200
|
offline CRUD with no provider call.
|
|
201
|
+
- **Memory evolution knobs** (all opt-in). `runbookSearch: true` registers the
|
|
202
|
+
gated `runbook_search` tool over the procedural tier; `consolidator:
|
|
203
|
+
{ learnedContext: true, learnedContextMaxChars }` maintains a compact
|
|
204
|
+
learned-context digest block from consolidation output; facts, episodes, and
|
|
205
|
+
rules carry an optional `owner` principal so multi-principal deployments can
|
|
206
|
+
filter reads per owner; and per-fact access counters feed the salience
|
|
207
|
+
`accessReinforcement` term.
|
|
196
208
|
- **Per-record `embedder_id` enforced.** Every embedded write registers
|
|
197
209
|
the embedder via the storage layer's `EmbeddingMetaRepository` and
|
|
198
210
|
records the canonical id (`'<provider>:<model>@<dim>'`); attempts to
|
|
@@ -215,7 +227,7 @@ any `EmbedderProvider`; the default is
|
|
|
215
227
|
(post-MVP).
|
|
216
228
|
- **`memory.compile(scope)` + `memory.metadata(scope)`.** The
|
|
217
229
|
interface used by the context engine (Phase 10d) to assemble the
|
|
218
|
-
six-layer memory-aware system prompt; `@graphorin/memory@0.
|
|
230
|
+
six-layer memory-aware system prompt; `@graphorin/memory@0.6.0`
|
|
219
231
|
ships the deterministic minimum-viable rendering so the agent
|
|
220
232
|
runtime in Phase 12 can already exercise the surface.
|
|
221
233
|
- **Memory-modification guard wiring.** Every tool's
|
|
@@ -277,7 +289,7 @@ console.log(hits[0]?.record.text);
|
|
|
277
289
|
|
|
278
290
|
| Tier | Surface (read) | Surface (write) |
|
|
279
291
|
|-------------|-----------------------------------------------|-------------------------------------------------------------|
|
|
280
|
-
| working | `list`, `read`, `compile` | `define`, `write`, `
|
|
292
|
+
| working | `list`, `read`, `compile` | `define`, `write`, `append`, `replace`, `rethink`, `forget`, `attach`, `detach` |
|
|
281
293
|
| session | `list`, `search`, `attributedFor` | `push`, `flushImportant`, `compact` |
|
|
282
294
|
| episodic | `recent`, `search` | `record` |
|
|
283
295
|
| semantic | `search` | `remember`, `supersede`, `forget` |
|
|
@@ -304,6 +316,11 @@ tool exactly once:
|
|
|
304
316
|
| `fact_history` | semantic | Trace a fact's bi-temporal supersede chain. |
|
|
305
317
|
| `fact_validate` | semantic | Promote a quarantined fact to active (audited). |
|
|
306
318
|
|
|
319
|
+
Two further tools are **gated** behind their configuration and absent by
|
|
320
|
+
default: `deep_recall` (registered when `iterativeRetrieval` is configured)
|
|
321
|
+
and `runbook_search` (procedural runbook lookup, registered when
|
|
322
|
+
`createMemory({ runbookSearch: true })`).
|
|
323
|
+
|
|
307
324
|
## Embedder migration
|
|
308
325
|
|
|
309
326
|
```ts
|
|
@@ -354,22 +371,22 @@ console.log(outcome.decision.kind); // 'supersede' | 'dedup' | 'pending' | 'admi
|
|
|
354
371
|
|
|
355
372
|
The pipeline visits five stages in order:
|
|
356
373
|
|
|
357
|
-
1. **Exact dedup**
|
|
374
|
+
1. **Exact dedup** - MD5 hash on the canonical (lowercase + collapsed-
|
|
358
375
|
whitespace + trimmed) candidate body short-circuits on a hit.
|
|
359
|
-
2. **Embedding three-zone**
|
|
376
|
+
2. **Embedding three-zone** - top-K neighbours from `searchVector`
|
|
360
377
|
classify the candidate into HOT (`>= 0.95`), NEAR-DUP
|
|
361
378
|
(`>= 0.85`), CONFLICT-CHECK (`> 0.4`), or COLD. HOT zone always
|
|
362
379
|
dedups (semantic identity outranks every other signal).
|
|
363
|
-
3. **Heuristic regex**
|
|
380
|
+
3. **Heuristic regex** - the active locale pack's supersede +
|
|
364
381
|
negation markers fire when the candidate has an explicit
|
|
365
382
|
change signal (`moved to`, `no longer`, `got promoted`, …) and
|
|
366
383
|
there is at least one existing candidate.
|
|
367
|
-
4. **Subject / predicate**
|
|
384
|
+
4. **Subject / predicate** - naive `(subject, predicate, object)`
|
|
368
385
|
split using the locale pack's predicate normalisers; matching
|
|
369
386
|
subject + predicate with a different object is a strong supersede
|
|
370
387
|
signal.
|
|
371
|
-
5. **Defer to deep LLM judge**
|
|
372
|
-
the candidate sits in CONFLICT-CHECK zone
|
|
388
|
+
5. **Defer to deep LLM judge** - Stages 1-4 yielded no decision but
|
|
389
|
+
the candidate sits in CONFLICT-CHECK zone - the row is admitted
|
|
373
390
|
`pending` and queued in `conflict_check_pending` for the
|
|
374
391
|
consolidator's deep phase (Phase 10c).
|
|
375
392
|
|
|
@@ -398,4 +415,4 @@ MIT © 2026 Oleksiy Stepurenko.
|
|
|
398
415
|
|
|
399
416
|
---
|
|
400
417
|
|
|
401
|
-
**Project Graphorin** · v0.
|
|
418
|
+
**Project Graphorin** · v0.6.1 · 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":";;;;;;;;;;;AA4BA,IAAI,mBAAmB;;;;;;;AAQvB,SAAgB,gCAAsC;AACpD,oBAAmB;;;;;;;;;;;AAYrB,eAAsB,oBAAoB,MAIZ;AAE5B,QADiB,uBAAuB,KAAK,WAAW,EAAE,CAAC,CAC3C,IAAI,KAAK,MAAM,KAAK,UAAU;;;;;;;AAQhD,SAAgB,uBAAuB,UAAmC,EAAE,EAAoB;CAC9F,MAAMA,OAAqB,QAAQ,QAAQ;CAC3C,MAAMC,aAAyB,QAAQ,cAAc;CACrD,MAAMC,aAAiC,OAAO,OAAO;EACnD,KAAK,QAAQ,YAAY,OAAO,4BAA4B;EAC5D,SAAS,QAAQ,YAAY,WAAW,4BAA4B;EACpE,MAAM,QAAQ,YAAY,QAAQ,4BAA4B;EAC/D,CAAC;AACF,oBAAmB,WAAW;CAC9B,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,MAAM,QAAQ,+BAAsB,IAAI,MAAM,EAAC,aAAa;CAElE,eAAe,IAAI,MAA4B,WAA4C;AACzF,MAAI,SAAS,OAAO;AAClB,OAAI,CAAC,kBAAkB;AACrB,uBAAmB;AACnB,gBAAY;;AAEd,UAAO;IAAE,MAAM;IAAS,OAAO;IAAe,QAAQ;IAAqB;;AAG7E,aAAW,KAAK,OAAO;EACvB,MAAM,gBAAgB,QAAQ,iBAAiB,KAAK,MAAM;AAE1D,SAAO,eACL,KAAK,QACL,mBACA;GACE,QAAQ,UAAU;GAClB,GAAI,UAAU,cAAc,SAAY,EAAE,WAAW,UAAU,WAAW,GAAG,EAAE;GAC/E,GAAI,UAAU,YAAY,SAAY,EAAE,SAAS,UAAU,SAAS,GAAG,EAAE;GAC1E,EACD;GACE,gCAAgC,UAAU;GAC1C,+BAA+B,WAAW;GAC3C,EACD,OAAO,SAAS;GACd,MAAM,WAAW,MAAM,wBAAwB,MAAM,WAAW,WAAW;GAO3E,MAAM,WAAW,MAAM,YANG;IACxB;IACA;IACA;IACA;IACD,CACsC;AACvC,SAAM,gBAAgB,UAAU,WAAW,eAAe,IAAI;AAC9D,QAAK,cAAc;IACjB,yBAAyB,SAAS;IAClC,4BAA4B,SAAS;IACrC,mCAAmC,SAAS;IAC5C,GAAI,SAAS,SAAS,WAAW,SAAS,eAAe,SACrD,EAAE,8BAA8B,SAAS,YAAY,GACrD,EAAE;IACN,GAAI,SAAS,SAAS,aAAa,SAAS,eAAe,SACvD,EAAE,8BAA8B,SAAS,YAAY,GACrD,EAAE;IACN,GAAI,SAAS,SAAS,UAClB,EAAE,0BAA0B,SAAS,UAAU,eAAe,GAC9D,SAAS,WAAW,SAClB,EAAE,0BAA0B,SAAS,QAAQ,GAC7C,EAAE;IACT,CAAC;AACF,UAAO;IAEV;;;;;;;;;;;;;;;;;;CAmBH,eAAe,YAAY,KAA8C;EACvE,MAAM,SAAS,MAAM,iBAAiB,SAAS,IAAI;AACnD,MAAI,OAAO,SAAS,WAClB,QAAO,qBAAqB,eAAe,QAAQ,IAAI,UAAU;EAGnE,MAAM,SAAS,MAAM,yBAAyB,SAAS,IAAI;AAC3D,MAAI,OAAO,SAAS,WAAW,OAAO,WAAW,qBAC/C,QAAO,qBAAqB,wBAAwB,QAAQ,IAAI,UAAU;AAG5E,MAAI,IAAI,SAAS,SAAS,GAAG;GAC3B,MAAM,SAAS,MAAM,qBAAqB,SAAS,IAAI;AACvD,OAAI,OAAO,SAAS,WAClB,QAAO,qBAAqB,mBAAmB,QAAQ,IAAI,UAAU;GAEvE,MAAM,SAAS,MAAM,uBAAuB,SAAS,IAAI;AACzD,OAAI,OAAO,SAAS,WAClB,QAAO,qBAAqB,qBAAqB,QAAQ,IAAI,UAAU;;AAI3E,MAAI,OAAO,SAAS,WAAW,OAAO,SAAS,QAC7C,QAAO,qBAAqB,wBAAwB,QAAQ,IAAI,UAAU;AAI5E,SAAO,qBAAqB,iBADb,MAAM,kBAAkB,SAAS,IAAI,EACC,IAAI,UAAU;;AAGrE,QAAO,OAAO,OAAO;EACnB;EACA;EACA;EACA;EACD,CAAC;;AAGJ,SAAS,qBACP,OACA,SACA,WACkB;AAClB,SAAQ,QAAQ,MAAhB;EACE,KAAK,WACH,QAAO;GAAE,MAAM;GAAS;GAAO,QAAQ;GAAwB;EACjE,KAAK,QACH,QAAO;GACL,MAAM;GACN;GACA,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;GACnE;EACH,KAAK,QACH,QAAO;GACL,MAAM;GACN;GACA,YAAY,QAAQ;GACpB,GAAI,QAAQ,eAAe,SAAY,EAAE,YAAY,QAAQ,YAAY,GAAG,EAAE;GAC9E,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;GACnE;EACH,KAAK,YACH,QAAO;GACL,MAAM;GACN;GACA,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GACjB;EACH,KAAK,UACH,QAAO;GACL,MAAM;GACN;GACA,aAAa,UAAU;GACvB,gBAAgB,QAAQ;GACxB,GAAI,QAAQ,eAAe,SAAY,EAAE,YAAY,QAAQ,YAAY,GAAG,EAAE;GAC9E,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;GACnE;;;AAIP,eAAe,gBACb,UACA,WACA,eACA,KACe;AACf,KAAI,kBAAkB,OAAW;CACjC,MAAM,QAAQ;EACZ,QAAQ,UAAU;EAClB,GAAI,UAAU,cAAc,SAAY,EAAE,WAAW,UAAU,WAAW,GAAG,EAAE;EAC/E,GAAI,UAAU,YAAY,SAAY,EAAE,SAAS,UAAU,SAAS,GAAG,EAAE;EAC1E;CACD,MAAM,gBAAgB,eAAe,SAAS;CAC9C,MAAMC,aAAiF;EACrF;EACA,aAAa,UAAU;EACvB,UAAU,SAAS;EACnB,OAAO,SAAS;EAChB,GAAI,kBAAkB,SAAY,EAAE,eAAe,GAAG,EAAE;EACxD,GAAI,SAAS,SAAS,WAAW,SAAS,eAAe,SACrD,EAAE,YAAY,SAAS,YAAY,GACnC,EAAE;EACN,GAAI,SAAS,SAAS,cAAc,EAAE,YAAY,SAAS,YAAY,GAAG,EAAE;EAC5E,GAAI,SAAS,SAAS,WAAW,SAAS,eAAe,SACrD,EAAE,YAAY,SAAS,YAAY,GACnC,EAAE;EACN,GAAI,SAAS,SAAS,aAAa,SAAS,eAAe,SACvD,EAAE,YAAY,SAAS,YAAY,GACnC,EAAE;EACN,GAAI,SAAS,SAAS,WAAW,SAAS,WAAW,SACjD,EAAE,QAAQ,SAAS,QAAQ,GAC3B,EAAE;EACN,GAAI,SAAS,SAAS,WAAW,SAAS,WAAW,SACjD,EAAE,QAAQ,SAAS,QAAQ,GAC3B,EAAE;EACP;AACD,OAAM,cAAc,eAAe,WAAW;AAC9C,KAAI,SAAS,SAAS,UACpB,OAAM,cAAc,eAAe;EACjC;EACA,QAAQ,UAAU;EAClB,eAAe,UAAU;EACzB,OAAO,SAAS;EAChB,gBAAgB,SAAS;EACzB,GAAI,SAAS,WAAW,SAAY,EAAE,QAAQ,SAAS,QAAQ,GAAG,EAAE;EACrE,CAAC;;AAKN,SAAS,eAAe,UAAgD;AACtE,SAAQ,SAAS,MAAjB;EACE,KAAK;AACH,OAAI,SAAS,WAAW,sBAAuB,QAAO;AACtD;EACF,KAAK;AACH,OAAI,SAAS,WAAW,qBAAsB,QAAO;AACrD,OAAI,SAAS,WAAW,0BAA2B,QAAO;AAC1D,OAAI,SAAS,WAAW,mBAAoB,QAAO;AACnD,OAAI,SAAS,WAAW,sCAAuC,QAAO;AACtE;EACF,KAAK;AACH,OAAI,SAAS,OAAO,WAAW,yBAAyB,CAAE,QAAO;AACjE,OAAI,SAAS,OAAO,WAAW,iBAAiB,CAAE,QAAO;AACzD,OAAI,SAAS,OAAO,WAAW,0BAA0B,CAAE,QAAO;AAClE;EACF,KAAK,UACH,QAAO;;;AAIb,eAAe,wBACb,MACA,WACA,MACyC;AACzC,KAAI,KAAK,aAAa,QAAQ,KAAK,eAAe,KAAM,QAAO,EAAE;CACjE,MAAM,UAAU,KAAK,MAAM;AAC3B,KAAI,OAAO,QAAQ,iBAAiB,WAAY,QAAO,EAAE;AACzD,YAAW,KAAK,OAAO;CAGvB,MAAM,CAAC,UAAU,MAAM,KAAK,SAAS,MAAM,CAAC,UAAU,KAAK,EAAE,EAAE,UAAU,WAAW,CAAC;AACrF,KAAI,WAAW,OAAW,QAAO,EAAE;AACnC,YAAW,KAAK,OAAO;CACvB,MAAM,QAAQ;EACZ,QAAQ,UAAU;EAClB,GAAI,UAAU,cAAc,SAAY,EAAE,WAAW,UAAU,WAAW,GAAG,EAAE;EAC/E,GAAI,UAAU,YAAY,SAAY,EAAE,SAAS,UAAU,SAAS,GAAG,EAAE;EAC1E;CAMD,IAAIC;AACJ,KAAI;AACF,SAAO,MAAM,QAAQ,aAAa,OAAO,QAAQ,KAAK,YAAY,KAAK;UAChE,KAAK;AACZ,MAAI,eAAe,SAAS,IAAI,SAAS,aAAc,OAAM;AAC7D,SAAO,EAAE;;AAEX,QAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,QAAQ,IAAI,OAAO,OAAO,UAAU,GAAG;;AAGlE,SAAS,mBAAmB,YAAsC;AAChE,KACE,EACE,WAAW,OAAO,WAAW,WAC7B,WAAW,WAAW,WAAW,QACjC,WAAW,OAAO,KAClB,WAAW,QAAQ,GAGrB,OAAM,IAAI,UACR,kGAAkG,KAAK,UAAU,WAAW,CAAC,GAC9H;;;;;;;;AAUL,SAAS,WAAW,QAAuC;AACzD,KAAI,WAAW,OAAW;AAC1B,KAAI,OAAO,SAAS;AAClB,MAAI,OAAO,OAAO,mBAAmB,WACnC,QAAO,gBAAgB;AAEzB,QAAM,IAAI,aAAa,8BAA8B,aAAa;;;AAItE,SAAS,aAAmB;AAI1B,SAAQ,KACN,sMAGD"}
|
|
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":";;;;;;;;;;;AA4BA,IAAI,mBAAmB;;;;;;;AAQvB,SAAgB,gCAAsC;AACpD,oBAAmB;;;;;;;;;;;AAYrB,eAAsB,oBAAoB,MAIZ;AAE5B,QADiB,uBAAuB,KAAK,WAAW,EAAE,CAAC,CAC3C,IAAI,KAAK,MAAM,KAAK,UAAU;;;;;;;AAQhD,SAAgB,uBAAuB,UAAmC,EAAE,EAAoB;CAC9F,MAAMA,OAAqB,QAAQ,QAAQ;CAC3C,MAAMC,aAAyB,QAAQ,cAAc;CACrD,MAAMC,aAAiC,OAAO,OAAO;EACnD,KAAK,QAAQ,YAAY,OAAO,4BAA4B;EAC5D,SAAS,QAAQ,YAAY,WAAW,4BAA4B;EACpE,MAAM,QAAQ,YAAY,QAAQ,4BAA4B;EAC/D,CAAC;AACF,oBAAmB,WAAW;CAC9B,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,MAAM,QAAQ,+BAAsB,IAAI,MAAM,EAAC,aAAa;CAElE,eAAe,IAAI,MAA4B,WAA4C;AACzF,MAAI,SAAS,OAAO;AAClB,OAAI,CAAC,kBAAkB;AACrB,uBAAmB;AACnB,gBAAY;;AAEd,UAAO;IAAE,MAAM;IAAS,OAAO;IAAe,QAAQ;IAAqB;;AAG7E,aAAW,KAAK,OAAO;EACvB,MAAM,gBAAgB,QAAQ,iBAAiB,KAAK,MAAM;AAE1D,SAAO,eACL,KAAK,QACL,mBACA;GACE,QAAQ,UAAU;GAClB,GAAI,UAAU,cAAc,SAAY,EAAE,WAAW,UAAU,WAAW,GAAG,EAAE;GAC/E,GAAI,UAAU,YAAY,SAAY,EAAE,SAAS,UAAU,SAAS,GAAG,EAAE;GAC1E,EACD;GACE,gCAAgC,UAAU;GAC1C,+BAA+B,WAAW;GAC3C,EACD,OAAO,SAAS;GACd,MAAM,WAAW,MAAM,wBAAwB,MAAM,WAAW,WAAW;GAO3E,MAAM,WAAW,MAAM,YANG;IACxB;IACA;IACA;IACA;IACD,CACsC;AACvC,SAAM,gBAAgB,UAAU,WAAW,eAAe,IAAI;AAC9D,QAAK,cAAc;IACjB,yBAAyB,SAAS;IAClC,4BAA4B,SAAS;IACrC,mCAAmC,SAAS;IAC5C,GAAI,SAAS,SAAS,WAAW,SAAS,eAAe,SACrD,EAAE,8BAA8B,SAAS,YAAY,GACrD,EAAE;IACN,GAAI,SAAS,SAAS,aAAa,SAAS,eAAe,SACvD,EAAE,8BAA8B,SAAS,YAAY,GACrD,EAAE;IACN,GAAI,SAAS,SAAS,UAClB,EAAE,0BAA0B,SAAS,UAAU,eAAe,GAC9D,SAAS,WAAW,SAClB,EAAE,0BAA0B,SAAS,QAAQ,GAC7C,EAAE;IACT,CAAC;AACF,UAAO;IAEV;;;;;;;;;;;;;;;;;;CAmBH,eAAe,YAAY,KAA8C;EACvE,MAAM,SAAS,MAAM,iBAAiB,SAAS,IAAI;AACnD,MAAI,OAAO,SAAS,WAClB,QAAO,qBAAqB,eAAe,QAAQ,IAAI,UAAU;EAGnE,MAAM,SAAS,MAAM,yBAAyB,SAAS,IAAI;AAC3D,MAAI,OAAO,SAAS,WAAW,OAAO,WAAW,qBAC/C,QAAO,qBAAqB,wBAAwB,QAAQ,IAAI,UAAU;AAG5E,MAAI,IAAI,SAAS,SAAS,GAAG;GAC3B,MAAM,SAAS,MAAM,qBAAqB,SAAS,IAAI;AACvD,OAAI,OAAO,SAAS,WAClB,QAAO,qBAAqB,mBAAmB,QAAQ,IAAI,UAAU;GAEvE,MAAM,SAAS,MAAM,uBAAuB,SAAS,IAAI;AACzD,OAAI,OAAO,SAAS,WAClB,QAAO,qBAAqB,qBAAqB,QAAQ,IAAI,UAAU;;AAI3E,MAAI,OAAO,SAAS,WAAW,OAAO,SAAS,QAC7C,QAAO,qBAAqB,wBAAwB,QAAQ,IAAI,UAAU;AAI5E,SAAO,qBAAqB,iBADb,MAAM,kBAAkB,SAAS,IAAI,EACC,IAAI,UAAU;;AAGrE,QAAO,OAAO,OAAO;EACnB;EACA;EACA;EACA;EACD,CAAC;;AAGJ,SAAS,qBACP,OACA,SACA,WACkB;AAClB,SAAQ,QAAQ,MAAhB;EACE,KAAK,WACH,QAAO;GAAE,MAAM;GAAS;GAAO,QAAQ;GAAwB;EACjE,KAAK,QACH,QAAO;GACL,MAAM;GACN;GACA,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;GACnE;EACH,KAAK,QACH,QAAO;GACL,MAAM;GACN;GACA,YAAY,QAAQ;GACpB,GAAI,QAAQ,eAAe,SAAY,EAAE,YAAY,QAAQ,YAAY,GAAG,EAAE;GAC9E,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;GACnE;EACH,KAAK,YACH,QAAO;GACL,MAAM;GACN;GACA,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GACjB;EACH,KAAK,UACH,QAAO;GACL,MAAM;GACN;GACA,aAAa,UAAU;GACvB,gBAAgB,QAAQ;GACxB,GAAI,QAAQ,eAAe,SAAY,EAAE,YAAY,QAAQ,YAAY,GAAG,EAAE;GAC9E,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;GACnE;;;AAIP,eAAe,gBACb,UACA,WACA,eACA,KACe;AACf,KAAI,kBAAkB,OAAW;CACjC,MAAM,QAAQ;EACZ,QAAQ,UAAU;EAClB,GAAI,UAAU,cAAc,SAAY,EAAE,WAAW,UAAU,WAAW,GAAG,EAAE;EAC/E,GAAI,UAAU,YAAY,SAAY,EAAE,SAAS,UAAU,SAAS,GAAG,EAAE;EAC1E;CACD,MAAM,gBAAgB,eAAe,SAAS;CAC9C,MAAMC,aAAiF;EACrF;EACA,aAAa,UAAU;EACvB,UAAU,SAAS;EACnB,OAAO,SAAS;EAChB,GAAI,kBAAkB,SAAY,EAAE,eAAe,GAAG,EAAE;EACxD,GAAI,SAAS,SAAS,WAAW,SAAS,eAAe,SACrD,EAAE,YAAY,SAAS,YAAY,GACnC,EAAE;EACN,GAAI,SAAS,SAAS,cAAc,EAAE,YAAY,SAAS,YAAY,GAAG,EAAE;EAC5E,GAAI,SAAS,SAAS,WAAW,SAAS,eAAe,SACrD,EAAE,YAAY,SAAS,YAAY,GACnC,EAAE;EACN,GAAI,SAAS,SAAS,aAAa,SAAS,eAAe,SACvD,EAAE,YAAY,SAAS,YAAY,GACnC,EAAE;EACN,GAAI,SAAS,SAAS,WAAW,SAAS,WAAW,SACjD,EAAE,QAAQ,SAAS,QAAQ,GAC3B,EAAE;EACN,GAAI,SAAS,SAAS,WAAW,SAAS,WAAW,SACjD,EAAE,QAAQ,SAAS,QAAQ,GAC3B,EAAE;EACP;AACD,OAAM,cAAc,eAAe,WAAW;AAC9C,KAAI,SAAS,SAAS,UACpB,OAAM,cAAc,eAAe;EACjC;EACA,QAAQ,UAAU;EAClB,eAAe,UAAU;EACzB,OAAO,SAAS;EAChB,gBAAgB,SAAS;EACzB,GAAI,SAAS,WAAW,SAAY,EAAE,QAAQ,SAAS,QAAQ,GAAG,EAAE;EACrE,CAAC;;AAKN,SAAS,eAAe,UAAgD;AACtE,SAAQ,SAAS,MAAjB;EACE,KAAK;AACH,OAAI,SAAS,WAAW,sBAAuB,QAAO;AACtD;EACF,KAAK;AACH,OAAI,SAAS,WAAW,qBAAsB,QAAO;AACrD,OAAI,SAAS,WAAW,0BAA2B,QAAO;AAC1D,OAAI,SAAS,WAAW,mBAAoB,QAAO;AACnD,OAAI,SAAS,WAAW,sCAAuC,QAAO;AACtE;EACF,KAAK;AACH,OAAI,SAAS,OAAO,WAAW,yBAAyB,CAAE,QAAO;AACjE,OAAI,SAAS,OAAO,WAAW,iBAAiB,CAAE,QAAO;AACzD,OAAI,SAAS,OAAO,WAAW,0BAA0B,CAAE,QAAO;AAClE;EACF,KAAK,UACH,QAAO;;;AAIb,eAAe,wBACb,MACA,WACA,MACyC;AACzC,KAAI,KAAK,aAAa,QAAQ,KAAK,eAAe,KAAM,QAAO,EAAE;CACjE,MAAM,UAAU,KAAK,MAAM;AAC3B,KAAI,OAAO,QAAQ,iBAAiB,WAAY,QAAO,EAAE;AACzD,YAAW,KAAK,OAAO;CAGvB,MAAM,CAAC,UAAU,MAAM,KAAK,SAAS,MAAM,CAAC,UAAU,KAAK,EAAE,EAAE,UAAU,WAAW,CAAC;AACrF,KAAI,WAAW,OAAW,QAAO,EAAE;AACnC,YAAW,KAAK,OAAO;CACvB,MAAM,QAAQ;EACZ,QAAQ,UAAU;EAClB,GAAI,UAAU,cAAc,SAAY,EAAE,WAAW,UAAU,WAAW,GAAG,EAAE;EAC/E,GAAI,UAAU,YAAY,SAAY,EAAE,SAAS,UAAU,SAAS,GAAG,EAAE;EAC1E;CAMD,IAAIC;AACJ,KAAI;AACF,SAAO,MAAM,QAAQ,aAAa,OAAO,QAAQ,KAAK,YAAY,KAAK;UAChE,KAAK;AACZ,MAAI,eAAe,SAAS,IAAI,SAAS,aAAc,OAAM;AAC7D,SAAO,EAAE;;AAEX,QAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,QAAQ,IAAI,OAAO,OAAO,UAAU,GAAG;;AAGlE,SAAS,mBAAmB,YAAsC;AAChE,KACE,EACE,WAAW,OAAO,WAAW,WAC7B,WAAW,WAAW,WAAW,QACjC,WAAW,OAAO,KAClB,WAAW,QAAQ,GAGrB,OAAM,IAAI,UACR,kGAAkG,KAAK,UAAU,WAAW,CAAC,GAC9H;;;;;;;;AAUL,SAAS,WAAW,QAAuC;AACzD,KAAI,WAAW,OAAW;AAC1B,KAAI,OAAO,SAAS;AAClB,MAAI,OAAO,OAAO,mBAAmB,WACnC,QAAO,gBAAgB;AAEzB,QAAM,IAAI,aAAa,8BAA8B,aAAa;;;AAItE,SAAS,aAAmB;AAI1B,SAAQ,KACN,sMAGD"}
|