@fgv/ts-agent-memory 5.1.0-39 → 5.1.0-41
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/.rush/temp/{b82cf6bdece20481260e6bab946179eeec9d7b46.tar.log → cbbdbe09515171b4eba8f2592be2dace1e4e8142.tar.log} +14 -2
- package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +2 -2
- package/.rush/temp/operation/build/all.log +2 -2
- package/.rush/temp/operation/build/log-chunks.jsonl +2 -2
- package/.rush/temp/operation/build/state.json +1 -1
- package/dist/packlets/converters/envelopeConverter.js +17 -3
- package/dist/packlets/converters/envelopeConverter.js.map +1 -1
- package/dist/packlets/index/memoryIndex.js +58 -10
- package/dist/packlets/index/memoryIndex.js.map +1 -1
- package/dist/packlets/ingest/cycleGuard.js +13 -6
- package/dist/packlets/ingest/cycleGuard.js.map +1 -1
- package/dist/packlets/ingest/hostStages.js.map +1 -1
- package/dist/packlets/ingest/model.js.map +1 -1
- package/dist/packlets/ingest/orchestrator.js +94 -49
- package/dist/packlets/ingest/orchestrator.js.map +1 -1
- package/dist/packlets/retrieve/hybridRetriever.js +13 -2
- package/dist/packlets/retrieve/hybridRetriever.js.map +1 -1
- package/dist/packlets/retrieve/linkTraversalRetriever.js +46 -57
- package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/dist/packlets/retrieve/recencyRetriever.js +3 -3
- package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/dist/packlets/retrieve/retriever.js +50 -7
- package/dist/packlets/retrieve/retriever.js.map +1 -1
- package/dist/packlets/retrieve/semanticRetriever.js +9 -3
- package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/dist/packlets/retrieve/structuredFilterRetriever.js +3 -3
- package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/dist/packlets/retrieve/tagRetriever.js +3 -3
- package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
- package/dist/packlets/retrieve/temporalRetrievers.js +3 -3
- package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/dist/packlets/store/fileTreeMemoryStore.js +72 -16
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/tools/memoryTools.js +104 -21
- package/dist/packlets/tools/memoryTools.js.map +1 -1
- package/dist/packlets/types/envelope.js +13 -1
- package/dist/packlets/types/envelope.js.map +1 -1
- package/dist/packlets/vector/inMemoryCosineIndex.js +22 -17
- package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/dist/packlets/vector/vectorIndex.js.map +1 -1
- package/dist/test/unit/converters/antagonistRoundTrip.test.js +3 -3
- package/dist/test/unit/converters/antagonistRoundTrip.test.js.map +1 -1
- package/dist/test/unit/converters/envelopeConverter.test.js +125 -8
- package/dist/test/unit/converters/envelopeConverter.test.js.map +1 -1
- package/dist/test/unit/index/memoryIndex.test.js +87 -25
- package/dist/test/unit/index/memoryIndex.test.js.map +1 -1
- package/dist/test/unit/ingest/antagonistCycleAndParity.test.js +20 -16
- package/dist/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -1
- package/dist/test/unit/ingest/cycleGuard.test.js +28 -1
- package/dist/test/unit/ingest/cycleGuard.test.js.map +1 -1
- package/dist/test/unit/ingest/orchestrator.test.js +187 -45
- package/dist/test/unit/ingest/orchestrator.test.js.map +1 -1
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +106 -31
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -1
- package/dist/test/unit/retrieve/retrievers.test.js +299 -37
- package/dist/test/unit/retrieve/retrievers.test.js.map +1 -1
- package/dist/test/unit/store/embedOnWrite.test.js +69 -12
- package/dist/test/unit/store/embedOnWrite.test.js.map +1 -1
- package/dist/test/unit/store/listScoped.test.js +109 -0
- package/dist/test/unit/store/listScoped.test.js.map +1 -0
- package/dist/test/unit/store/rankAxis.test.js +254 -0
- package/dist/test/unit/store/rankAxis.test.js.map +1 -0
- package/dist/test/unit/tools/memoryTools.test.js +280 -11
- package/dist/test/unit/tools/memoryTools.test.js.map +1 -1
- package/dist/test/unit/types/writePolicy.test.js +9 -2
- package/dist/test/unit/types/writePolicy.test.js.map +1 -1
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js +95 -35
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -1
- package/dist/test/unit/vector/vectorIndex.test.js +24 -15
- package/dist/test/unit/vector/vectorIndex.test.js.map +1 -1
- package/dist/ts-agent-memory.d.ts +384 -97
- package/etc/ts-agent-memory.api.md +64 -22
- package/lib/packlets/converters/envelopeConverter.d.ts +8 -1
- package/lib/packlets/converters/envelopeConverter.d.ts.map +1 -1
- package/lib/packlets/converters/envelopeConverter.js +18 -4
- package/lib/packlets/converters/envelopeConverter.js.map +1 -1
- package/lib/packlets/index/memoryIndex.d.ts +42 -10
- package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
- package/lib/packlets/index/memoryIndex.js +58 -10
- package/lib/packlets/index/memoryIndex.js.map +1 -1
- package/lib/packlets/ingest/cycleGuard.d.ts +5 -5
- package/lib/packlets/ingest/cycleGuard.d.ts.map +1 -1
- package/lib/packlets/ingest/cycleGuard.js +13 -6
- package/lib/packlets/ingest/cycleGuard.js.map +1 -1
- package/lib/packlets/ingest/hostStages.d.ts +3 -3
- package/lib/packlets/ingest/hostStages.d.ts.map +1 -1
- package/lib/packlets/ingest/hostStages.js.map +1 -1
- package/lib/packlets/ingest/model.d.ts +25 -14
- package/lib/packlets/ingest/model.d.ts.map +1 -1
- package/lib/packlets/ingest/model.js.map +1 -1
- package/lib/packlets/ingest/orchestrator.d.ts +18 -4
- package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
- package/lib/packlets/ingest/orchestrator.js +93 -48
- package/lib/packlets/ingest/orchestrator.js.map +1 -1
- package/lib/packlets/retrieve/hybridRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/hybridRetriever.js +12 -1
- package/lib/packlets/retrieve/hybridRetriever.js.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +18 -23
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.js +45 -56
- package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.js +2 -2
- package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/lib/packlets/retrieve/retriever.d.ts +70 -11
- package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/retriever.js +52 -7
- package/lib/packlets/retrieve/retriever.js.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.js +9 -3
- package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.js +2 -2
- package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.js +2 -2
- package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.js +3 -3
- package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +57 -3
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +72 -16
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/tools/memoryTools.d.ts +24 -0
- package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
- package/lib/packlets/tools/memoryTools.js +104 -21
- package/lib/packlets/tools/memoryTools.js.map +1 -1
- package/lib/packlets/types/envelope.d.ts +61 -8
- package/lib/packlets/types/envelope.d.ts.map +1 -1
- package/lib/packlets/types/envelope.js +14 -0
- package/lib/packlets/types/envelope.js.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -5
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.js +22 -17
- package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/lib/packlets/vector/vectorIndex.d.ts +46 -20
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
- package/lib/packlets/vector/vectorIndex.js.map +1 -1
- package/lib/test/unit/converters/antagonistRoundTrip.test.js +3 -3
- package/lib/test/unit/converters/antagonistRoundTrip.test.js.map +1 -1
- package/lib/test/unit/converters/envelopeConverter.test.js +124 -7
- package/lib/test/unit/converters/envelopeConverter.test.js.map +1 -1
- package/lib/test/unit/index/memoryIndex.test.js +86 -24
- package/lib/test/unit/index/memoryIndex.test.js.map +1 -1
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.js +20 -16
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -1
- package/lib/test/unit/ingest/cycleGuard.test.js +28 -1
- package/lib/test/unit/ingest/cycleGuard.test.js.map +1 -1
- package/lib/test/unit/ingest/orchestrator.test.js +186 -44
- package/lib/test/unit/ingest/orchestrator.test.js.map +1 -1
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +106 -31
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -1
- package/lib/test/unit/retrieve/retrievers.test.js +298 -36
- package/lib/test/unit/retrieve/retrievers.test.js.map +1 -1
- package/lib/test/unit/store/embedOnWrite.test.js +68 -11
- package/lib/test/unit/store/embedOnWrite.test.js.map +1 -1
- package/lib/test/unit/store/listScoped.test.d.ts +2 -0
- package/lib/test/unit/store/listScoped.test.d.ts.map +1 -0
- package/lib/test/unit/store/listScoped.test.js +111 -0
- package/lib/test/unit/store/listScoped.test.js.map +1 -0
- package/lib/test/unit/store/rankAxis.test.d.ts +2 -0
- package/lib/test/unit/store/rankAxis.test.d.ts.map +1 -0
- package/lib/test/unit/store/rankAxis.test.js +256 -0
- package/lib/test/unit/store/rankAxis.test.js.map +1 -0
- package/lib/test/unit/tools/memoryTools.test.js +280 -11
- package/lib/test/unit/tools/memoryTools.test.js.map +1 -1
- package/lib/test/unit/types/writePolicy.test.js +9 -2
- package/lib/test/unit/types/writePolicy.test.js.map +1 -1
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js +95 -35
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -1
- package/lib/test/unit/vector/vectorIndex.test.js +24 -15
- package/lib/test/unit/vector/vectorIndex.test.js.map +1 -1
- package/package.json +7 -7
- package/rush-logs/ts-agent-memory.build.cache.log +1 -1
- package/rush-logs/ts-agent-memory.build.log +2 -2
- package/src/packlets/converters/envelopeConverter.ts +27 -4
- package/src/packlets/index/memoryIndex.ts +86 -22
- package/src/packlets/ingest/cycleGuard.ts +22 -11
- package/src/packlets/ingest/hostStages.ts +3 -3
- package/src/packlets/ingest/model.ts +25 -14
- package/src/packlets/ingest/orchestrator.ts +143 -67
- package/src/packlets/retrieve/hybridRetriever.ts +14 -1
- package/src/packlets/retrieve/linkTraversalRetriever.ts +51 -62
- package/src/packlets/retrieve/recencyRetriever.ts +3 -3
- package/src/packlets/retrieve/retriever.ts +97 -13
- package/src/packlets/retrieve/semanticRetriever.ts +10 -5
- package/src/packlets/retrieve/structuredFilterRetriever.ts +3 -3
- package/src/packlets/retrieve/tagRetriever.ts +3 -3
- package/src/packlets/retrieve/temporalRetrievers.ts +3 -3
- package/src/packlets/store/fileTreeMemoryStore.ts +117 -12
- package/src/packlets/tools/memoryTools.ts +152 -25
- package/src/packlets/types/envelope.ts +66 -8
- package/src/packlets/vector/inMemoryCosineIndex.ts +45 -22
- package/src/packlets/vector/vectorIndex.ts +47 -20
- package/src/test/unit/converters/antagonistRoundTrip.test.ts +3 -3
- package/src/test/unit/converters/envelopeConverter.test.ts +168 -11
- package/src/test/unit/index/memoryIndex.test.ts +99 -14
- package/src/test/unit/ingest/antagonistCycleAndParity.test.ts +23 -18
- package/src/test/unit/ingest/cycleGuard.test.ts +44 -2
- package/src/test/unit/ingest/orchestrator.test.ts +234 -41
- package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +134 -35
- package/src/test/unit/retrieve/retrievers.test.ts +381 -25
- package/src/test/unit/store/embedOnWrite.test.ts +83 -11
- package/src/test/unit/store/listScoped.test.ts +138 -0
- package/src/test/unit/store/rankAxis.test.ts +349 -0
- package/src/test/unit/tools/memoryTools.test.ts +362 -13
- package/src/test/unit/types/writePolicy.test.ts +11 -2
- package/src/test/unit/vector/inMemoryCosineIndex.test.ts +115 -39
- package/src/test/unit/vector/vectorIndex.test.ts +33 -17
- package/temp/build/lint/_eslint-5eVG3S6w.json +41 -33
- package/temp/build/typescript/ts_8nwakTlr.json +1 -1
- package/temp/ts-agent-memory.api.json +1080 -136
- package/temp/ts-agent-memory.api.md +64 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inMemoryCosineIndex.js","sourceRoot":"","sources":["../../../src/packlets/vector/inMemoryCosineIndex.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,IAAI,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAItD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,mBAAmB;IAK9B;QACE,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;QAClD,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAC9B,CAAC;IAED,4CAA4C;IAC5C,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM;QAClB,OAAO,OAAO,CAAC,IAAI,mBAAmB,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,qCAAqC;IAC9B,GAAG,CAAC,EAAY,EAAE,MAAoB;QAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,6BAA6B,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;QAClC,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YAC7C,OAAO,OAAO,CAAC,OAAO,CACpB,IAAI,CACF,6BAA6B,EAAE,gBAAgB,MAAM,CAAC,MAAM,mCAAmC,IAAI,CAAC,UAAU,EAAE,CACjH,CACF,CAAC;QACJ,CAAC;QACD,6EAA6E;QAC7E,0DAA0D;QAC1D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACjD,4EAA4E;QAC5E,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAY,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,wCAAwC;IACjC,MAAM,CAAC,EAAY;QACxB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,uCAAuC;IAChC,KAAK,CAAC,MAAoB,EAAE,IAAY;QAC7C,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC,OAAO,CACpB,IAAI,CACF,iCAAiC,MAAM,CAAC,MAAM,mCAAmC,IAAI,CAAC,UAAU,EAAE,CACnG,CACF,CAAC;QACJ,CAAC;QACD,MAAM,cAAc,GAAW,mBAAmB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACtE,MAAM,IAAI,GAAsB,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,mBAAmB,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACxF,CAAC;QACD,6EAA6E;QAC7E,wEAAwE;QACxE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACnF,CAAC;IAED;;;;;;;;;;;;;OAaG;IACI,KAAK,CAAC,OAAO,CAAC,MAA2B,EAAE,KAAqB;QACrE,4EAA4E;QAC5E,wEAAwE;QACxE,YAAY;QACZ,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,MAAM,MAAM,GAAkD,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAClF,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC,iDAAiD,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QACjF,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAyB,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;YAC3D,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;gBACzB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,OAAO,IAAI,CAAC,oCAAoC,MAAM,CAAC,QAAQ,CAAC,EAAE,aAAa,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;YACrG,CAAC;YACD,MAAM,KAAK,GAAmB,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjF,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;gBACtB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,OAAO,IAAI,CAAC,yBAAyB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,4DAA4D;IACpD,MAAM;QACZ,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAC9B,CAAC;IAED,qDAAqD;IAC7C,MAAM,CAAC,UAAU,CAAC,MAAoB;QAC5C,IAAI,GAAG,GAAW,CAAC,CAAC;QACpB,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/C,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,OAAO,CAAC,KAAmB,EAAE,cAAsB,EAAE,MAAoB;QACtF,MAAM,eAAe,GAAW,mBAAmB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACvE,IAAI,cAAc,KAAK,CAAC,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;YAClD,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,GAAG,GAAW,CAAC,CAAC;QACpB,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,GAAG,GAAG,CAAC,cAAc,GAAG,eAAe,CAAC,CAAC;IAClD,CAAC;CACF","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IMemoryRecord, MemoryId } from '../types';\nimport { IMemoryRecordSource, IVectorIndex, IVectorQueryHit, MemoryEmbedder } from './vectorIndex';\n\n/**\n * The brute-force, in-memory cosine {@link IVectorIndex}. Stores one\n * `Float32Array` per record and answers a query by computing cosine similarity\n * against every stored vector, returning the top-k by descending score.\n *\n * @remarks\n * This is the **complete** vector implementation for the fgv regime — large-N is\n * explicitly out of scope (the seam stays open for a consumer to swap an external\n * ANN backend once N grows beyond \"thousands of records\"). No external dependency\n * and no ANN structure: a linear scan over a few thousand vectors is well within\n * an interactive budget.\n *\n * The index has a single dimension established by the first vector added; every\n * subsequent `add` and every `query` vector must match that dimension or fail\n * loudly — a mismatched dimension is an embedder-wiring bug, never a silent\n * zero-similarity result. {@link InMemoryCosineIndex.rebuild | rebuild} clears\n * the index, so a re-embed with a different model (hence dimension) is supported.\n *\n * Persistence (a JSON sidecar) is deliberately out of scope for this layer — the\n * index is in-memory and rebuilt from the store via `rebuild`; a sidecar is a\n * future nicety.\n * @public\n */\nexport class InMemoryCosineIndex implements IVectorIndex {\n private readonly _vectors: Map<MemoryId, Float32Array>;\n /** The dimension of every stored vector; `undefined` until the first `add`. */\n private _dimension: number | undefined;\n\n private constructor() {\n this._vectors = new Map<MemoryId, Float32Array>();\n this._dimension = undefined;\n }\n\n /** The number of vectors currently held. */\n public get size(): number {\n return this._vectors.size;\n }\n\n /** Family-convention factory. */\n public static create(): Result<InMemoryCosineIndex> {\n return succeed(new InMemoryCosineIndex());\n }\n\n /** {@inheritDoc IVectorIndex.add} */\n public add(id: MemoryId, vector: Float32Array): Promise<Result<string>> {\n if (vector.length === 0) {\n return Promise.resolve(fail(`vector index: cannot add '${id}': empty vector`));\n }\n if (this._dimension === undefined) {\n this._dimension = vector.length;\n } else if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: cannot add '${id}': dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n // Defensive copy: the caller may reuse or mutate the buffer after `add`, and\n // the index must keep serving the embedding it was given.\n this._vectors.set(id, Float32Array.from(vector));\n // The in-memory index keys entries by id, so the id IS the entry reference.\n return Promise.resolve(succeed(id as string));\n }\n\n /** {@inheritDoc IVectorIndex.remove} */\n public remove(id: MemoryId): Promise<Result<MemoryId>> {\n this._vectors.delete(id);\n return Promise.resolve(succeed(id));\n }\n\n /** {@inheritDoc IVectorIndex.query} */\n public query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (topK <= 0 || this._vectors.size === 0) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n const queryMagnitude: number = InMemoryCosineIndex._magnitude(vector);\n const hits: IVectorQueryHit[] = [];\n for (const [id, stored] of this._vectors) {\n hits.push({ id, score: InMemoryCosineIndex._cosine(vector, queryMagnitude, stored) });\n }\n // Descending by score; a `seq`-free tiebreak is unnecessary here because the\n // caller (SemanticRetriever) re-resolves hits against the record index.\n hits.sort((a, b) => b.score - a.score);\n return Promise.resolve(succeed(hits.length > topK ? hits.slice(0, topK) : hits));\n }\n\n /**\n * Re-embed every record from `source` and rebuild the index from scratch.\n * Clears the current contents (and the established dimension) first, so a\n * re-embed with a different model is supported. Returns the number of vectors\n * indexed.\n *\n * On any failure (list, embed, or add) the index is rolled back to empty\n * rather than left in a partially-rebuilt state — a caller that retries a query\n * after a failed rebuild sees a clean empty index, never a half-populated one.\n *\n * @param source - The record source to re-embed (an {@link IMemoryStore}\n * satisfies this structurally).\n * @param embed - The embedder applied to each record.\n */\n public async rebuild(source: IMemoryRecordSource, embed: MemoryEmbedder): Promise<Result<number>> {\n // Reset up front so the \"any failure leaves the index empty\" contract holds\n // even when the listing itself fails (no stale vectors survive a failed\n // rebuild).\n this._reset();\n const listed: Result<ReadonlyArray<IMemoryRecord<unknown>>> = await source.list();\n if (listed.isFailure()) {\n return fail(`vector index rebuild: failed to list records: ${listed.message}`);\n }\n for (const record of listed.value) {\n const embedded: Result<Float32Array> = await embed(record);\n if (embedded.isFailure()) {\n this._reset();\n return fail(`vector index rebuild: embedding '${record.envelope.id}' failed: ${embedded.message}`);\n }\n const added: Result<string> = await this.add(record.envelope.id, embedded.value);\n if (added.isFailure()) {\n this._reset();\n return fail(`vector index rebuild: ${added.message}`);\n }\n }\n return succeed(this._vectors.size);\n }\n\n /** Empty the index and forget the established dimension. */\n private _reset(): void {\n this._vectors.clear();\n this._dimension = undefined;\n }\n\n /** The Euclidean magnitude (L2 norm) of a vector. */\n private static _magnitude(vector: Float32Array): number {\n let sum: number = 0;\n for (let i: number = 0; i < vector.length; i++) {\n sum += vector[i] * vector[i];\n }\n return Math.sqrt(sum);\n }\n\n /**\n * Cosine similarity between the query (whose magnitude is precomputed once and\n * reused across the scan) and a stored vector. A zero-magnitude vector on\n * either side yields `0` rather than `NaN` — a degenerate vector is simply\n * maximally dissimilar, not an error.\n */\n private static _cosine(query: Float32Array, queryMagnitude: number, stored: Float32Array): number {\n const storedMagnitude: number = InMemoryCosineIndex._magnitude(stored);\n if (queryMagnitude === 0 || storedMagnitude === 0) {\n return 0;\n }\n let dot: number = 0;\n for (let i: number = 0; i < query.length; i++) {\n dot += query[i] * stored[i];\n }\n return dot / (queryMagnitude * storedMagnitude);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"inMemoryCosineIndex.js","sourceRoot":"","sources":["../../../src/packlets/vector/inMemoryCosineIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IEdgeTarget, edgeTargetKey } from '../types';\nimport {\n IMemoryRecordSource,\n IScopedMemoryRecord,\n IVectorIndex,\n IVectorQueryHit,\n MemoryEmbedder\n} from './vectorIndex';\n\n/** One stored embedding: the scope-qualified address plus its vector. */\ninterface IStoredVector {\n readonly target: IEdgeTarget;\n readonly vector: Float32Array;\n}\n\n/**\n * The brute-force, in-memory cosine {@link IVectorIndex}. Stores one\n * `Float32Array` per record and answers a query by computing cosine similarity\n * against every stored vector, returning the top-k by descending score.\n *\n * @remarks\n * This is the **complete** vector implementation for the fgv regime — large-N is\n * explicitly out of scope (the seam stays open for a consumer to swap an external\n * ANN backend once N grows beyond \"thousands of records\"). No external dependency\n * and no ANN structure: a linear scan over a few thousand vectors is well within\n * an interactive budget.\n *\n * The index has a single dimension established by the first vector added; every\n * subsequent `add` and every `query` vector must match that dimension or fail\n * loudly — a mismatched dimension is an embedder-wiring bug, never a silent\n * zero-similarity result. {@link InMemoryCosineIndex.rebuild | rebuild} clears\n * the index, so a re-embed with a different model (hence dimension) is supported.\n *\n * Persistence (a JSON sidecar) is deliberately out of scope for this layer — the\n * index is in-memory and rebuilt from the store via `rebuild`; a sidecar is a\n * future nicety.\n * @public\n */\nexport class InMemoryCosineIndex implements IVectorIndex {\n /**\n * Stored embeddings keyed by the canonical {@link edgeTargetKey} of the\n * record's scope-qualified address, so two records that share a filename stem\n * across scopes occupy distinct entries and never overwrite each other.\n */\n private readonly _vectors: Map<string, IStoredVector>;\n /** The dimension of every stored vector; `undefined` until the first `add`. */\n private _dimension: number | undefined;\n\n private constructor() {\n this._vectors = new Map<string, IStoredVector>();\n this._dimension = undefined;\n }\n\n /** The number of vectors currently held. */\n public get size(): number {\n return this._vectors.size;\n }\n\n /** Family-convention factory. */\n public static create(): Result<InMemoryCosineIndex> {\n return succeed(new InMemoryCosineIndex());\n }\n\n /** {@inheritDoc IVectorIndex.add} */\n public add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>> {\n const key: string = edgeTargetKey(target);\n if (vector.length === 0) {\n return Promise.resolve(fail(`vector index: cannot add '${key}': empty vector`));\n }\n if (this._dimension === undefined) {\n this._dimension = vector.length;\n } else if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: cannot add '${key}': dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n // Defensive copy: the caller may reuse or mutate the buffer after `add`, and\n // the index must keep serving the embedding it was given.\n this._vectors.set(key, { target, vector: Float32Array.from(vector) });\n // The in-memory index keys entries by the canonical scoped-target string, so\n // that key IS the entry reference.\n return Promise.resolve(succeed(key));\n }\n\n /** {@inheritDoc IVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n this._vectors.delete(edgeTargetKey(target));\n return Promise.resolve(succeed(target));\n }\n\n /** {@inheritDoc IVectorIndex.query} */\n public query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (topK <= 0 || this._vectors.size === 0) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n const queryMagnitude: number = InMemoryCosineIndex._magnitude(vector);\n const hits: IVectorQueryHit[] = [];\n for (const stored of this._vectors.values()) {\n hits.push({\n target: stored.target,\n score: InMemoryCosineIndex._cosine(vector, queryMagnitude, stored.vector)\n });\n }\n // Descending by score; a `seq`-free tiebreak is unnecessary here because the\n // caller (SemanticRetriever) re-resolves hits against the record index.\n hits.sort((a, b) => b.score - a.score);\n return Promise.resolve(succeed(hits.length > topK ? hits.slice(0, topK) : hits));\n }\n\n /**\n * Re-embed every record from `source` and rebuild the index from scratch.\n * Clears the current contents (and the established dimension) first, so a\n * re-embed with a different model is supported. Returns the number of vectors\n * indexed.\n *\n * On any failure (list, embed, or add) the index is rolled back to empty\n * rather than left in a partially-rebuilt state — a caller that retries a query\n * after a failed rebuild sees a clean empty index, never a half-populated one.\n *\n * @param source - The scope-qualified record source to re-embed.\n * @param embed - The embedder applied to each record.\n */\n public async rebuild(source: IMemoryRecordSource, embed: MemoryEmbedder): Promise<Result<number>> {\n // Reset up front so the \"any failure leaves the index empty\" contract holds\n // even when the listing itself fails (no stale vectors survive a failed\n // rebuild).\n this._reset();\n const listed: Result<ReadonlyArray<IScopedMemoryRecord>> = await source.list();\n if (listed.isFailure()) {\n return fail(`vector index rebuild: failed to list records: ${listed.message}`);\n }\n for (const scoped of listed.value) {\n const embedded: Result<Float32Array> = await embed(scoped.record);\n if (embedded.isFailure()) {\n this._reset();\n return fail(\n `vector index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${embedded.message}`\n );\n }\n const added: Result<string> = await this.add(scoped.target, embedded.value);\n if (added.isFailure()) {\n this._reset();\n return fail(`vector index rebuild: ${added.message}`);\n }\n }\n return succeed(this._vectors.size);\n }\n\n /** Empty the index and forget the established dimension. */\n private _reset(): void {\n this._vectors.clear();\n this._dimension = undefined;\n }\n\n /** The Euclidean magnitude (L2 norm) of a vector. */\n private static _magnitude(vector: Float32Array): number {\n let sum: number = 0;\n for (let i: number = 0; i < vector.length; i++) {\n sum += vector[i] * vector[i];\n }\n return Math.sqrt(sum);\n }\n\n /**\n * Cosine similarity between the query (whose magnitude is precomputed once and\n * reused across the scan) and a stored vector. A zero-magnitude vector on\n * either side yields `0` rather than `NaN` — a degenerate vector is simply\n * maximally dissimilar, not an error.\n */\n private static _cosine(query: Float32Array, queryMagnitude: number, stored: Float32Array): number {\n const storedMagnitude: number = InMemoryCosineIndex._magnitude(stored);\n if (queryMagnitude === 0 || storedMagnitude === 0) {\n return 0;\n }\n let dot: number = 0;\n for (let i: number = 0; i < query.length; i++) {\n dot += query[i] * stored[i];\n }\n return dot / (queryMagnitude * storedMagnitude);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vectorIndex.js","sourceRoot":"","sources":["../../../src/packlets/vector/vectorIndex.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result } from '@fgv/ts-utils';\nimport {
|
|
1
|
+
{"version":3,"file":"vectorIndex.js","sourceRoot":"","sources":["../../../src/packlets/vector/vectorIndex.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result } from '@fgv/ts-utils';\nimport { IEdgeTarget, IMemoryRecord } from '../types';\n\n/**\n * A single hit returned by {@link IVectorIndex.query}: the matched record's\n * scope-qualified {@link IEdgeTarget | address} and the backend's similarity\n * score (higher = more similar; the exact scale is backend-defined). Hits are\n * returned in descending score order.\n *\n * @remarks\n * The address is a `(scope, id)` pair, NOT a bare {@link MemoryId} — per-scope\n * codecs (e.g. the medium-term codec's `turn-<n>` stems) legally mint the same\n * stem under different scopes, so a bare id could not disambiguate two records\n * that share a stem. The caller re-resolves the hit against the record index by\n * the same scoped address.\n * @public\n */\nexport interface IVectorQueryHit {\n /** The scope-qualified address of the matched record. */\n readonly target: IEdgeTarget;\n /** Backend similarity score; higher is more similar. */\n readonly score: number;\n}\n\n/**\n * The vector-index seam an embedding backend implements to make\n * {@link SemanticRetriever | semantic recall} operational.\n *\n * @remarks\n * Vectors cross this seam as `Float32Array` (the in-memory representation an\n * embedding model produces); `number[]` is reserved for the JSON-wire edges\n * (e.g. a provider's embedding response). The in-package brute-force cosine\n * implementation is {@link InMemoryCosineIndex}; a consumer can swap an external\n * ANN backend behind the same seam once N grows beyond the in-memory regime.\n *\n * Every operation returns a `Result` (async, since a real backend does I/O) so\n * failure is explicit and never throws across the seam.\n * @public\n */\nexport interface IVectorIndex {\n /**\n * Add (or replace) the embedding for the scope-qualified `target`. Returns the\n * opaque {@link IMemoryEnvelope.embeddingRef | embeddingRef} the store stamps\n * onto the envelope so a later read knows the record is embedded. Keying on the\n * `(scope, id)` address (not a bare id) is load-bearing: two records that share\n * a filename stem across scopes must not clobber each other's embedding.\n */\n add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>>;\n\n /**\n * Remove the embedding for the scope-qualified `target`. Returns the removed\n * target. Idempotent — removing a target with no embedding still succeeds\n * (returns the target).\n */\n remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>>;\n\n /**\n * Return the `topK` nearest records to `vector`, in descending score order.\n */\n query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;\n}\n\n/**\n * Embeds a complete record into a vector for the store's embed-on-write hook.\n * Async and `Result`-returning, since a real embedder does a network call (cloud\n * provider) or in-process model inference. The consumer wires this — the core\n * package never calls an embedding provider directly, staying embedder-agnostic.\n * @public\n */\nexport type MemoryEmbedder = (record: IMemoryRecord<unknown>) => Promise<Result<Float32Array>>;\n\n/**\n * A record paired with its scope-qualified {@link IEdgeTarget | address}, as\n * yielded by {@link IMemoryRecordSource.list}. The address is required because\n * {@link InMemoryCosineIndex.rebuild} keys each re-embedded entry on the\n * scope-qualified target, not a bare {@link MemoryId} — two records that share a\n * filename stem across scopes must not collide when the whole vault is re-indexed.\n * @public\n */\nexport interface IScopedMemoryRecord {\n /** The record's scope-qualified `(scope, id)` address. */\n readonly target: IEdgeTarget;\n /** The record itself, passed to the embedder. */\n readonly record: IMemoryRecord<unknown>;\n}\n\n/**\n * The minimal record-source surface {@link InMemoryCosineIndex.rebuild} reads to\n * re-embed an entire vault. Each entry carries the record's scope-qualified\n * address (see {@link IScopedMemoryRecord}) so the rebuild keys the vector index\n * exactly as the incremental embed-on-write path does. A consumer backs this with\n * the store's scoped index — the vector packlet does not import the store packlet\n * (which depends on the vector packlet for {@link IVectorIndex}, so the reverse\n * import would be a cycle).\n * @public\n */\nexport interface IMemoryRecordSource {\n /** List every record in the vault, each paired with its scoped address. */\n list(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;\n}\n"]}
|
|
@@ -35,7 +35,7 @@ describe('antagonist — full on-disk round-trip preserves every optional field'
|
|
|
35
35
|
links: [
|
|
36
36
|
{
|
|
37
37
|
type: 'rel',
|
|
38
|
-
target: 'doc-2',
|
|
38
|
+
target: { scope: 'knowledge', id: 'doc-2' },
|
|
39
39
|
confidence: 0.42,
|
|
40
40
|
provenance: { source: 'agent', by: 'curator', extra: { nested: true } },
|
|
41
41
|
valid_at: 111,
|
|
@@ -51,7 +51,7 @@ describe('antagonist — full on-disk round-trip preserves every optional field'
|
|
|
51
51
|
by: 'erik',
|
|
52
52
|
model: 'gpt-5',
|
|
53
53
|
confidence: 0.87,
|
|
54
|
-
derivedFrom: 'turn-3',
|
|
54
|
+
derivedFrom: { scope: 'conversations/c1', id: 'turn-3' },
|
|
55
55
|
// Opaque extension keys (per IProvenance's `[key: string]: unknown` arm).
|
|
56
56
|
sentiment: { score: 0.5 },
|
|
57
57
|
epistemic: 'belief'
|
|
@@ -73,7 +73,7 @@ describe('antagonist — full on-disk round-trip preserves every optional field'
|
|
|
73
73
|
entityId: 'doc-2',
|
|
74
74
|
kind,
|
|
75
75
|
tags: [],
|
|
76
|
-
links: [{ type: 'rel', target: 'doc-3' }],
|
|
76
|
+
links: [{ type: 'rel', target: { scope: 'knowledge', id: 'doc-3' } }],
|
|
77
77
|
created: 0,
|
|
78
78
|
updated: 0,
|
|
79
79
|
seq: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"antagonistRoundTrip.test.js","sourceRoot":"","sources":["../../../../src/test/unit/converters/antagonistRoundTrip.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;GAOG;AAEH,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EACL,qBAAqB,EAGrB,eAAe,EACf,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAExB,MAAM,IAAI,GAAG,MAAiC,CAAC;AAE/C,SAAS,QAAQ;IACf,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC;IACrD,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,QAAQ,CAAC,qEAAqE,EAAE,GAAG,EAAE;IACnF,4EAA4E;IAC5E,0EAA0E;IAC1E,wEAAwE;IACxE,+EAA+E;IAC/E,6EAA6E;IAC7E,6EAA6E;IAC7E,IAAI,CAAC,kGAAkG,EAAE,GAAG,EAAE;QAC5G,MAAM,QAAQ,GAAoB;YAChC,EAAE,EAAE,OAAgC;YACpC,QAAQ,EAAE,OAAsC;YAChD,IAAI;YACJ,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,CAAuC;YACxD,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,KAAc;oBACpB,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"antagonistRoundTrip.test.js","sourceRoot":"","sources":["../../../../src/test/unit/converters/antagonistRoundTrip.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\n/**\n * Antagonist torture test — convert/validate round-trip symmetry (target class\n * 7): every OPTIONAL field the envelope/edge/provenance shapes carry must\n * survive a full on-disk round-trip (serialize → YAML text → parse), not just a\n * single `Converter.convert` pass in memory. This exercises the exact path a\n * corrupted or field-dropping serializer/parser pair would break, mirroring the\n * `aiClientToolConfig`/`annotations` field-drop class named in the brief.\n */\n\nimport '@fgv/ts-utils-jest';\nimport { Converters } from '@fgv/ts-utils';\nimport {\n BodyConverterRegistry,\n IBodyConverterRegistry,\n IMemoryEnvelope,\n parseMemoryFile,\n serializeMemoryFile\n} from '../../../index';\n\nconst kind = 'note' as IMemoryEnvelope['kind'];\n\nfunction registry(): IBodyConverterRegistry {\n const reg = BodyConverterRegistry.create().orThrow();\n reg.register(kind, Converters.string);\n return reg;\n}\n\ndescribe('antagonist — full on-disk round-trip preserves every optional field', () => {\n // Wrong impl this catches: a serializer/parser pair where one side silently\n // drops an optional field (the exact class of bug the brief calls out for\n // `aiClientToolConfig`/`annotations`-shaped converters) — e.g. omitting\n // `temporal.invalid_at: null`, an edge's `valid_at`/`invalid_at`/`provenance`,\n // a `null` `embeddingRef`, or a provenance extension key, because the author\n // forgot to thread it through both the YAML emit AND the envelope Converter.\n test('every optional envelope/edge/provenance field set simultaneously survives a full YAML round-trip', () => {\n const envelope: IMemoryEnvelope = {\n id: 'doc-1' as IMemoryEnvelope['id'],\n entityId: 'doc-1' as IMemoryEnvelope['entityId'],\n kind,\n tags: ['t1', 't2'] as unknown as IMemoryEnvelope['tags'],\n links: [\n {\n type: 'rel' as never,\n target: { scope: 'knowledge', id: 'doc-2' } as never,\n confidence: 0.42,\n provenance: { source: 'agent', by: 'curator', extra: { nested: true } },\n valid_at: 111,\n\n invalid_at: null\n }\n ],\n created: 1000,\n updated: 2000,\n seq: 7,\n contentHash: 'abc123',\n provenance: {\n source: 'host-ingest',\n by: 'erik',\n model: 'gpt-5',\n confidence: 0.87,\n derivedFrom: { scope: 'conversations/c1', id: 'turn-3' } as never,\n // Opaque extension keys (per IProvenance's `[key: string]: unknown` arm).\n sentiment: { score: 0.5 },\n epistemic: 'belief'\n },\n\n temporal: { valid_at: 500, invalid_at: null },\n\n embeddingRef: null\n };\n\n const raw = serializeMemoryFile(envelope, 'the body text').orThrow();\n expect(parseMemoryFile(raw, registry())).toSucceedAndSatisfy((record) => {\n // Deep-equal the ENTIRE envelope, not field-by-field — a partial assertion\n // list is exactly how a single dropped field slips through review.\n expect(record.envelope).toEqual(envelope);\n expect(record.body).toBe('the body text');\n });\n });\n\n test('an absent temporal/embeddingRef/edge-optionals round-trips to fully absent (no null-vs-undefined drift)', () => {\n const envelope: IMemoryEnvelope = {\n id: 'doc-2' as IMemoryEnvelope['id'],\n entityId: 'doc-2' as IMemoryEnvelope['entityId'],\n kind,\n tags: [],\n links: [{ type: 'rel' as never, target: { scope: 'knowledge', id: 'doc-3' } as never }],\n created: 0,\n updated: 0,\n seq: 0,\n contentHash: '',\n provenance: { source: 'agent' }\n };\n const raw = serializeMemoryFile(envelope, 'body').orThrow();\n expect(parseMemoryFile(raw, registry())).toSucceedAndSatisfy((record) => {\n expect(record.envelope.temporal).toBeUndefined();\n expect(record.envelope.embeddingRef).toBeUndefined();\n expect(record.envelope.links[0].confidence).toBeUndefined();\n expect(record.envelope.links[0].provenance).toBeUndefined();\n expect(record.envelope.links[0].valid_at).toBeUndefined();\n expect(record.envelope.links[0].invalid_at).toBeUndefined();\n expect(record.envelope).toEqual(envelope);\n });\n });\n});\n"]}
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import '@fgv/ts-utils-jest';
|
|
6
6
|
import { Converters } from '@fgv/ts-utils';
|
|
7
|
-
import { BodyConverterRegistry, edgeConverter, envelopeConverter, envelopeYamlConverter, joinFrontmatter, parseMemoryFile, provenanceConverter, serializeMemoryFile, splitFrontmatter, temporalConverter } from '../../../packlets/converters';
|
|
7
|
+
import { BodyConverterRegistry, edgeConverter, edgeTargetConverter, envelopeConverter, envelopeYamlConverter, joinFrontmatter, parseMemoryFile, provenanceConverter, serializeMemoryFile, splitFrontmatter, temporalConverter } from '../../../packlets/converters';
|
|
8
8
|
const validEnvelopeObject = {
|
|
9
9
|
id: 'intro-to-rust',
|
|
10
10
|
entityId: 'intro-to-rust',
|
|
11
11
|
kind: 'knowledge',
|
|
12
12
|
tags: ['rust', 'systems'],
|
|
13
|
-
links: [{ type: 'related', target: 'ownership' }],
|
|
13
|
+
links: [{ type: 'related', target: { scope: 'knowledge', id: 'ownership' } }],
|
|
14
14
|
created: 1000,
|
|
15
15
|
updated: 2000,
|
|
16
16
|
seq: 5,
|
|
@@ -31,6 +31,23 @@ describe('provenanceConverter', () => {
|
|
|
31
31
|
test('accepts an arbitrary source string (open vocabulary)', () => {
|
|
32
32
|
expect(provenanceConverter.convert({ source: 'host-ingest' })).toSucceedWith({ source: 'host-ingest' });
|
|
33
33
|
});
|
|
34
|
+
test('validates a scope-qualified derivedFrom (nested { scope, id })', () => {
|
|
35
|
+
expect(provenanceConverter.convert({
|
|
36
|
+
source: 'host-ingest',
|
|
37
|
+
derivedFrom: { scope: 'conversations/conv-a', id: 'turn-3' }
|
|
38
|
+
})).toSucceedAndSatisfy((p) => {
|
|
39
|
+
expect(p.derivedFrom).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
test('rejects a bare-string derivedFrom (the pre-scoped format)', () => {
|
|
43
|
+
expect(provenanceConverter.convert({ source: 'host-ingest', derivedFrom: 'turn-3' })).toFail();
|
|
44
|
+
});
|
|
45
|
+
test('rejects a derivedFrom missing scope', () => {
|
|
46
|
+
expect(provenanceConverter.convert({ source: 'host-ingest', derivedFrom: { id: 'turn-3' } })).toFailWith(/scope/i);
|
|
47
|
+
});
|
|
48
|
+
test('rejects a derivedFrom missing id', () => {
|
|
49
|
+
expect(provenanceConverter.convert({ source: 'host-ingest', derivedFrom: { scope: 'conversations/conv-a' } })).toFailWith(/id/i);
|
|
50
|
+
});
|
|
34
51
|
test('fails when source is missing', () => {
|
|
35
52
|
expect(provenanceConverter.convert({ by: 'erik' })).toFail();
|
|
36
53
|
});
|
|
@@ -38,18 +55,34 @@ describe('provenanceConverter', () => {
|
|
|
38
55
|
expect(provenanceConverter.convert('nope')).toFail();
|
|
39
56
|
});
|
|
40
57
|
});
|
|
58
|
+
describe('edgeTargetConverter', () => {
|
|
59
|
+
test('validates a scope-qualified target', () => {
|
|
60
|
+
expect(edgeTargetConverter.convert({ scope: 'conversations/conv-a', id: 'turn-3' })).toSucceedAndSatisfy((t) => {
|
|
61
|
+
expect(t).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
test('rejects a target missing scope', () => {
|
|
65
|
+
expect(edgeTargetConverter.convert({ id: 'turn-3' })).toFailWith(/scope/i);
|
|
66
|
+
});
|
|
67
|
+
test('rejects a target missing id', () => {
|
|
68
|
+
expect(edgeTargetConverter.convert({ scope: 'conversations/conv-a' })).toFailWith(/id/i);
|
|
69
|
+
});
|
|
70
|
+
test('rejects a bare-string target (the pre-scoped format)', () => {
|
|
71
|
+
expect(edgeTargetConverter.convert('turn-3')).toFail();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
41
74
|
describe('edgeConverter', () => {
|
|
42
75
|
test('validates a full attributed edge', () => {
|
|
43
76
|
expect(edgeConverter.convert({
|
|
44
77
|
type: 'mtm-ref',
|
|
45
|
-
target: 'turn-3',
|
|
78
|
+
target: { scope: 'conversations/conv-a', id: 'turn-3' },
|
|
46
79
|
confidence: 0.8,
|
|
47
80
|
provenance: { source: 'agent' },
|
|
48
81
|
valid_at: 100,
|
|
49
82
|
invalid_at: 200
|
|
50
83
|
})).toSucceedAndSatisfy((edge) => {
|
|
51
84
|
expect(edge.type).toBe('mtm-ref');
|
|
52
|
-
expect(edge.target).
|
|
85
|
+
expect(edge.target).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });
|
|
53
86
|
expect(edge.confidence).toBe(0.8);
|
|
54
87
|
expect(edge.provenance).toEqual({ source: 'agent' });
|
|
55
88
|
expect(edge.valid_at).toBe(100);
|
|
@@ -57,23 +90,26 @@ describe('edgeConverter', () => {
|
|
|
57
90
|
});
|
|
58
91
|
});
|
|
59
92
|
test('validates a minimal edge', () => {
|
|
60
|
-
expect(edgeConverter.convert({ type: 'related', target: 'ownership' })).toSucceedAndSatisfy((edge) => {
|
|
93
|
+
expect(edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'ownership' } })).toSucceedAndSatisfy((edge) => {
|
|
61
94
|
expect(edge.type).toBe('related');
|
|
62
|
-
expect(edge.target).
|
|
95
|
+
expect(edge.target).toEqual({ scope: 'knowledge', id: 'ownership' });
|
|
63
96
|
expect(edge.confidence).toBeUndefined();
|
|
64
97
|
});
|
|
65
98
|
});
|
|
66
99
|
test('accepts a null invalid_at (still valid)', () => {
|
|
67
|
-
expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: null })).toSucceedAndSatisfy((edge) => {
|
|
100
|
+
expect(edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'x' }, invalid_at: null })).toSucceedAndSatisfy((edge) => {
|
|
68
101
|
expect(edge.invalid_at).toBeNull();
|
|
69
102
|
});
|
|
70
103
|
});
|
|
71
104
|
test('fails when invalid_at is neither a number nor null', () => {
|
|
72
|
-
expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: 'soon' })).toFail();
|
|
105
|
+
expect(edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'x' }, invalid_at: 'soon' })).toFail();
|
|
73
106
|
});
|
|
74
107
|
test('fails when target is missing', () => {
|
|
75
108
|
expect(edgeConverter.convert({ type: 'related' })).toFail();
|
|
76
109
|
});
|
|
110
|
+
test('fails when target is a bare string (rejects the pre-scoped format)', () => {
|
|
111
|
+
expect(edgeConverter.convert({ type: 'related', target: 'ownership' })).toFail();
|
|
112
|
+
});
|
|
77
113
|
});
|
|
78
114
|
describe('temporalConverter', () => {
|
|
79
115
|
test('validates a temporal block', () => {
|
|
@@ -110,6 +146,28 @@ describe('envelopeConverter', () => {
|
|
|
110
146
|
test('fails when embeddingRef is neither a string nor null', () => {
|
|
111
147
|
expect(envelopeConverter.convert(Object.assign(Object.assign({}, validEnvelopeObject), { embeddingRef: 42 }))).toFail();
|
|
112
148
|
});
|
|
149
|
+
test('rank is absent when not present', () => {
|
|
150
|
+
expect(envelopeConverter.convert(validEnvelopeObject)).toSucceedAndSatisfy((envelope) => {
|
|
151
|
+
expect(envelope.rank).toBeUndefined();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
test('validates and preserves a numeric rank', () => {
|
|
155
|
+
expect(envelopeConverter.convert(Object.assign(Object.assign({}, validEnvelopeObject), { rank: 42 }))).toSucceedAndSatisfy((envelope) => {
|
|
156
|
+
expect(envelope.rank).toBe(42);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
test('serialize/parse round-trips rank through frontmatter', () => {
|
|
160
|
+
const registry = BodyConverterRegistry.create().orThrow();
|
|
161
|
+
registry.register('knowledge', Converters.string);
|
|
162
|
+
const envelope = envelopeConverter.convert(Object.assign(Object.assign({}, validEnvelopeObject), { rank: 7 })).orThrow();
|
|
163
|
+
const file = serializeMemoryFile(envelope, 'body').orThrow();
|
|
164
|
+
expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {
|
|
165
|
+
expect(record.envelope.rank).toBe(7);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
test('fails when rank is not a number', () => {
|
|
169
|
+
expect(envelopeConverter.convert(Object.assign(Object.assign({}, validEnvelopeObject), { rank: 'high' }))).toFail();
|
|
170
|
+
});
|
|
113
171
|
test('fails when a required field is missing', () => {
|
|
114
172
|
const missing = Object.assign({}, validEnvelopeObject);
|
|
115
173
|
delete missing.contentHash;
|
|
@@ -176,6 +234,65 @@ describe('parseMemoryFile / serializeMemoryFile', () => {
|
|
|
176
234
|
expect(record.body).toBe('The knowledge body.');
|
|
177
235
|
});
|
|
178
236
|
});
|
|
237
|
+
test('round-trips a scope-qualified edge target through the frontmatter intact', () => {
|
|
238
|
+
const envelope = envelopeConverter
|
|
239
|
+
.convert(Object.assign(Object.assign({}, validEnvelopeObject), { links: [{ type: 'mtm-ref', target: { scope: 'conversations/conv-a', id: 'turn-3' } }] }))
|
|
240
|
+
.orThrow();
|
|
241
|
+
const file = serializeMemoryFile(envelope, 'body').orThrow();
|
|
242
|
+
// On-wire the target is a nested object, NOT a bare scalar.
|
|
243
|
+
expect(file).toMatch(/scope: conversations\/conv-a/);
|
|
244
|
+
expect(file).toMatch(/id: turn-3/);
|
|
245
|
+
expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {
|
|
246
|
+
expect(record.envelope.links).toEqual([
|
|
247
|
+
{ type: 'mtm-ref', target: { scope: 'conversations/conv-a', id: 'turn-3' } }
|
|
248
|
+
]);
|
|
249
|
+
// Serializing the reparsed record reproduces byte-identical output.
|
|
250
|
+
expect(serializeMemoryFile(record.envelope, record.body)).toSucceedWith(file);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
test('round-trips a scope-qualified provenance.derivedFrom through the frontmatter intact', () => {
|
|
254
|
+
const envelope = envelopeConverter
|
|
255
|
+
.convert(Object.assign(Object.assign({}, validEnvelopeObject), { provenance: {
|
|
256
|
+
source: 'host-ingest',
|
|
257
|
+
derivedFrom: { scope: 'conversations/conv-a', id: 'turn-3' }
|
|
258
|
+
} }))
|
|
259
|
+
.orThrow();
|
|
260
|
+
const file = serializeMemoryFile(envelope, 'body').orThrow();
|
|
261
|
+
// On-wire the back-reference is a nested object, NOT a bare scalar.
|
|
262
|
+
expect(file).toMatch(/scope: conversations\/conv-a/);
|
|
263
|
+
expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {
|
|
264
|
+
expect(record.envelope.provenance.derivedFrom).toEqual({
|
|
265
|
+
scope: 'conversations/conv-a',
|
|
266
|
+
id: 'turn-3'
|
|
267
|
+
});
|
|
268
|
+
expect(serializeMemoryFile(record.envelope, record.body)).toSucceedWith(file);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
test('two records whose derivedFrom share a stem across scopes round-trip as distinct scoped refs', () => {
|
|
272
|
+
// Same idStem ('turn-3'), different scopes — exactly the ambiguity a bare
|
|
273
|
+
// MemoryId could not disambiguate. Each must round-trip to ITS OWN scope.
|
|
274
|
+
const makeFile = (scope) => {
|
|
275
|
+
const envelope = envelopeConverter
|
|
276
|
+
.convert(Object.assign(Object.assign({}, validEnvelopeObject), { provenance: { source: 'host-ingest', derivedFrom: { scope, id: 'turn-3' } } }))
|
|
277
|
+
.orThrow();
|
|
278
|
+
return serializeMemoryFile(envelope, 'body').orThrow();
|
|
279
|
+
};
|
|
280
|
+
const fileA = makeFile('conversations/conv-a');
|
|
281
|
+
const fileB = makeFile('conversations/conv-b');
|
|
282
|
+
expect(fileA).not.toBe(fileB);
|
|
283
|
+
expect(parseMemoryFile(fileA, registry)).toSucceedAndSatisfy((record) => {
|
|
284
|
+
expect(record.envelope.provenance.derivedFrom).toEqual({
|
|
285
|
+
scope: 'conversations/conv-a',
|
|
286
|
+
id: 'turn-3'
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
expect(parseMemoryFile(fileB, registry)).toSucceedAndSatisfy((record) => {
|
|
290
|
+
expect(record.envelope.provenance.derivedFrom).toEqual({
|
|
291
|
+
scope: 'conversations/conv-b',
|
|
292
|
+
id: 'turn-3'
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
179
296
|
test('fails when the frontmatter cannot be split', () => {
|
|
180
297
|
expect(parseMemoryFile('no delimiters', registry)).toFailWith(/missing opening frontmatter delimiter/i);
|
|
181
298
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelopeConverter.test.js","sourceRoot":"","sources":["../../../../src/test/unit/converters/envelopeConverter.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport '@fgv/ts-utils-jest';\nimport { Converters } from '@fgv/ts-utils';\nimport {\n BodyConverterRegistry,\n edgeConverter,\n envelopeConverter,\n envelopeYamlConverter,\n joinFrontmatter,\n parseMemoryFile,\n provenanceConverter,\n serializeMemoryFile,\n splitFrontmatter,\n temporalConverter\n} from '../../../packlets/converters';\nimport { IMemoryEnvelope, Kind } from '../../../packlets/types';\n\nconst validEnvelopeObject: Record<string, unknown> = {\n id: 'intro-to-rust',\n entityId: 'intro-to-rust',\n kind: 'knowledge',\n tags: ['rust', 'systems'],\n links: [{ type: 'related', target: 'ownership' }],\n created: 1000,\n updated: 2000,\n seq: 5,\n contentHash: 'crc-abc',\n provenance: { source: 'agent', by: 'curator' }\n};\n\ndescribe('provenanceConverter', () => {\n test('validates the known fields', () => {\n expect(\n provenanceConverter.convert({ source: 'human', by: 'erik', model: 'gpt', confidence: 0.9 })\n ).toSucceedWith({ source: 'human', by: 'erik', model: 'gpt', confidence: 0.9 });\n });\n\n test('preserves opaque extension keys verbatim', () => {\n expect(\n provenanceConverter.convert({ source: 'agent', sentiment: { score: 0.5 }, epistemic: 'belief' })\n ).toSucceedAndSatisfy((p) => {\n expect(p.source).toBe('agent');\n expect(p.sentiment).toEqual({ score: 0.5 });\n expect(p.epistemic).toBe('belief');\n });\n });\n\n test('accepts an arbitrary source string (open vocabulary)', () => {\n expect(provenanceConverter.convert({ source: 'host-ingest' })).toSucceedWith({ source: 'host-ingest' });\n });\n\n test('fails when source is missing', () => {\n expect(provenanceConverter.convert({ by: 'erik' })).toFail();\n });\n\n test('fails on a non-object', () => {\n expect(provenanceConverter.convert('nope')).toFail();\n });\n});\n\ndescribe('edgeConverter', () => {\n test('validates a full attributed edge', () => {\n expect(\n edgeConverter.convert({\n type: 'mtm-ref',\n target: 'turn-3',\n confidence: 0.8,\n provenance: { source: 'agent' },\n valid_at: 100,\n invalid_at: 200\n })\n ).toSucceedAndSatisfy((edge) => {\n expect(edge.type).toBe('mtm-ref');\n expect(edge.target).toBe('turn-3');\n expect(edge.confidence).toBe(0.8);\n expect(edge.provenance).toEqual({ source: 'agent' });\n expect(edge.valid_at).toBe(100);\n expect(edge.invalid_at).toBe(200);\n });\n });\n\n test('validates a minimal edge', () => {\n expect(edgeConverter.convert({ type: 'related', target: 'ownership' })).toSucceedAndSatisfy((edge) => {\n expect(edge.type).toBe('related');\n expect(edge.target).toBe('ownership');\n expect(edge.confidence).toBeUndefined();\n });\n });\n\n test('accepts a null invalid_at (still valid)', () => {\n expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: null })).toSucceedAndSatisfy(\n (edge) => {\n expect(edge.invalid_at).toBeNull();\n }\n );\n });\n\n test('fails when invalid_at is neither a number nor null', () => {\n expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: 'soon' })).toFail();\n });\n\n test('fails when target is missing', () => {\n expect(edgeConverter.convert({ type: 'related' })).toFail();\n });\n});\n\ndescribe('temporalConverter', () => {\n test('validates a temporal block', () => {\n expect(temporalConverter.convert({ valid_at: 1, invalid_at: null })).toSucceedWith({\n valid_at: 1,\n invalid_at: null\n });\n });\n\n test('validates an empty temporal block', () => {\n expect(temporalConverter.convert({})).toSucceedWith({});\n });\n});\n\ndescribe('envelopeConverter', () => {\n test('validates a full envelope without optional blocks', () => {\n expect(envelopeConverter.convert(validEnvelopeObject)).toSucceedAndSatisfy((envelope) => {\n expect(envelope.id).toBe('intro-to-rust');\n expect(envelope.tags).toEqual(['rust', 'systems']);\n expect(envelope.links).toHaveLength(1);\n expect(envelope.temporal).toBeUndefined();\n expect(envelope.embeddingRef).toBeUndefined();\n });\n });\n\n test('validates a temporal envelope with a string embeddingRef', () => {\n expect(\n envelopeConverter.convert({\n ...validEnvelopeObject,\n temporal: { valid_at: 10 },\n embeddingRef: 'vec-1'\n })\n ).toSucceedAndSatisfy((envelope) => {\n expect(envelope.temporal).toEqual({ valid_at: 10 });\n expect(envelope.embeddingRef).toBe('vec-1');\n });\n });\n\n test('accepts a null embeddingRef (not embedded)', () => {\n expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: null })).toSucceedAndSatisfy(\n (envelope) => {\n expect(envelope.embeddingRef).toBeNull();\n }\n );\n });\n\n test('fails when embeddingRef is neither a string nor null', () => {\n expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: 42 })).toFail();\n });\n\n test('fails when a required field is missing', () => {\n const missing: Record<string, unknown> = { ...validEnvelopeObject };\n delete missing.contentHash;\n expect(envelopeConverter.convert(missing)).toFail();\n });\n});\n\ndescribe('envelopeYamlConverter', () => {\n test('parses an envelope from a YAML string', () => {\n const yaml = serializeMemoryFile(\n envelopeConverter.convert(validEnvelopeObject).orThrow(),\n 'body text'\n ).orThrow();\n const frontmatter = splitFrontmatter(yaml).orThrow().frontmatter;\n expect(envelopeYamlConverter.convert(frontmatter)).toSucceedAndSatisfy((envelope) => {\n expect(envelope.id).toBe('intro-to-rust');\n });\n });\n\n test('fails on a YAML string that is not an object', () => {\n expect(envelopeYamlConverter.convert('just a scalar')).toFail();\n });\n});\n\ndescribe('splitFrontmatter / joinFrontmatter', () => {\n test('splits a well-formed memory file', () => {\n const raw = '---\\nid: x\\nkind: knowledge\\n---\\nThe body.\\nSecond line.';\n expect(splitFrontmatter(raw)).toSucceedWith({\n frontmatter: 'id: x\\nkind: knowledge',\n body: 'The body.\\nSecond line.'\n });\n });\n\n test('strips trailing CR so CRLF-authored files parse identically to LF', () => {\n const raw = '---\\r\\nid: x\\r\\nkind: knowledge\\r\\n---\\r\\nThe body.\\r\\nSecond line.';\n expect(splitFrontmatter(raw)).toSucceedWith({\n frontmatter: 'id: x\\nkind: knowledge',\n body: 'The body.\\nSecond line.'\n });\n });\n\n test('fails when the opening delimiter is missing', () => {\n expect(splitFrontmatter('no frontmatter here')).toFailWith(/missing opening frontmatter delimiter/i);\n });\n\n test('fails when the closing delimiter is missing', () => {\n expect(splitFrontmatter('---\\nid: x\\nstill going')).toFailWith(/missing closing frontmatter delimiter/i);\n });\n\n test('join adds a trailing newline to the frontmatter when absent', () => {\n expect(joinFrontmatter('id: x', 'body')).toBe('---\\nid: x\\n---\\nbody');\n });\n\n test('join does not double the trailing newline when present', () => {\n expect(joinFrontmatter('id: x\\n', 'body')).toBe('---\\nid: x\\n---\\nbody');\n });\n\n test('split and join round-trip', () => {\n const raw = '---\\nid: x\\n---\\nbody content';\n const parts = splitFrontmatter(raw).orThrow();\n expect(joinFrontmatter(parts.frontmatter, parts.body)).toBe(raw);\n });\n});\n\ndescribe('parseMemoryFile / serializeMemoryFile', () => {\n let registry: BodyConverterRegistry;\n const knowledgeKind: Kind = 'knowledge' as Kind;\n\n beforeEach(() => {\n registry = BodyConverterRegistry.create().orThrow();\n registry.register(knowledgeKind, Converters.string);\n });\n\n test('serializes then parses a record round-trip', () => {\n const envelope = envelopeConverter.convert(validEnvelopeObject).orThrow();\n const file = serializeMemoryFile(envelope, 'The knowledge body.').orThrow();\n expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {\n expect(record.envelope.id).toBe('intro-to-rust');\n expect(record.body).toBe('The knowledge body.');\n });\n });\n\n test('fails when the frontmatter cannot be split', () => {\n expect(parseMemoryFile('no delimiters', registry)).toFailWith(/missing opening frontmatter delimiter/i);\n });\n\n test('fails with envelope context when the envelope is invalid', () => {\n const file = '---\\nid: x\\n---\\nbody';\n expect(parseMemoryFile(file, registry)).toFailWith(/invalid envelope/i);\n });\n\n test('fails with body context when the body fails the registered converter', () => {\n const envelope = envelopeConverter.convert({ ...validEnvelopeObject, kind: 'unregistered' }).orThrow();\n const file = serializeMemoryFile(envelope, 'body').orThrow();\n expect(parseMemoryFile(file, registry)).toFailWith(/invalid body.*no converter registered/i);\n });\n\n test('serialize fails (with id context) when the envelope cannot be dumped to YAML', () => {\n const unserializable = { ...validEnvelopeObject, weird: BigInt(10) } as unknown as IMemoryEnvelope;\n expect(serializeMemoryFile(unserializable, 'body')).toFailWith(/failed to serialize envelope/i);\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"envelopeConverter.test.js","sourceRoot":"","sources":["../../../../src/test/unit/converters/envelopeConverter.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport '@fgv/ts-utils-jest';\nimport { Converters } from '@fgv/ts-utils';\nimport {\n BodyConverterRegistry,\n edgeConverter,\n edgeTargetConverter,\n envelopeConverter,\n envelopeYamlConverter,\n joinFrontmatter,\n parseMemoryFile,\n provenanceConverter,\n serializeMemoryFile,\n splitFrontmatter,\n temporalConverter\n} from '../../../packlets/converters';\nimport { IMemoryEnvelope, Kind } from '../../../packlets/types';\n\nconst validEnvelopeObject: Record<string, unknown> = {\n id: 'intro-to-rust',\n entityId: 'intro-to-rust',\n kind: 'knowledge',\n tags: ['rust', 'systems'],\n links: [{ type: 'related', target: { scope: 'knowledge', id: 'ownership' } }],\n created: 1000,\n updated: 2000,\n seq: 5,\n contentHash: 'crc-abc',\n provenance: { source: 'agent', by: 'curator' }\n};\n\ndescribe('provenanceConverter', () => {\n test('validates the known fields', () => {\n expect(\n provenanceConverter.convert({ source: 'human', by: 'erik', model: 'gpt', confidence: 0.9 })\n ).toSucceedWith({ source: 'human', by: 'erik', model: 'gpt', confidence: 0.9 });\n });\n\n test('preserves opaque extension keys verbatim', () => {\n expect(\n provenanceConverter.convert({ source: 'agent', sentiment: { score: 0.5 }, epistemic: 'belief' })\n ).toSucceedAndSatisfy((p) => {\n expect(p.source).toBe('agent');\n expect(p.sentiment).toEqual({ score: 0.5 });\n expect(p.epistemic).toBe('belief');\n });\n });\n\n test('accepts an arbitrary source string (open vocabulary)', () => {\n expect(provenanceConverter.convert({ source: 'host-ingest' })).toSucceedWith({ source: 'host-ingest' });\n });\n\n test('validates a scope-qualified derivedFrom (nested { scope, id })', () => {\n expect(\n provenanceConverter.convert({\n source: 'host-ingest',\n derivedFrom: { scope: 'conversations/conv-a', id: 'turn-3' }\n })\n ).toSucceedAndSatisfy((p) => {\n expect(p.derivedFrom).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });\n });\n });\n\n test('rejects a bare-string derivedFrom (the pre-scoped format)', () => {\n expect(provenanceConverter.convert({ source: 'host-ingest', derivedFrom: 'turn-3' })).toFail();\n });\n\n test('rejects a derivedFrom missing scope', () => {\n expect(provenanceConverter.convert({ source: 'host-ingest', derivedFrom: { id: 'turn-3' } })).toFailWith(\n /scope/i\n );\n });\n\n test('rejects a derivedFrom missing id', () => {\n expect(\n provenanceConverter.convert({ source: 'host-ingest', derivedFrom: { scope: 'conversations/conv-a' } })\n ).toFailWith(/id/i);\n });\n\n test('fails when source is missing', () => {\n expect(provenanceConverter.convert({ by: 'erik' })).toFail();\n });\n\n test('fails on a non-object', () => {\n expect(provenanceConverter.convert('nope')).toFail();\n });\n});\n\ndescribe('edgeTargetConverter', () => {\n test('validates a scope-qualified target', () => {\n expect(edgeTargetConverter.convert({ scope: 'conversations/conv-a', id: 'turn-3' })).toSucceedAndSatisfy(\n (t) => {\n expect(t).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });\n }\n );\n });\n\n test('rejects a target missing scope', () => {\n expect(edgeTargetConverter.convert({ id: 'turn-3' })).toFailWith(/scope/i);\n });\n\n test('rejects a target missing id', () => {\n expect(edgeTargetConverter.convert({ scope: 'conversations/conv-a' })).toFailWith(/id/i);\n });\n\n test('rejects a bare-string target (the pre-scoped format)', () => {\n expect(edgeTargetConverter.convert('turn-3')).toFail();\n });\n});\n\ndescribe('edgeConverter', () => {\n test('validates a full attributed edge', () => {\n expect(\n edgeConverter.convert({\n type: 'mtm-ref',\n target: { scope: 'conversations/conv-a', id: 'turn-3' },\n confidence: 0.8,\n provenance: { source: 'agent' },\n valid_at: 100,\n invalid_at: 200\n })\n ).toSucceedAndSatisfy((edge) => {\n expect(edge.type).toBe('mtm-ref');\n expect(edge.target).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });\n expect(edge.confidence).toBe(0.8);\n expect(edge.provenance).toEqual({ source: 'agent' });\n expect(edge.valid_at).toBe(100);\n expect(edge.invalid_at).toBe(200);\n });\n });\n\n test('validates a minimal edge', () => {\n expect(\n edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'ownership' } })\n ).toSucceedAndSatisfy((edge) => {\n expect(edge.type).toBe('related');\n expect(edge.target).toEqual({ scope: 'knowledge', id: 'ownership' });\n expect(edge.confidence).toBeUndefined();\n });\n });\n\n test('accepts a null invalid_at (still valid)', () => {\n expect(\n edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'x' }, invalid_at: null })\n ).toSucceedAndSatisfy((edge) => {\n expect(edge.invalid_at).toBeNull();\n });\n });\n\n test('fails when invalid_at is neither a number nor null', () => {\n expect(\n edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'x' }, invalid_at: 'soon' })\n ).toFail();\n });\n\n test('fails when target is missing', () => {\n expect(edgeConverter.convert({ type: 'related' })).toFail();\n });\n\n test('fails when target is a bare string (rejects the pre-scoped format)', () => {\n expect(edgeConverter.convert({ type: 'related', target: 'ownership' })).toFail();\n });\n});\n\ndescribe('temporalConverter', () => {\n test('validates a temporal block', () => {\n expect(temporalConverter.convert({ valid_at: 1, invalid_at: null })).toSucceedWith({\n valid_at: 1,\n invalid_at: null\n });\n });\n\n test('validates an empty temporal block', () => {\n expect(temporalConverter.convert({})).toSucceedWith({});\n });\n});\n\ndescribe('envelopeConverter', () => {\n test('validates a full envelope without optional blocks', () => {\n expect(envelopeConverter.convert(validEnvelopeObject)).toSucceedAndSatisfy((envelope) => {\n expect(envelope.id).toBe('intro-to-rust');\n expect(envelope.tags).toEqual(['rust', 'systems']);\n expect(envelope.links).toHaveLength(1);\n expect(envelope.temporal).toBeUndefined();\n expect(envelope.embeddingRef).toBeUndefined();\n });\n });\n\n test('validates a temporal envelope with a string embeddingRef', () => {\n expect(\n envelopeConverter.convert({\n ...validEnvelopeObject,\n temporal: { valid_at: 10 },\n embeddingRef: 'vec-1'\n })\n ).toSucceedAndSatisfy((envelope) => {\n expect(envelope.temporal).toEqual({ valid_at: 10 });\n expect(envelope.embeddingRef).toBe('vec-1');\n });\n });\n\n test('accepts a null embeddingRef (not embedded)', () => {\n expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: null })).toSucceedAndSatisfy(\n (envelope) => {\n expect(envelope.embeddingRef).toBeNull();\n }\n );\n });\n\n test('fails when embeddingRef is neither a string nor null', () => {\n expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: 42 })).toFail();\n });\n\n test('rank is absent when not present', () => {\n expect(envelopeConverter.convert(validEnvelopeObject)).toSucceedAndSatisfy((envelope) => {\n expect(envelope.rank).toBeUndefined();\n });\n });\n\n test('validates and preserves a numeric rank', () => {\n expect(envelopeConverter.convert({ ...validEnvelopeObject, rank: 42 })).toSucceedAndSatisfy(\n (envelope) => {\n expect(envelope.rank).toBe(42);\n }\n );\n });\n\n test('serialize/parse round-trips rank through frontmatter', () => {\n const registry = BodyConverterRegistry.create().orThrow();\n registry.register('knowledge' as Kind, Converters.string);\n const envelope = envelopeConverter.convert({ ...validEnvelopeObject, rank: 7 }).orThrow();\n const file = serializeMemoryFile(envelope, 'body').orThrow();\n expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {\n expect(record.envelope.rank).toBe(7);\n });\n });\n\n test('fails when rank is not a number', () => {\n expect(envelopeConverter.convert({ ...validEnvelopeObject, rank: 'high' })).toFail();\n });\n\n test('fails when a required field is missing', () => {\n const missing: Record<string, unknown> = { ...validEnvelopeObject };\n delete missing.contentHash;\n expect(envelopeConverter.convert(missing)).toFail();\n });\n});\n\ndescribe('envelopeYamlConverter', () => {\n test('parses an envelope from a YAML string', () => {\n const yaml = serializeMemoryFile(\n envelopeConverter.convert(validEnvelopeObject).orThrow(),\n 'body text'\n ).orThrow();\n const frontmatter = splitFrontmatter(yaml).orThrow().frontmatter;\n expect(envelopeYamlConverter.convert(frontmatter)).toSucceedAndSatisfy((envelope) => {\n expect(envelope.id).toBe('intro-to-rust');\n });\n });\n\n test('fails on a YAML string that is not an object', () => {\n expect(envelopeYamlConverter.convert('just a scalar')).toFail();\n });\n});\n\ndescribe('splitFrontmatter / joinFrontmatter', () => {\n test('splits a well-formed memory file', () => {\n const raw = '---\\nid: x\\nkind: knowledge\\n---\\nThe body.\\nSecond line.';\n expect(splitFrontmatter(raw)).toSucceedWith({\n frontmatter: 'id: x\\nkind: knowledge',\n body: 'The body.\\nSecond line.'\n });\n });\n\n test('strips trailing CR so CRLF-authored files parse identically to LF', () => {\n const raw = '---\\r\\nid: x\\r\\nkind: knowledge\\r\\n---\\r\\nThe body.\\r\\nSecond line.';\n expect(splitFrontmatter(raw)).toSucceedWith({\n frontmatter: 'id: x\\nkind: knowledge',\n body: 'The body.\\nSecond line.'\n });\n });\n\n test('fails when the opening delimiter is missing', () => {\n expect(splitFrontmatter('no frontmatter here')).toFailWith(/missing opening frontmatter delimiter/i);\n });\n\n test('fails when the closing delimiter is missing', () => {\n expect(splitFrontmatter('---\\nid: x\\nstill going')).toFailWith(/missing closing frontmatter delimiter/i);\n });\n\n test('join adds a trailing newline to the frontmatter when absent', () => {\n expect(joinFrontmatter('id: x', 'body')).toBe('---\\nid: x\\n---\\nbody');\n });\n\n test('join does not double the trailing newline when present', () => {\n expect(joinFrontmatter('id: x\\n', 'body')).toBe('---\\nid: x\\n---\\nbody');\n });\n\n test('split and join round-trip', () => {\n const raw = '---\\nid: x\\n---\\nbody content';\n const parts = splitFrontmatter(raw).orThrow();\n expect(joinFrontmatter(parts.frontmatter, parts.body)).toBe(raw);\n });\n});\n\ndescribe('parseMemoryFile / serializeMemoryFile', () => {\n let registry: BodyConverterRegistry;\n const knowledgeKind: Kind = 'knowledge' as Kind;\n\n beforeEach(() => {\n registry = BodyConverterRegistry.create().orThrow();\n registry.register(knowledgeKind, Converters.string);\n });\n\n test('serializes then parses a record round-trip', () => {\n const envelope = envelopeConverter.convert(validEnvelopeObject).orThrow();\n const file = serializeMemoryFile(envelope, 'The knowledge body.').orThrow();\n expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {\n expect(record.envelope.id).toBe('intro-to-rust');\n expect(record.body).toBe('The knowledge body.');\n });\n });\n\n test('round-trips a scope-qualified edge target through the frontmatter intact', () => {\n const envelope = envelopeConverter\n .convert({\n ...validEnvelopeObject,\n links: [{ type: 'mtm-ref', target: { scope: 'conversations/conv-a', id: 'turn-3' } }]\n })\n .orThrow();\n const file = serializeMemoryFile(envelope, 'body').orThrow();\n // On-wire the target is a nested object, NOT a bare scalar.\n expect(file).toMatch(/scope: conversations\\/conv-a/);\n expect(file).toMatch(/id: turn-3/);\n expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {\n expect(record.envelope.links).toEqual([\n { type: 'mtm-ref', target: { scope: 'conversations/conv-a', id: 'turn-3' } }\n ]);\n // Serializing the reparsed record reproduces byte-identical output.\n expect(serializeMemoryFile(record.envelope, record.body as string)).toSucceedWith(file);\n });\n });\n\n test('round-trips a scope-qualified provenance.derivedFrom through the frontmatter intact', () => {\n const envelope = envelopeConverter\n .convert({\n ...validEnvelopeObject,\n provenance: {\n source: 'host-ingest',\n derivedFrom: { scope: 'conversations/conv-a', id: 'turn-3' }\n }\n })\n .orThrow();\n const file = serializeMemoryFile(envelope, 'body').orThrow();\n // On-wire the back-reference is a nested object, NOT a bare scalar.\n expect(file).toMatch(/scope: conversations\\/conv-a/);\n expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {\n expect(record.envelope.provenance.derivedFrom).toEqual({\n scope: 'conversations/conv-a',\n id: 'turn-3'\n });\n expect(serializeMemoryFile(record.envelope, record.body as string)).toSucceedWith(file);\n });\n });\n\n test('two records whose derivedFrom share a stem across scopes round-trip as distinct scoped refs', () => {\n // Same idStem ('turn-3'), different scopes — exactly the ambiguity a bare\n // MemoryId could not disambiguate. Each must round-trip to ITS OWN scope.\n const makeFile = (scope: string): string => {\n const envelope = envelopeConverter\n .convert({\n ...validEnvelopeObject,\n provenance: { source: 'host-ingest', derivedFrom: { scope, id: 'turn-3' } }\n })\n .orThrow();\n return serializeMemoryFile(envelope, 'body').orThrow();\n };\n const fileA = makeFile('conversations/conv-a');\n const fileB = makeFile('conversations/conv-b');\n expect(fileA).not.toBe(fileB);\n expect(parseMemoryFile(fileA, registry)).toSucceedAndSatisfy((record) => {\n expect(record.envelope.provenance.derivedFrom).toEqual({\n scope: 'conversations/conv-a',\n id: 'turn-3'\n });\n });\n expect(parseMemoryFile(fileB, registry)).toSucceedAndSatisfy((record) => {\n expect(record.envelope.provenance.derivedFrom).toEqual({\n scope: 'conversations/conv-b',\n id: 'turn-3'\n });\n });\n });\n\n test('fails when the frontmatter cannot be split', () => {\n expect(parseMemoryFile('no delimiters', registry)).toFailWith(/missing opening frontmatter delimiter/i);\n });\n\n test('fails with envelope context when the envelope is invalid', () => {\n const file = '---\\nid: x\\n---\\nbody';\n expect(parseMemoryFile(file, registry)).toFailWith(/invalid envelope/i);\n });\n\n test('fails with body context when the body fails the registered converter', () => {\n const envelope = envelopeConverter.convert({ ...validEnvelopeObject, kind: 'unregistered' }).orThrow();\n const file = serializeMemoryFile(envelope, 'body').orThrow();\n expect(parseMemoryFile(file, registry)).toFailWith(/invalid body.*no converter registered/i);\n });\n\n test('serialize fails (with id context) when the envelope cannot be dumped to YAML', () => {\n const unserializable = { ...validEnvelopeObject, weird: BigInt(10) } as unknown as IMemoryEnvelope;\n expect(serializeMemoryFile(unserializable, 'body')).toFailWith(/failed to serialize envelope/i);\n });\n});\n"]}
|