@graphorin/memory 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/dist/conflict/index.d.ts +3 -0
- package/dist/conflict/index.js +7 -0
- package/dist/conflict/locale-packs/en.js +262 -0
- package/dist/conflict/locale-packs/en.js.map +1 -0
- package/dist/conflict/locale-packs/index.js +4 -0
- package/dist/conflict/locale-packs/types.d.ts +103 -0
- package/dist/conflict/locale-packs/types.d.ts.map +1 -0
- package/dist/conflict/locale-packs/types.js +55 -0
- package/dist/conflict/locale-packs/types.js.map +1 -0
- package/dist/conflict/pipeline.d.ts +35 -0
- package/dist/conflict/pipeline.d.ts.map +1 -0
- package/dist/conflict/pipeline.js +257 -0
- package/dist/conflict/pipeline.js.map +1 -0
- package/dist/conflict/stages/helpers.js +66 -0
- package/dist/conflict/stages/helpers.js.map +1 -0
- package/dist/conflict/stages/stage1-exact-dedup.js +21 -0
- package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js +37 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js +45 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -0
- package/dist/conflict/stages/stage4-subject-predicate.js +31 -0
- package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js +22 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -0
- package/dist/conflict/types.d.ts +222 -0
- package/dist/conflict/types.d.ts.map +1 -0
- package/dist/conflict/types.js +50 -0
- package/dist/conflict/types.js.map +1 -0
- package/dist/consolidator/budget.d.ts +1 -0
- package/dist/consolidator/budget.js +214 -0
- package/dist/consolidator/budget.js.map +1 -0
- package/dist/consolidator/decay.d.ts +35 -0
- package/dist/consolidator/decay.d.ts.map +1 -0
- package/dist/consolidator/decay.js +91 -0
- package/dist/consolidator/decay.js.map +1 -0
- package/dist/consolidator/dlq.js +58 -0
- package/dist/consolidator/dlq.js.map +1 -0
- package/dist/consolidator/errors.d.ts +54 -0
- package/dist/consolidator/errors.d.ts.map +1 -0
- package/dist/consolidator/errors.js +67 -0
- package/dist/consolidator/errors.js.map +1 -0
- package/dist/consolidator/idempotency.d.ts +1 -0
- package/dist/consolidator/idempotency.js +22 -0
- package/dist/consolidator/idempotency.js.map +1 -0
- package/dist/consolidator/index.d.ts +34 -0
- package/dist/consolidator/index.d.ts.map +1 -0
- package/dist/consolidator/index.js +163 -0
- package/dist/consolidator/index.js.map +1 -0
- package/dist/consolidator/lock.d.ts +2 -0
- package/dist/consolidator/lock.js +89 -0
- package/dist/consolidator/lock.js.map +1 -0
- package/dist/consolidator/noise-filter.d.ts +2 -0
- package/dist/consolidator/noise-filter.js +173 -0
- package/dist/consolidator/noise-filter.js.map +1 -0
- package/dist/consolidator/phases/deep.d.ts +4 -0
- package/dist/consolidator/phases/deep.js +198 -0
- package/dist/consolidator/phases/deep.js.map +1 -0
- package/dist/consolidator/phases/induce.d.ts +154 -0
- package/dist/consolidator/phases/induce.d.ts.map +1 -0
- package/dist/consolidator/phases/induce.js +253 -0
- package/dist/consolidator/phases/induce.js.map +1 -0
- package/dist/consolidator/phases/light.d.ts +4 -0
- package/dist/consolidator/phases/light.js +99 -0
- package/dist/consolidator/phases/light.js.map +1 -0
- package/dist/consolidator/phases/reflect.js +287 -0
- package/dist/consolidator/phases/reflect.js.map +1 -0
- package/dist/consolidator/phases/standard.d.ts +8 -0
- package/dist/consolidator/phases/standard.js +499 -0
- package/dist/consolidator/phases/standard.js.map +1 -0
- package/dist/consolidator/reconcile.js +192 -0
- package/dist/consolidator/reconcile.js.map +1 -0
- package/dist/consolidator/runtime.d.ts +62 -0
- package/dist/consolidator/runtime.d.ts.map +1 -0
- package/dist/consolidator/runtime.js +580 -0
- package/dist/consolidator/runtime.js.map +1 -0
- package/dist/consolidator/scheduler.d.ts +111 -0
- package/dist/consolidator/scheduler.d.ts.map +1 -0
- package/dist/consolidator/scheduler.js +71 -0
- package/dist/consolidator/scheduler.js.map +1 -0
- package/dist/consolidator/triggers.d.ts +40 -0
- package/dist/consolidator/triggers.d.ts.map +1 -0
- package/dist/consolidator/triggers.js +94 -0
- package/dist/consolidator/triggers.js.map +1 -0
- package/dist/consolidator/types.d.ts +397 -0
- package/dist/consolidator/types.d.ts.map +1 -0
- package/dist/consolidator/types.js +117 -0
- package/dist/consolidator/types.js.map +1 -0
- package/dist/context-engine/annotations.d.ts +118 -0
- package/dist/context-engine/annotations.d.ts.map +1 -0
- package/dist/context-engine/annotations.js +102 -0
- package/dist/context-engine/annotations.js.map +1 -0
- package/dist/context-engine/auto-recall.d.ts +52 -0
- package/dist/context-engine/auto-recall.d.ts.map +1 -0
- package/dist/context-engine/auto-recall.js +41 -0
- package/dist/context-engine/auto-recall.js.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts +55 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.js +97 -0
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -0
- package/dist/context-engine/compaction/compactor.d.ts +52 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -0
- package/dist/context-engine/compaction/compactor.js +196 -0
- package/dist/context-engine/compaction/compactor.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +27 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts +18 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +47 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +32 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -0
- package/dist/context-engine/compaction/hooks/types.d.ts +42 -0
- package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -0
- package/dist/context-engine/compaction/index.d.ts +9 -0
- package/dist/context-engine/compaction/index.js +9 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -0
- package/dist/context-engine/compaction/thresholds.d.ts +68 -0
- package/dist/context-engine/compaction/thresholds.d.ts.map +1 -0
- package/dist/context-engine/compaction/thresholds.js +63 -0
- package/dist/context-engine/compaction/thresholds.js.map +1 -0
- package/dist/context-engine/compaction/types.d.ts +202 -0
- package/dist/context-engine/compaction/types.d.ts.map +1 -0
- package/dist/context-engine/engine.d.ts +258 -0
- package/dist/context-engine/engine.d.ts.map +1 -0
- package/dist/context-engine/engine.js +424 -0
- package/dist/context-engine/engine.js.map +1 -0
- package/dist/context-engine/index.d.ts +68 -0
- package/dist/context-engine/index.d.ts.map +1 -0
- package/dist/context-engine/index.js +24 -0
- package/dist/context-engine/locale-packs/en.d.ts +14 -0
- package/dist/context-engine/locale-packs/en.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/en.js +87 -0
- package/dist/context-engine/locale-packs/en.js.map +1 -0
- package/dist/context-engine/locale-packs/index.js +4 -0
- package/dist/context-engine/locale-packs/resolver.d.ts +41 -0
- package/dist/context-engine/locale-packs/resolver.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/resolver.js +119 -0
- package/dist/context-engine/locale-packs/resolver.js.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +119 -0
- package/dist/context-engine/locale-packs/types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.js +24 -0
- package/dist/context-engine/locale-packs/types.js.map +1 -0
- package/dist/context-engine/metadata.d.ts +40 -0
- package/dist/context-engine/metadata.d.ts.map +1 -0
- package/dist/context-engine/metadata.js +66 -0
- package/dist/context-engine/metadata.js.map +1 -0
- package/dist/context-engine/preambles/inbound-en.d.ts +20 -0
- package/dist/context-engine/preambles/inbound-en.d.ts.map +1 -0
- package/dist/context-engine/preambles/inbound-en.js +23 -0
- package/dist/context-engine/preambles/inbound-en.js.map +1 -0
- package/dist/context-engine/privacy-filter.d.ts +108 -0
- package/dist/context-engine/privacy-filter.d.ts.map +1 -0
- package/dist/context-engine/privacy-filter.js +113 -0
- package/dist/context-engine/privacy-filter.js.map +1 -0
- package/dist/context-engine/templates/base-en.d.ts +19 -0
- package/dist/context-engine/templates/base-en.d.ts.map +1 -0
- package/dist/context-engine/templates/base-en.js +22 -0
- package/dist/context-engine/templates/base-en.js.map +1 -0
- package/dist/context-engine/templates/composer.d.ts +55 -0
- package/dist/context-engine/templates/composer.d.ts.map +1 -0
- package/dist/context-engine/templates/composer.js +51 -0
- package/dist/context-engine/templates/composer.js.map +1 -0
- package/dist/context-engine/token-budget.d.ts +99 -0
- package/dist/context-engine/token-budget.d.ts.map +1 -0
- package/dist/context-engine/token-budget.js +211 -0
- package/dist/context-engine/token-budget.js.map +1 -0
- package/dist/context-engine/token-counter.d.ts +54 -0
- package/dist/context-engine/token-counter.d.ts.map +1 -0
- package/dist/context-engine/token-counter.js +81 -0
- package/dist/context-engine/token-counter.js.map +1 -0
- package/dist/errors/index.d.ts +171 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +206 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/facade.d.ts +278 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +265 -0
- package/dist/facade.js.map +1 -0
- package/dist/graph/entity-resolver.d.ts +135 -0
- package/dist/graph/entity-resolver.d.ts.map +1 -0
- package/dist/graph/entity-resolver.js +202 -0
- package/dist/graph/entity-resolver.js.map +1 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/contextualize.d.ts +14 -0
- package/dist/internal/contextualize.d.ts.map +1 -0
- package/dist/internal/contextualize.js +110 -0
- package/dist/internal/contextualize.js.map +1 -0
- package/dist/internal/embedder-binding.js +50 -0
- package/dist/internal/embedder-binding.js.map +1 -0
- package/dist/internal/id.js +24 -0
- package/dist/internal/id.js.map +1 -0
- package/dist/internal/injection-heuristics.js +64 -0
- package/dist/internal/injection-heuristics.js.map +1 -0
- package/dist/internal/spans.js +35 -0
- package/dist/internal/spans.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +742 -0
- package/dist/internal/storage-adapter.d.ts.map +1 -0
- package/dist/migration/embedder-migration.d.ts +124 -0
- package/dist/migration/embedder-migration.d.ts.map +1 -0
- package/dist/migration/embedder-migration.js +109 -0
- package/dist/migration/embedder-migration.js.map +1 -0
- package/dist/migration/index.d.ts +2 -0
- package/dist/migration/index.js +3 -0
- package/dist/search/explain.d.ts +74 -0
- package/dist/search/explain.d.ts.map +1 -0
- package/dist/search/explain.js +50 -0
- package/dist/search/explain.js.map +1 -0
- package/dist/search/index.d.ts +6 -0
- package/dist/search/index.js +6 -0
- package/dist/search/iterative.d.ts +223 -0
- package/dist/search/iterative.d.ts.map +1 -0
- package/dist/search/iterative.js +330 -0
- package/dist/search/iterative.js.map +1 -0
- package/dist/search/query-transform.d.ts +112 -0
- package/dist/search/query-transform.d.ts.map +1 -0
- package/dist/search/query-transform.js +184 -0
- package/dist/search/query-transform.js.map +1 -0
- package/dist/search/rrf.d.ts +95 -0
- package/dist/search/rrf.d.ts.map +1 -0
- package/dist/search/rrf.js +166 -0
- package/dist/search/rrf.js.map +1 -0
- package/dist/search/types.d.ts +50 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.d.ts +142 -0
- package/dist/tiers/episodic-memory.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.js +254 -0
- package/dist/tiers/episodic-memory.js.map +1 -0
- package/dist/tiers/index.d.ts +8 -0
- package/dist/tiers/index.js +9 -0
- package/dist/tiers/insight-memory.d.ts +71 -0
- package/dist/tiers/insight-memory.d.ts.map +1 -0
- package/dist/tiers/insight-memory.js +133 -0
- package/dist/tiers/insight-memory.js.map +1 -0
- package/dist/tiers/procedural-memory.d.ts +147 -0
- package/dist/tiers/procedural-memory.d.ts.map +1 -0
- package/dist/tiers/procedural-memory.js +267 -0
- package/dist/tiers/procedural-memory.js.map +1 -0
- package/dist/tiers/semantic-memory.d.ts +461 -0
- package/dist/tiers/semantic-memory.d.ts.map +1 -0
- package/dist/tiers/semantic-memory.js +631 -0
- package/dist/tiers/semantic-memory.js.map +1 -0
- package/dist/tiers/session-memory.d.ts +117 -0
- package/dist/tiers/session-memory.d.ts.map +1 -0
- package/dist/tiers/session-memory.js +163 -0
- package/dist/tiers/session-memory.js.map +1 -0
- package/dist/tiers/shared-memory.d.ts +29 -0
- package/dist/tiers/shared-memory.d.ts.map +1 -0
- package/dist/tiers/shared-memory.js +58 -0
- package/dist/tiers/shared-memory.js.map +1 -0
- package/dist/tiers/working-memory.d.ts +129 -0
- package/dist/tiers/working-memory.d.ts.map +1 -0
- package/dist/tiers/working-memory.js +229 -0
- package/dist/tiers/working-memory.js.map +1 -0
- package/dist/tools/block-tools.d.ts +98 -0
- package/dist/tools/block-tools.d.ts.map +1 -0
- package/dist/tools/block-tools.js +114 -0
- package/dist/tools/block-tools.js.map +1 -0
- package/dist/tools/fact-tools.d.ts +294 -0
- package/dist/tools/fact-tools.d.ts.map +1 -0
- package/dist/tools/fact-tools.js +293 -0
- package/dist/tools/fact-tools.js.map +1 -0
- package/dist/tools/index.d.ts +35 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/recall-tools.d.ts +209 -0
- package/dist/tools/recall-tools.d.ts.map +1 -0
- package/dist/tools/recall-tools.js +170 -0
- package/dist/tools/recall-tools.js.map +1 -0
- package/dist/tools/types.d.ts +40 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { enLocalePack } from "./locale-packs/en.js";
|
|
2
|
+
import "./locale-packs/index.js";
|
|
3
|
+
import { withMemorySpan } from "../internal/spans.js";
|
|
4
|
+
import { stage1ExactDedup } from "./stages/stage1-exact-dedup.js";
|
|
5
|
+
import { stage2EmbeddingThreeZone } from "./stages/stage2-embedding-three-zone.js";
|
|
6
|
+
import { stage3HeuristicRegex } from "./stages/stage3-heuristic-regex.js";
|
|
7
|
+
import { stage4SubjectPredicate } from "./stages/stage4-subject-predicate.js";
|
|
8
|
+
import { stage5DeferToDeep } from "./stages/stage5-defer-to-deep.js";
|
|
9
|
+
import { DEFAULT_CONFLICT_THRESHOLDS } from "./types.js";
|
|
10
|
+
|
|
11
|
+
//#region src/conflict/pipeline.ts
|
|
12
|
+
let bypassWarnedOnce = false;
|
|
13
|
+
/**
|
|
14
|
+
* Reset the one-shot bypass-warning flag. Test-only helper — production
|
|
15
|
+
* callers never invoke this.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
function _resetBypassWarningForTesting() {
|
|
20
|
+
bypassWarnedOnce = false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* One-shot helper that mirrors RB-02 §8.1's `runConflictPipeline({...})`
|
|
24
|
+
* spec — convenient for callers that do not need to pre-build + cache
|
|
25
|
+
* the pipeline. Production wiring should still go through
|
|
26
|
+
* {@link createConflictPipeline} (`SemanticMemory` re-uses the cached
|
|
27
|
+
* instance per `Memory`).
|
|
28
|
+
*
|
|
29
|
+
* @stable
|
|
30
|
+
*/
|
|
31
|
+
async function runConflictPipeline(args) {
|
|
32
|
+
return createConflictPipeline(args.options ?? {}).run(args.deps, args.candidate);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns a frozen pipeline handle wired with the supplied options.
|
|
36
|
+
*
|
|
37
|
+
* @stable
|
|
38
|
+
*/
|
|
39
|
+
function createConflictPipeline(options = {}) {
|
|
40
|
+
const mode = options.mode ?? "on";
|
|
41
|
+
const localePack = options.localePack ?? enLocalePack;
|
|
42
|
+
const thresholds = Object.freeze({
|
|
43
|
+
hot: options.thresholds?.hot ?? DEFAULT_CONFLICT_THRESHOLDS.hot,
|
|
44
|
+
nearDup: options.thresholds?.nearDup ?? DEFAULT_CONFLICT_THRESHOLDS.nearDup,
|
|
45
|
+
cold: options.thresholds?.cold ?? DEFAULT_CONFLICT_THRESHOLDS.cold
|
|
46
|
+
});
|
|
47
|
+
validateThresholds(thresholds);
|
|
48
|
+
const stage2TopK = options.stage2TopK ?? 5;
|
|
49
|
+
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
50
|
+
async function run(deps, candidate) {
|
|
51
|
+
if (mode === "off") {
|
|
52
|
+
if (!bypassWarnedOnce) {
|
|
53
|
+
bypassWarnedOnce = true;
|
|
54
|
+
warnBypass();
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
kind: "admit",
|
|
58
|
+
stage: "exact-dedup",
|
|
59
|
+
reason: "pipeline-mode-off"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
checkAbort(deps.signal);
|
|
63
|
+
const conflictStore = options.conflictStore ?? deps.store.conflicts;
|
|
64
|
+
return withMemorySpan(deps.tracer, "memory.conflict", {
|
|
65
|
+
userId: candidate.userId,
|
|
66
|
+
...candidate.sessionId !== void 0 ? { sessionId: candidate.sessionId } : {},
|
|
67
|
+
...candidate.agentId !== void 0 ? { agentId: candidate.agentId } : {}
|
|
68
|
+
}, {
|
|
69
|
+
"memory.conflict.candidate_id": candidate.id,
|
|
70
|
+
"memory.conflict.locale_pack": localePack.id
|
|
71
|
+
}, async (span) => {
|
|
72
|
+
const existing = await collectVectorCandidates(deps, candidate, stage2TopK);
|
|
73
|
+
const decision = await orchestrate({
|
|
74
|
+
candidate,
|
|
75
|
+
existing,
|
|
76
|
+
localePack,
|
|
77
|
+
thresholds
|
|
78
|
+
});
|
|
79
|
+
await persistDecision(decision, candidate, conflictStore, now);
|
|
80
|
+
span.setAttributes({
|
|
81
|
+
"memory.conflict.stage": decision.stage,
|
|
82
|
+
"memory.conflict.decision": decision.kind,
|
|
83
|
+
"memory.conflict.candidate_count": existing.length,
|
|
84
|
+
...decision.kind === "dedup" && decision.similarity !== void 0 ? { "memory.conflict.similarity": decision.similarity } : {},
|
|
85
|
+
...decision.kind === "pending" && decision.similarity !== void 0 ? { "memory.conflict.similarity": decision.similarity } : {},
|
|
86
|
+
...decision.kind !== "admit" ? { "memory.conflict.reason": decision.reason ?? "unspecified" } : decision.reason !== void 0 ? { "memory.conflict.reason": decision.reason } : {}
|
|
87
|
+
});
|
|
88
|
+
return decision;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Visit the five stages in the order documented by RB-02 §8.1 and
|
|
93
|
+
* the conflict-resolution policy (DEC-117). Stage 1 short-circuits
|
|
94
|
+
* exact duplicates. Stage 2 always computes the zone but the
|
|
95
|
+
* orchestrator splits its decision in two halves: the HOT zone is
|
|
96
|
+
* the only zone that overrides Stage 3 / Stage 4 (semantic identity
|
|
97
|
+
* outranks any explicit user marker); every other zone runs Stages
|
|
98
|
+
* 3 and 4 first to give the heuristic markers a chance to supersede
|
|
99
|
+
* before falling back to Stage 2's default decision.
|
|
100
|
+
*
|
|
101
|
+
* Stage 3 is declared "orthogonal to Stage 2", which is why the
|
|
102
|
+
* heuristic stages run regardless of Stage 2's COLD / NEAR-DUP /
|
|
103
|
+
* CONFLICT-CHECK assignment as long as Stage 2 surfaced at least
|
|
104
|
+
* one candidate. The HOT-zone short-circuit preserves the dedup
|
|
105
|
+
* invariant for semantic identity.
|
|
106
|
+
*/
|
|
107
|
+
async function orchestrate(ctx) {
|
|
108
|
+
const stage1 = await stage1ExactDedup.evaluate(ctx);
|
|
109
|
+
if (stage1.kind !== "continue") return mapOutcomeToDecision("exact-dedup", stage1, ctx.candidate);
|
|
110
|
+
const stage2 = await stage2EmbeddingThreeZone.evaluate(ctx);
|
|
111
|
+
if (stage2.kind === "dedup" && stage2.reason === "embedding-hot-zone") return mapOutcomeToDecision("embedding-three-zone", stage2, ctx.candidate);
|
|
112
|
+
if (ctx.existing.length > 0) {
|
|
113
|
+
const stage3 = await stage3HeuristicRegex.evaluate(ctx);
|
|
114
|
+
if (stage3.kind !== "continue") return mapOutcomeToDecision("heuristic-regex", stage3, ctx.candidate);
|
|
115
|
+
const stage4 = await stage4SubjectPredicate.evaluate(ctx);
|
|
116
|
+
if (stage4.kind !== "continue") return mapOutcomeToDecision("subject-predicate", stage4, ctx.candidate);
|
|
117
|
+
}
|
|
118
|
+
if (stage2.kind === "dedup" || stage2.kind === "admit") return mapOutcomeToDecision("embedding-three-zone", stage2, ctx.candidate);
|
|
119
|
+
return mapOutcomeToDecision("defer-to-deep", await stage5DeferToDeep.evaluate(ctx), ctx.candidate);
|
|
120
|
+
}
|
|
121
|
+
return Object.freeze({
|
|
122
|
+
mode,
|
|
123
|
+
localePack,
|
|
124
|
+
thresholds,
|
|
125
|
+
run
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function mapOutcomeToDecision(stage, outcome, candidate) {
|
|
129
|
+
switch (outcome.kind) {
|
|
130
|
+
case "continue": return {
|
|
131
|
+
kind: "admit",
|
|
132
|
+
stage,
|
|
133
|
+
reason: "continue-fallthrough"
|
|
134
|
+
};
|
|
135
|
+
case "admit": return {
|
|
136
|
+
kind: "admit",
|
|
137
|
+
stage,
|
|
138
|
+
...outcome.reason !== void 0 ? { reason: outcome.reason } : {}
|
|
139
|
+
};
|
|
140
|
+
case "dedup": return {
|
|
141
|
+
kind: "dedup",
|
|
142
|
+
stage,
|
|
143
|
+
existingId: outcome.existingId,
|
|
144
|
+
...outcome.similarity !== void 0 ? { similarity: outcome.similarity } : {},
|
|
145
|
+
...outcome.reason !== void 0 ? { reason: outcome.reason } : {}
|
|
146
|
+
};
|
|
147
|
+
case "supersede": return {
|
|
148
|
+
kind: "supersede",
|
|
149
|
+
stage,
|
|
150
|
+
existingId: outcome.existingId,
|
|
151
|
+
reason: outcome.reason
|
|
152
|
+
};
|
|
153
|
+
case "pending": return {
|
|
154
|
+
kind: "pending",
|
|
155
|
+
stage,
|
|
156
|
+
candidateId: candidate.id,
|
|
157
|
+
conflictingIds: outcome.conflictingIds,
|
|
158
|
+
...outcome.similarity !== void 0 ? { similarity: outcome.similarity } : {},
|
|
159
|
+
...outcome.reason !== void 0 ? { reason: outcome.reason } : {}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function persistDecision(decision, candidate, conflictStore, now) {
|
|
164
|
+
if (conflictStore === void 0) return;
|
|
165
|
+
const scope = {
|
|
166
|
+
userId: candidate.userId,
|
|
167
|
+
...candidate.sessionId !== void 0 ? { sessionId: candidate.sessionId } : {},
|
|
168
|
+
...candidate.agentId !== void 0 ? { agentId: candidate.agentId } : {}
|
|
169
|
+
};
|
|
170
|
+
const detectionZone = decisionToZone(decision);
|
|
171
|
+
const auditInput = {
|
|
172
|
+
scope,
|
|
173
|
+
candidateId: candidate.id,
|
|
174
|
+
decision: decision.kind,
|
|
175
|
+
stage: decision.stage,
|
|
176
|
+
...detectionZone !== void 0 ? { detectionZone } : {},
|
|
177
|
+
...decision.kind === "dedup" && decision.existingId !== void 0 ? { existingId: decision.existingId } : {},
|
|
178
|
+
...decision.kind === "supersede" ? { existingId: decision.existingId } : {},
|
|
179
|
+
...decision.kind === "dedup" && decision.similarity !== void 0 ? { similarity: decision.similarity } : {},
|
|
180
|
+
...decision.kind === "pending" && decision.similarity !== void 0 ? { similarity: decision.similarity } : {},
|
|
181
|
+
...decision.kind !== "admit" && decision.reason !== void 0 ? { reason: decision.reason } : {},
|
|
182
|
+
...decision.kind === "admit" && decision.reason !== void 0 ? { reason: decision.reason } : {}
|
|
183
|
+
};
|
|
184
|
+
await conflictStore.recordDecision(auditInput);
|
|
185
|
+
if (decision.kind === "pending") await conflictStore.enqueuePending({
|
|
186
|
+
scope,
|
|
187
|
+
factId: candidate.id,
|
|
188
|
+
candidateText: candidate.text,
|
|
189
|
+
stage: decision.stage,
|
|
190
|
+
conflictingIds: decision.conflictingIds,
|
|
191
|
+
...decision.reason !== void 0 ? { reason: decision.reason } : {}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function decisionToZone(decision) {
|
|
195
|
+
switch (decision.kind) {
|
|
196
|
+
case "admit":
|
|
197
|
+
if (decision.reason === "embedding-cold-zone") return "cold";
|
|
198
|
+
return;
|
|
199
|
+
case "dedup":
|
|
200
|
+
if (decision.reason === "embedding-hot-zone") return "hot";
|
|
201
|
+
if (decision.reason === "embedding-near-dup-zone") return "near-dup";
|
|
202
|
+
if (decision.reason === "exact-hash-match") return "exact";
|
|
203
|
+
if (decision.reason === "subject-predicate-match-same-object") return "subject-predicate";
|
|
204
|
+
return;
|
|
205
|
+
case "supersede":
|
|
206
|
+
if (decision.reason.startsWith("regex-supersede-marker")) return "heuristic";
|
|
207
|
+
if (decision.reason.startsWith("regex-negation")) return "heuristic";
|
|
208
|
+
if (decision.reason.startsWith("subject-predicate-match")) return "subject-predicate";
|
|
209
|
+
return;
|
|
210
|
+
case "pending": return "conflict-check";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function collectVectorCandidates(deps, candidate, topK) {
|
|
214
|
+
if (deps.embedder === null || deps.embedderId === null) return [];
|
|
215
|
+
const adapter = deps.store.semantic;
|
|
216
|
+
if (typeof adapter.searchVector !== "function") return [];
|
|
217
|
+
checkAbort(deps.signal);
|
|
218
|
+
const [vector] = await deps.embedder.embed([candidate.text], { taskType: "passage" });
|
|
219
|
+
if (vector === void 0) return [];
|
|
220
|
+
checkAbort(deps.signal);
|
|
221
|
+
const scope = {
|
|
222
|
+
userId: candidate.userId,
|
|
223
|
+
...candidate.sessionId !== void 0 ? { sessionId: candidate.sessionId } : {},
|
|
224
|
+
...candidate.agentId !== void 0 ? { agentId: candidate.agentId } : {}
|
|
225
|
+
};
|
|
226
|
+
let hits;
|
|
227
|
+
try {
|
|
228
|
+
hits = await adapter.searchVector(scope, vector, deps.embedderId, topK);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
if (err instanceof Error && err.name === "AbortError") throw err;
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
return [...hits].filter((hit) => hit.record.id !== candidate.id);
|
|
234
|
+
}
|
|
235
|
+
function validateThresholds(thresholds) {
|
|
236
|
+
if (!(thresholds.hot >= thresholds.nearDup && thresholds.nearDup >= thresholds.cold && thresholds.hot <= 1 && thresholds.cold >= 0)) throw new TypeError(`[graphorin/memory] invalid conflict thresholds: expected 0 <= cold <= nearDup <= hot <= 1, got ${JSON.stringify(thresholds)}.`);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Throw the canonical `AbortError` when the supplied signal is
|
|
240
|
+
* already aborted. Safe to call with `undefined`.
|
|
241
|
+
*
|
|
242
|
+
* @internal
|
|
243
|
+
*/
|
|
244
|
+
function checkAbort(signal) {
|
|
245
|
+
if (signal === void 0) return;
|
|
246
|
+
if (signal.aborted) {
|
|
247
|
+
if (typeof signal.throwIfAborted === "function") signal.throwIfAborted();
|
|
248
|
+
throw new DOMException("The operation was aborted.", "AbortError");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function warnBypass() {
|
|
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
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
export { _resetBypassWarningForTesting, createConflictPipeline, runConflictPipeline };
|
|
257
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","names":["mode: 'on' | 'off'","localePack: LocalePack","thresholds: ConflictThresholds","auditInput: Parameters<NonNullable<typeof conflictStore>['recordDecision']>[0]","hits: ReadonlyArray<MemoryHit<Fact>>"],"sources":["../../src/conflict/pipeline.ts"],"sourcesContent":["/**\n * `runConflictPipeline` — orchestrator for the multi-stage conflict\n * resolution pipeline shipped in `@graphorin/memory` Phase 10b\n * (DEC-117 / ADR-018 ext / RB-02).\n *\n * @packageDocumentation\n */\n\nimport type { Fact, MemoryHit } from '@graphorin/core';\nimport { withMemorySpan } from '../internal/spans.js';\nimport { enLocalePack, type LocalePack } from './locale-packs/index.js';\nimport { stage1ExactDedup } from './stages/stage1-exact-dedup.js';\nimport { stage2EmbeddingThreeZone } from './stages/stage2-embedding-three-zone.js';\nimport { stage3HeuristicRegex } from './stages/stage3-heuristic-regex.js';\nimport { stage4SubjectPredicate } from './stages/stage4-subject-predicate.js';\nimport { stage5DeferToDeep } from './stages/stage5-defer-to-deep.js';\nimport {\n type ConflictDecision,\n type ConflictPipeline,\n type ConflictPipelineDeps,\n type ConflictPipelineOptions,\n type ConflictStage,\n type ConflictThresholds,\n DEFAULT_CONFLICT_THRESHOLDS,\n type StageContext,\n type StageOutcome,\n} from './types.js';\n\nlet bypassWarnedOnce = false;\n\n/**\n * Reset the one-shot bypass-warning flag. Test-only helper — production\n * callers never invoke this.\n *\n * @internal\n */\nexport function _resetBypassWarningForTesting(): void {\n bypassWarnedOnce = false;\n}\n\n/**\n * One-shot helper that mirrors RB-02 §8.1's `runConflictPipeline({...})`\n * spec — convenient for callers that do not need to pre-build + cache\n * the pipeline. Production wiring should still go through\n * {@link createConflictPipeline} (`SemanticMemory` re-uses the cached\n * instance per `Memory`).\n *\n * @stable\n */\nexport async function runConflictPipeline(args: {\n readonly candidate: Fact;\n readonly deps: ConflictPipelineDeps;\n readonly options?: ConflictPipelineOptions;\n}): Promise<ConflictDecision> {\n const pipeline = createConflictPipeline(args.options ?? {});\n return pipeline.run(args.deps, args.candidate);\n}\n\n/**\n * Returns a frozen pipeline handle wired with the supplied options.\n *\n * @stable\n */\nexport function createConflictPipeline(options: ConflictPipelineOptions = {}): ConflictPipeline {\n const mode: 'on' | 'off' = options.mode ?? 'on';\n const localePack: LocalePack = options.localePack ?? enLocalePack;\n const thresholds: ConflictThresholds = Object.freeze({\n hot: options.thresholds?.hot ?? DEFAULT_CONFLICT_THRESHOLDS.hot,\n nearDup: options.thresholds?.nearDup ?? DEFAULT_CONFLICT_THRESHOLDS.nearDup,\n cold: options.thresholds?.cold ?? DEFAULT_CONFLICT_THRESHOLDS.cold,\n });\n validateThresholds(thresholds);\n const stage2TopK = options.stage2TopK ?? 5;\n const now = options.now ?? ((): string => new Date().toISOString());\n\n async function run(deps: ConflictPipelineDeps, candidate: Fact): Promise<ConflictDecision> {\n if (mode === 'off') {\n if (!bypassWarnedOnce) {\n bypassWarnedOnce = true;\n warnBypass();\n }\n return { kind: 'admit', stage: 'exact-dedup', reason: 'pipeline-mode-off' };\n }\n\n checkAbort(deps.signal);\n const conflictStore = options.conflictStore ?? deps.store.conflicts;\n\n return withMemorySpan(\n deps.tracer,\n 'memory.conflict',\n {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n },\n {\n 'memory.conflict.candidate_id': candidate.id,\n 'memory.conflict.locale_pack': localePack.id,\n },\n async (span) => {\n const existing = await collectVectorCandidates(deps, candidate, stage2TopK);\n const ctx: StageContext = {\n candidate,\n existing,\n localePack,\n thresholds,\n };\n const decision = await orchestrate(ctx);\n await persistDecision(decision, candidate, conflictStore, now);\n span.setAttributes({\n 'memory.conflict.stage': decision.stage,\n 'memory.conflict.decision': decision.kind,\n 'memory.conflict.candidate_count': existing.length,\n ...(decision.kind === 'dedup' && decision.similarity !== undefined\n ? { 'memory.conflict.similarity': decision.similarity }\n : {}),\n ...(decision.kind === 'pending' && decision.similarity !== undefined\n ? { 'memory.conflict.similarity': decision.similarity }\n : {}),\n ...(decision.kind !== 'admit'\n ? { 'memory.conflict.reason': decision.reason ?? 'unspecified' }\n : decision.reason !== undefined\n ? { 'memory.conflict.reason': decision.reason }\n : {}),\n });\n return decision;\n },\n );\n }\n\n /**\n * Visit the five stages in the order documented by RB-02 §8.1 and\n * the conflict-resolution policy (DEC-117). Stage 1 short-circuits\n * exact duplicates. Stage 2 always computes the zone but the\n * orchestrator splits its decision in two halves: the HOT zone is\n * the only zone that overrides Stage 3 / Stage 4 (semantic identity\n * outranks any explicit user marker); every other zone runs Stages\n * 3 and 4 first to give the heuristic markers a chance to supersede\n * before falling back to Stage 2's default decision.\n *\n * Stage 3 is declared \"orthogonal to Stage 2\", which is why the\n * heuristic stages run regardless of Stage 2's COLD / NEAR-DUP /\n * CONFLICT-CHECK assignment as long as Stage 2 surfaced at least\n * one candidate. The HOT-zone short-circuit preserves the dedup\n * invariant for semantic identity.\n */\n async function orchestrate(ctx: StageContext): Promise<ConflictDecision> {\n const stage1 = await stage1ExactDedup.evaluate(ctx);\n if (stage1.kind !== 'continue') {\n return mapOutcomeToDecision('exact-dedup', stage1, ctx.candidate);\n }\n\n const stage2 = await stage2EmbeddingThreeZone.evaluate(ctx);\n if (stage2.kind === 'dedup' && stage2.reason === 'embedding-hot-zone') {\n return mapOutcomeToDecision('embedding-three-zone', stage2, ctx.candidate);\n }\n\n if (ctx.existing.length > 0) {\n const stage3 = await stage3HeuristicRegex.evaluate(ctx);\n if (stage3.kind !== 'continue') {\n return mapOutcomeToDecision('heuristic-regex', stage3, ctx.candidate);\n }\n const stage4 = await stage4SubjectPredicate.evaluate(ctx);\n if (stage4.kind !== 'continue') {\n return mapOutcomeToDecision('subject-predicate', stage4, ctx.candidate);\n }\n }\n\n if (stage2.kind === 'dedup' || stage2.kind === 'admit') {\n return mapOutcomeToDecision('embedding-three-zone', stage2, ctx.candidate);\n }\n\n const stage5 = await stage5DeferToDeep.evaluate(ctx);\n return mapOutcomeToDecision('defer-to-deep', stage5, ctx.candidate);\n }\n\n return Object.freeze({\n mode,\n localePack,\n thresholds,\n run,\n }) satisfies ConflictPipeline;\n}\n\nfunction mapOutcomeToDecision(\n stage: ConflictStage,\n outcome: StageOutcome,\n candidate: Fact,\n): ConflictDecision {\n switch (outcome.kind) {\n case 'continue':\n return { kind: 'admit', stage, reason: 'continue-fallthrough' };\n case 'admit':\n return {\n kind: 'admit',\n stage,\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n case 'dedup':\n return {\n kind: 'dedup',\n stage,\n existingId: outcome.existingId,\n ...(outcome.similarity !== undefined ? { similarity: outcome.similarity } : {}),\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n case 'supersede':\n return {\n kind: 'supersede',\n stage,\n existingId: outcome.existingId,\n reason: outcome.reason,\n };\n case 'pending':\n return {\n kind: 'pending',\n stage,\n candidateId: candidate.id,\n conflictingIds: outcome.conflictingIds,\n ...(outcome.similarity !== undefined ? { similarity: outcome.similarity } : {}),\n ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),\n };\n }\n}\n\nasync function persistDecision(\n decision: ConflictDecision,\n candidate: Fact,\n conflictStore: ConflictPipelineDeps['store']['conflicts'],\n now: () => string,\n): Promise<void> {\n if (conflictStore === undefined) return;\n const scope = {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n };\n const detectionZone = decisionToZone(decision);\n const auditInput: Parameters<NonNullable<typeof conflictStore>['recordDecision']>[0] = {\n scope,\n candidateId: candidate.id,\n decision: decision.kind,\n stage: decision.stage,\n ...(detectionZone !== undefined ? { detectionZone } : {}),\n ...(decision.kind === 'dedup' && decision.existingId !== undefined\n ? { existingId: decision.existingId }\n : {}),\n ...(decision.kind === 'supersede' ? { existingId: decision.existingId } : {}),\n ...(decision.kind === 'dedup' && decision.similarity !== undefined\n ? { similarity: decision.similarity }\n : {}),\n ...(decision.kind === 'pending' && decision.similarity !== undefined\n ? { similarity: decision.similarity }\n : {}),\n ...(decision.kind !== 'admit' && decision.reason !== undefined\n ? { reason: decision.reason }\n : {}),\n ...(decision.kind === 'admit' && decision.reason !== undefined\n ? { reason: decision.reason }\n : {}),\n };\n await conflictStore.recordDecision(auditInput);\n if (decision.kind === 'pending') {\n await conflictStore.enqueuePending({\n scope,\n factId: candidate.id,\n candidateText: candidate.text,\n stage: decision.stage,\n conflictingIds: decision.conflictingIds,\n ...(decision.reason !== undefined ? { reason: decision.reason } : {}),\n });\n }\n void now;\n}\n\nfunction decisionToZone(decision: ConflictDecision): string | undefined {\n switch (decision.kind) {\n case 'admit':\n if (decision.reason === 'embedding-cold-zone') return 'cold';\n return undefined;\n case 'dedup':\n if (decision.reason === 'embedding-hot-zone') return 'hot';\n if (decision.reason === 'embedding-near-dup-zone') return 'near-dup';\n if (decision.reason === 'exact-hash-match') return 'exact';\n if (decision.reason === 'subject-predicate-match-same-object') return 'subject-predicate';\n return undefined;\n case 'supersede':\n if (decision.reason.startsWith('regex-supersede-marker')) return 'heuristic';\n if (decision.reason.startsWith('regex-negation')) return 'heuristic';\n if (decision.reason.startsWith('subject-predicate-match')) return 'subject-predicate';\n return undefined;\n case 'pending':\n return 'conflict-check';\n }\n}\n\nasync function collectVectorCandidates(\n deps: ConflictPipelineDeps,\n candidate: Fact,\n topK: number,\n): Promise<ReadonlyArray<MemoryHit<Fact>>> {\n if (deps.embedder === null || deps.embedderId === null) return [];\n const adapter = deps.store.semantic;\n if (typeof adapter.searchVector !== 'function') return [];\n checkAbort(deps.signal);\n // PS-10: the candidate is compared against stored facts, which are embedded\n // as `passage` — keep it in the same space for the asymmetric (E5) embedder.\n const [vector] = await deps.embedder.embed([candidate.text], { taskType: 'passage' });\n if (vector === undefined) return [];\n checkAbort(deps.signal);\n const scope = {\n userId: candidate.userId,\n ...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),\n ...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),\n };\n // Vector search may throw when the storage layer's vector extension\n // is missing or mis-configured (e.g. `sqlite-vec` not loaded). Fall\n // back to \"no candidates\" so the conflict pipeline degrades to the\n // 10a straight-through write rather than blocking the agent loop.\n // AbortError is re-raised so cancellation propagates up.\n let hits: ReadonlyArray<MemoryHit<Fact>>;\n try {\n hits = await adapter.searchVector(scope, vector, deps.embedderId, topK);\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') throw err;\n return [];\n }\n return [...hits].filter((hit) => hit.record.id !== candidate.id);\n}\n\nfunction validateThresholds(thresholds: ConflictThresholds): void {\n if (\n !(\n thresholds.hot >= thresholds.nearDup &&\n thresholds.nearDup >= thresholds.cold &&\n thresholds.hot <= 1 &&\n thresholds.cold >= 0\n )\n ) {\n throw new TypeError(\n `[graphorin/memory] invalid conflict thresholds: expected 0 <= cold <= nearDup <= hot <= 1, got ${JSON.stringify(thresholds)}.`,\n );\n }\n}\n\n/**\n * Throw the canonical `AbortError` when the supplied signal is\n * already aborted. Safe to call with `undefined`.\n *\n * @internal\n */\nfunction checkAbort(signal: AbortSignal | undefined): void {\n if (signal === undefined) return;\n if (signal.aborted) {\n if (typeof signal.throwIfAborted === 'function') {\n signal.throwIfAborted();\n }\n throw new DOMException('The operation was aborted.', 'AbortError');\n }\n}\n\nfunction warnBypass(): void {\n // Use the structured logger once it lands (Phase 04 follow-up). For\n // now, surface a one-shot console.warn so operators notice the\n // regression risk per the Phase 10b spec (\"WARN-once per process\").\n console.warn(\n '[graphorin/memory] conflict pipeline disabled (mode: \"off\"). ' +\n 'Semantic writes fall back to a straight-through path — bi-temporal supersede / dedup ' +\n 'will not fire until you re-enable the pipeline.',\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { md5 } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/conflict/stages/helpers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Internal helpers shared across the five pipeline stages.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Lowercase + collapse whitespace + trim. Used by Stage 1 (exact
|
|
12
|
+
* dedup) to compute the canonical fact body that feeds the MD5
|
|
13
|
+
* comparison.
|
|
14
|
+
*/
|
|
15
|
+
function canonicaliseFactBody(text) {
|
|
16
|
+
return text.replace(/\s+/g, " ").trim().toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Stable hash for a canonical fact body. We re-use the framework's
|
|
20
|
+
* MD5 helper to stay consistent with the storage layer's existing
|
|
21
|
+
* dedup hash column (see `facts.hash`).
|
|
22
|
+
*/
|
|
23
|
+
function fastFactHash(text) {
|
|
24
|
+
return md5(text);
|
|
25
|
+
}
|
|
26
|
+
/** Strip leading articles + trim a candidate subject token. */
|
|
27
|
+
function normaliseSubject(input, pack) {
|
|
28
|
+
const tokens = canonicaliseFactBody(input).split(" ");
|
|
29
|
+
const stop = new Set(pack.subjectStopWords.map((s) => s.toLowerCase()));
|
|
30
|
+
return tokens.filter((token) => token.length > 0 && !stop.has(token)).join(" ");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Naive subject / predicate / object split. The locale pack supplies
|
|
34
|
+
* the verb tokens used as predicate normalisers — the first matching
|
|
35
|
+
* verb in the sentence becomes the predicate and splits the rest
|
|
36
|
+
* into subject (left side) and object (right side). The split is
|
|
37
|
+
* intentionally simple: anything more clever lives behind the entity-
|
|
38
|
+
* linking layer (post-MVP).
|
|
39
|
+
*/
|
|
40
|
+
function splitSubjectPredicateObject(text, pack) {
|
|
41
|
+
const canonical = canonicaliseFactBody(text);
|
|
42
|
+
if (canonical.length === 0) return null;
|
|
43
|
+
const tokens = canonical.split(" ");
|
|
44
|
+
const verbs = new Set(pack.predicateNormalisers.map((v) => v.toLowerCase()));
|
|
45
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
46
|
+
const token = tokens[i];
|
|
47
|
+
if (token === void 0) continue;
|
|
48
|
+
const stripped = token.replace(/[.,!?;:]/g, "");
|
|
49
|
+
if (verbs.has(stripped)) {
|
|
50
|
+
const subject = normaliseSubject(tokens.slice(0, i).join(" "), pack);
|
|
51
|
+
const predicate = stripped;
|
|
52
|
+
const object = canonicaliseFactBody(tokens.slice(i + 1).join(" "));
|
|
53
|
+
if (subject.length === 0 || object.length === 0) continue;
|
|
54
|
+
return {
|
|
55
|
+
subject,
|
|
56
|
+
predicate,
|
|
57
|
+
object
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { canonicaliseFactBody, fastFactHash, splitSubjectPredicateObject };
|
|
66
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","names":[],"sources":["../../../src/conflict/stages/helpers.ts"],"sourcesContent":["/**\n * Internal helpers shared across the five pipeline stages.\n *\n * @packageDocumentation\n * @internal\n */\n\nimport { md5 } from '@graphorin/core';\nimport type { LocalePack } from '../locale-packs/index.js';\n\n/**\n * Lowercase + collapse whitespace + trim. Used by Stage 1 (exact\n * dedup) to compute the canonical fact body that feeds the MD5\n * comparison.\n */\nexport function canonicaliseFactBody(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().toLowerCase();\n}\n\n/**\n * Stable hash for a canonical fact body. We re-use the framework's\n * MD5 helper to stay consistent with the storage layer's existing\n * dedup hash column (see `facts.hash`).\n */\nexport function fastFactHash(text: string): string {\n return md5(text);\n}\n\n/** Strip leading articles + trim a candidate subject token. */\nexport function normaliseSubject(input: string, pack: LocalePack): string {\n const tokens = canonicaliseFactBody(input).split(' ');\n const stop = new Set(pack.subjectStopWords.map((s) => s.toLowerCase()));\n return tokens.filter((token) => token.length > 0 && !stop.has(token)).join(' ');\n}\n\n/**\n * Naive subject / predicate / object split. The locale pack supplies\n * the verb tokens used as predicate normalisers — the first matching\n * verb in the sentence becomes the predicate and splits the rest\n * into subject (left side) and object (right side). The split is\n * intentionally simple: anything more clever lives behind the entity-\n * linking layer (post-MVP).\n */\nexport function splitSubjectPredicateObject(\n text: string,\n pack: LocalePack,\n): { readonly subject: string; readonly predicate: string; readonly object: string } | null {\n const canonical = canonicaliseFactBody(text);\n if (canonical.length === 0) return null;\n const tokens = canonical.split(' ');\n const verbs = new Set(pack.predicateNormalisers.map((v) => v.toLowerCase()));\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === undefined) continue;\n const stripped = token.replace(/[.,!?;:]/g, '');\n if (verbs.has(stripped)) {\n const subject = normaliseSubject(tokens.slice(0, i).join(' '), pack);\n const predicate = stripped;\n const object = canonicaliseFactBody(tokens.slice(i + 1).join(' '));\n if (subject.length === 0 || object.length === 0) continue;\n return { subject, predicate, object };\n }\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,SAAgB,qBAAqB,MAAsB;AACzD,QAAO,KAAK,QAAQ,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa;;;;;;;AAQvD,SAAgB,aAAa,MAAsB;AACjD,QAAO,IAAI,KAAK;;;AAIlB,SAAgB,iBAAiB,OAAe,MAA0B;CACxE,MAAM,SAAS,qBAAqB,MAAM,CAAC,MAAM,IAAI;CACrD,MAAM,OAAO,IAAI,IAAI,KAAK,iBAAiB,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC;AACvE,QAAO,OAAO,QAAQ,UAAU,MAAM,SAAS,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC,KAAK,IAAI;;;;;;;;;;AAWjF,SAAgB,4BACd,MACA,MAC0F;CAC1F,MAAM,YAAY,qBAAqB,KAAK;AAC5C,KAAI,UAAU,WAAW,EAAG,QAAO;CACnC,MAAM,SAAS,UAAU,MAAM,IAAI;CACnC,MAAM,QAAQ,IAAI,IAAI,KAAK,qBAAqB,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC;AAC5E,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,QAAQ,OAAO;AACrB,MAAI,UAAU,OAAW;EACzB,MAAM,WAAW,MAAM,QAAQ,aAAa,GAAG;AAC/C,MAAI,MAAM,IAAI,SAAS,EAAE;GACvB,MAAM,UAAU,iBAAiB,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,KAAK;GACpE,MAAM,YAAY;GAClB,MAAM,SAAS,qBAAqB,OAAO,MAAM,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAClE,OAAI,QAAQ,WAAW,KAAK,OAAO,WAAW,EAAG;AACjD,UAAO;IAAE;IAAS;IAAW;IAAQ;;;AAGzC,QAAO"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { canonicaliseFactBody, fastFactHash } from "./helpers.js";
|
|
2
|
+
|
|
3
|
+
//#region src/conflict/stages/stage1-exact-dedup.ts
|
|
4
|
+
const stage1ExactDedup = {
|
|
5
|
+
id: "exact-dedup",
|
|
6
|
+
async evaluate(ctx) {
|
|
7
|
+
const candidateHash = fastFactHash(canonicaliseFactBody(ctx.candidate.text));
|
|
8
|
+
if (ctx.existing.length === 0) return { kind: "continue" };
|
|
9
|
+
for (const hit of ctx.existing) if (fastFactHash(canonicaliseFactBody(hit.record.text)) === candidateHash) return {
|
|
10
|
+
kind: "dedup",
|
|
11
|
+
existingId: hit.record.id,
|
|
12
|
+
similarity: 1,
|
|
13
|
+
reason: "exact-hash-match"
|
|
14
|
+
};
|
|
15
|
+
return { kind: "continue" };
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { stage1ExactDedup };
|
|
21
|
+
//# sourceMappingURL=stage1-exact-dedup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage1-exact-dedup.js","names":["stage1ExactDedup: PipelineStage"],"sources":["../../../src/conflict/stages/stage1-exact-dedup.ts"],"sourcesContent":["/**\n * Stage 1 — Exact dedup. Computes an MD5 hash on the canonical fact\n * body (lowercase + trimmed + collapsed whitespace) and short-circuits\n * to `dedup` when an existing fact in the supplied vector hit list\n * shares the same canonical form.\n *\n * Stage 1 only inspects `existing` when Stage 2 already populated it\n * for the orchestrator (re-using the embedding round-trip). When the\n * orchestrator calls Stage 1 with an empty list the stage falls\n * through to `'continue'` — Stage 2 will gather candidates and Stage 1\n * is folded into Stage 2's HOT zone via the same canonical-form\n * comparison.\n *\n * @packageDocumentation\n */\n\nimport type { PipelineStage } from '../types.js';\nimport { canonicaliseFactBody, fastFactHash } from './helpers.js';\n\nexport const stage1ExactDedup: PipelineStage = {\n id: 'exact-dedup',\n async evaluate(ctx) {\n const candidateHash = fastFactHash(canonicaliseFactBody(ctx.candidate.text));\n if (ctx.existing.length === 0) {\n return { kind: 'continue' };\n }\n for (const hit of ctx.existing) {\n const existingHash = fastFactHash(canonicaliseFactBody(hit.record.text));\n if (existingHash === candidateHash) {\n return {\n kind: 'dedup',\n existingId: hit.record.id,\n similarity: 1,\n reason: 'exact-hash-match',\n };\n }\n }\n return { kind: 'continue' };\n },\n};\n"],"mappings":";;;AAmBA,MAAaA,mBAAkC;CAC7C,IAAI;CACJ,MAAM,SAAS,KAAK;EAClB,MAAM,gBAAgB,aAAa,qBAAqB,IAAI,UAAU,KAAK,CAAC;AAC5E,MAAI,IAAI,SAAS,WAAW,EAC1B,QAAO,EAAE,MAAM,YAAY;AAE7B,OAAK,MAAM,OAAO,IAAI,SAEpB,KADqB,aAAa,qBAAqB,IAAI,OAAO,KAAK,CAAC,KACnD,cACnB,QAAO;GACL,MAAM;GACN,YAAY,IAAI,OAAO;GACvB,YAAY;GACZ,QAAQ;GACT;AAGL,SAAO,EAAE,MAAM,YAAY;;CAE9B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/conflict/stages/stage2-embedding-three-zone.ts
|
|
2
|
+
const stage2EmbeddingThreeZone = {
|
|
3
|
+
id: "embedding-three-zone",
|
|
4
|
+
async evaluate(ctx) {
|
|
5
|
+
if (ctx.existing.length === 0) return {
|
|
6
|
+
kind: "admit",
|
|
7
|
+
reason: "no-existing-candidates"
|
|
8
|
+
};
|
|
9
|
+
const top = ctx.existing[0];
|
|
10
|
+
if (top === void 0) return {
|
|
11
|
+
kind: "admit",
|
|
12
|
+
reason: "no-existing-candidates"
|
|
13
|
+
};
|
|
14
|
+
const similarity = top.score;
|
|
15
|
+
if (similarity >= ctx.thresholds.hot) return {
|
|
16
|
+
kind: "dedup",
|
|
17
|
+
existingId: top.record.id,
|
|
18
|
+
similarity,
|
|
19
|
+
reason: "embedding-hot-zone"
|
|
20
|
+
};
|
|
21
|
+
if (similarity >= ctx.thresholds.nearDup) return {
|
|
22
|
+
kind: "dedup",
|
|
23
|
+
existingId: top.record.id,
|
|
24
|
+
similarity,
|
|
25
|
+
reason: "embedding-near-dup-zone"
|
|
26
|
+
};
|
|
27
|
+
if (similarity > ctx.thresholds.cold) return { kind: "continue" };
|
|
28
|
+
return {
|
|
29
|
+
kind: "admit",
|
|
30
|
+
reason: "embedding-cold-zone"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { stage2EmbeddingThreeZone };
|
|
37
|
+
//# sourceMappingURL=stage2-embedding-three-zone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage2-embedding-three-zone.js","names":["stage2EmbeddingThreeZone: PipelineStage"],"sources":["../../../src/conflict/stages/stage2-embedding-three-zone.ts"],"sourcesContent":["/**\n * Stage 2 — Embedding three-zone classifier (RB-02 §8.1).\n *\n * Inspects the candidate similarity against the top-K nearest\n * neighbours surfaced by the semantic vector store and routes the\n * decision into one of four zones:\n *\n * - HOT (`sim ≥ thresholds.hot`): silent dedup — the existing fact is\n * semantically identical, no audit-worthy conflict.\n * - NEAR-DUP (`thresholds.nearDup ≤ sim < thresholds.hot`): same\n * fact phrased differently — dedup with `near-dup` zone label so\n * operators can spot the case in `fact_conflicts`.\n * - CONFLICT-CHECK (`thresholds.cold < sim < thresholds.nearDup`):\n * candidate sounds related; defer decision to Stages 3 → 4 → 5.\n * - COLD (`sim ≤ thresholds.cold`): no conflict — admit the candidate.\n *\n * @packageDocumentation\n */\n\nimport type { PipelineStage, StageOutcome } from '../types.js';\n\nexport const stage2EmbeddingThreeZone: PipelineStage = {\n id: 'embedding-three-zone',\n async evaluate(ctx): Promise<StageOutcome> {\n if (ctx.existing.length === 0) {\n return { kind: 'admit', reason: 'no-existing-candidates' };\n }\n const top = ctx.existing[0];\n if (top === undefined) {\n return { kind: 'admit', reason: 'no-existing-candidates' };\n }\n const similarity = top.score;\n if (similarity >= ctx.thresholds.hot) {\n return {\n kind: 'dedup',\n existingId: top.record.id,\n similarity,\n reason: 'embedding-hot-zone',\n };\n }\n if (similarity >= ctx.thresholds.nearDup) {\n return {\n kind: 'dedup',\n existingId: top.record.id,\n similarity,\n reason: 'embedding-near-dup-zone',\n };\n }\n if (similarity > ctx.thresholds.cold) {\n return { kind: 'continue' };\n }\n return { kind: 'admit', reason: 'embedding-cold-zone' };\n },\n};\n"],"mappings":";AAqBA,MAAaA,2BAA0C;CACrD,IAAI;CACJ,MAAM,SAAS,KAA4B;AACzC,MAAI,IAAI,SAAS,WAAW,EAC1B,QAAO;GAAE,MAAM;GAAS,QAAQ;GAA0B;EAE5D,MAAM,MAAM,IAAI,SAAS;AACzB,MAAI,QAAQ,OACV,QAAO;GAAE,MAAM;GAAS,QAAQ;GAA0B;EAE5D,MAAM,aAAa,IAAI;AACvB,MAAI,cAAc,IAAI,WAAW,IAC/B,QAAO;GACL,MAAM;GACN,YAAY,IAAI,OAAO;GACvB;GACA,QAAQ;GACT;AAEH,MAAI,cAAc,IAAI,WAAW,QAC/B,QAAO;GACL,MAAM;GACN,YAAY,IAAI,OAAO;GACvB;GACA,QAAQ;GACT;AAEH,MAAI,aAAa,IAAI,WAAW,KAC9B,QAAO,EAAE,MAAM,YAAY;AAE7B,SAAO;GAAE,MAAM;GAAS,QAAQ;GAAuB;;CAE1D"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { evaluateMarkers } from "../locale-packs/types.js";
|
|
2
|
+
import "../locale-packs/index.js";
|
|
3
|
+
|
|
4
|
+
//#region src/conflict/stages/stage3-heuristic-regex.ts
|
|
5
|
+
/**
|
|
6
|
+
* Stage 3 — Heuristic regex (per-locale, pluggable).
|
|
7
|
+
*
|
|
8
|
+
* Inspects the candidate fact body for explicit supersede or negation
|
|
9
|
+
* markers from the active locale pack (English by default). When a
|
|
10
|
+
* marker fires the stage emits a `supersede` outcome against the
|
|
11
|
+
* highest-scoring CONFLICT-CHECK candidate from Stage 2.
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
const stage3HeuristicRegex = {
|
|
16
|
+
id: "heuristic-regex",
|
|
17
|
+
async evaluate(ctx) {
|
|
18
|
+
const top = ctx.existing[0];
|
|
19
|
+
if (top === void 0) return { kind: "continue" };
|
|
20
|
+
const candidateText = ctx.candidate.text;
|
|
21
|
+
const supersede = evaluateMarkers(candidateText, ctx.localePack.supersedeMarkers);
|
|
22
|
+
if (supersede.matched) return {
|
|
23
|
+
kind: "supersede",
|
|
24
|
+
existingId: top.record.id,
|
|
25
|
+
reason: formatReason("regex-supersede-marker", supersede)
|
|
26
|
+
};
|
|
27
|
+
const negation = evaluateMarkers(candidateText, ctx.localePack.negationMarkers);
|
|
28
|
+
if (negation.matched) return {
|
|
29
|
+
kind: "supersede",
|
|
30
|
+
existingId: top.record.id,
|
|
31
|
+
reason: formatReason("regex-negation", negation)
|
|
32
|
+
};
|
|
33
|
+
return { kind: "continue" };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function formatReason(base, match) {
|
|
37
|
+
const parts = [base];
|
|
38
|
+
if (match.kind !== void 0) parts.push(`kind=${match.kind}`);
|
|
39
|
+
if (match.excerpt !== void 0) parts.push(`marker=${match.excerpt}`);
|
|
40
|
+
return parts.join("; ");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { stage3HeuristicRegex };
|
|
45
|
+
//# sourceMappingURL=stage3-heuristic-regex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage3-heuristic-regex.js","names":["stage3HeuristicRegex: PipelineStage"],"sources":["../../../src/conflict/stages/stage3-heuristic-regex.ts"],"sourcesContent":["/**\n * Stage 3 — Heuristic regex (per-locale, pluggable).\n *\n * Inspects the candidate fact body for explicit supersede or negation\n * markers from the active locale pack (English by default). When a\n * marker fires the stage emits a `supersede` outcome against the\n * highest-scoring CONFLICT-CHECK candidate from Stage 2.\n *\n * @packageDocumentation\n */\n\nimport { evaluateMarkers } from '../locale-packs/index.js';\nimport type { PipelineStage, StageOutcome } from '../types.js';\n\nexport const stage3HeuristicRegex: PipelineStage = {\n id: 'heuristic-regex',\n async evaluate(ctx): Promise<StageOutcome> {\n const top = ctx.existing[0];\n if (top === undefined) {\n return { kind: 'continue' };\n }\n const candidateText = ctx.candidate.text;\n const supersede = evaluateMarkers(candidateText, ctx.localePack.supersedeMarkers);\n if (supersede.matched) {\n return {\n kind: 'supersede',\n existingId: top.record.id,\n reason: formatReason('regex-supersede-marker', supersede),\n };\n }\n const negation = evaluateMarkers(candidateText, ctx.localePack.negationMarkers);\n if (negation.matched) {\n return {\n kind: 'supersede',\n existingId: top.record.id,\n reason: formatReason('regex-negation', negation),\n };\n }\n return { kind: 'continue' };\n },\n};\n\nfunction formatReason(\n base: string,\n match: { kind?: string | undefined; excerpt?: string | undefined },\n): string {\n const parts = [base];\n if (match.kind !== undefined) parts.push(`kind=${match.kind}`);\n if (match.excerpt !== undefined) parts.push(`marker=${match.excerpt}`);\n return parts.join('; ');\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAaA,uBAAsC;CACjD,IAAI;CACJ,MAAM,SAAS,KAA4B;EACzC,MAAM,MAAM,IAAI,SAAS;AACzB,MAAI,QAAQ,OACV,QAAO,EAAE,MAAM,YAAY;EAE7B,MAAM,gBAAgB,IAAI,UAAU;EACpC,MAAM,YAAY,gBAAgB,eAAe,IAAI,WAAW,iBAAiB;AACjF,MAAI,UAAU,QACZ,QAAO;GACL,MAAM;GACN,YAAY,IAAI,OAAO;GACvB,QAAQ,aAAa,0BAA0B,UAAU;GAC1D;EAEH,MAAM,WAAW,gBAAgB,eAAe,IAAI,WAAW,gBAAgB;AAC/E,MAAI,SAAS,QACX,QAAO;GACL,MAAM;GACN,YAAY,IAAI,OAAO;GACvB,QAAQ,aAAa,kBAAkB,SAAS;GACjD;AAEH,SAAO,EAAE,MAAM,YAAY;;CAE9B;AAED,SAAS,aACP,MACA,OACQ;CACR,MAAM,QAAQ,CAAC,KAAK;AACpB,KAAI,MAAM,SAAS,OAAW,OAAM,KAAK,QAAQ,MAAM,OAAO;AAC9D,KAAI,MAAM,YAAY,OAAW,OAAM,KAAK,UAAU,MAAM,UAAU;AACtE,QAAO,MAAM,KAAK,KAAK"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { splitSubjectPredicateObject } from "./helpers.js";
|
|
2
|
+
|
|
3
|
+
//#region src/conflict/stages/stage4-subject-predicate.ts
|
|
4
|
+
const stage4SubjectPredicate = {
|
|
5
|
+
id: "subject-predicate",
|
|
6
|
+
async evaluate(ctx) {
|
|
7
|
+
const candidateTriple = splitSubjectPredicateObject(ctx.candidate.text, ctx.localePack);
|
|
8
|
+
if (candidateTriple === null) return { kind: "continue" };
|
|
9
|
+
for (const hit of ctx.existing) {
|
|
10
|
+
const existingTriple = splitSubjectPredicateObject(hit.record.text, ctx.localePack);
|
|
11
|
+
if (existingTriple === null) continue;
|
|
12
|
+
if (candidateTriple.subject !== existingTriple.subject) continue;
|
|
13
|
+
if (candidateTriple.predicate !== existingTriple.predicate) continue;
|
|
14
|
+
if (candidateTriple.object === existingTriple.object) return {
|
|
15
|
+
kind: "dedup",
|
|
16
|
+
existingId: hit.record.id,
|
|
17
|
+
reason: "subject-predicate-match-same-object"
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
kind: "supersede",
|
|
21
|
+
existingId: hit.record.id,
|
|
22
|
+
reason: `subject-predicate-match; subject=${candidateTriple.subject}; predicate=${candidateTriple.predicate}`
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return { kind: "continue" };
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { stage4SubjectPredicate };
|
|
31
|
+
//# sourceMappingURL=stage4-subject-predicate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage4-subject-predicate.js","names":["stage4SubjectPredicate: PipelineStage"],"sources":["../../../src/conflict/stages/stage4-subject-predicate.ts"],"sourcesContent":["/**\n * Stage 4 — Subject / predicate match.\n *\n * Naively splits each fact body into `(subject, predicate, object)`\n * triples using the locale pack's predicate normalisers. When the\n * candidate and an existing CONFLICT-CHECK fact share the same\n * subject + predicate but differ on the object, Stage 4 marks the\n * existing fact as superseded.\n *\n * The split is intentionally simple: anything more sophisticated\n * (full entity linking) lives in the post-MVP knowledge-graph layer.\n *\n * @packageDocumentation\n */\n\nimport type { PipelineStage, StageOutcome } from '../types.js';\nimport { splitSubjectPredicateObject } from './helpers.js';\n\nexport const stage4SubjectPredicate: PipelineStage = {\n id: 'subject-predicate',\n async evaluate(ctx): Promise<StageOutcome> {\n const candidateTriple = splitSubjectPredicateObject(ctx.candidate.text, ctx.localePack);\n if (candidateTriple === null) {\n return { kind: 'continue' };\n }\n for (const hit of ctx.existing) {\n const existingTriple = splitSubjectPredicateObject(hit.record.text, ctx.localePack);\n if (existingTriple === null) continue;\n if (candidateTriple.subject !== existingTriple.subject) continue;\n if (candidateTriple.predicate !== existingTriple.predicate) continue;\n if (candidateTriple.object === existingTriple.object) {\n return {\n kind: 'dedup',\n existingId: hit.record.id,\n reason: 'subject-predicate-match-same-object',\n };\n }\n return {\n kind: 'supersede',\n existingId: hit.record.id,\n reason: `subject-predicate-match; subject=${candidateTriple.subject}; predicate=${candidateTriple.predicate}`,\n };\n }\n return { kind: 'continue' };\n },\n};\n"],"mappings":";;;AAkBA,MAAaA,yBAAwC;CACnD,IAAI;CACJ,MAAM,SAAS,KAA4B;EACzC,MAAM,kBAAkB,4BAA4B,IAAI,UAAU,MAAM,IAAI,WAAW;AACvF,MAAI,oBAAoB,KACtB,QAAO,EAAE,MAAM,YAAY;AAE7B,OAAK,MAAM,OAAO,IAAI,UAAU;GAC9B,MAAM,iBAAiB,4BAA4B,IAAI,OAAO,MAAM,IAAI,WAAW;AACnF,OAAI,mBAAmB,KAAM;AAC7B,OAAI,gBAAgB,YAAY,eAAe,QAAS;AACxD,OAAI,gBAAgB,cAAc,eAAe,UAAW;AAC5D,OAAI,gBAAgB,WAAW,eAAe,OAC5C,QAAO;IACL,MAAM;IACN,YAAY,IAAI,OAAO;IACvB,QAAQ;IACT;AAEH,UAAO;IACL,MAAM;IACN,YAAY,IAAI,OAAO;IACvB,QAAQ,oCAAoC,gBAAgB,QAAQ,cAAc,gBAAgB;IACnG;;AAEH,SAAO,EAAE,MAAM,YAAY;;CAE9B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/conflict/stages/stage5-defer-to-deep.ts
|
|
2
|
+
const stage5DeferToDeep = {
|
|
3
|
+
id: "defer-to-deep",
|
|
4
|
+
async evaluate(ctx) {
|
|
5
|
+
if (ctx.existing.length === 0) return {
|
|
6
|
+
kind: "admit",
|
|
7
|
+
reason: "stage5-no-candidates"
|
|
8
|
+
};
|
|
9
|
+
const conflictingIds = ctx.existing.map((hit) => hit.record.id);
|
|
10
|
+
const top = ctx.existing[0];
|
|
11
|
+
return {
|
|
12
|
+
kind: "pending",
|
|
13
|
+
conflictingIds,
|
|
14
|
+
...top !== void 0 ? { similarity: top.score } : {},
|
|
15
|
+
reason: "awaiting-deep-llm-judge"
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { stage5DeferToDeep };
|
|
22
|
+
//# sourceMappingURL=stage5-defer-to-deep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage5-defer-to-deep.js","names":["stage5DeferToDeep: PipelineStage"],"sources":["../../../src/conflict/stages/stage5-defer-to-deep.ts"],"sourcesContent":["/**\n * Stage 5 — Defer to deep LLM judge.\n *\n * Reached only when Stages 1–4 left the decision unresolved. The\n * stage records every CONFLICT-CHECK candidate id surfaced by Stage 2\n * so the orchestrator can hand the queue write to the storage layer\n * (the actual `conflict_check_pending` insert is the orchestrator's\n * responsibility — Stage 5 only computes the payload).\n *\n * The candidate fact itself is admitted with a `pending` flag so it\n * remains searchable; the deep phase (Phase 10c) drains the queue and\n * applies the resolution later.\n *\n * @packageDocumentation\n */\n\nimport type { PipelineStage, StageOutcome } from '../types.js';\n\nexport const stage5DeferToDeep: PipelineStage = {\n id: 'defer-to-deep',\n async evaluate(ctx): Promise<StageOutcome> {\n if (ctx.existing.length === 0) {\n return { kind: 'admit', reason: 'stage5-no-candidates' };\n }\n const conflictingIds = ctx.existing.map((hit) => hit.record.id);\n const top = ctx.existing[0];\n return {\n kind: 'pending',\n conflictingIds,\n ...(top !== undefined ? { similarity: top.score } : {}),\n reason: 'awaiting-deep-llm-judge',\n };\n },\n};\n"],"mappings":";AAkBA,MAAaA,oBAAmC;CAC9C,IAAI;CACJ,MAAM,SAAS,KAA4B;AACzC,MAAI,IAAI,SAAS,WAAW,EAC1B,QAAO;GAAE,MAAM;GAAS,QAAQ;GAAwB;EAE1D,MAAM,iBAAiB,IAAI,SAAS,KAAK,QAAQ,IAAI,OAAO,GAAG;EAC/D,MAAM,MAAM,IAAI,SAAS;AACzB,SAAO;GACL,MAAM;GACN;GACA,GAAI,QAAQ,SAAY,EAAE,YAAY,IAAI,OAAO,GAAG,EAAE;GACtD,QAAQ;GACT;;CAEJ"}
|