@remnic/core 9.3.699 → 9.3.701
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/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +14 -10
- package/dist/access-cli.js +30 -28
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +18 -14
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +17 -13
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +16 -12
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DftqtNUy.d.ts → access-service-CGVWK6lZ.d.ts} +2 -2
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +13 -9
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/action-confidence.js +2 -2
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-D7YDNNF7.d.ts → catalog-DBIghceA.d.ts} +27 -53
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +8 -4
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-E6GOVHHJ.js → chunk-27LQPUMZ.js} +3 -3
- package/dist/{chunk-XJNBEDFE.js → chunk-3FAMU5TX.js} +31 -74
- package/dist/chunk-3FAMU5TX.js.map +1 -0
- package/dist/chunk-3JJWNZTT.js +446 -0
- package/dist/chunk-3JJWNZTT.js.map +1 -0
- package/dist/{chunk-JYVD2XZ4.js → chunk-4HIAWLA2.js} +58 -25
- package/dist/chunk-4HIAWLA2.js.map +1 -0
- package/dist/{chunk-A62RAIBN.js → chunk-6W2D6FGG.js} +2 -2
- package/dist/{chunk-COEZR6F5.js → chunk-7TAQEPLE.js} +2 -2
- package/dist/{chunk-TIVZ4MCG.js → chunk-D75JXBV4.js} +3 -3
- package/dist/{chunk-YY2HGKWR.js → chunk-DEDQXIDL.js} +2 -2
- package/dist/{chunk-XFG3PVZE.js → chunk-FUCJAZ25.js} +8 -8
- package/dist/{chunk-EZWQZYLK.js → chunk-HF4N43Q7.js} +2 -2
- package/dist/{chunk-HZ5DRA4Q.js → chunk-HXHKLVAS.js} +20 -20
- package/dist/{chunk-AC5LO7IU.js → chunk-I4WBNM3M.js} +7 -1
- package/dist/chunk-I4WBNM3M.js.map +1 -0
- package/dist/{chunk-QVTSLRKT.js → chunk-IJEZMWKA.js} +3 -3
- package/dist/{chunk-ZXUOAFUG.js → chunk-IKNQAGBV.js} +1 -1
- package/dist/chunk-IKNQAGBV.js.map +1 -0
- package/dist/{chunk-RC3CNIPK.js → chunk-IO5NQEGZ.js} +2 -2
- package/dist/{chunk-2ULWWAQH.js → chunk-ISLJ5WIM.js} +2 -2
- package/dist/{chunk-2KJG6ZZK.js → chunk-IYOPIG3E.js} +2 -2
- package/dist/{chunk-M4DQWUKX.js → chunk-JKOKX3PS.js} +62 -163
- package/dist/chunk-JKOKX3PS.js.map +1 -0
- package/dist/{chunk-SJQ4HY3E.js → chunk-JO3E5VGS.js} +2 -2
- package/dist/{chunk-3LSSFCB4.js → chunk-K4DWSPMW.js} +11 -1
- package/dist/chunk-K4DWSPMW.js.map +1 -0
- package/dist/{chunk-IZ5E6ZTK.js → chunk-KF4TXW7Z.js} +6 -6
- package/dist/{chunk-CQ4PGFMC.js → chunk-KS7WQ4BZ.js} +4 -4
- package/dist/chunk-LTJAMRGI.js +286 -0
- package/dist/chunk-LTJAMRGI.js.map +1 -0
- package/dist/{chunk-PJIHCOGV.js → chunk-MNU5G4TK.js} +2 -2
- package/dist/{chunk-2K64VH66.js → chunk-ODTWHSY2.js} +69 -45
- package/dist/chunk-ODTWHSY2.js.map +1 -0
- package/dist/{chunk-LIWU4QI6.js → chunk-OLOYQZFB.js} +5 -5
- package/dist/{chunk-MRFVMSTR.js → chunk-OMNTQPZO.js} +20 -1
- package/dist/{chunk-MRFVMSTR.js.map → chunk-OMNTQPZO.js.map} +1 -1
- package/dist/{chunk-JI6HWBYL.js → chunk-Q4O3ET6F.js} +2 -2
- package/dist/{chunk-N55RJT4N.js → chunk-QP37KL5H.js} +2 -2
- package/dist/{chunk-LPIWEKZ3.js → chunk-SDPDU2PM.js} +3 -3
- package/dist/{chunk-RIC5U67B.js → chunk-SFMRLXIV.js} +232 -4
- package/dist/chunk-SFMRLXIV.js.map +1 -0
- package/dist/{chunk-RTN2BLZM.js → chunk-T5QAZIBO.js} +2 -2
- package/dist/{chunk-ANZLT74L.js → chunk-TFVVONWD.js} +2 -2
- package/dist/{chunk-SIDSEXUG.js → chunk-WFEZUGU5.js} +2 -2
- package/dist/{chunk-FSEQXHEZ.js → chunk-XTIRCSIH.js} +2 -2
- package/dist/{chunk-XL5RSHZP.js → chunk-YO4MBK3I.js} +2 -2
- package/dist/{chunk-AH2JUU6X.js → chunk-Z2YFLXHU.js} +2 -2
- package/dist/{chunk-MRX6S22R.js → chunk-ZT7B64BE.js} +2 -2
- package/dist/{chunk-UDDSC6PO.js → chunk-ZYNMX6IU.js} +54 -6
- package/dist/chunk-ZYNMX6IU.js.map +1 -0
- package/dist/{cli-DUMkkdLl.d.ts → cli-D3XeenwN.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +32 -28
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +7 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +7 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +7 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +6 -6
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-PLR6J5DN.js → forget-6SOIPUMQ.js} +32 -1
- package/dist/forget-6SOIPUMQ.js.map +1 -0
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +21 -10
- package/dist/index.js +65 -43
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +7 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -3
- package/dist/maintenance/rebuild-memory-projection.js +8 -4
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-provenance.js +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +8 -4
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +32 -3
- package/dist/namespaces/storage.js +7 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +19 -2
- package/dist/operator-toolkit.js +17 -11
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +9 -5
- package/dist/{orchestrator-Dv8JQu3-.d.ts → orchestrator-BzMCZlKn.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +22 -20
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +4 -4
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +5 -5
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +4 -4
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +3 -3
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +28 -28
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-CgREi9E0.d.ts → semantic-consolidation-BtUfv-AL.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +8 -4
- package/dist/semantic-rule-promotion.js +7 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +7 -3
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +306 -2
- package/dist/storage.js +6 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -2
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/summary-snapshot.js +2 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-supersession.js +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/dist/{types-CeZYSC-E.d.ts → types-PuiPZ9iE.d.ts} +34 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/utils/serialize-mutations.js +5 -280
- package/dist/utils/serialize-mutations.js.map +1 -1
- package/dist/verified-recall.js +7 -3
- package/package.json +2 -2
- package/src/config.ts +10 -0
- package/src/lifecycle/tombstones.test.ts +355 -0
- package/src/lifecycle/tombstones.ts +807 -0
- package/src/maintenance/forget.ts +41 -0
- package/src/maintenance/pattern-reinforcement.ts +47 -0
- package/src/memory-provenance.ts +12 -0
- package/src/namespaces/catalog.test.ts +222 -14
- package/src/namespaces/catalog.ts +54 -187
- package/src/namespaces/storage.ts +120 -80
- package/src/operator-toolkit.ts +77 -0
- package/src/orchestrator.ts +16 -0
- package/src/review/index.ts +48 -3
- package/src/storage.ts +329 -2
- package/src/summary-snapshot.test.ts +63 -1
- package/src/summary-snapshot.ts +61 -80
- package/src/temporal-supersession.ts +38 -0
- package/src/types.ts +34 -0
- package/src/utils/serialize-mutations.ts +10 -6
- package/dist/chunk-2K64VH66.js.map +0 -1
- package/dist/chunk-3LSSFCB4.js.map +0 -1
- package/dist/chunk-AC5LO7IU.js.map +0 -1
- package/dist/chunk-JYVD2XZ4.js.map +0 -1
- package/dist/chunk-M4DQWUKX.js.map +0 -1
- package/dist/chunk-RIC5U67B.js.map +0 -1
- package/dist/chunk-UDDSC6PO.js.map +0 -1
- package/dist/chunk-XJNBEDFE.js.map +0 -1
- package/dist/chunk-ZXUOAFUG.js.map +0 -1
- package/dist/forget-PLR6J5DN.js.map +0 -1
- /package/dist/{chunk-E6GOVHHJ.js.map → chunk-27LQPUMZ.js.map} +0 -0
- /package/dist/{chunk-A62RAIBN.js.map → chunk-6W2D6FGG.js.map} +0 -0
- /package/dist/{chunk-COEZR6F5.js.map → chunk-7TAQEPLE.js.map} +0 -0
- /package/dist/{chunk-TIVZ4MCG.js.map → chunk-D75JXBV4.js.map} +0 -0
- /package/dist/{chunk-YY2HGKWR.js.map → chunk-DEDQXIDL.js.map} +0 -0
- /package/dist/{chunk-XFG3PVZE.js.map → chunk-FUCJAZ25.js.map} +0 -0
- /package/dist/{chunk-EZWQZYLK.js.map → chunk-HF4N43Q7.js.map} +0 -0
- /package/dist/{chunk-HZ5DRA4Q.js.map → chunk-HXHKLVAS.js.map} +0 -0
- /package/dist/{chunk-QVTSLRKT.js.map → chunk-IJEZMWKA.js.map} +0 -0
- /package/dist/{chunk-RC3CNIPK.js.map → chunk-IO5NQEGZ.js.map} +0 -0
- /package/dist/{chunk-2ULWWAQH.js.map → chunk-ISLJ5WIM.js.map} +0 -0
- /package/dist/{chunk-2KJG6ZZK.js.map → chunk-IYOPIG3E.js.map} +0 -0
- /package/dist/{chunk-SJQ4HY3E.js.map → chunk-JO3E5VGS.js.map} +0 -0
- /package/dist/{chunk-IZ5E6ZTK.js.map → chunk-KF4TXW7Z.js.map} +0 -0
- /package/dist/{chunk-CQ4PGFMC.js.map → chunk-KS7WQ4BZ.js.map} +0 -0
- /package/dist/{chunk-PJIHCOGV.js.map → chunk-MNU5G4TK.js.map} +0 -0
- /package/dist/{chunk-LIWU4QI6.js.map → chunk-OLOYQZFB.js.map} +0 -0
- /package/dist/{chunk-JI6HWBYL.js.map → chunk-Q4O3ET6F.js.map} +0 -0
- /package/dist/{chunk-N55RJT4N.js.map → chunk-QP37KL5H.js.map} +0 -0
- /package/dist/{chunk-LPIWEKZ3.js.map → chunk-SDPDU2PM.js.map} +0 -0
- /package/dist/{chunk-RTN2BLZM.js.map → chunk-T5QAZIBO.js.map} +0 -0
- /package/dist/{chunk-ANZLT74L.js.map → chunk-TFVVONWD.js.map} +0 -0
- /package/dist/{chunk-SIDSEXUG.js.map → chunk-WFEZUGU5.js.map} +0 -0
- /package/dist/{chunk-FSEQXHEZ.js.map → chunk-XTIRCSIH.js.map} +0 -0
- /package/dist/{chunk-XL5RSHZP.js.map → chunk-YO4MBK3I.js.map} +0 -0
- /package/dist/{chunk-AH2JUU6X.js.map → chunk-Z2YFLXHU.js.map} +0 -0
- /package/dist/{chunk-MRX6S22R.js.map → chunk-ZT7B64BE.js.map} +0 -0
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import type { StorageManager } from "../storage.js";
|
|
25
|
+
import { buildRetiredFactTombstoneInputs } from "../lifecycle/tombstones.js";
|
|
26
|
+
import { supersessionKeysForFact } from "../temporal-supersession.js";
|
|
25
27
|
import type { MemoryFile } from "../types.js";
|
|
26
28
|
|
|
27
29
|
export interface ForgetMemoryRequest {
|
|
@@ -100,6 +102,45 @@ export async function forgetMemory(
|
|
|
100
102
|
actor: "remnic-forget",
|
|
101
103
|
reasonCode: "operator_forget",
|
|
102
104
|
});
|
|
105
|
+
// Issue #1579 thread OchiF: emit a LIVE tombstone at forget time so a
|
|
106
|
+
// forgotten FACT cannot resurrect via re-extraction/import before an
|
|
107
|
+
// operator runs `remnic doctor --rebuild-tombstones`. Without this, the
|
|
108
|
+
// non-resurrection chokepoint in writeMemory has nothing to match — the
|
|
109
|
+
// forgotten fact's content hash is gone from the active index but no
|
|
110
|
+
// tombstone exists until a manual rebuild derives one — so the same fact
|
|
111
|
+
// written again through writeMemory becomes active. Mirrors supersedeMemory
|
|
112
|
+
// (contradiction) and applyTemporalSupersession via the shared
|
|
113
|
+
// buildRetiredFactTombstoneInputs helper. Best-effort (rule 34): a tombstone
|
|
114
|
+
// append failure must not fail the forget, which already succeeded on disk.
|
|
115
|
+
if (memory.frontmatter.category === "fact") {
|
|
116
|
+
for (const input of buildRetiredFactTombstoneInputs(
|
|
117
|
+
{
|
|
118
|
+
id,
|
|
119
|
+
content: memory.content,
|
|
120
|
+
...(memory.frontmatter.contentHash
|
|
121
|
+
? { contentHash: memory.frontmatter.contentHash }
|
|
122
|
+
: {}),
|
|
123
|
+
...(memory.frontmatter.entityRef
|
|
124
|
+
? { entityRef: memory.frontmatter.entityRef }
|
|
125
|
+
: {}),
|
|
126
|
+
...(memory.frontmatter.structuredAttributes
|
|
127
|
+
? { structuredAttributes: memory.frontmatter.structuredAttributes }
|
|
128
|
+
: {}),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
reason: "retraction",
|
|
132
|
+
createdBy: "user_correction",
|
|
133
|
+
createdAt: forgottenAt,
|
|
134
|
+
supersessionKeysForFact,
|
|
135
|
+
},
|
|
136
|
+
)) {
|
|
137
|
+
try {
|
|
138
|
+
await storage.appendTombstone(input);
|
|
139
|
+
} catch {
|
|
140
|
+
// Best-effort — the forget already succeeded on disk.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
103
144
|
return {
|
|
104
145
|
id,
|
|
105
146
|
path: memory.path,
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
import { clusterByKey } from "../procedural/reinforcement-core.js";
|
|
36
|
+
import { buildRetiredFactTombstoneInputs, type TombstoneReason, type TombstoneCreatedBy } from "../lifecycle/tombstones.js";
|
|
37
|
+
import { supersessionKeysForFact } from "../temporal-supersession.js";
|
|
36
38
|
import type {
|
|
37
39
|
MemoryFile,
|
|
38
40
|
MemoryFrontmatter,
|
|
@@ -50,6 +52,21 @@ export interface PatternReinforcementStorage {
|
|
|
50
52
|
memory: MemoryFile,
|
|
51
53
|
patch: Partial<MemoryFrontmatter>,
|
|
52
54
|
): Promise<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Best-effort tombstone append for retired duplicates (issue #1579 thread
|
|
57
|
+
* Oblq9). Optional so unit-test stubs need not implement it; the production
|
|
58
|
+
* StorageManager satisfies it via its appendTombstone chokepoint.
|
|
59
|
+
*/
|
|
60
|
+
appendTombstone?(input: {
|
|
61
|
+
reason: TombstoneReason;
|
|
62
|
+
createdBy: TombstoneCreatedBy;
|
|
63
|
+
sourceMemoryId: string;
|
|
64
|
+
contentHash?: string;
|
|
65
|
+
rawContent: string;
|
|
66
|
+
entityRef?: string;
|
|
67
|
+
supersessionKey?: string;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
}): Promise<string | null>;
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
export interface PatternReinforcementOptions {
|
|
@@ -345,6 +362,36 @@ export async function runPatternReinforcement(
|
|
|
345
362
|
await storage.writeMemoryFrontmatter(dup, patch);
|
|
346
363
|
supersededIds.push(dup.frontmatter.id);
|
|
347
364
|
result.duplicatesSuperseded += 1;
|
|
365
|
+
// Issue #1579 thread Oblq9 / Ocn0C / OcoPs: emit a tombstone so the
|
|
366
|
+
// retired duplicate cannot resurrect through re-extraction / import.
|
|
367
|
+
// Pattern reinforcement retires via writeMemoryFrontmatter which
|
|
368
|
+
// bypasses the writeMemory chokepoint; without this emit, the same
|
|
369
|
+
// content re-extracted before a manual rebuild would be stored active.
|
|
370
|
+
// Threads Ocn0C/OcoPs: emit one tombstone PER derived supersession key
|
|
371
|
+
// via buildRetiredFactTombstoneInputs (same helper as supersedeMemory +
|
|
372
|
+
// forgetMemory), not a single entityRef-only record — a paraphrased
|
|
373
|
+
// re-extraction with the same entity/attribute can only be caught by
|
|
374
|
+
// the keyed tier. Best-effort (the interface method is optional; a
|
|
375
|
+
// failure must not fail the reinforcement job).
|
|
376
|
+
if (storage.appendTombstone) {
|
|
377
|
+
for (const input of buildRetiredFactTombstoneInputs(
|
|
378
|
+
{
|
|
379
|
+
id: dup.frontmatter.id,
|
|
380
|
+
content: dup.content,
|
|
381
|
+
contentHash: dup.frontmatter.contentHash,
|
|
382
|
+
entityRef: dup.frontmatter.entityRef,
|
|
383
|
+
structuredAttributes: dup.frontmatter.structuredAttributes,
|
|
384
|
+
},
|
|
385
|
+
{ reason: "supersession", createdBy: "supersession", createdAt: nowIso, supersessionKeysForFact },
|
|
386
|
+
)) {
|
|
387
|
+
try {
|
|
388
|
+
await storage.appendTombstone(input);
|
|
389
|
+
} catch {
|
|
390
|
+
// Best-effort: a tombstone append failure must not fail the
|
|
391
|
+
// reinforcement job (gotcha #13 / rule 34 spirit).
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
348
395
|
}
|
|
349
396
|
|
|
350
397
|
result.clusters.push({
|
package/src/memory-provenance.ts
CHANGED
|
@@ -328,6 +328,18 @@ function inferSafetyReasons(input: {
|
|
|
328
328
|
if (frontmatter.verificationState === "disputed") {
|
|
329
329
|
reasons.push("verification=disputed");
|
|
330
330
|
}
|
|
331
|
+
// Issue #1579 — x-ray visibility (rule 34: never a silent drop). When a
|
|
332
|
+
// memory carries `blockedBy`, it was tombstone-blocked at the storage
|
|
333
|
+
// chokepoint. Surface the tombstone id (and the matched tier when known)
|
|
334
|
+
// in the safety reasons so every recall x-ray consumer (CLI / HTTP / MCP)
|
|
335
|
+
// can attribute the pending_review state to a tombstone rather than a
|
|
336
|
+
// generic low-confidence review.
|
|
337
|
+
if (typeof frontmatter.blockedBy === "string" && frontmatter.blockedBy.length > 0) {
|
|
338
|
+
reasons.push(`tombstone-blocked=${frontmatter.blockedBy}`);
|
|
339
|
+
if (frontmatter.tombstoneBlockTier) {
|
|
340
|
+
reasons.push(`tombstone-tier=${frontmatter.tombstoneBlockTier}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
331
343
|
if (stale) reasons.push("stale=true");
|
|
332
344
|
|
|
333
345
|
const boundaryScopes = userContextScopes.filter(isUserBoundaryScope);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveDefaultNamespaceRoot,
|
|
12
12
|
resolveNamespaceStorageRoot,
|
|
13
13
|
} from "./storage.js";
|
|
14
|
+
import { withHeldFileLock } from "../utils/serialize-mutations.js";
|
|
14
15
|
|
|
15
16
|
function makeConfig(memoryDir: string, overrides: Partial<PluginConfig> = {}): PluginConfig {
|
|
16
17
|
return {
|
|
@@ -2171,8 +2172,16 @@ test("registerConfiguredNamespaces skips an unsafe configured name without abort
|
|
|
2171
2172
|
|
|
2172
2173
|
// ── Round 7 (codex P2 — NBsGP): two catalog instances in the SAME process
|
|
2173
2174
|
// sharing a memoryDir must not treat each other's rebuild lock as self-held. A
|
|
2174
|
-
// touch on instance B must DROP its append while
|
|
2175
|
-
// instead of skipping the wait (same PID) and appending into
|
|
2175
|
+
// touch on instance B must DROP its append while a foreign lock is held,
|
|
2176
|
+
// instead of skipping the wait (same PID) and appending into the holder's
|
|
2177
|
+
// window.
|
|
2178
|
+
//
|
|
2179
|
+
// Issue #1524 note: the catalog now delegates to the shared withHeldFileLock
|
|
2180
|
+
// utility, which generates a per-CALL owner uuid (stronger than the previous
|
|
2181
|
+
// per-instance lockOwnerId). ANY foreign lock — including one written by
|
|
2182
|
+
// another call on the SAME instance — is therefore not self-held. The test
|
|
2183
|
+
// seeds a foreign lock with an arbitrary uuid + a heartbeat that keeps it
|
|
2184
|
+
// fresh, then asserts a touch on a fresh instance waits then DROPS.
|
|
2176
2185
|
test("a same-process second instance does not treat another instance's lock as self-held", async () => {
|
|
2177
2186
|
const memoryDir = await mkMemoryDir();
|
|
2178
2187
|
try {
|
|
@@ -2183,10 +2192,12 @@ test("a same-process second instance does not treat another instance's lock as s
|
|
|
2183
2192
|
await mkdir(stateDir, { recursive: true });
|
|
2184
2193
|
const lockPath = path.join(stateDir, "namespaces.rebuild.lock");
|
|
2185
2194
|
|
|
2186
|
-
//
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2195
|
+
// A foreign held lock: a different PID + a real UUID owner id (so it is
|
|
2196
|
+
// NEVER mistaken for self by any catalog instance in any process) + a fresh
|
|
2197
|
+
// mtime. A heartbeat keeps it fresh so it is never broken as stale during
|
|
2198
|
+
// the touch's bounded wait.
|
|
2199
|
+
const foreignOwner = "00000000-0000-4000-8000-000000000000";
|
|
2200
|
+
await writeFile(lockPath, `999999 ${foreignOwner} ${new Date().toISOString()}\n`, "utf8");
|
|
2190
2201
|
const hb = setInterval(() => {
|
|
2191
2202
|
const now = new Date();
|
|
2192
2203
|
utimes(lockPath, now, now).catch(() => undefined);
|
|
@@ -2194,17 +2205,17 @@ test("a same-process second instance does not treat another instance's lock as s
|
|
|
2194
2205
|
hb.unref?.();
|
|
2195
2206
|
|
|
2196
2207
|
try {
|
|
2197
|
-
//
|
|
2198
|
-
//
|
|
2199
|
-
const
|
|
2208
|
+
// The catalog instance must NOT consider the foreign lock self-held; its
|
|
2209
|
+
// touch waits then DROPS on timeout (no append).
|
|
2210
|
+
const instance = new NamespaceCatalog(makeConfig(memoryDir));
|
|
2200
2211
|
const started = Date.now();
|
|
2201
|
-
await
|
|
2212
|
+
await instance.markWrite(ns, { discoveredBy: "write", storageDir: tokenDir });
|
|
2202
2213
|
const waited = Date.now() - started;
|
|
2203
|
-
assert.ok(waited >= 4_000, "
|
|
2214
|
+
assert.ok(waited >= 4_000, "the catalog must wait on the foreign lock, not skip it as self");
|
|
2204
2215
|
assert.equal(
|
|
2205
|
-
await
|
|
2216
|
+
await instance.getNamespaceRecord(ns),
|
|
2206
2217
|
null,
|
|
2207
|
-
"
|
|
2218
|
+
"the touch must DROP while the foreign lock is held (no overwrite race)",
|
|
2208
2219
|
);
|
|
2209
2220
|
} finally {
|
|
2210
2221
|
clearInterval(hb);
|
|
@@ -2609,6 +2620,98 @@ test("a dropped resolve registration (hook returns false) is retried on a later
|
|
|
2609
2620
|
}
|
|
2610
2621
|
});
|
|
2611
2622
|
|
|
2623
|
+
// ── In-flight dedup under a DROPPED hook (cursor Medium 06f58a7c, codex P2).
|
|
2624
|
+
// The serializer strictly orders queued tasks, but ordering alone does not
|
|
2625
|
+
// collapse a burst when the hook returns `false` (dropped registration, e.g. a
|
|
2626
|
+
// rebuild-lock timeout): `notifiedResolved` stays unset, so every queued sibling
|
|
2627
|
+
// task passes its re-check and re-invokes the hook — N serial lock waits. The
|
|
2628
|
+
// `inFlightResolveHooks` marker (set synchronously before queueing) collapses the
|
|
2629
|
+
// burst to a single hook invocation; the drop stays retryable on a LATER
|
|
2630
|
+
// storageFor(). This test PROVE-FAILS without the marker (calls === N) and PASSES
|
|
2631
|
+
// with it (calls === 1).
|
|
2632
|
+
test("a burst of concurrent storageFor() with a DROPPED hook fires it ONCE (not once per queued task)", async () => {
|
|
2633
|
+
const memoryDir = await mkMemoryDir();
|
|
2634
|
+
try {
|
|
2635
|
+
let calls = 0;
|
|
2636
|
+
// Gated hook that DROPS (returns false) once released. This is the real
|
|
2637
|
+
// scenario the reviewers flagged: the catalog's onResolve waits on the
|
|
2638
|
+
// rebuild lock (slow), times out, and returns false. While that hook is
|
|
2639
|
+
// in-flight, a burst of cache hits must collapse to the one in-flight
|
|
2640
|
+
// registration instead of each queueing its own serial lock wait.
|
|
2641
|
+
let release!: () => void;
|
|
2642
|
+
const gate = new Promise<void>((r) => {
|
|
2643
|
+
release = r;
|
|
2644
|
+
});
|
|
2645
|
+
const router = new NamespaceStorageRouter(makeConfig(memoryDir), {
|
|
2646
|
+
onResolve: async () => {
|
|
2647
|
+
calls += 1;
|
|
2648
|
+
await gate;
|
|
2649
|
+
return false; // dropped (rebuild-lock timeout analogue)
|
|
2650
|
+
},
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2653
|
+
// A burst of concurrent cache hits while the hook is IN-FLIGHT. Without the
|
|
2654
|
+
// in-flight dedup marker each enqueued task would re-run the dropped hook
|
|
2655
|
+
// once the first settles (N serial lock waits); with it they collapse.
|
|
2656
|
+
const N = 8;
|
|
2657
|
+
await Promise.all(Array.from({ length: N }, () => router.storageFor("project-origin-burst-drop")));
|
|
2658
|
+
// Let the in-flight hook settle as DROPPED.
|
|
2659
|
+
release();
|
|
2660
|
+
await router.whenResolveHooksSettled();
|
|
2661
|
+
assert.equal(calls, 1, "a dropped hook fires ONCE under a burst, not once per queued task");
|
|
2662
|
+
|
|
2663
|
+
// The drop must remain retryable: a later storageFor() re-fires the hook.
|
|
2664
|
+
await router.storageFor("project-origin-burst-drop");
|
|
2665
|
+
await router.whenResolveHooksSettled();
|
|
2666
|
+
assert.equal(calls, 2, "a dropped registration is retried on the next storageFor()");
|
|
2667
|
+
} finally {
|
|
2668
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2669
|
+
}
|
|
2670
|
+
});
|
|
2671
|
+
|
|
2672
|
+
// ── Composite-key in-flight dedup (cursor Medium, codex P2): the in-flight
|
|
2673
|
+
// marker must be keyed by (namespace, storageDir), NOT namespace alone. A
|
|
2674
|
+
// CHANGED storageDir (migration/realignment) for the same namespace while
|
|
2675
|
+
// another dir's hook is pending must still get its OWN hook invocation — it is
|
|
2676
|
+
// not collapsed onto the old dir's pending registration. A namespace-only key
|
|
2677
|
+
// would silently drop the new-dir notification.
|
|
2678
|
+
test("a CHANGED storageDir for the same namespace is not collapsed onto a pending hook", async () => {
|
|
2679
|
+
const memoryDir = await mkMemoryDir();
|
|
2680
|
+
try {
|
|
2681
|
+
const seen: string[] = [];
|
|
2682
|
+
let release!: () => void;
|
|
2683
|
+
const gate = new Promise<void>((r) => {
|
|
2684
|
+
release = r;
|
|
2685
|
+
});
|
|
2686
|
+
const router = new NamespaceStorageRouter(makeConfig(memoryDir), {
|
|
2687
|
+
onResolve: async (_ns, dir) => {
|
|
2688
|
+
seen.push(dir);
|
|
2689
|
+
await gate;
|
|
2690
|
+
},
|
|
2691
|
+
});
|
|
2692
|
+
// notifyResolved is private; reach it via the same cast pattern other tests
|
|
2693
|
+
// use for router internals, so we can drive two distinct dirs directly.
|
|
2694
|
+
const internals = router as unknown as {
|
|
2695
|
+
notifyResolved(namespace: string, storageDir: string): void;
|
|
2696
|
+
};
|
|
2697
|
+
const dirA = path.join(memoryDir, "dir-a");
|
|
2698
|
+
const dirB = path.join(memoryDir, "dir-b");
|
|
2699
|
+
// dirA's hook is IN-FLIGHT (gated). dirB is a DIFFERENT dir for the same
|
|
2700
|
+
// namespace — it must NOT be collapsed onto dirA's pending registration.
|
|
2701
|
+
internals.notifyResolved("project-origin-dir-change", dirA);
|
|
2702
|
+
internals.notifyResolved("project-origin-dir-change", dirB);
|
|
2703
|
+
release();
|
|
2704
|
+
await router.whenResolveHooksSettled();
|
|
2705
|
+
assert.deepEqual(
|
|
2706
|
+
seen.sort(),
|
|
2707
|
+
[dirA, dirB].sort(),
|
|
2708
|
+
"both distinct dirs fire their own hook; the new dir is not collapsed onto the pending one",
|
|
2709
|
+
);
|
|
2710
|
+
} finally {
|
|
2711
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2714
|
+
|
|
2612
2715
|
// ── Round 7 (codex P2 — NDxiS): a configured non-default namespace must be seeded
|
|
2613
2716
|
// with the ROUTER-resolved root, not a blanket tokenized dir. When a legacy raw
|
|
2614
2717
|
// root (`namespaces/<rawname>`) already exists, the router serves it, so the
|
|
@@ -2777,8 +2880,35 @@ class SeamCatalog extends NamespaceCatalog {
|
|
|
2777
2880
|
(this as unknown as { onBeforeBreakStaleUnlinkForTest?: () => Promise<void> }).onBeforeBreakStaleUnlinkForTest =
|
|
2778
2881
|
fn;
|
|
2779
2882
|
}
|
|
2883
|
+
/**
|
|
2884
|
+
* Drive the catalog's break-stale path through the shared util (issue #1524
|
|
2885
|
+
* adoption). The catalog no longer owns a private breakStaleRebuildLock; the
|
|
2886
|
+
* util's breakStaleLock fires inside its acquire loop, which is what
|
|
2887
|
+
* withHeldCatalogLock now invokes. We trigger that path with a SHORT maxWaitMs
|
|
2888
|
+
* so a surviving replacement lock is observed quickly (the production
|
|
2889
|
+
* REBUILD_LOCK_MAX_WAIT_MS would force a 5s wait on the NG7Bg-replacement
|
|
2890
|
+
* case). The seam is forwarded exactly as production does.
|
|
2891
|
+
*/
|
|
2780
2892
|
async callBreakStaleRebuildLock(): Promise<void> {
|
|
2781
|
-
|
|
2893
|
+
const seam = (this as unknown as { onBeforeBreakStaleUnlinkForTest?: () => Promise<void> })
|
|
2894
|
+
.onBeforeBreakStaleUnlinkForTest;
|
|
2895
|
+
// Match the catalog's lock config (stale/heartbeat/poll); only maxWaitMs
|
|
2896
|
+
// is shortened for test speed. The break-stale invariant does not depend
|
|
2897
|
+
// on maxWaitMs — the seam fires inside breakStaleLock regardless.
|
|
2898
|
+
await withHeldFileLock(
|
|
2899
|
+
(this as unknown as { rebuildLockPath: string }).rebuildLockPath,
|
|
2900
|
+
{
|
|
2901
|
+
staleMs: 30_000,
|
|
2902
|
+
maxWaitMs: 200,
|
|
2903
|
+
pollMs: 10,
|
|
2904
|
+
heartbeatMs: 10_000,
|
|
2905
|
+
onBeforeBreakStaleUnlinkForTest: seam,
|
|
2906
|
+
},
|
|
2907
|
+
async () => {
|
|
2908
|
+
// No-op: we only need the acquire loop to invoke breakStaleLock so the
|
|
2909
|
+
// seam fires and the replacement/stale-lock invariant is exercised.
|
|
2910
|
+
},
|
|
2911
|
+
);
|
|
2782
2912
|
}
|
|
2783
2913
|
}
|
|
2784
2914
|
|
|
@@ -3354,3 +3484,81 @@ test("listNamespaces prefers configured token owners over stale literal token al
|
|
|
3354
3484
|
await rm(memoryDir, { recursive: true, force: true });
|
|
3355
3485
|
}
|
|
3356
3486
|
});
|
|
3487
|
+
|
|
3488
|
+
// ── Issue #1524 adoption prove-fail: catalog mutations route through the
|
|
3489
|
+
// shared MutationSerializer (instance-scoped `criticalSection`). The defect
|
|
3490
|
+
// class is a naive bare-.then(fn) chain that silently drops subsequent sections
|
|
3491
|
+
// after a rejection — exactly the poison-chain bug the shared util prevents.
|
|
3492
|
+
// We force the FIRST serialized section to reject, then assert the SECOND
|
|
3493
|
+
// section STILL runs (its record lands in the catalog). Pre-fix (a poison
|
|
3494
|
+
// chain) the second section would be skipped.
|
|
3495
|
+
test("catalog queueCritical recovers after a prior section rejects (issue #1524 poison-chain prove-fail)", async () => {
|
|
3496
|
+
const memoryDir = await mkMemoryDir();
|
|
3497
|
+
try {
|
|
3498
|
+
const catalog = new NamespaceCatalog(makeConfig(memoryDir));
|
|
3499
|
+
// Reach the private serializer to inject a failing section ahead of a real
|
|
3500
|
+
// one. Both target the SAME key ("catalog") so they share a chain.
|
|
3501
|
+
const serializer = (catalog as unknown as {
|
|
3502
|
+
criticalSection: {
|
|
3503
|
+
serialize<T>(key: string, task: () => Promise<T>): Promise<T>;
|
|
3504
|
+
};
|
|
3505
|
+
}).criticalSection;
|
|
3506
|
+
|
|
3507
|
+
let secondRan = false;
|
|
3508
|
+
const [, second] = await Promise.allSettled([
|
|
3509
|
+
serializer.serialize("catalog", async () => {
|
|
3510
|
+
throw new Error("intentional first-section failure");
|
|
3511
|
+
}),
|
|
3512
|
+
serializer.serialize("catalog", async () => {
|
|
3513
|
+
secondRan = true;
|
|
3514
|
+
}),
|
|
3515
|
+
]);
|
|
3516
|
+
assert.equal(second.status, "fulfilled", "second section settled (ran or skipped?)");
|
|
3517
|
+
assert.equal(secondRan, true, "second section MUST run after the first rejected (chain recovered)");
|
|
3518
|
+
// And a real catalog op still works through the same chain after the failure.
|
|
3519
|
+
await catalog.markWrite("project-origin-poison-recovery", { discoveredBy: "write" });
|
|
3520
|
+
const record = await catalog.getNamespaceRecord("project-origin-poison-recovery");
|
|
3521
|
+
assert.ok(record, "the catalog is fully usable after a rejected section (chain not poisoned)");
|
|
3522
|
+
} finally {
|
|
3523
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
3524
|
+
}
|
|
3525
|
+
});
|
|
3526
|
+
|
|
3527
|
+
// ── Issue #1524 adoption prove-fail: NamespaceStorageRouter resolve-hooks
|
|
3528
|
+
// route through the shared MutationSerializer (`resolveSerializer`). Same
|
|
3529
|
+
// defect class — a poison chain would skip the second hook after the first
|
|
3530
|
+
// rejected. We fire two notifications for the SAME namespace; the first hook
|
|
3531
|
+
// rejects, the second MUST still run.
|
|
3532
|
+
test("router resolve-hook serializer recovers after a prior hook rejects (issue #1524 poison-chain prove-fail)", async () => {
|
|
3533
|
+
const memoryDir = await mkMemoryDir();
|
|
3534
|
+
try {
|
|
3535
|
+
let secondCalls = 0;
|
|
3536
|
+
let firstCalls = 0;
|
|
3537
|
+
let rejectNext = true;
|
|
3538
|
+
const router = new NamespaceStorageRouter(makeConfig(memoryDir), {
|
|
3539
|
+
onResolve: async () => {
|
|
3540
|
+
if (rejectNext) {
|
|
3541
|
+
firstCalls += 1;
|
|
3542
|
+
rejectNext = false;
|
|
3543
|
+
throw new Error("intentional first-hook failure");
|
|
3544
|
+
}
|
|
3545
|
+
secondCalls += 1;
|
|
3546
|
+
},
|
|
3547
|
+
});
|
|
3548
|
+
// Two storageFor calls for the same namespace. The first triggers the hook
|
|
3549
|
+
// (which rejects); the second queues behind it through the serializer.
|
|
3550
|
+
// Both calls themselves must resolve (the rejection is best-effort inside
|
|
3551
|
+
// the hook wrapper, never surfaced to the storage caller).
|
|
3552
|
+
await router.storageFor("project-origin-router-poison");
|
|
3553
|
+
await router.whenResolveHooksSettled();
|
|
3554
|
+
await router.storageFor("project-origin-router-poison");
|
|
3555
|
+
await router.whenResolveHooksSettled();
|
|
3556
|
+
assert.ok(firstCalls >= 1, "the first (rejecting) hook fired");
|
|
3557
|
+
assert.ok(
|
|
3558
|
+
secondCalls >= 1,
|
|
3559
|
+
"the second hook MUST fire after the first rejected (serializer recovered, not poisoned)",
|
|
3560
|
+
);
|
|
3561
|
+
} finally {
|
|
3562
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
3563
|
+
}
|
|
3564
|
+
});
|