@remnic/core 9.63.6 → 9.63.8
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-admin-ops-surface.d.ts +3 -2
- package/dist/access-admin-ops-surface.js +31 -27
- package/dist/access-authorization-probe.d.ts +3 -2
- package/dist/access-authorization-probe.js +32 -28
- package/dist/access-boundary.d.ts +3 -2
- package/dist/access-boundary.js +31 -27
- package/dist/access-cli.js +98 -95
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts +3 -2
- package/dist/access-extraction-force-flush.js +31 -27
- package/dist/access-http-lcm-compaction.d.ts +3 -2
- package/dist/access-http-lifecycle-flush.d.ts +3 -2
- package/dist/access-http-offline-stream.d.ts +3 -2
- package/dist/access-http-query.js +33 -29
- package/dist/access-http.d.ts +4 -3
- package/dist/access-http.js +47 -43
- package/dist/access-identity-continuity-surface.d.ts +2 -1
- package/dist/access-identity-continuity-surface.js +31 -27
- package/dist/access-lcm-surface.d.ts +3 -2
- package/dist/access-lcm-surface.js +31 -27
- package/dist/access-mcp-output-schemas.js +1 -1
- package/dist/access-mcp.d.ts +3 -2
- package/dist/access-mcp.js +38 -34
- package/dist/access-namespace-preflight.js +31 -27
- package/dist/access-observe-write-surface.d.ts +3 -2
- package/dist/access-observe-write-surface.js +31 -27
- package/dist/access-offline-manifest.d.ts +2 -1
- package/dist/access-offline-manifest.js +5 -4
- package/dist/access-operations-batch.js +32 -28
- package/dist/access-operations.d.ts +3 -2
- package/dist/access-operations.js +36 -32
- package/dist/access-recall-concurrency.d.ts +3 -2
- package/dist/access-recall-concurrency.js +31 -27
- package/dist/access-recall-response.d.ts +3 -2
- package/dist/access-recall-response.js +31 -27
- package/dist/access-recall-surface.d.ts +3 -2
- package/dist/access-recall-surface.js +31 -27
- package/dist/access-schema.js +6 -5
- package/dist/{access-service-cKQrte9Z.d.ts → access-service-BaPa_km6.d.ts} +1 -1
- package/dist/access-service-helpers.d.ts +3 -2
- package/dist/access-service.d.ts +3 -2
- package/dist/access-service.js +31 -27
- package/dist/access-surface-catalog.d.ts +3 -2
- package/dist/access-surface-catalog.js +33 -29
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +4 -2
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-BKRJDMKX.js → auto-sync-3SHMZVQS.js} +5 -3
- package/dist/{auto-sync-BKRJDMKX.js.map → auto-sync-3SHMZVQS.js.map} +1 -1
- package/dist/bootstrap.d.ts +2 -1
- package/dist/briefing.js +10 -10
- package/dist/calibration.d.ts +2 -1
- package/dist/calibration.js +7 -0
- package/dist/calibration.js.map +1 -1
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/causal-consolidation.js +11 -11
- package/dist/census-validation.d.ts +19 -0
- package/dist/census-validation.js +14 -0
- package/dist/census-validation.js.map +1 -0
- package/dist/{chunk-LEPOP46W.js → chunk-227UFSEQ.js} +4 -4
- package/dist/chunk-2YA5N2BY.js +65 -0
- package/dist/chunk-2YA5N2BY.js.map +1 -0
- package/dist/{chunk-PB4WVYGC.js → chunk-3IMYRX3W.js} +2 -2
- package/dist/{chunk-R6UMNP5C.js → chunk-3T6HIIKA.js} +2 -2
- package/dist/{chunk-77QZW2D6.js → chunk-3TDAA7VA.js} +2 -2
- package/dist/{chunk-TWTUGCTY.js → chunk-3YTTAGPB.js} +6 -1
- package/dist/chunk-3YTTAGPB.js.map +1 -0
- package/dist/{chunk-DXDAQZ2K.js → chunk-42TIIO2M.js} +2 -2
- package/dist/{chunk-Y7COCXY2.js → chunk-4F67JJPG.js} +2 -2
- package/dist/{chunk-OW7NVMGQ.js → chunk-5OOTHWGJ.js} +2 -2
- package/dist/{chunk-KYTRF64R.js → chunk-5SCFDQEJ.js} +2 -2
- package/dist/{chunk-DYZR3Z6D.js → chunk-5TF5STAV.js} +4 -4
- package/dist/{chunk-GG4DN63W.js → chunk-62Y5AGCM.js} +2 -2
- package/dist/{chunk-PMVPCZJZ.js → chunk-6433HV72.js} +2 -2
- package/dist/{chunk-KHCUVYCY.js → chunk-6UKSOXAO.js} +3 -3
- package/dist/{chunk-GF7RQWXC.js → chunk-AJRJDXYR.js} +2 -2
- package/dist/{chunk-YEQCMA5L.js → chunk-BOSIHHOH.js} +2 -2
- package/dist/{chunk-IVXVLDNT.js → chunk-BPQ7L27U.js} +77 -92
- package/dist/chunk-BPQ7L27U.js.map +1 -0
- package/dist/{chunk-7LF2OHIJ.js → chunk-BRHKW7GY.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-LX4TNSAE.js → chunk-CBY54QYW.js} +4 -4
- package/dist/{chunk-CIAI24TQ.js → chunk-DGUVXXAU.js} +3 -3
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-QI5RDDEL.js → chunk-EDZNW3HE.js} +3 -3
- package/dist/chunk-FEHIOYML.js +168 -0
- package/dist/chunk-FEHIOYML.js.map +1 -0
- package/dist/{chunk-Z4N7SVVF.js → chunk-FZEEWOI2.js} +9 -4
- package/dist/chunk-FZEEWOI2.js.map +1 -0
- package/dist/{chunk-6YMEU6OQ.js → chunk-G72IPMR7.js} +2 -2
- package/dist/{chunk-7AF243WZ.js → chunk-GBVRVYF4.js} +1 -1
- package/dist/{chunk-FPEZ74F7.js → chunk-I2HTKOOP.js} +3 -3
- package/dist/{chunk-R7TZ2HCS.js → chunk-JG22O53B.js} +58 -3
- package/dist/chunk-JG22O53B.js.map +1 -0
- package/dist/{chunk-VPHSMJ2E.js → chunk-JPWSYHLB.js} +2 -2
- package/dist/{chunk-X7LEDA74.js → chunk-KFB4YNXM.js} +2 -2
- package/dist/{chunk-I5FLIB2J.js → chunk-KXWE62IH.js} +2 -2
- package/dist/chunk-LQWOFU6L.js +17 -0
- package/dist/chunk-LQWOFU6L.js.map +1 -0
- package/dist/{chunk-TSTI6A3A.js → chunk-LTYH2L6E.js} +13 -13
- package/dist/{chunk-ZUPKNWRZ.js → chunk-MIUL6AUZ.js} +3 -3
- package/dist/{chunk-EJY3HJMT.js → chunk-MYI3RKI6.js} +2 -2
- package/dist/{chunk-UCLMKNHA.js → chunk-N7V3P4V5.js} +2 -2
- package/dist/{chunk-NA56NSKL.js → chunk-O52TB5SL.js} +2 -2
- package/dist/{chunk-7KQB4EBS.js → chunk-OPPBTPUU.js} +4 -4
- package/dist/{chunk-ERPBRXVZ.js → chunk-PHKU6BZ2.js} +2 -2
- package/dist/{chunk-GTD4Q62X.js → chunk-PQHNZ63P.js} +4 -4
- package/dist/{chunk-FHKXVTM5.js → chunk-Q5XKKDFQ.js} +16 -16
- package/dist/{chunk-QRWMS6SI.js → chunk-Q6OL23WM.js} +2 -2
- package/dist/{chunk-V3KIRICY.js → chunk-QQYHYW2N.js} +3 -3
- package/dist/{chunk-YFCEBB5P.js → chunk-QTXNS3HB.js} +2 -2
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/{chunk-E4KMY4KR.js → chunk-SNEUAZ6B.js} +2 -2
- package/dist/{chunk-F6JYBVL6.js → chunk-SY5JUJ2X.js} +52 -52
- package/dist/{chunk-EFKZT6AY.js → chunk-T42L3S6A.js} +6 -11
- package/dist/chunk-T42L3S6A.js.map +1 -0
- package/dist/{chunk-YXMHUBN2.js → chunk-TOP3FSDW.js} +25 -102
- package/dist/chunk-TOP3FSDW.js.map +1 -0
- package/dist/{chunk-PBRCYYO7.js → chunk-ULZFZL7K.js} +2 -2
- package/dist/{chunk-YYQAKIDJ.js → chunk-VD2OT77P.js} +2 -2
- package/dist/chunk-VD2OT77P.js.map +1 -0
- package/dist/{chunk-IPLYGWQF.js → chunk-VDX2J7OX.js} +6 -6
- package/dist/{chunk-UGHCZS6K.js → chunk-VFZQXYN5.js} +2 -2
- package/dist/{chunk-NKB43PGI.js → chunk-VYMICVRA.js} +2 -2
- package/dist/{chunk-OETHJBAL.js → chunk-WBPD6SBZ.js} +109 -116
- package/dist/chunk-WBPD6SBZ.js.map +1 -0
- package/dist/{chunk-2UM6WHME.js → chunk-XE2QCCT2.js} +2 -2
- package/dist/{chunk-SYVGVK4S.js → chunk-XTMYB2HY.js} +2 -2
- package/dist/{chunk-AYVCKGA6.js → chunk-YTXNDEUS.js} +2 -2
- package/dist/{chunk-3Z5TGRFA.js → chunk-ZOAAY2OE.js} +9 -9
- package/dist/{chunk-L3CHRBHE.js → chunk-ZRUEP4IK.js} +17 -11
- package/dist/chunk-ZRUEP4IK.js.map +1 -0
- package/dist/{chunk-PRDJMOST.js → chunk-ZVZHG7AI.js} +65 -15
- package/dist/chunk-ZVZHG7AI.js.map +1 -0
- package/dist/{chunk-RYST72N5.js → chunk-ZWYXSF2T.js} +4 -154
- package/dist/chunk-ZWYXSF2T.js.map +1 -0
- package/dist/{cli-BFBDYV0D.d.ts → cli-D0tV7eeU.d.ts} +2 -2
- package/dist/cli.d.ts +4 -3
- package/dist/cli.js +69 -65
- package/dist/compounding/engine.js +10 -10
- package/dist/config.js +4 -2
- package/dist/connectors/codex-materialize-runner.js +10 -10
- package/dist/connectors/index.js +10 -10
- package/dist/consolidation-provenance-check.js +2 -2
- package/dist/convergence-refresh.js +12 -12
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +12 -12
- package/dist/entity-retrieval.js +10 -10
- package/dist/explicit-capture.d.ts +2 -1
- package/dist/external-wiki-access.d.ts +3 -2
- package/dist/external-wiki-access.js +32 -28
- package/dist/external-wiki-mcp-tools.d.ts +3 -2
- package/dist/extraction-judge.d.ts +2 -1
- package/dist/extraction-judge.js +6 -1
- package/dist/extraction-normalization.js +2 -2
- package/dist/extraction-prompt.d.ts +20 -1
- package/dist/extraction-prompt.js +11 -1
- package/dist/extraction.js +13 -13
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/included-memories.d.ts +11 -0
- package/dist/included-memories.js +14 -0
- package/dist/included-memories.js.map +1 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +117 -114
- package/dist/lcm/index.js +3 -3
- package/dist/maintenance/memory-governance.js +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- package/dist/mcp-memory-inspector-app.d.ts +3 -2
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +10 -10
- package/dist/offline-sync.js +2 -1
- package/dist/operator-doctor-corpus.js +13 -13
- package/dist/operator-toolkit.js +23 -21
- package/dist/orchestration/maintenance.js +16 -16
- package/dist/{orchestrator-CSc9GfQv.d.ts → orchestrator-Bay_AofR.d.ts} +2 -3
- package/dist/orchestrator.d.ts +2 -1
- package/dist/orchestrator.js +100 -97
- package/dist/{public-http-8PcrDIYi.d.ts → public-http-BtjiyKah.d.ts} +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -0
- package/dist/recall-state.d.ts +6 -2
- package/dist/recall-state.js +6 -2
- package/dist/reconcile/cursor.js +7 -3
- package/dist/reconcile/cursor.js.map +1 -1
- package/dist/reconcile/manifest.js +3 -7
- package/dist/reconcile/plan.d.ts +15 -1
- package/dist/reconcile/plan.js +6 -10
- package/dist/resume-bundles.js +5 -3
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +3 -3
- package/dist/semantic-consolidation.js +16 -16
- package/dist/semantic-rule-promotion.js +10 -10
- package/dist/semantic-rule-verifier.js +10 -10
- package/dist/source-agent-qualifier.js +10 -10
- package/dist/storage.js +9 -9
- package/dist/support-passport/index.d.ts +6 -5
- package/dist/support-passport/index.js +34 -30
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/verified-recall.js +10 -10
- package/package.json +2 -2
- package/src/access-mcp-output-schemas.ts +1 -1
- package/src/access-recall-response.ts +4 -2
- package/src/access-recall-surface.ts +9 -9
- package/src/access-service-namespace.test.ts +9 -9
- package/src/access-service-raw-excerpt-read-gate.test.ts +1 -1
- package/src/calibration.ts +3 -1
- package/src/census-validation.ts +25 -0
- package/src/coding/architecture-surfaces.test.ts +9 -10
- package/src/coding/architecture-surfaces.ts +2 -35
- package/src/coding/codegraph-runtime-loader.test.ts +228 -0
- package/src/coding/codegraph-runtime.ts +30 -50
- package/src/coding/codegraph-surfaces.ts +2 -2
- package/src/coding/coding-knowledge-config.ts +35 -1
- package/src/coding/decision-surfaces.test.ts +9 -10
- package/src/coding/decision-surfaces.ts +2 -40
- package/src/coding/session-delta-surfaces.test.ts +9 -10
- package/src/coding/session-delta-surfaces.ts +2 -33
- package/src/extraction-judge.ts +3 -1
- package/src/extraction-output-language.test.ts +116 -0
- package/src/extraction-prompt.ts +70 -1
- package/src/extraction.ts +23 -95
- package/src/included-memories.ts +88 -0
- package/src/offline-sync.ts +4 -4
- package/src/orchestration/recall-internal-deps.ts +2 -4
- package/src/orchestration/recall-internal.ts +9 -13
- package/src/orchestration/recall-result-formatter-display-safe.test.ts +13 -16
- package/src/orchestration/recall-result-formatter.ts +32 -45
- package/src/orchestration/recall-section-coordinator.ts +20 -26
- package/src/orchestrator.ts +2 -4
- package/src/recall-explain-renderer.test.ts +4 -0
- package/src/recall-state.test.ts +108 -5
- package/src/recall-state.ts +85 -23
- package/src/recall-token-budget.test.ts +1 -1
- package/src/reconcile/census-parity.test.ts +152 -0
- package/src/reconcile/cursor.ts +5 -5
- package/src/reconcile/manifest.ts +4 -11
- package/src/reconcile/plan.ts +18 -15
- package/dist/chunk-EFKZT6AY.js.map +0 -1
- package/dist/chunk-IVXVLDNT.js.map +0 -1
- package/dist/chunk-L3CHRBHE.js.map +0 -1
- package/dist/chunk-OETHJBAL.js.map +0 -1
- package/dist/chunk-PRDJMOST.js.map +0 -1
- package/dist/chunk-R7TZ2HCS.js.map +0 -1
- package/dist/chunk-RYST72N5.js.map +0 -1
- package/dist/chunk-TWTUGCTY.js.map +0 -1
- package/dist/chunk-YXMHUBN2.js.map +0 -1
- package/dist/chunk-YYQAKIDJ.js.map +0 -1
- package/dist/chunk-Z4N7SVVF.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{chunk-LEPOP46W.js.map → chunk-227UFSEQ.js.map} +0 -0
- /package/dist/{chunk-PB4WVYGC.js.map → chunk-3IMYRX3W.js.map} +0 -0
- /package/dist/{chunk-R6UMNP5C.js.map → chunk-3T6HIIKA.js.map} +0 -0
- /package/dist/{chunk-77QZW2D6.js.map → chunk-3TDAA7VA.js.map} +0 -0
- /package/dist/{chunk-DXDAQZ2K.js.map → chunk-42TIIO2M.js.map} +0 -0
- /package/dist/{chunk-Y7COCXY2.js.map → chunk-4F67JJPG.js.map} +0 -0
- /package/dist/{chunk-OW7NVMGQ.js.map → chunk-5OOTHWGJ.js.map} +0 -0
- /package/dist/{chunk-KYTRF64R.js.map → chunk-5SCFDQEJ.js.map} +0 -0
- /package/dist/{chunk-DYZR3Z6D.js.map → chunk-5TF5STAV.js.map} +0 -0
- /package/dist/{chunk-GG4DN63W.js.map → chunk-62Y5AGCM.js.map} +0 -0
- /package/dist/{chunk-PMVPCZJZ.js.map → chunk-6433HV72.js.map} +0 -0
- /package/dist/{chunk-KHCUVYCY.js.map → chunk-6UKSOXAO.js.map} +0 -0
- /package/dist/{chunk-GF7RQWXC.js.map → chunk-AJRJDXYR.js.map} +0 -0
- /package/dist/{chunk-YEQCMA5L.js.map → chunk-BOSIHHOH.js.map} +0 -0
- /package/dist/{chunk-7LF2OHIJ.js.map → chunk-BRHKW7GY.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-LX4TNSAE.js.map → chunk-CBY54QYW.js.map} +0 -0
- /package/dist/{chunk-CIAI24TQ.js.map → chunk-DGUVXXAU.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-QI5RDDEL.js.map → chunk-EDZNW3HE.js.map} +0 -0
- /package/dist/{chunk-6YMEU6OQ.js.map → chunk-G72IPMR7.js.map} +0 -0
- /package/dist/{chunk-7AF243WZ.js.map → chunk-GBVRVYF4.js.map} +0 -0
- /package/dist/{chunk-FPEZ74F7.js.map → chunk-I2HTKOOP.js.map} +0 -0
- /package/dist/{chunk-VPHSMJ2E.js.map → chunk-JPWSYHLB.js.map} +0 -0
- /package/dist/{chunk-X7LEDA74.js.map → chunk-KFB4YNXM.js.map} +0 -0
- /package/dist/{chunk-I5FLIB2J.js.map → chunk-KXWE62IH.js.map} +0 -0
- /package/dist/{chunk-TSTI6A3A.js.map → chunk-LTYH2L6E.js.map} +0 -0
- /package/dist/{chunk-ZUPKNWRZ.js.map → chunk-MIUL6AUZ.js.map} +0 -0
- /package/dist/{chunk-EJY3HJMT.js.map → chunk-MYI3RKI6.js.map} +0 -0
- /package/dist/{chunk-UCLMKNHA.js.map → chunk-N7V3P4V5.js.map} +0 -0
- /package/dist/{chunk-NA56NSKL.js.map → chunk-O52TB5SL.js.map} +0 -0
- /package/dist/{chunk-7KQB4EBS.js.map → chunk-OPPBTPUU.js.map} +0 -0
- /package/dist/{chunk-ERPBRXVZ.js.map → chunk-PHKU6BZ2.js.map} +0 -0
- /package/dist/{chunk-GTD4Q62X.js.map → chunk-PQHNZ63P.js.map} +0 -0
- /package/dist/{chunk-FHKXVTM5.js.map → chunk-Q5XKKDFQ.js.map} +0 -0
- /package/dist/{chunk-QRWMS6SI.js.map → chunk-Q6OL23WM.js.map} +0 -0
- /package/dist/{chunk-V3KIRICY.js.map → chunk-QQYHYW2N.js.map} +0 -0
- /package/dist/{chunk-YFCEBB5P.js.map → chunk-QTXNS3HB.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-E4KMY4KR.js.map → chunk-SNEUAZ6B.js.map} +0 -0
- /package/dist/{chunk-F6JYBVL6.js.map → chunk-SY5JUJ2X.js.map} +0 -0
- /package/dist/{chunk-PBRCYYO7.js.map → chunk-ULZFZL7K.js.map} +0 -0
- /package/dist/{chunk-IPLYGWQF.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-UGHCZS6K.js.map → chunk-VFZQXYN5.js.map} +0 -0
- /package/dist/{chunk-NKB43PGI.js.map → chunk-VYMICVRA.js.map} +0 -0
- /package/dist/{chunk-2UM6WHME.js.map → chunk-XE2QCCT2.js.map} +0 -0
- /package/dist/{chunk-SYVGVK4S.js.map → chunk-XTMYB2HY.js.map} +0 -0
- /package/dist/{chunk-AYVCKGA6.js.map → chunk-YTXNDEUS.js.map} +0 -0
- /package/dist/{chunk-3Z5TGRFA.js.map → chunk-ZOAAY2OE.js.map} +0 -0
package/src/recall-state.ts
CHANGED
|
@@ -12,6 +12,22 @@ import type {
|
|
|
12
12
|
RecallPlanMode,
|
|
13
13
|
RecallTierExplain,
|
|
14
14
|
} from "./types.js";
|
|
15
|
+
import {
|
|
16
|
+
coerceIncludedMemories,
|
|
17
|
+
type IncludedMemory,
|
|
18
|
+
} from "./included-memories.js";
|
|
19
|
+
|
|
20
|
+
export type { IncludedMemory } from "./included-memories.js";
|
|
21
|
+
export { coerceIncludedMemories } from "./included-memories.js";
|
|
22
|
+
|
|
23
|
+
// Session keys are caller-supplied (host session IDs) and land in indexed
|
|
24
|
+
// assignments on plain-object state. A key matching one of these would
|
|
25
|
+
// pollute Object.prototype (or read junk off it), so every read/write
|
|
26
|
+
// through this module rejects them — same contract load() already enforced.
|
|
27
|
+
function isUnsafeStateKey(key: string): boolean {
|
|
28
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
|
|
16
32
|
export interface LastRecallBudgetSummary {
|
|
17
33
|
requestedTopK?: number;
|
|
@@ -36,6 +52,7 @@ export interface LastRecallSnapshot {
|
|
|
36
52
|
queryHash: string;
|
|
37
53
|
queryLen: number;
|
|
38
54
|
memoryIds: string[];
|
|
55
|
+
includedMemories?: IncludedMemory[];
|
|
39
56
|
namespace?: string;
|
|
40
57
|
recallNamespaces?: string[];
|
|
41
58
|
traceId?: string;
|
|
@@ -172,6 +189,13 @@ type StateFileWriter = (filePath: string, content: string) => Promise<void>;
|
|
|
172
189
|
* recordedAt are compared. Extracted so every annotation surface applies
|
|
173
190
|
* identical guards (CLAUDE.md rule 22).
|
|
174
191
|
*/
|
|
192
|
+
function cloneTierExplain(
|
|
193
|
+
tierExplain: RecallTierExplain | undefined,
|
|
194
|
+
): RecallTierExplain | undefined {
|
|
195
|
+
if (!tierExplain) return undefined;
|
|
196
|
+
return structuredClone(tierExplain);
|
|
197
|
+
}
|
|
198
|
+
|
|
175
199
|
function snapshotMatchesExpectedIdentity(
|
|
176
200
|
current: LastRecallSnapshot,
|
|
177
201
|
expected?: { writeNonce?: string; traceId?: string; recordedAt?: string },
|
|
@@ -182,34 +206,42 @@ function snapshotMatchesExpectedIdentity(
|
|
|
182
206
|
}
|
|
183
207
|
const hasExpectedTraceId =
|
|
184
208
|
typeof expected.traceId === "string" && expected.traceId.length > 0;
|
|
185
|
-
if (hasExpectedTraceId)
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
if (expected.recordedAt !== undefined) {
|
|
209
|
+
if (hasExpectedTraceId && current.traceId !== expected.traceId) return false;
|
|
210
|
+
if (typeof expected.recordedAt === "string" && expected.recordedAt.length > 0) {
|
|
189
211
|
return current.recordedAt === expected.recordedAt;
|
|
190
212
|
}
|
|
191
213
|
return true;
|
|
192
214
|
}
|
|
193
215
|
|
|
194
|
-
function
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
216
|
+
function hydrateLastRecallSnapshot(snapshot: LastRecallSnapshot): LastRecallSnapshot {
|
|
217
|
+
const includedMemories = coerceIncludedMemories(snapshot);
|
|
218
|
+
const budgets = snapshot.budgetsApplied
|
|
219
|
+
? {
|
|
220
|
+
...snapshot.budgetsApplied,
|
|
221
|
+
includedMemoryIds: includedMemories.map((memory) => memory.id),
|
|
222
|
+
includedMemoryPaths: includedMemories.map((memory) => memory.path),
|
|
223
|
+
includedMemoryNamespaces: includedMemories.map((memory) => memory.namespace),
|
|
224
|
+
}
|
|
225
|
+
: undefined;
|
|
226
|
+
return {
|
|
227
|
+
...snapshot,
|
|
228
|
+
includedMemories,
|
|
229
|
+
memoryIds: includedMemories.map((memory) => memory.id),
|
|
230
|
+
resultPaths: includedMemories.map((memory) => memory.path),
|
|
231
|
+
resultNamespaces: includedMemories.map((memory) => memory.namespace),
|
|
232
|
+
...(budgets ? { budgetsApplied: budgets } : {}),
|
|
233
|
+
};
|
|
199
234
|
}
|
|
200
235
|
|
|
201
|
-
/**
|
|
202
|
-
* Deep-copy a LastRecallSnapshot so callers that receive it cannot
|
|
203
|
-
* mutate the store's internal state through mutable array/object
|
|
204
|
-
* fields. Same structuredClone rationale as cloneTierExplain above.
|
|
205
|
-
*/
|
|
206
236
|
function cloneLastRecallSnapshot(
|
|
207
237
|
snapshot: LastRecallSnapshot | null,
|
|
208
238
|
): LastRecallSnapshot | null {
|
|
209
239
|
if (!snapshot) return null;
|
|
210
|
-
return structuredClone(snapshot);
|
|
240
|
+
return structuredClone(hydrateLastRecallSnapshot(snapshot));
|
|
211
241
|
}
|
|
212
242
|
|
|
243
|
+
|
|
244
|
+
|
|
213
245
|
export interface TierMigrationCycleSummary {
|
|
214
246
|
trigger: "extraction" | "maintenance" | "manual";
|
|
215
247
|
scanned: number;
|
|
@@ -319,8 +351,22 @@ export class LastRecallStore {
|
|
|
319
351
|
async load(): Promise<void> {
|
|
320
352
|
try {
|
|
321
353
|
const raw = await readFile(this.statePath, "utf-8");
|
|
322
|
-
const parsed = JSON.parse(raw)
|
|
323
|
-
if (parsed
|
|
354
|
+
const parsed: unknown = JSON.parse(raw);
|
|
355
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
356
|
+
this.state = {};
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const next: LastRecallState = {};
|
|
360
|
+
for (const [sessionKey, snapshot] of Object.entries(parsed)) {
|
|
361
|
+
if (sessionKey === "__proto__" || sessionKey === "constructor" || sessionKey === "prototype") {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
next[sessionKey] = hydrateLastRecallSnapshot(snapshot as LastRecallSnapshot);
|
|
368
|
+
}
|
|
369
|
+
this.state = next;
|
|
324
370
|
} catch {
|
|
325
371
|
this.state = {};
|
|
326
372
|
}
|
|
@@ -329,6 +375,7 @@ export class LastRecallStore {
|
|
|
329
375
|
get(sessionKey: string): LastRecallSnapshot | null {
|
|
330
376
|
// Defensive copy: callers must not be able to mutate internal state
|
|
331
377
|
// by reaching into array/object fields on the returned snapshot.
|
|
378
|
+
if (isUnsafeStateKey(sessionKey)) return null;
|
|
332
379
|
return cloneLastRecallSnapshot(this.state[sessionKey] ?? null);
|
|
333
380
|
}
|
|
334
381
|
|
|
@@ -352,7 +399,8 @@ export class LastRecallStore {
|
|
|
352
399
|
async record(opts: {
|
|
353
400
|
sessionKey: string;
|
|
354
401
|
query: string;
|
|
355
|
-
memoryIds
|
|
402
|
+
memoryIds?: string[];
|
|
403
|
+
includedMemories?: IncludedMemory[];
|
|
356
404
|
namespace?: string;
|
|
357
405
|
recallNamespaces?: string[];
|
|
358
406
|
traceId?: string;
|
|
@@ -386,6 +434,10 @@ export class LastRecallStore {
|
|
|
386
434
|
*/
|
|
387
435
|
backendDegradations?: SearchDegradation[];
|
|
388
436
|
}): Promise<void> {
|
|
437
|
+
if (isUnsafeStateKey(opts.sessionKey)) {
|
|
438
|
+
log.debug("last recall record skipped: unsafe session key");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
389
441
|
const now = new Date().toISOString();
|
|
390
442
|
const queryHash = createHash("sha256").update(opts.query).digest("hex");
|
|
391
443
|
|
|
@@ -393,13 +445,15 @@ export class LastRecallStore {
|
|
|
393
445
|
// cloneLastRecallSnapshot so caller arrays/objects passed in
|
|
394
446
|
// `opts` cannot retain a live reference to the persisted state and
|
|
395
447
|
// tear it after `record()` returns.
|
|
448
|
+
const includedMemories = coerceIncludedMemories(opts);
|
|
396
449
|
const liveSnapshot: LastRecallSnapshot = {
|
|
397
450
|
sessionKey: opts.sessionKey,
|
|
398
451
|
recordedAt: now,
|
|
399
452
|
queryHash,
|
|
400
453
|
writeNonce: randomUUID(),
|
|
401
454
|
queryLen: opts.query.length,
|
|
402
|
-
|
|
455
|
+
includedMemories,
|
|
456
|
+
memoryIds: includedMemories.map((memory) => memory.id),
|
|
403
457
|
namespace: opts.namespace,
|
|
404
458
|
recallNamespaces: opts.recallNamespaces,
|
|
405
459
|
traceId: opts.traceId,
|
|
@@ -410,8 +464,6 @@ export class LastRecallStore {
|
|
|
410
464
|
sourcesUsed: opts.sourcesUsed,
|
|
411
465
|
budgetsApplied: opts.budgetsApplied,
|
|
412
466
|
latencyMs: opts.latencyMs,
|
|
413
|
-
resultPaths: opts.resultPaths,
|
|
414
|
-
resultNamespaces: opts.resultNamespaces,
|
|
415
467
|
policyVersion: opts.policyVersion,
|
|
416
468
|
identityInjectionMode: opts.identityInjection?.mode,
|
|
417
469
|
identityInjectedChars: opts.identityInjection?.injectedChars,
|
|
@@ -810,6 +862,7 @@ export class LastRecallStore {
|
|
|
810
862
|
tierExplain: RecallTierExplain,
|
|
811
863
|
expected?: { writeNonce?: string; traceId?: string; recordedAt?: string },
|
|
812
864
|
): Promise<void> {
|
|
865
|
+
if (isUnsafeStateKey(sessionKey)) return;
|
|
813
866
|
const current = this.state[sessionKey];
|
|
814
867
|
if (!current) return;
|
|
815
868
|
if (!snapshotMatchesExpectedIdentity(current, expected)) return;
|
|
@@ -952,7 +1005,15 @@ export class RecallHandleHistoryStore {
|
|
|
952
1005
|
try {
|
|
953
1006
|
const raw = await readFile(this.statePath, "utf-8");
|
|
954
1007
|
const parsed = JSON.parse(raw);
|
|
955
|
-
if (parsed && typeof parsed === "object"
|
|
1008
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1009
|
+
const next: Record<string, Array<{ at: string; ids: string[] }>> = {};
|
|
1010
|
+
for (const [sessionKey, entries] of Object.entries(parsed)) {
|
|
1011
|
+
if (isUnsafeStateKey(sessionKey)) continue;
|
|
1012
|
+
if (!Array.isArray(entries)) continue;
|
|
1013
|
+
next[sessionKey] = entries;
|
|
1014
|
+
}
|
|
1015
|
+
this.state = next;
|
|
1016
|
+
}
|
|
956
1017
|
} catch {
|
|
957
1018
|
this.state = {};
|
|
958
1019
|
}
|
|
@@ -963,7 +1024,7 @@ export class RecallHandleHistoryStore {
|
|
|
963
1024
|
* The ring is capped at {@link maxDepth}; older entries drop off the tail.
|
|
964
1025
|
*/
|
|
965
1026
|
async record(sessionKey: string, memoryIds: readonly string[]): Promise<void> {
|
|
966
|
-
if (!sessionKey) return;
|
|
1027
|
+
if (!sessionKey || isUnsafeStateKey(sessionKey)) return;
|
|
967
1028
|
const ids = memoryIds.filter((id): id is string => typeof id === "string" && id.length > 0);
|
|
968
1029
|
const entry = { at: new Date().toISOString(), ids };
|
|
969
1030
|
const prior = this.state[sessionKey] ?? [];
|
|
@@ -982,6 +1043,7 @@ export class RecallHandleHistoryStore {
|
|
|
982
1043
|
* Empty array when the session has no recorded history.
|
|
983
1044
|
*/
|
|
984
1045
|
recent(sessionKey: string, depth: number = this.maxDepth): Array<readonly string[]> {
|
|
1046
|
+
if (isUnsafeStateKey(sessionKey)) return [];
|
|
985
1047
|
const entries = this.state[sessionKey];
|
|
986
1048
|
if (!entries || entries.length === 0) return [];
|
|
987
1049
|
const limit = Math.max(0, Math.min(depth, entries.length));
|
|
@@ -46,6 +46,6 @@ test("recall reserves one memory chunk that fits both character and token budget
|
|
|
46
46
|
|
|
47
47
|
const assembled = coordinator.assembleRecallSections(buckets);
|
|
48
48
|
|
|
49
|
-
assert.deepEqual(assembled.
|
|
49
|
+
assert.deepEqual(assembled.includedMemories, [{ id: "latin", path: "memories/latin.md" }]);
|
|
50
50
|
assert.ok(estimateTokenCount(assembled.sections.join("\n\n---\n\n")) <= 50);
|
|
51
51
|
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
OFFLINE_SYNC_MAX_MTIME_MS,
|
|
6
|
+
OFFLINE_SYNC_SNAPSHOT_FORMAT,
|
|
7
|
+
normalizeOfflineSyncSnapshot,
|
|
8
|
+
} from "../offline-sync.js";
|
|
9
|
+
import {
|
|
10
|
+
type ReconcilePlanEntry,
|
|
11
|
+
ReconcilePlanInputError,
|
|
12
|
+
compareReconcilePlanEntries,
|
|
13
|
+
planNamespaceReconciliation,
|
|
14
|
+
planReconciliation,
|
|
15
|
+
semanticAgreementKey,
|
|
16
|
+
} from "./plan.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Issue #2477: offline-sync and the reconcile planner validate the same peer
|
|
20
|
+
* census fields, and cursor/manifest order and key plan entries the same way.
|
|
21
|
+
* These tests fail the moment one surface drifts from the other, which was
|
|
22
|
+
* previously a silent convergence bug.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const PATH = "facts/2026-03-01/a.md";
|
|
26
|
+
const GOOD_SHA = "a".repeat(64);
|
|
27
|
+
|
|
28
|
+
type CensusOutcome = { accepted: boolean; digest?: string };
|
|
29
|
+
|
|
30
|
+
/** Validate one census file record through the offline-sync snapshot surface. */
|
|
31
|
+
function offlineSyncOutcome(overrides: Record<string, unknown>): CensusOutcome {
|
|
32
|
+
try {
|
|
33
|
+
const snapshot = normalizeOfflineSyncSnapshot({
|
|
34
|
+
format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
|
|
35
|
+
schemaVersion: 1,
|
|
36
|
+
createdAt: "2026-08-17T00:00:00.000Z",
|
|
37
|
+
sourceId: "peer",
|
|
38
|
+
includeTranscripts: false,
|
|
39
|
+
files: [{ path: PATH, bytes: 1, mtimeMs: 0, sha256: GOOD_SHA, ...overrides }],
|
|
40
|
+
});
|
|
41
|
+
return { accepted: true, digest: snapshot.files[0]?.sha256 };
|
|
42
|
+
} catch {
|
|
43
|
+
return { accepted: false };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Validate the same record through the reconcile planner's census surface. */
|
|
48
|
+
function reconcileOutcome(overrides: Record<string, unknown>): CensusOutcome {
|
|
49
|
+
try {
|
|
50
|
+
const entries = planNamespaceReconciliation({
|
|
51
|
+
namespace: "default",
|
|
52
|
+
local: [{ path: PATH, sha256: GOOD_SHA, mtimeMs: 0, ...overrides }],
|
|
53
|
+
peer: [],
|
|
54
|
+
});
|
|
55
|
+
return { accepted: true, digest: entries.find((e) => e.path === PATH)?.localSha256 };
|
|
56
|
+
} catch (err) {
|
|
57
|
+
assert.ok(err instanceof ReconcilePlanInputError, "plan census rejects must stay ReconcilePlanInputError");
|
|
58
|
+
return { accepted: false };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const SHA256_FIXTURES: unknown[] = [
|
|
63
|
+
"b".repeat(64), // accepted, already canonical
|
|
64
|
+
"B".repeat(64), // accepted, canonicalized to lowercase
|
|
65
|
+
"b".repeat(63), // too short
|
|
66
|
+
"b".repeat(65), // too long
|
|
67
|
+
"z".repeat(64), // not hex
|
|
68
|
+
"",
|
|
69
|
+
42,
|
|
70
|
+
null,
|
|
71
|
+
{ length: 64 },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const MTIME_FIXTURES: unknown[] = [
|
|
75
|
+
0,
|
|
76
|
+
1.25, // fractional mtimes are valid
|
|
77
|
+
OFFLINE_SYNC_MAX_MTIME_MS,
|
|
78
|
+
OFFLINE_SYNC_MAX_MTIME_MS + 1,
|
|
79
|
+
-1,
|
|
80
|
+
Number.NaN,
|
|
81
|
+
Number.POSITIVE_INFINITY,
|
|
82
|
+
"100",
|
|
83
|
+
null,
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
test("sha256 census fixtures accept and canonicalize identically on both surfaces", () => {
|
|
87
|
+
const offline = SHA256_FIXTURES.map((sha256) => offlineSyncOutcome({ sha256 }));
|
|
88
|
+
const reconcile = SHA256_FIXTURES.map((sha256) => reconcileOutcome({ sha256 }));
|
|
89
|
+
assert.deepEqual(reconcile, offline);
|
|
90
|
+
assert.deepEqual(
|
|
91
|
+
offline.map((o) => o.accepted),
|
|
92
|
+
[true, true, false, false, false, false, false, false, false]
|
|
93
|
+
);
|
|
94
|
+
assert.equal(offline[1].digest, "b".repeat(64));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("mtimeMs census fixtures accept and reject identically on both surfaces", () => {
|
|
98
|
+
const offline = MTIME_FIXTURES.map((mtimeMs) => offlineSyncOutcome({ mtimeMs }));
|
|
99
|
+
const reconcile = MTIME_FIXTURES.map((mtimeMs) => reconcileOutcome({ mtimeMs }));
|
|
100
|
+
assert.deepEqual(reconcile, offline);
|
|
101
|
+
assert.deepEqual(
|
|
102
|
+
offline.map((o) => o.accepted),
|
|
103
|
+
[true, true, true, false, false, false, false, false, false]
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("compareReconcilePlanEntries is the total order plan output itself uses", () => {
|
|
108
|
+
const entry = (namespace: string, path: string): ReconcilePlanEntry => ({
|
|
109
|
+
path,
|
|
110
|
+
namespace,
|
|
111
|
+
action: "identical",
|
|
112
|
+
reason: "same_content",
|
|
113
|
+
});
|
|
114
|
+
const a = entry("alpha", "facts/a.md");
|
|
115
|
+
const b = entry("beta", "facts/a.md");
|
|
116
|
+
const c = entry("beta", "facts/b.md");
|
|
117
|
+
const sorted = [c, { ...a }, b, a].sort(compareReconcilePlanEntries);
|
|
118
|
+
assert.deepEqual(
|
|
119
|
+
sorted.map((e) => [e.namespace, e.path]),
|
|
120
|
+
[
|
|
121
|
+
["alpha", "facts/a.md"],
|
|
122
|
+
["alpha", "facts/a.md"],
|
|
123
|
+
["beta", "facts/a.md"],
|
|
124
|
+
["beta", "facts/b.md"],
|
|
125
|
+
]
|
|
126
|
+
);
|
|
127
|
+
assert.equal(compareReconcilePlanEntries(a, { ...a }), 0);
|
|
128
|
+
|
|
129
|
+
const plan = planReconciliation([
|
|
130
|
+
{ namespace: "beta", local: [], peer: [] },
|
|
131
|
+
{ namespace: "alpha", local: [{ path: "facts/z.md", sha256: GOOD_SHA }], peer: [] },
|
|
132
|
+
]);
|
|
133
|
+
assert.ok(plan.entries.length > 0);
|
|
134
|
+
for (let i = 1; i < plan.entries.length; i += 1) {
|
|
135
|
+
assert.ok(compareReconcilePlanEntries(plan.entries[i - 1], plan.entries[i]) <= 0);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("semanticAgreementKey pins the ordered path-pair identity shared by cursor and manifest", () => {
|
|
140
|
+
const localFirst = semanticAgreementKey({
|
|
141
|
+
local: { path: "facts/local.md", sha256: GOOD_SHA },
|
|
142
|
+
peer: { path: "facts/peer.md", sha256: GOOD_SHA },
|
|
143
|
+
});
|
|
144
|
+
assert.equal(localFirst, "facts/local.md\0facts/peer.md");
|
|
145
|
+
assert.notEqual(
|
|
146
|
+
localFirst,
|
|
147
|
+
semanticAgreementKey({
|
|
148
|
+
local: { path: "facts/peer.md", sha256: GOOD_SHA },
|
|
149
|
+
peer: { path: "facts/local.md", sha256: GOOD_SHA },
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
});
|
package/src/reconcile/cursor.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
type ReconcilePlanEntry,
|
|
6
|
+
type ReconcileSemanticAgreement,
|
|
7
|
+
semanticAgreementKey,
|
|
8
|
+
} from "./plan.js";
|
|
5
9
|
|
|
6
10
|
export type ConvergeRefreshTarget = "local" | "receiver";
|
|
7
11
|
|
|
@@ -76,10 +80,6 @@ function digestAfterReconcile(entry: ReconcilePlanEntry): string | undefined {
|
|
|
76
80
|
return entry.localSha256 ?? entry.peerSha256;
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
function semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {
|
|
80
|
-
return `${agreement.local.path}\0${agreement.peer.path}`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
83
|
export function deriveConvergeCursorBase(
|
|
84
84
|
entries: readonly ReconcilePlanEntry[],
|
|
85
85
|
namespace: string,
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
type ReconcilePlanEntry,
|
|
13
13
|
type ReconcileSemanticAgreement,
|
|
14
14
|
type ReconcileSemanticChange,
|
|
15
|
+
compareReconcilePlanEntries,
|
|
16
|
+
semanticAgreementKey,
|
|
15
17
|
summarizeReconcilePlan,
|
|
16
18
|
} from "./plan.js";
|
|
17
19
|
|
|
@@ -218,15 +220,6 @@ function contentHashRows(
|
|
|
218
220
|
return rows;
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
function comparePlanEntries(left: ReconcilePlanEntry, right: ReconcilePlanEntry): number {
|
|
222
|
-
if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;
|
|
223
|
-
return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {
|
|
227
|
-
return `${agreement.local.path}\0${agreement.peer.path}`;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
223
|
function classifySemanticChange(
|
|
231
224
|
current: ReconcileSemanticAgreement,
|
|
232
225
|
prior: ReconcileSemanticAgreement | undefined
|
|
@@ -396,13 +389,13 @@ export function collapseActiveFactDuplicates(
|
|
|
396
389
|
if (removed.size > 0 || replacements.length > 0) {
|
|
397
390
|
entriesByNamespace.set(
|
|
398
391
|
namespace,
|
|
399
|
-
[...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(
|
|
392
|
+
[...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(compareReconcilePlanEntries)
|
|
400
393
|
);
|
|
401
394
|
}
|
|
402
395
|
}
|
|
403
396
|
|
|
404
397
|
if (!changed) return plan;
|
|
405
|
-
const entries = [...entriesByNamespace.values()].flat().sort(
|
|
398
|
+
const entries = [...entriesByNamespace.values()].flat().sort(compareReconcilePlanEntries);
|
|
406
399
|
return {
|
|
407
400
|
entries,
|
|
408
401
|
byNamespace: summarizeReconcilePlan(entries),
|
package/src/reconcile/plan.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { CENSUS_MAX_MTIME_MS, isCensusMtimeMs, isSha256Hex } from "../census-validation.js";
|
|
1
2
|
import { normalizeNamespaceIdentity } from "../namespaces/identity.js";
|
|
2
|
-
import { OFFLINE_SYNC_MAX_MTIME_MS } from "../offline-sync.js";
|
|
3
3
|
import { validateArchiveRelativePath } from "../transfer/fs-utils.js";
|
|
4
4
|
import type { OfflineSyncFileState } from "../offline-sync.js";
|
|
5
5
|
import {
|
|
@@ -197,8 +197,7 @@ export class ReconcilePlanInputError extends Error {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
/**
|
|
201
|
-
const SHA256_PATTERN = /^[a-f0-9]{64}$/i;
|
|
200
|
+
/** Shared with offline-sync's census validation (issue #2477): one acceptance set. */
|
|
202
201
|
|
|
203
202
|
/**
|
|
204
203
|
* Validate a census record's identity fields.
|
|
@@ -247,14 +246,9 @@ function assertCensusRecord(
|
|
|
247
246
|
* deliberately does not require an integer.
|
|
248
247
|
*/
|
|
249
248
|
function assertMtimeMs(value: unknown, context: string): number {
|
|
250
|
-
if (
|
|
251
|
-
typeof value !== "number"
|
|
252
|
-
|| !Number.isFinite(value)
|
|
253
|
-
|| value < 0
|
|
254
|
-
|| value > OFFLINE_SYNC_MAX_MTIME_MS
|
|
255
|
-
) {
|
|
249
|
+
if (!isCensusMtimeMs(value)) {
|
|
256
250
|
throw new ReconcilePlanInputError(
|
|
257
|
-
`reconcile: ${context} has an out-of-range mtimeMs; expected a finite value between 0 and ${
|
|
251
|
+
`reconcile: ${context} has an out-of-range mtimeMs; expected a finite value between 0 and ${CENSUS_MAX_MTIME_MS}`,
|
|
258
252
|
);
|
|
259
253
|
}
|
|
260
254
|
return value;
|
|
@@ -267,7 +261,7 @@ function assertMtimeMs(value: unknown, context: string): number {
|
|
|
267
261
|
* and tombstone lookups would silently miss.
|
|
268
262
|
*/
|
|
269
263
|
function assertDigest(value: unknown, context: string): string {
|
|
270
|
-
if (
|
|
264
|
+
if (!isSha256Hex(value)) {
|
|
271
265
|
throw new ReconcilePlanInputError(
|
|
272
266
|
`reconcile: ${context} must carry a 64-character sha256 hex digest`,
|
|
273
267
|
);
|
|
@@ -503,14 +497,23 @@ function assertConflictPolicy(value: ConvergeConflictPolicy | undefined): Conver
|
|
|
503
497
|
* Total ordering for plan entries (§12): namespace, then path. Both are unique
|
|
504
498
|
* per entry, so equal keys are impossible and the comparator never has to
|
|
505
499
|
* return 0 for distinct rows — the output is byte-identical across runs, which
|
|
506
|
-
* is what makes a convergence report diffable.
|
|
500
|
+
* is what makes a convergence report diffable. Cursor and manifest sort with
|
|
501
|
+
* this same export; a local copy on either side is how ordering drifts (§2477).
|
|
507
502
|
*/
|
|
508
|
-
function
|
|
503
|
+
export function compareReconcilePlanEntries(a: ReconcilePlanEntry, b: ReconcilePlanEntry): number {
|
|
509
504
|
if (a.namespace !== b.namespace) return a.namespace < b.namespace ? -1 : 1;
|
|
510
505
|
if (a.path !== b.path) return a.path < b.path ? -1 : 1;
|
|
511
506
|
return 0;
|
|
512
507
|
}
|
|
513
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Identity of a semantic-duplicate pair: the ordered (local, peer) path pair.
|
|
511
|
+
* Cursor derivation and manifest collapse must key prior agreements with this
|
|
512
|
+
* same export or a stored cursor stops matching the plan that wrote it.
|
|
513
|
+
*/
|
|
514
|
+
export function semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {
|
|
515
|
+
return `${agreement.local.path}\0${agreement.peer.path}`;
|
|
516
|
+
}
|
|
514
517
|
|
|
515
518
|
function resolveConflict(
|
|
516
519
|
policy: ConvergeConflictPolicy,
|
|
@@ -795,7 +798,7 @@ export function planNamespaceReconciliation(
|
|
|
795
798
|
});
|
|
796
799
|
}
|
|
797
800
|
|
|
798
|
-
return entries.sort(
|
|
801
|
+
return entries.sort(compareReconcilePlanEntries);
|
|
799
802
|
}
|
|
800
803
|
|
|
801
804
|
/** Aggregate entries into the per-namespace convergence report (#2150). */
|
|
@@ -852,7 +855,7 @@ export function planReconciliation(
|
|
|
852
855
|
seenNamespaces.add(name);
|
|
853
856
|
entries.push(...planNamespaceReconciliation(namespace, options));
|
|
854
857
|
}
|
|
855
|
-
entries.sort(
|
|
858
|
+
entries.sort(compareReconcilePlanEntries);
|
|
856
859
|
return {
|
|
857
860
|
entries,
|
|
858
861
|
byNamespace: summarizeReconcilePlan(entries),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reconcile/manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { computeLegacyContentHash, normalizeLegacyContent } from \"../content-hash.js\";\nimport { inferMemoryStatus } from \"../memory-lifecycle-ledger-utils.js\";\nimport { ContentHashIndex, type ContentHashPathEntry } from \"../storage/content-hash-index.js\";\nimport { DEFAULT_CITATION_FORMAT, stripCitationForTemplate } from \"../source-attribution.js\";\nimport { storedContentIdentityCandidates } from \"../structured-attributes.js\";\nimport type { MemoryFrontmatter, MemoryStatus } from \"../types.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport {\n type ReconcileFileState,\n type ReconcilePlan,\n type ReconcilePlanEntry,\n type ReconcileSemanticAgreement,\n type ReconcileSemanticChange,\n summarizeReconcilePlan,\n} from \"./plan.js\";\n\nexport const RECONCILE_MANIFEST_FORMAT = \"remnic-reconcile-manifest\";\nexport const RECONCILE_MANIFEST_SCHEMA_VERSION = 1;\nconst CONTENT_HASH_NORMALIZER_VERSION = 4;\n/**\n * Version of the identity-resolution LOGIC (alias recovery, candidate\n * ordering), independent of the content normalizer: a cached manifest entry\n * written before an identity-resolution change carries an older value and is\n * reparsed even when its normalizerVersion still matches (issue #2367\n * review round 2). Bump when parsedMemoryIdentity's output shape or\n * semantics change.\n */\nconst IDENTITY_RESOLUTION_VERSION = 2;\n\nexport interface ReconcileMemoryIdentity {\n id: string;\n category: string;\n contentHash: string;\n /**\n * Recovered current-normalizer identities for a record whose persisted hash\n * is ambiguous under the legacy normalizer (issue #2367). The persisted\n * hash stays primary; these participate in duplicate bucketing so a\n * replica carrying the current form of the same fact still collapses.\n */\n contentHashAliases?: readonly string[];\n /** See IDENTITY_RESOLUTION_VERSION; absent on pre-versioning cache entries. */\n normalizerVersion?: number;\n identityResolutionVersion?: number;\n status: MemoryStatus;\n}\n\nexport interface ReconcileManifestFile extends ReconcileFileState {\n memory?: ReconcileMemoryIdentity;\n}\n\ntype ActiveFactManifestFile = ReconcileManifestFile & { memory: ReconcileMemoryIdentity };\n\nexport interface ReconcileManifest {\n format: typeof RECONCILE_MANIFEST_FORMAT;\n schemaVersion: typeof RECONCILE_MANIFEST_SCHEMA_VERSION;\n files: ReconcileManifestFile[];\n}\n\nexport type ReconcileMemoryParser = (\n raw: string,\n) => { frontmatter: MemoryFrontmatter; content: string } | null;\n\nexport interface BuildReconcileManifestOptions {\n files: Iterable<ReconcileFileState>;\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>;\n parseMemory: ReconcileMemoryParser;\n cachedFiles?: Iterable<ReconcileManifestFile>;\n citationTemplate?: string;\n}\n\nconst SHA256_PATTERN = /^[a-f0-9]{64}$/i;\nconst MEMORY_DIRS = new Set(RECALL_FALLBACK_DIRS);\n\nfunction isMemoryPath(filePath: string): boolean {\n if (!filePath.endsWith(\".md\")) return false;\n const segments = filePath.split(\"/\");\n let index = 0;\n if (segments[index] === \"cold\" || segments[index] === \"archive\") index += 1;\n return MEMORY_DIRS.has(segments[index] ?? \"\");\n}\n\n\nfunction parsedMemoryIdentity(\n filePath: string,\n raw: Buffer | string,\n parseMemory: ReconcileMemoryParser,\n citationTemplate: string,\n): ReconcileMemoryIdentity | undefined {\n if (!isMemoryPath(filePath)) return undefined;\n const parsed = parseMemory(Buffer.isBuffer(raw) ? raw.toString(\"utf8\") : raw);\n if (!parsed?.frontmatter.id) return undefined;\n\n const storedHash =\n parsed.frontmatter.contentHash &&\n SHA256_PATTERN.test(parsed.frontmatter.contentHash)\n ? parsed.frontmatter.contentHash.toLowerCase()\n : undefined;\n const candidates = storedContentIdentityCandidates(parsed.content, (content) =>\n stripCitationForTemplate(content, citationTemplate)\n );\n const bodyHash = ContentHashIndex.computeHash(candidates[0] ?? \"\");\n const currentMatch = candidates.find(\n (content) => ContentHashIndex.computeHash(content) === storedHash\n );\n const legacyMatch = [...candidates].reverse().find(\n (content) => computeLegacyContentHash(content) === storedHash\n );\n let contentHash: string;\n let contentHashAliases: string[] | undefined;\n if (currentMatch) {\n contentHash = ContentHashIndex.computeHash(currentMatch);\n } else if (legacyMatch && normalizeLegacyContent(legacyMatch).length > 0) {\n // A non-empty legacy equality is ambiguous (issue #2367): the persisted\n // hash may be an explicit contentHashSource identity that merely collides\n // with the body's ASCII skeleton under the lossy legacy normalizer, and\n // nothing in the record proves which it is. Keep the persisted hash as\n // the primary identity and expose the recovered current identity as an\n // alias so replicas carrying either form still collapse.\n contentHash = storedHash ?? bodyHash;\n const recovered = ContentHashIndex.computeHash(legacyMatch);\n if (recovered !== contentHash) contentHashAliases = [recovered];\n } else {\n // An EMPTY legacy skeleton identifies nothing (every non-ASCII body maps\n // to it), so preserving it would collapse unrelated records. Recovering\n // the current identity here is repair, not replacement.\n contentHash = legacyMatch ? ContentHashIndex.computeHash(legacyMatch) : storedHash ?? bodyHash;\n }\n\n return {\n id: parsed.frontmatter.id,\n category: parsed.frontmatter.category,\n contentHash,\n ...(contentHashAliases ? { contentHashAliases } : {}),\n normalizerVersion: CONTENT_HASH_NORMALIZER_VERSION,\n identityResolutionVersion: IDENTITY_RESOLUTION_VERSION,\n status: inferMemoryStatus(parsed.frontmatter, filePath),\n };\n}\n\nexport async function buildReconcileManifestFile(\n file: ReconcileFileState,\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>,\n parseMemory: ReconcileMemoryParser,\n citationTemplate = DEFAULT_CITATION_FORMAT,\n): Promise<ReconcileManifestFile> {\n let raw: Buffer | string | null = null;\n if (isMemoryPath(file.path)) {\n try {\n raw = await readFile(file);\n } catch {\n raw = null;\n }\n }\n if (raw !== null && createHash(\"sha256\").update(raw).digest(\"hex\") !== file.sha256.toLowerCase()) {\n raw = null;\n }\n const memory = raw === null ? undefined : parsedMemoryIdentity(file.path, raw, parseMemory, citationTemplate);\n return { ...file, ...(memory ? { memory } : {}) };\n}\n\nexport async function buildReconcileManifest(options: BuildReconcileManifestOptions): Promise<ReconcileManifest> {\n const cachedByPath = new Map<string, ReconcileManifestFile>();\n for (const cached of options.cachedFiles ?? []) {\n cachedByPath.set(cached.path, cached);\n }\n\n const files: ReconcileManifestFile[] = [];\n for (const file of options.files) {\n const cached = cachedByPath.get(file.path);\n if (\n cached?.sha256.toLowerCase() === file.sha256.toLowerCase() &&\n (cached.memory === undefined ||\n (cached.memory.normalizerVersion === CONTENT_HASH_NORMALIZER_VERSION &&\n cached.memory.identityResolutionVersion === IDENTITY_RESOLUTION_VERSION))\n ) {\n files.push({ ...file, ...(cached.memory ? { memory: cached.memory } : {}) });\n continue;\n }\n\n files.push(await buildReconcileManifestFile(\n file,\n options.readFile,\n options.parseMemory,\n options.citationTemplate,\n ));\n }\n files.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));\n return {\n format: RECONCILE_MANIFEST_FORMAT,\n schemaVersion: RECONCILE_MANIFEST_SCHEMA_VERSION,\n files,\n };\n}\n\nfunction activeFactByPath(manifest: ReconcileManifest | undefined): Map<string, ActiveFactManifestFile> {\n const result = new Map<string, ActiveFactManifestFile>();\n for (const file of manifest?.files ?? []) {\n if (file.memory?.category === \"fact\" && file.memory.status === \"active\") {\n result.set(file.path, file as ActiveFactManifestFile);\n }\n }\n return result;\n}\n\nfunction memoryIdentityHashes(memory: ReconcileMemoryIdentity): string[] {\n return [memory.contentHash, ...(memory.contentHashAliases ?? [])];\n}\n\nfunction contentHashRows(\n files: Iterable<ActiveFactManifestFile>,\n contentHash: string,\n): ContentHashPathEntry[] {\n const rows: ContentHashPathEntry[] = [];\n for (const file of files) {\n rows.push({ path: file.path, contentHash });\n }\n return rows;\n}\n\nfunction comparePlanEntries(left: ReconcilePlanEntry, right: ReconcilePlanEntry): number {\n if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;\n return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;\n}\n\nfunction semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {\n return `${agreement.local.path}\\0${agreement.peer.path}`;\n}\n\nfunction classifySemanticChange(\n current: ReconcileSemanticAgreement,\n prior: ReconcileSemanticAgreement | undefined\n): ReconcileSemanticChange {\n if (!prior) return \"unchanged\";\n const localChanged = current.local.sha256 !== prior.local.sha256;\n const peerChanged = current.peer.sha256 !== prior.peer.sha256;\n if (localChanged && peerChanged) return \"both_modified\";\n if (localChanged) return \"local_changed\";\n if (peerChanged) return \"peer_changed\";\n return \"unchanged\";\n}\n\nexport function collapseActiveFactDuplicates(\n plan: ReconcilePlan,\n localManifests: ReadonlyMap<string, ReconcileManifest>,\n peerManifests: ReadonlyMap<string, ReconcileManifest>,\n priorSemanticAgreements?: ReadonlyMap<string, readonly ReconcileSemanticAgreement[]>,\n): ReconcilePlan {\n const entriesByNamespace = new Map<string, ReconcilePlanEntry[]>();\n for (const entry of plan.entries) {\n const entries = entriesByNamespace.get(entry.namespace) ?? [];\n entries.push({ ...entry });\n entriesByNamespace.set(entry.namespace, entries);\n }\n\n let changed = false;\n for (const namespace of new Set([...localManifests.keys(), ...peerManifests.keys()])) {\n const entries = entriesByNamespace.get(namespace) ?? [];\n const localManifest = localManifests.get(namespace);\n const peerManifest = peerManifests.get(namespace);\n const localByPath = activeFactByPath(localManifest);\n const peerByPath = activeFactByPath(peerManifest);\n const localFilesByPath = new Map((localManifest?.files ?? []).map((file) => [file.path, file]));\n const peerFilesByPath = new Map((peerManifest?.files ?? []).map((file) => [file.path, file]));\n const priorSemanticByPathPair = new Map(\n (priorSemanticAgreements?.get(namespace) ?? []).map((agreement) => [\n semanticAgreementKey(agreement),\n agreement,\n ])\n );\n const localByHash = new Map<string, ActiveFactManifestFile[]>();\n const peerByHash = new Map<string, ActiveFactManifestFile[]>();\n\n for (const file of localByPath.values()) {\n for (const hash of memoryIdentityHashes(file.memory)) {\n const bucket = localByHash.get(hash) ?? [];\n bucket.push(file);\n localByHash.set(hash, bucket);\n }\n }\n for (const file of peerByPath.values()) {\n for (const hash of memoryIdentityHashes(file.memory)) {\n const bucket = peerByHash.get(hash) ?? [];\n bucket.push(file);\n peerByHash.set(hash, bucket);\n }\n }\n\n const removed = new Set<ReconcilePlanEntry>();\n const replacements: ReconcilePlanEntry[] = [];\n const processedPathPairs = new Set<string>();\n for (const hash of new Set([...localByHash.keys(), ...peerByHash.keys()])) {\n const localCandidates = (localByHash.get(hash) ?? []).filter((file) => {\n const opposite = peerFilesByPath.get(file.path);\n const activeOpposite = peerByPath.get(file.path);\n return opposite === undefined || (\n activeOpposite !== undefined &&\n memoryIdentityHashes(activeOpposite.memory).includes(hash)\n );\n });\n const peerCandidates = (peerByHash.get(hash) ?? []).filter((file) => {\n const opposite = localFilesByPath.get(file.path);\n const activeOpposite = localByPath.get(file.path);\n return opposite === undefined || (\n activeOpposite !== undefined &&\n memoryIdentityHashes(activeOpposite.memory).includes(hash)\n );\n });\n const localPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(localCandidates, hash));\n const peerPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(peerCandidates, hash));\n\n if (localPath && peerPath && localPath !== peerPath) {\n const pathPair = `${localPath}\\0${peerPath}`;\n if (processedPathPairs.has(pathPair)) continue;\n processedPathPairs.add(pathPair);\n const localFile = localByPath.get(localPath);\n const peerFile = peerByPath.get(peerPath);\n if (!localFile || !peerFile) continue;\n const duplicatePaths = new Set([\n ...localCandidates.map((file) => file.path),\n ...peerCandidates.map((file) => file.path),\n ]);\n const authoritativeSamePathEntries = new Set(entries.filter(\n (entry) =>\n duplicatePaths.has(entry.path)\n && localFilesByPath.has(entry.path)\n && peerFilesByPath.has(entry.path)\n && entry.action !== \"identical\"\n ));\n if (authoritativeSamePathEntries.size > 0) {\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path)\n && !authoritativeSamePathEntries.has(entry)\n && (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n changed = true;\n }\n }\n continue;\n }\n const unsafeEntry = entries.some(\n (entry) => duplicatePaths.has(entry.path) && (entry.action === \"suppress\" || entry.action === \"conflict\")\n );\n if (unsafeEntry) continue;\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path) &&\n (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n }\n }\n const semanticAgreement: ReconcileSemanticAgreement = {\n local: { path: localPath, sha256: localFile.sha256 },\n peer: { path: peerPath, sha256: peerFile.sha256 },\n };\n replacements.push({\n path: localPath < peerPath ? localPath : peerPath,\n namespace,\n action: \"identical\",\n reason: \"semantic_duplicate\",\n semanticAgreement,\n semanticChange: classifySemanticChange(\n semanticAgreement,\n priorSemanticByPathPair.get(semanticAgreementKey(semanticAgreement))\n ),\n });\n changed = true;\n continue;\n }\n\n const sameSideEntries = entries.filter((entry) => {\n if (localPath && entry.reason === \"local_only\") {\n return localCandidates.some((file) => file.path === entry.path);\n }\n if (peerPath && entry.reason === \"peer_only\") {\n return peerCandidates.some((file) => file.path === entry.path);\n }\n return false;\n });\n const canonicalPath = localPath ?? peerPath;\n const hasSharedCanonical = localPath !== undefined && localPath === peerPath;\n if (!canonicalPath || sameSideEntries.length === 0 || (!hasSharedCanonical && sameSideEntries.length < 2)) {\n continue;\n }\n for (const entry of sameSideEntries) {\n if (entry.path !== canonicalPath) {\n removed.add(entry);\n changed = true;\n }\n }\n }\n\n if (removed.size > 0 || replacements.length > 0) {\n entriesByNamespace.set(\n namespace,\n [...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(comparePlanEntries)\n );\n }\n }\n\n if (!changed) return plan;\n const entries = [...entriesByNamespace.values()].flat().sort(comparePlanEntries);\n return {\n entries,\n byNamespace: summarizeReconcilePlan(entries),\n converged: entries.every((entry) => entry.action === \"identical\"),\n };\n}\n"],"mappings":"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","names":["entries"]}
|