@remnic/core 9.3.725 → 9.3.727
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 +28 -27
- package/dist/access-cli.js +65 -63
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +20 -5
- package/dist/access-http.js +34 -33
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +31 -30
- package/dist/access-operations-batch.js +29 -28
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +30 -29
- package/dist/{access-service-B2PlPtxh.d.ts → access-service-Bgpjxo4f.d.ts} +36 -4
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +27 -26
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +3 -3
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- 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 +9 -8
- 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-CzUb-GyD.d.ts → catalog-bMczlO2G.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +10 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-KGZF2VO3.js → chunk-27QQC7ZX.js} +80 -19
- package/dist/{chunk-KGZF2VO3.js.map → chunk-27QQC7ZX.js.map} +1 -1
- package/dist/{chunk-HCVWF657.js → chunk-2KAYTPPT.js} +12 -12
- package/dist/{chunk-4SF232E6.js → chunk-36SEDFAU.js} +19 -7
- package/dist/chunk-36SEDFAU.js.map +1 -0
- package/dist/{chunk-ZK2L72VN.js → chunk-37436GML.js} +2 -2
- package/dist/{chunk-SC2YAWRF.js → chunk-3TSTGI6F.js} +2 -2
- package/dist/{chunk-FACTVMXQ.js → chunk-4H4F3BFL.js} +2 -2
- package/dist/{chunk-WAFDHKES.js → chunk-4NOI577P.js} +2 -2
- package/dist/{chunk-YQMPUYHE.js → chunk-4NR2PDZ5.js} +2 -2
- package/dist/{chunk-TAPSNIMA.js → chunk-5CEJH5ZN.js} +4 -4
- package/dist/{chunk-FN6QP7JQ.js → chunk-6PY5YJAV.js} +100 -14
- package/dist/{chunk-FN6QP7JQ.js.map → chunk-6PY5YJAV.js.map} +1 -1
- package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
- package/dist/{chunk-4R3CGCI5.js → chunk-7RLCCNHU.js} +15 -12
- package/dist/chunk-7RLCCNHU.js.map +1 -0
- package/dist/{chunk-XGTBNVIU.js → chunk-AAD2ZS6P.js} +11 -1
- package/dist/chunk-AAD2ZS6P.js.map +1 -0
- package/dist/{chunk-Z3FGC3QE.js → chunk-AQLOP5F4.js} +139 -30
- package/dist/chunk-AQLOP5F4.js.map +1 -0
- package/dist/{chunk-FXCIOLRB.js → chunk-ASKXIAHD.js} +2 -2
- package/dist/{chunk-5OPYYPJN.js → chunk-CBWI22JN.js} +2 -2
- package/dist/{chunk-ZOHMPFOB.js → chunk-CKFLLAIS.js} +3 -3
- package/dist/{chunk-GZQXOQQ2.js → chunk-CSRXQ6NB.js} +5 -5
- package/dist/{chunk-QR67FU5G.js → chunk-CTOQEZSN.js} +2 -2
- package/dist/{chunk-ZUDM75KG.js → chunk-DCWIQFNA.js} +4 -4
- package/dist/{chunk-6CWGS2LY.js → chunk-DKJCRGWM.js} +3 -3
- package/dist/{chunk-JJJCVCCQ.js → chunk-DMQWV6SQ.js} +2 -2
- package/dist/{chunk-F3XZN24Z.js → chunk-DRUDC2UX.js} +2 -2
- package/dist/{chunk-4WZ3J7M6.js → chunk-EY6MDMHK.js} +2 -2
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-C4X4QGIO.js → chunk-G6NKALRN.js} +5 -2
- package/dist/chunk-G6NKALRN.js.map +1 -0
- package/dist/{chunk-6SQQOJ4U.js → chunk-GKRLPHPU.js} +2 -2
- package/dist/{chunk-D7EBTQ24.js → chunk-HOHVIVDR.js} +2 -2
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-UBHVAGO7.js → chunk-JYLFZWUX.js} +2 -2
- package/dist/{chunk-LD53WPMU.js → chunk-LQ6JI4VH.js} +4 -4
- package/dist/chunk-LTHVM4XN.js +109 -0
- package/dist/chunk-LTHVM4XN.js.map +1 -0
- package/dist/{chunk-WM7DNKBD.js → chunk-M4V2JVSZ.js} +2 -2
- package/dist/{chunk-I2HL5M6T.js → chunk-MJIHJ7OG.js} +52 -52
- package/dist/{chunk-J334N6QV.js → chunk-MLKWJOAJ.js} +2 -2
- package/dist/{chunk-C6BWLYW5.js → chunk-N3VNUM4Z.js} +171 -79
- package/dist/chunk-N3VNUM4Z.js.map +1 -0
- package/dist/{chunk-DF3I6JGP.js → chunk-NSA5ZRUL.js} +15 -15
- package/dist/{chunk-VI5SGGXB.js → chunk-OEEW4KJA.js} +4 -4
- package/dist/{chunk-7AG54AF3.js → chunk-PUR2CKX2.js} +1 -1
- package/dist/chunk-PUR2CKX2.js.map +1 -0
- package/dist/{chunk-NT36GU6X.js → chunk-R7IYU5DZ.js} +2 -2
- package/dist/{chunk-TT5U3UDO.js → chunk-SNKPUJHR.js} +4 -4
- package/dist/{chunk-T55EGZSJ.js → chunk-UVPL565C.js} +2 -2
- package/dist/chunk-W5H6ZRPT.js +14 -0
- package/dist/chunk-W5H6ZRPT.js.map +1 -0
- package/dist/{chunk-YQGHW5ML.js → chunk-WRFKZEO6.js} +4 -4
- package/dist/{chunk-KLFYKC3K.js → chunk-XPHVAOBT.js} +195 -74
- package/dist/chunk-XPHVAOBT.js.map +1 -0
- package/dist/{chunk-H7HUQIY7.js → chunk-Y4YD5TYW.js} +3 -3
- package/dist/{chunk-MDBZTH7N.js → chunk-YKBHOBJK.js} +44 -10
- package/dist/chunk-YKBHOBJK.js.map +1 -0
- package/dist/{chunk-YUYPWJD3.js → chunk-YOLZS7NY.js} +2 -2
- package/dist/{chunk-6OIDDKAA.js → chunk-YTU7WKYN.js} +35 -9
- package/dist/chunk-YTU7WKYN.js.map +1 -0
- package/dist/{cli-IOlDHSBT.d.ts → cli-BLME420h.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +60 -59
- package/dist/compat/codex-cli-fallback.d.ts +1 -0
- package/dist/compat/codex-cli-fallback.js +22 -0
- package/dist/compat/codex-cli-fallback.js.map +1 -0
- package/dist/compat/codex-thread-key.d.ts +1 -0
- package/dist/compat/codex-thread-key.js +18 -0
- package/dist/compat/codex-thread-key.js.map +1 -0
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +9 -8
- 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 +3 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +9 -8
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +9 -8
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/content-hash.d.ts +14 -0
- package/dist/content-hash.js +10 -0
- package/dist/content-hash.js.map +1 -0
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- 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/dashboard-runtime.js +2 -2
- 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 +9 -8
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-faithfulness.js +2 -2
- 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-redaction-rules.d.ts +33 -0
- package/dist/extraction-redaction-rules.js +14 -0
- package/dist/extraction-redaction-rules.js.map +1 -0
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/faithfulness-config.d.ts +1 -0
- package/dist/faithfulness-config.js +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-OEE5OELK.js → forget-WYLR4ZFV.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +107 -105
- 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/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +4 -4
- 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/local-model-endpoint.d.ts +9 -4
- package/dist/local-model-endpoint.js +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +9 -8
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -8
- package/dist/maintenance/rebuild-memory-projection.js +10 -9
- 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-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 +19 -18
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +9 -8
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +28 -27
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +11 -10
- package/dist/{orchestrator-C_OMSIMZ.d.ts → orchestrator-DqPAz8ot.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +51 -49
- 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/provenance.js +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 +3 -3
- 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 +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +4 -4
- 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 +22 -22
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +14 -14
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-B8LiYtvx.d.ts → semantic-consolidation-Cl15ksZy.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +10 -9
- package/dist/semantic-rule-promotion.js +9 -8
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +9 -8
- 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 +3 -3
- package/dist/storage.js +9 -8
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +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/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DmTaqob2.d.ts → types-ChQb9D6s.d.ts} +8 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +9 -8
- package/package.json +26 -2
- package/src/access-http.ts +68 -1
- package/src/admin/admin-surfaces.test.ts +289 -0
- package/src/admin/admin-surfaces.ts +138 -7
- package/src/bitemporal-dedup-backfill.test.ts +294 -0
- package/src/chat/chat-engine.test.ts +66 -1
- package/src/chat/chat-http.test.ts +119 -1
- package/src/chat/chat-http.ts +252 -97
- package/src/chat/chat-session.ts +143 -7
- package/src/compat/codex-cli-fallback.ts +23 -0
- package/src/compat/codex-subpath-aliases.test.ts +93 -0
- package/src/compat/codex-thread-key.ts +23 -0
- package/src/config.test.ts +50 -0
- package/src/config.ts +21 -4
- package/src/content-hash.ts +24 -0
- package/src/correction/correction-access-wiring.ts +104 -19
- package/src/correction/correction-contract.ts +68 -10
- package/src/correction/correction-executor.test.ts +112 -2
- package/src/correction/correction-executor.ts +65 -5
- package/src/correction/correction-planner.test.ts +47 -0
- package/src/correction/correction-planner.ts +40 -1
- package/src/correction/correction-storage-integration.test.ts +183 -0
- package/src/extraction-faithfulness.test.ts +240 -0
- package/src/extraction-faithfulness.ts +58 -6
- package/src/extraction-redaction-rules.test.ts +213 -0
- package/src/extraction-redaction-rules.ts +208 -0
- package/src/faithfulness-config.ts +16 -1
- package/src/local-model-endpoint.ts +24 -3
- package/src/orchestrator.ts +252 -9
- package/src/provenance-extraction.test.ts +266 -0
- package/src/provenance.ts +66 -16
- package/src/storage.ts +6 -10
- package/src/types.ts +8 -0
- package/dist/chunk-4R3CGCI5.js.map +0 -1
- package/dist/chunk-4SF232E6.js.map +0 -1
- package/dist/chunk-6OIDDKAA.js.map +0 -1
- package/dist/chunk-7AG54AF3.js.map +0 -1
- package/dist/chunk-C4X4QGIO.js.map +0 -1
- package/dist/chunk-C6BWLYW5.js.map +0 -1
- package/dist/chunk-KLFYKC3K.js.map +0 -1
- package/dist/chunk-MDBZTH7N.js.map +0 -1
- package/dist/chunk-XGTBNVIU.js.map +0 -1
- package/dist/chunk-Z3FGC3QE.js.map +0 -1
- /package/dist/{chunk-HCVWF657.js.map → chunk-2KAYTPPT.js.map} +0 -0
- /package/dist/{chunk-ZK2L72VN.js.map → chunk-37436GML.js.map} +0 -0
- /package/dist/{chunk-SC2YAWRF.js.map → chunk-3TSTGI6F.js.map} +0 -0
- /package/dist/{chunk-FACTVMXQ.js.map → chunk-4H4F3BFL.js.map} +0 -0
- /package/dist/{chunk-WAFDHKES.js.map → chunk-4NOI577P.js.map} +0 -0
- /package/dist/{chunk-YQMPUYHE.js.map → chunk-4NR2PDZ5.js.map} +0 -0
- /package/dist/{chunk-TAPSNIMA.js.map → chunk-5CEJH5ZN.js.map} +0 -0
- /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
- /package/dist/{chunk-FXCIOLRB.js.map → chunk-ASKXIAHD.js.map} +0 -0
- /package/dist/{chunk-5OPYYPJN.js.map → chunk-CBWI22JN.js.map} +0 -0
- /package/dist/{chunk-ZOHMPFOB.js.map → chunk-CKFLLAIS.js.map} +0 -0
- /package/dist/{chunk-GZQXOQQ2.js.map → chunk-CSRXQ6NB.js.map} +0 -0
- /package/dist/{chunk-QR67FU5G.js.map → chunk-CTOQEZSN.js.map} +0 -0
- /package/dist/{chunk-ZUDM75KG.js.map → chunk-DCWIQFNA.js.map} +0 -0
- /package/dist/{chunk-6CWGS2LY.js.map → chunk-DKJCRGWM.js.map} +0 -0
- /package/dist/{chunk-JJJCVCCQ.js.map → chunk-DMQWV6SQ.js.map} +0 -0
- /package/dist/{chunk-F3XZN24Z.js.map → chunk-DRUDC2UX.js.map} +0 -0
- /package/dist/{chunk-4WZ3J7M6.js.map → chunk-EY6MDMHK.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-6SQQOJ4U.js.map → chunk-GKRLPHPU.js.map} +0 -0
- /package/dist/{chunk-D7EBTQ24.js.map → chunk-HOHVIVDR.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-UBHVAGO7.js.map → chunk-JYLFZWUX.js.map} +0 -0
- /package/dist/{chunk-LD53WPMU.js.map → chunk-LQ6JI4VH.js.map} +0 -0
- /package/dist/{chunk-WM7DNKBD.js.map → chunk-M4V2JVSZ.js.map} +0 -0
- /package/dist/{chunk-I2HL5M6T.js.map → chunk-MJIHJ7OG.js.map} +0 -0
- /package/dist/{chunk-J334N6QV.js.map → chunk-MLKWJOAJ.js.map} +0 -0
- /package/dist/{chunk-DF3I6JGP.js.map → chunk-NSA5ZRUL.js.map} +0 -0
- /package/dist/{chunk-VI5SGGXB.js.map → chunk-OEEW4KJA.js.map} +0 -0
- /package/dist/{chunk-NT36GU6X.js.map → chunk-R7IYU5DZ.js.map} +0 -0
- /package/dist/{chunk-TT5U3UDO.js.map → chunk-SNKPUJHR.js.map} +0 -0
- /package/dist/{chunk-T55EGZSJ.js.map → chunk-UVPL565C.js.map} +0 -0
- /package/dist/{chunk-YQGHW5ML.js.map → chunk-WRFKZEO6.js.map} +0 -0
- /package/dist/{chunk-H7HUQIY7.js.map → chunk-Y4YD5TYW.js.map} +0 -0
- /package/dist/{chunk-YUYPWJD3.js.map → chunk-YOLZS7NY.js.map} +0 -0
- /package/dist/{forget-OEE5OELK.js.map → forget-WYLR4ZFV.js.map} +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import * as canonicalCliFallback from "../cli-fallback.js";
|
|
7
|
+
import * as canonicalThreadKey from "../thread-key.js";
|
|
8
|
+
import * as aliasCliFallback from "./codex-cli-fallback.js";
|
|
9
|
+
import * as aliasThreadKey from "./codex-thread-key.js";
|
|
10
|
+
|
|
11
|
+
const pkgJsonPath = fileURLToPath(new URL("../../package.json", import.meta.url));
|
|
12
|
+
|
|
13
|
+
test("codex-cli-fallback alias re-exports the same value symbols as cli-fallback", () => {
|
|
14
|
+
// export * forwards live bindings by reference, so the function objects and
|
|
15
|
+
// the test-hooks namespace are identical (===) across both subpaths.
|
|
16
|
+
assert.equal(
|
|
17
|
+
aliasCliFallback.callCodexCliFallback,
|
|
18
|
+
canonicalCliFallback.callCodexCliFallback,
|
|
19
|
+
);
|
|
20
|
+
assert.equal(
|
|
21
|
+
aliasCliFallback.setCodexCliFallbackRunnerForProcess,
|
|
22
|
+
canonicalCliFallback.setCodexCliFallbackRunnerForProcess,
|
|
23
|
+
);
|
|
24
|
+
assert.equal(
|
|
25
|
+
aliasCliFallback.__codexCliFallbackTestHooks,
|
|
26
|
+
canonicalCliFallback.__codexCliFallbackTestHooks,
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("codex-thread-key alias re-exports the same symbol as thread-key", () => {
|
|
31
|
+
assert.equal(
|
|
32
|
+
aliasThreadKey.CODEX_THREAD_KEY_PREFIX,
|
|
33
|
+
canonicalThreadKey.CODEX_THREAD_KEY_PREFIX,
|
|
34
|
+
);
|
|
35
|
+
assert.equal(aliasThreadKey.CODEX_THREAD_KEY_PREFIX, "codex-thread:");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("every named runtime export of cli-fallback is forwarded by the alias", () => {
|
|
39
|
+
for (const key of Object.keys(canonicalCliFallback)) {
|
|
40
|
+
assert.equal(
|
|
41
|
+
key in aliasCliFallback,
|
|
42
|
+
true,
|
|
43
|
+
`alias missing forwarded export: ${key}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("importing the old subpath resolves to the same symbol as the new subpath", async () => {
|
|
49
|
+
// @remnic/core self-references its own exports map. Under --conditions=
|
|
50
|
+
// remnic-source (set by preflight/test NODE_OPTIONS) the subpaths resolve to
|
|
51
|
+
// src/. Both specifiers land on the same module record, so the exported
|
|
52
|
+
// function is the same object reference.
|
|
53
|
+
const [byNew, byOld] = await Promise.all([
|
|
54
|
+
import("@remnic/core/cli-fallback"),
|
|
55
|
+
import("@remnic/core/codex-cli-fallback"),
|
|
56
|
+
]);
|
|
57
|
+
assert.equal(byOld.callCodexCliFallback, byNew.callCodexCliFallback);
|
|
58
|
+
|
|
59
|
+
const [newKey, oldKey] = await Promise.all([
|
|
60
|
+
import("@remnic/core/thread-key"),
|
|
61
|
+
import("@remnic/core/codex-thread-key"),
|
|
62
|
+
]);
|
|
63
|
+
assert.equal(oldKey.CODEX_THREAD_KEY_PREFIX, newKey.CODEX_THREAD_KEY_PREFIX);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("the deprecated subpaths are registered with a removal timeline in the exports map", () => {
|
|
67
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as {
|
|
68
|
+
exports: Record<string, { deprecated?: string; import?: string }>;
|
|
69
|
+
};
|
|
70
|
+
const aliasEntries = [
|
|
71
|
+
"./codex-cli-fallback",
|
|
72
|
+
"./codex-cli-fallback.js",
|
|
73
|
+
"./codex-thread-key",
|
|
74
|
+
"./codex-thread-key.js",
|
|
75
|
+
];
|
|
76
|
+
for (const subpath of aliasEntries) {
|
|
77
|
+
const entry = pkg.exports[subpath];
|
|
78
|
+
assert.ok(entry, `missing exports entry for ${subpath}`);
|
|
79
|
+
assert.ok(
|
|
80
|
+
entry.deprecated,
|
|
81
|
+
`exports entry for ${subpath} missing a deprecated field`,
|
|
82
|
+
);
|
|
83
|
+
assert.match(
|
|
84
|
+
entry.deprecated,
|
|
85
|
+
/v?10\.0\.0|remov/i,
|
|
86
|
+
`deprecated field for ${subpath} should state a removal timeline`,
|
|
87
|
+
);
|
|
88
|
+
assert.ok(
|
|
89
|
+
entry.import?.includes("/compat/"),
|
|
90
|
+
`exports entry for ${subpath} should target the compat re-export module`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecated alias subpath `@remnic/core/codex-thread-key`.
|
|
3
|
+
*
|
|
4
|
+
* PR #1638 renamed `src/codex-thread-key.ts` -> `src/thread-key.ts` to
|
|
5
|
+
* graduate the host-prefixed name per CLAUDE.md rule 31. This thin re-export
|
|
6
|
+
* keeps the old subpath resolving during the compat window so external
|
|
7
|
+
* consumers importing `@remnic/core/codex-thread-key` do not hit
|
|
8
|
+
* ERR_PACKAGE_PATH_NOT_EXPORTED. It forwards every export to the canonical
|
|
9
|
+
* module -- no logic is duplicated.
|
|
10
|
+
*
|
|
11
|
+
* Removal timeline: v10.0.0 (see issue #1643).
|
|
12
|
+
* Import from `@remnic/core/thread-key` instead.
|
|
13
|
+
*/
|
|
14
|
+
export * from "../thread-key.js";
|
|
15
|
+
|
|
16
|
+
process.emitWarning(
|
|
17
|
+
"@remnic/core/codex-thread-key is a deprecated alias; import from @remnic/core/thread-key instead. Removed in v10.0.0 (issue #1643).",
|
|
18
|
+
{
|
|
19
|
+
type: "DeprecationWarning",
|
|
20
|
+
code: "REMNIC_DEP_CORE_SUBPATH_CODEX_THREAD_KEY",
|
|
21
|
+
detail: "PR #1638 renamed codex-thread-key.ts to thread-key.ts.",
|
|
22
|
+
},
|
|
23
|
+
);
|
package/src/config.test.ts
CHANGED
|
@@ -2092,3 +2092,53 @@ test("parseConfig rejects invalid correction.enabled instead of silently enablin
|
|
|
2092
2092
|
assert.equal(parseConfig({ correctionEnabled: "0" }).correctionEnabled, false);
|
|
2093
2093
|
assert.equal(parseConfig({}).correctionEnabled, true);
|
|
2094
2094
|
});
|
|
2095
|
+
|
|
2096
|
+
test("parseConfig rejects invalid correction.planTtlHours instead of silently defaulting (#1678)", () => {
|
|
2097
|
+
// Nested form: correction.planTtlHours.
|
|
2098
|
+
for (const value of [0, -1, 0.5, "0", "-5", "abc", true, null]) {
|
|
2099
|
+
assert.throws(
|
|
2100
|
+
() => parseConfig({ correction: { planTtlHours: value } } as Record<string, unknown>),
|
|
2101
|
+
/Invalid correction\.planTtlHours/,
|
|
2102
|
+
`nested planTtlHours=${JSON.stringify(value)} should throw`,
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
// Flat form: correctionPlanTtlHours.
|
|
2106
|
+
for (const value of [0, -3, "abc", true]) {
|
|
2107
|
+
assert.throws(
|
|
2108
|
+
() => parseConfig({ correctionPlanTtlHours: value } as Record<string, unknown>),
|
|
2109
|
+
/Invalid correction\.planTtlHours/,
|
|
2110
|
+
`flat correctionPlanTtlHours=${JSON.stringify(value)} should throw`,
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2113
|
+
// Valid values resolve (no regression on the happy path).
|
|
2114
|
+
assert.equal(parseConfig({ correction: { planTtlHours: 48 } }).correctionPlanTtlHours, 48);
|
|
2115
|
+
assert.equal(parseConfig({ correctionPlanTtlHours: 12 }).correctionPlanTtlHours, 12);
|
|
2116
|
+
assert.equal(parseConfig({}).correctionPlanTtlHours, 24, "absent → documented default");
|
|
2117
|
+
});
|
|
2118
|
+
|
|
2119
|
+
test("parseConfig correction.maxAffected rejects non-integers (Number.isInteger, #1678)", () => {
|
|
2120
|
+
// The error message says "integer" — 3.7 must be rejected, not silently floored to 3.
|
|
2121
|
+
for (const value of [3.7, 2.5, "1.5", 0.99]) {
|
|
2122
|
+
assert.throws(
|
|
2123
|
+
() => parseConfig({ correction: { maxAffected: value } } as Record<string, unknown>),
|
|
2124
|
+
/Invalid correction\.maxAffected/,
|
|
2125
|
+
`maxAffected=${JSON.stringify(value)} should throw (non-integer)`,
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
for (const value of [0, -1, "abc"]) {
|
|
2129
|
+
assert.throws(
|
|
2130
|
+
() => parseConfig({ correction: { maxAffected: value } } as Record<string, unknown>),
|
|
2131
|
+
/Invalid correction\.maxAffected/,
|
|
2132
|
+
`maxAffected=${JSON.stringify(value)} should throw`,
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
// Flat form.
|
|
2136
|
+
assert.throws(
|
|
2137
|
+
() => parseConfig({ correctionMaxAffected: 4.2 } as Record<string, unknown>),
|
|
2138
|
+
/Invalid correction\.maxAffected/,
|
|
2139
|
+
);
|
|
2140
|
+
// Valid integers resolve.
|
|
2141
|
+
assert.equal(parseConfig({ correction: { maxAffected: 5 } }).correctionMaxAffected, 5);
|
|
2142
|
+
assert.equal(parseConfig({ correctionMaxAffected: 20 }).correctionMaxAffected, 20);
|
|
2143
|
+
assert.equal(parseConfig({}).correctionMaxAffected, 10, "absent → documented default");
|
|
2144
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -2130,17 +2130,34 @@ export function parseConfig(
|
|
|
2130
2130
|
const raw = rawNested !== undefined ? rawNested : cfg.correctionMaxAffected;
|
|
2131
2131
|
if (raw === undefined || raw === null) return 10;
|
|
2132
2132
|
const n = coerceNumber(raw);
|
|
2133
|
-
|
|
2133
|
+
// #1678 (thread Oiup6): the error message says "integer" — enforce it
|
|
2134
|
+
// with Number.isInteger so 3.7 is rejected rather than silently floored.
|
|
2135
|
+
if (n === undefined || !Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
|
|
2134
2136
|
throw new Error(`Invalid correction.maxAffected: expected an integer >= 1, got ${JSON.stringify(raw)}`);
|
|
2135
2137
|
}
|
|
2136
|
-
return
|
|
2138
|
+
return n;
|
|
2137
2139
|
})(),
|
|
2138
2140
|
correctionPlanTtlHours: (() => {
|
|
2139
2141
|
const nested = (cfg.correction as Record<string, unknown> | undefined)?.planTtlHours;
|
|
2140
2142
|
const fromNested = nested !== undefined ? coerceNumber(nested) : undefined;
|
|
2141
|
-
if (fromNested !== undefined && fromNested
|
|
2143
|
+
if (fromNested !== undefined && fromNested >= 1) return fromNested;
|
|
2144
|
+
// #1678 (threads Ohtvi/Ohyvf): reject invalid (0/negative/non-numeric)
|
|
2145
|
+
// instead of silently defaulting to 24h — completes the config-reject
|
|
2146
|
+
// class already enforced for enabled/applyRequiresConfirm/maxAffected.
|
|
2147
|
+
if (nested !== undefined && fromNested === undefined) {
|
|
2148
|
+
throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(nested)}`);
|
|
2149
|
+
}
|
|
2150
|
+
if (fromNested !== undefined && fromNested < 1) {
|
|
2151
|
+
throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(nested)}`);
|
|
2152
|
+
}
|
|
2142
2153
|
const fromFlat = coerceNumber(cfg.correctionPlanTtlHours);
|
|
2143
|
-
if (fromFlat !== undefined && fromFlat
|
|
2154
|
+
if (fromFlat !== undefined && fromFlat >= 1) return fromFlat;
|
|
2155
|
+
if (cfg.correctionPlanTtlHours !== undefined && fromFlat === undefined) {
|
|
2156
|
+
throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(cfg.correctionPlanTtlHours)}`);
|
|
2157
|
+
}
|
|
2158
|
+
if (fromFlat !== undefined && fromFlat < 1) {
|
|
2159
|
+
throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(cfg.correctionPlanTtlHours)}`);
|
|
2160
|
+
}
|
|
2144
2161
|
return 24;
|
|
2145
2162
|
})(),
|
|
2146
2163
|
// Passive correction capture (issue #1581). Parsed from
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* content-hash.ts — standalone content normalization + SHA-256 hashing.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `ContentHashIndex` (storage.ts) so correction modules and
|
|
5
|
+
* other consumers can hash content WITHOUT importing the storage god file
|
|
6
|
+
* (issue #1533 directStorageImports ratchet). The static methods on
|
|
7
|
+
* `ContentHashIndex` delegate here to guarantee identical results.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
|
|
12
|
+
/** Normalize content: lowercase, strip non-alphanumerics, collapse whitespace. */
|
|
13
|
+
export function normalizeContent(content: string): string {
|
|
14
|
+
return content
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9\s]/g, " ")
|
|
17
|
+
.replace(/\s+/g, " ")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Normalize content and compute SHA-256 hash. */
|
|
22
|
+
export function computeContentHash(content: string): string {
|
|
23
|
+
return createHash("sha256").update(normalizeContent(content)).digest("hex");
|
|
24
|
+
}
|
|
@@ -21,10 +21,14 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import path from "node:path";
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
24
25
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
25
26
|
import type { Orchestrator } from "../orchestrator.js";
|
|
26
27
|
import type { MemoryFile, MemoryStatus, PluginConfig } from "../types.js";
|
|
27
28
|
import { stripAttributesSuffix } from "../structured-attributes.js";
|
|
29
|
+
import { supersessionKeysForFact } from "../temporal-supersession.js";
|
|
30
|
+
import { computeContentHash } from "../content-hash.js";
|
|
31
|
+
import { sanitizeMemoryContent } from "../sanitize.js";
|
|
28
32
|
import {
|
|
29
33
|
CorrectionContractError,
|
|
30
34
|
validateCorrectionAction,
|
|
@@ -168,7 +172,7 @@ async function searchMemories(
|
|
|
168
172
|
const storage = await wiring.orchestrator.getStorage(ns);
|
|
169
173
|
const all = await storage.readAllMemories();
|
|
170
174
|
for (const m of all) {
|
|
171
|
-
if (m
|
|
175
|
+
if (!isEligibleCorrectionCandidate(m)) continue;
|
|
172
176
|
const hay = `${m.content} ${m.frontmatter.tags?.join(" ") ?? ""}`.toLowerCase();
|
|
173
177
|
let hits = 0;
|
|
174
178
|
for (const tok of tokens) {
|
|
@@ -265,7 +269,7 @@ async function expandEntityNeighbors(
|
|
|
265
269
|
for (const m of all) {
|
|
266
270
|
if (out.length >= limit) break;
|
|
267
271
|
if (seen.has(m.frontmatter.id)) continue;
|
|
268
|
-
if (m
|
|
272
|
+
if (!isEligibleCorrectionCandidate(m)) continue;
|
|
269
273
|
if (m.frontmatter.entityRef && seedRefs.has(m.frontmatter.entityRef)) {
|
|
270
274
|
out.push(toCandidate(m, ns, 0.5));
|
|
271
275
|
seen.add(m.frontmatter.id);
|
|
@@ -427,9 +431,22 @@ async function applyEditMemory(
|
|
|
427
431
|
// The StorageManager's writeMemoryFrontmatter snapshots the prior version
|
|
428
432
|
// internally when page-versioning is configured (issue #371), so every edit
|
|
429
433
|
// is revertable without the correction layer forking versioning logic.
|
|
434
|
+
//
|
|
435
|
+
// #1672 item 3: recompute the contentHash from the patched body so the
|
|
436
|
+
// dedup index + tombstone exact tier match the NEW content. writeMemory
|
|
437
|
+
// computes this at write time, but writeMemoryFrontmatter preserves the
|
|
438
|
+
// prior frontmatter hash — without this the hash no longer matches the
|
|
439
|
+
// patched body and a later re-extraction of the same fact dedup-misses.
|
|
440
|
+
const contentHashForPatch =
|
|
441
|
+
existing.frontmatter.category === "fact"
|
|
442
|
+
? computeContentHash(sanitizeMemoryContent(patch).text)
|
|
443
|
+
: undefined;
|
|
430
444
|
await storage.writeMemoryFrontmatter(
|
|
431
445
|
{ ...existing, content: patch },
|
|
432
|
-
{
|
|
446
|
+
{
|
|
447
|
+
updated: new Date().toISOString(),
|
|
448
|
+
...(contentHashForPatch ? { contentHash: contentHashForPatch } : {}),
|
|
449
|
+
},
|
|
433
450
|
);
|
|
434
451
|
return memoryId;
|
|
435
452
|
}
|
|
@@ -535,6 +552,8 @@ async function appendTombstoneFn(
|
|
|
535
552
|
rawContent: string;
|
|
536
553
|
entityRef?: string;
|
|
537
554
|
supersessionKey?: string;
|
|
555
|
+
supersessionKeys?: string[];
|
|
556
|
+
contentHash?: string;
|
|
538
557
|
},
|
|
539
558
|
): Promise<string | null> {
|
|
540
559
|
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
@@ -550,20 +569,46 @@ async function appendTombstoneFn(
|
|
|
550
569
|
typeof storage.isTombstonesEnabled === "function"
|
|
551
570
|
? storage.isTombstonesEnabled()
|
|
552
571
|
: true;
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
572
|
+
// #1672 item 4: emit one tombstone per derived supersession key (mirrors
|
|
573
|
+
// temporal-supersession / forget / pattern-reinforcement) so a paraphrased
|
|
574
|
+
// re-observation placing the attribute at a different key position is still
|
|
575
|
+
// blocked at the keyed tier. When no keys are derived, emit a single
|
|
576
|
+
// content-only tombstone. Each carries the canonical contentHash so the
|
|
577
|
+
// exact tier also matches re-extraction.
|
|
578
|
+
const keys =
|
|
579
|
+
input.supersessionKeys && input.supersessionKeys.length > 0
|
|
580
|
+
? input.supersessionKeys
|
|
581
|
+
: input.supersessionKey
|
|
582
|
+
? [input.supersessionKey]
|
|
583
|
+
: [undefined];
|
|
584
|
+
const writtenIds: string[] = [];
|
|
585
|
+
let firstId: string | null = null;
|
|
586
|
+
for (const key of keys) {
|
|
587
|
+
const result = await storage.appendTombstone({
|
|
588
|
+
reason: input.reason,
|
|
589
|
+
createdBy: "user_correction",
|
|
590
|
+
sourceMemoryId: input.sourceMemoryId,
|
|
591
|
+
rawContent: input.rawContent,
|
|
592
|
+
...(input.entityRef ? { entityRef: input.entityRef } : {}),
|
|
593
|
+
...(key ? { supersessionKey: key } : {}),
|
|
594
|
+
...(input.contentHash ? { contentHash: input.contentHash } : {}),
|
|
595
|
+
});
|
|
596
|
+
if (result === null && enabled) {
|
|
597
|
+
// Rollback already-written tombstones so a partial multi-key failure
|
|
598
|
+
// does not leave incomplete resurrection blocking for the still-active
|
|
599
|
+
// memory (review thread #1). Best-effort — a dangling tombstone is the
|
|
600
|
+
// safer failure mode (false-positive block vs. false-negative leak).
|
|
601
|
+
for (const id of writtenIds) {
|
|
602
|
+
try { await storage.revokeTombstone(id, "user_correction"); } catch { /* best-effort */ }
|
|
603
|
+
}
|
|
604
|
+
throw new CorrectionContractError(
|
|
605
|
+
`tombstone persistence failed for memory ${input.sourceMemoryId} (tombstones enabled but store returned null — I/O error swallowed)`,
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (firstId === null) firstId = result;
|
|
609
|
+
if (result) writtenIds.push(result);
|
|
565
610
|
}
|
|
566
|
-
return
|
|
611
|
+
return firstId;
|
|
567
612
|
}
|
|
568
613
|
|
|
569
614
|
async function registerRedactionRuleFn(
|
|
@@ -576,9 +621,14 @@ async function registerRedactionRuleFn(
|
|
|
576
621
|
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
577
622
|
const dir = path.join(storage.dir, "state", "corrections", "redaction-rules");
|
|
578
623
|
await mkdir(dir, { recursive: true });
|
|
579
|
-
// Idempotent: filename is a slug of the pattern so
|
|
580
|
-
// pattern overwrites rather than duplicates
|
|
581
|
-
|
|
624
|
+
// Idempotent: filename is a slug + short hash of the full pattern so
|
|
625
|
+
// re-registering the same pattern overwrites rather than duplicates, while
|
|
626
|
+
// distinct patterns that slug identically (e.g. "abc+def" vs "abc.def" →
|
|
627
|
+
// "abc-def") no longer collide and silently remove each other's enforcement
|
|
628
|
+
// (review thread P1).
|
|
629
|
+
const slugBase = pattern.replace(/[^a-zA-Z0-9]+/g, "-").slice(0, 48) || "rule";
|
|
630
|
+
const patternHash = createHash("sha256").update(pattern).digest("hex").slice(0, 16);
|
|
631
|
+
const slug = `${slugBase}-${patternHash}`;
|
|
582
632
|
await writeFile(
|
|
583
633
|
path.join(dir, `${slug}.json`),
|
|
584
634
|
`${JSON.stringify({ pattern, namespace, createdAt: new Date().toISOString() })}\n`,
|
|
@@ -686,6 +736,22 @@ function toCandidate(m: MemoryFile, namespace: string, score: number): PlannerCa
|
|
|
686
736
|
} satisfies PlannerCandidate & { namespace?: string };
|
|
687
737
|
}
|
|
688
738
|
|
|
739
|
+
/**
|
|
740
|
+
* Lifecycle-aware eligibility for a correction candidate (#1672 item 2). A
|
|
741
|
+
* memory is eligible only when it is `status: "active"` AND not archived.
|
|
742
|
+
* Planning a correction against a memory that was archived via the
|
|
743
|
+
* `archivedAt`/archive path but still marked active would surface stale
|
|
744
|
+
* content the user already set aside — and apply would reject the rescope
|
|
745
|
+
* (non-active source) or supersede a fact the user intended gone. Both the
|
|
746
|
+
* status flip and the archive timestamp are checked so the candidate set
|
|
747
|
+
* stays consistent with the rescope/retire apply-time guards.
|
|
748
|
+
*/
|
|
749
|
+
function isEligibleCorrectionCandidate(m: MemoryFile): boolean {
|
|
750
|
+
if (m.frontmatter.status && m.frontmatter.status !== "active") return false;
|
|
751
|
+
if (typeof m.frontmatter.archivedAt === "string" && m.frontmatter.archivedAt.length > 0) return false;
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
|
|
689
755
|
function toExecutorMemory(m: MemoryFile): ExecutorMemory {
|
|
690
756
|
const fm = m.frontmatter;
|
|
691
757
|
// The tombstone hash must use the ORIGINAL unsuffixed body: writeMemory
|
|
@@ -693,12 +759,27 @@ function toExecutorMemory(m: MemoryFile): ExecutorMemory {
|
|
|
693
759
|
// hashing m.content would never match the pre-suffix content hash (thread
|
|
694
760
|
// OhX2N, rule 23). Strip the suffix when attributes are present.
|
|
695
761
|
const rawBody = fm.structuredAttributes ? stripAttributesSuffix(m.content) : m.content;
|
|
762
|
+
// #1672 item 4: derive the full supersession-key set from the memory's
|
|
763
|
+
// structuredAttributes so the tombstone's keyed tier blocks paraphrased
|
|
764
|
+
// re-observations that place the attribute at a different key position
|
|
765
|
+
// (mirrors temporal-supersession's per-key emission). Also forward the
|
|
766
|
+
// canonical contentHash so the exact tier matches re-extraction without
|
|
767
|
+
// recomputing from a citation-annotated body.
|
|
768
|
+
const supersessionKeys = supersessionKeysForFact({
|
|
769
|
+
entityRef: fm.entityRef,
|
|
770
|
+
structuredAttributes: fm.structuredAttributes,
|
|
771
|
+
});
|
|
696
772
|
return {
|
|
697
773
|
memoryId: fm.id,
|
|
698
774
|
content: m.content,
|
|
699
775
|
category: fm.category,
|
|
700
776
|
rawContent: rawBody,
|
|
701
777
|
...(fm.entityRef ? { entityRef: fm.entityRef } : {}),
|
|
778
|
+
...(supersessionKeys.length > 0 ? { supersessionKeys } : {}),
|
|
779
|
+
...(supersessionKeys.length > 0 ? { supersessionKey: supersessionKeys[0] } : {}),
|
|
780
|
+
...(typeof fm.contentHash === "string" && fm.contentHash.length > 0
|
|
781
|
+
? { contentHash: fm.contentHash }
|
|
782
|
+
: {}),
|
|
702
783
|
} satisfies ExecutorMemory;
|
|
703
784
|
}
|
|
704
785
|
|
|
@@ -884,4 +965,8 @@ export {
|
|
|
884
965
|
buildClassifyPrompt,
|
|
885
966
|
CLASSIFY_SYSTEM_PROMPT,
|
|
886
967
|
parseClassifyResponse,
|
|
968
|
+
// #1672: exported for focused storage-integration regression tests.
|
|
969
|
+
isEligibleCorrectionCandidate,
|
|
970
|
+
applyEditMemory,
|
|
971
|
+
appendTombstoneFn,
|
|
887
972
|
};
|
|
@@ -263,25 +263,50 @@ export function validateRedactionPattern(pattern: string): string {
|
|
|
263
263
|
`redaction_rule.pattern exceeds the ${REDACTION_PATTERN_MAX}-character bound.`,
|
|
264
264
|
);
|
|
265
265
|
}
|
|
266
|
-
// Reject patterns that could match an unbounded string (
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
if (isRegexLike(trimmed) &&
|
|
266
|
+
// Reject patterns that could match an unbounded string (overly-broad) OR
|
|
267
|
+
// exhibit catastrophic backtracking (nested quantifiers like `(a+)+`). We do
|
|
268
|
+
// NOT execute the regex (that would be ReDoS itself); we only inspect shape.
|
|
269
|
+
if (isRegexLike(trimmed) && isUnsafeRedactionRegex(trimmed)) {
|
|
270
270
|
throw new CorrectionContractError(
|
|
271
|
-
"redaction_rule.pattern is
|
|
271
|
+
"redaction_rule.pattern is unsafe — use a bounded literal or a regex without nested quantifiers / overlapping alternation.",
|
|
272
272
|
);
|
|
273
273
|
}
|
|
274
|
+
// #1669 P2: reject zero-width regex (empty body or matches empty string)
|
|
275
|
+
// that would withhold every fact.
|
|
276
|
+
if (isRegexLike(trimmed)) {
|
|
277
|
+
const regexBody = trimmed.slice(1, -1);
|
|
278
|
+
if (regexBody.length === 0) {
|
|
279
|
+
throw new CorrectionContractError("redaction_rule.pattern body is empty — use a non-empty regex.");
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
if (new RegExp(regexBody).test("")) {
|
|
283
|
+
throw new CorrectionContractError(
|
|
284
|
+
"redaction_rule.pattern matches the empty string — it would withhold every fact.",
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
} catch (e) {
|
|
288
|
+
if (e instanceof CorrectionContractError) throw e;
|
|
289
|
+
// Compilation failed — the extraction-time defense handles it.
|
|
290
|
+
}
|
|
291
|
+
}
|
|
274
292
|
return trimmed;
|
|
275
293
|
}
|
|
276
294
|
|
|
277
|
-
/**
|
|
295
|
+
/** Only `/.../`-wrapped patterns are treated as regex; everything else is a
|
|
296
|
+
* literal (review thread P1 — consistent with extraction-redaction-rules.ts). */
|
|
278
297
|
function isRegexLike(pattern: string): boolean {
|
|
279
|
-
|
|
280
|
-
return /[\\^$.|?*+()[\]{}]/.test(pattern);
|
|
298
|
+
return pattern.startsWith("/") && pattern.endsWith("/") && pattern.length >= 2;
|
|
281
299
|
}
|
|
282
300
|
|
|
283
|
-
/**
|
|
284
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Reject a regex that would either match an arbitrary-length run of any
|
|
303
|
+
* character (overly-broad) OR exhibit catastrophic backtracking (ReDoS).
|
|
304
|
+
* Mirrors the safe-regex heuristic in extraction-redaction-rules.ts so the
|
|
305
|
+
* apply-time chokepoint and the extraction-time defense agree on what is
|
|
306
|
+
* pathological — intentionally not imported to keep the two modules decoupled
|
|
307
|
+
* (contract owns validation; extraction owns consultation).
|
|
308
|
+
*/
|
|
309
|
+
function isUnsafeRedactionRegex(pattern: string): boolean {
|
|
285
310
|
const body = pattern.startsWith("/") && pattern.endsWith("/")
|
|
286
311
|
? pattern.slice(1, -1)
|
|
287
312
|
: pattern;
|
|
@@ -289,6 +314,39 @@ function isOverlyBroadRegex(pattern: string): boolean {
|
|
|
289
314
|
if (/(?:^|[^\\])\(\.\*\)|(?:^|[^\\])\.\*|(?:^|[^\\])\.\+|^\.([^*+]?)$/.test(body)) {
|
|
290
315
|
return true;
|
|
291
316
|
}
|
|
317
|
+
// Nested quantifier: a group (...) whose body ends with a quantifier and
|
|
318
|
+
// which is itself quantified → exponential blowup on near-miss inputs
|
|
319
|
+
// (classic ReDoS shapes: (a+)+, (a*)*, (a?)+). Also catches overlapping
|
|
320
|
+
// alternation under repetition: (a|a)+ where branches share a prefix.
|
|
321
|
+
if (body.length > 512) return true;
|
|
322
|
+
for (let i = 0; i < body.length; i++) {
|
|
323
|
+
if (body[i] !== "(") continue;
|
|
324
|
+
let depth = 1;
|
|
325
|
+
let j = i + 1;
|
|
326
|
+
while (j < body.length && depth > 0) {
|
|
327
|
+
if (body[j] === "\\") { j += 2; continue; }
|
|
328
|
+
if (body[j] === "(") depth++;
|
|
329
|
+
else if (body[j] === ")") depth--;
|
|
330
|
+
j++;
|
|
331
|
+
}
|
|
332
|
+
if (depth !== 0) continue;
|
|
333
|
+
const afterGroup = body[j];
|
|
334
|
+
if (afterGroup !== "+" && afterGroup !== "*" && afterGroup !== "{") continue;
|
|
335
|
+
const groupBody = body.slice(i + 1, j - 1);
|
|
336
|
+
if (/[+*?]$/.test(groupBody) || /\{\d+,?\d*\}[+*?]?$/.test(groupBody)) {
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
if (groupBody.includes("|")) {
|
|
340
|
+
const branches = groupBody.split("|");
|
|
341
|
+
if (branches.length >= 2) {
|
|
342
|
+
const firstChars = new Set(branches.map((b) => b[0]).filter(Boolean));
|
|
343
|
+
if (firstChars.size < branches.filter((b) => b.length > 0).length) {
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
i = j;
|
|
349
|
+
}
|
|
292
350
|
return false;
|
|
293
351
|
}
|
|
294
352
|
|