@remnic/core 1.1.0 → 1.1.2
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-audit.d.ts +56 -0
- package/dist/access-audit.js +9 -0
- package/dist/access-cli.js +70 -53
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +16 -9
- package/dist/access-http.js +26 -18
- package/dist/access-mcp.d.ts +16 -9
- package/dist/access-mcp.js +30 -8
- package/dist/access-schema.d.ts +124 -33
- package/dist/access-schema.js +5 -1
- package/dist/{access-service-HmO1Trrx.d.ts → access-service-Br8ZydTK.d.ts} +158 -63
- package/dist/access-service.d.ts +13 -6
- package/dist/access-service.js +23 -14
- package/dist/bootstrap.d.ts +6 -3
- package/dist/briefing.d.ts +1 -0
- package/dist/briefing.js +8 -6
- package/dist/buffer-surprise-report.d.ts +70 -0
- package/dist/buffer-surprise-report.js +7 -0
- package/dist/buffer-surprise-report.js.map +1 -0
- package/dist/buffer-surprise.d.ts +98 -0
- package/dist/buffer-surprise.js +11 -0
- package/dist/buffer-surprise.js.map +1 -0
- package/dist/buffer.d.ts +100 -2
- package/dist/buffer.js +1 -1
- package/dist/calibration.js +6 -6
- package/dist/causal-behavior.js +4 -4
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +19 -18
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +4 -4
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-QNJMBKFK.js → chunk-2LGMW3DJ.js} +3 -2
- package/dist/chunk-2LGMW3DJ.js.map +1 -0
- package/dist/{chunk-QDYXG4CS.js → chunk-3FPTCC3Z.js} +4 -3
- package/dist/chunk-3FPTCC3Z.js.map +1 -0
- package/dist/chunk-3GPTTA4J.js +57 -0
- package/dist/chunk-3GPTTA4J.js.map +1 -0
- package/dist/{chunk-ITRLGI2T.js → chunk-3OGMS3PE.js} +2 -2
- package/dist/{chunk-DEPL3635.js → chunk-3YGHKTBF.js} +1446 -196
- package/dist/chunk-3YGHKTBF.js.map +1 -0
- package/dist/{chunk-BLKTA7MM.js → chunk-4HQS2HPX.js} +54 -21
- package/dist/chunk-4HQS2HPX.js.map +1 -0
- package/dist/chunk-54V4BZWP.js +139 -0
- package/dist/chunk-54V4BZWP.js.map +1 -0
- package/dist/chunk-5JRF2PZA.js +67 -0
- package/dist/chunk-5JRF2PZA.js.map +1 -0
- package/dist/chunk-64NJRYU2.js +332 -0
- package/dist/chunk-64NJRYU2.js.map +1 -0
- package/dist/{chunk-OIT5QGG4.js → chunk-6AUUAZEX.js} +72 -2
- package/dist/chunk-6AUUAZEX.js.map +1 -0
- package/dist/{chunk-3QHL5ABG.js → chunk-6YJHX2DL.js} +191 -10
- package/dist/chunk-6YJHX2DL.js.map +1 -0
- package/dist/chunk-AJU4PJGY.js +126 -0
- package/dist/chunk-AJU4PJGY.js.map +1 -0
- package/dist/chunk-ASAITVLA.js +64 -0
- package/dist/chunk-ASAITVLA.js.map +1 -0
- package/dist/{chunk-44ICJRF3.js → chunk-AYXIPSZO.js} +5 -5
- package/dist/{chunk-MBJHSA7F.js → chunk-BECYBZLX.js} +265 -20
- package/dist/chunk-BECYBZLX.js.map +1 -0
- package/dist/chunk-C4SQJZAF.js +486 -0
- package/dist/chunk-C4SQJZAF.js.map +1 -0
- package/dist/{chunk-6UJ47TVX.js → chunk-CUPFXL3J.js} +2 -2
- package/dist/chunk-DF3RVK3X.js +119 -0
- package/dist/chunk-DF3RVK3X.js.map +1 -0
- package/dist/{chunk-N42IWANG.js → chunk-DG6YMRDC.js} +3 -3
- package/dist/chunk-DGVM5SFL.js +69 -0
- package/dist/chunk-DGVM5SFL.js.map +1 -0
- package/dist/{chunk-3SV6CQHO.js → chunk-DIXB44VE.js} +102 -66
- package/dist/chunk-DIXB44VE.js.map +1 -0
- package/dist/chunk-EIR5VLIH.js +90 -0
- package/dist/chunk-EIR5VLIH.js.map +1 -0
- package/dist/{chunk-GV6NLQ4X.js → chunk-F5VP6YCB.js} +374 -16
- package/dist/chunk-F5VP6YCB.js.map +1 -0
- package/dist/{chunk-6ZH4TU6I.js → chunk-FAAFWE4G.js} +2 -1
- package/dist/chunk-FAAFWE4G.js.map +1 -0
- package/dist/{chunk-7WQ6SLIE.js → chunk-FVA6TGI3.js} +2 -2
- package/dist/{chunk-PAORGQRI.js → chunk-GA5P7RST.js} +37 -23
- package/dist/chunk-GA5P7RST.js.map +1 -0
- package/dist/chunk-GDFS42HT.js +206 -0
- package/dist/chunk-GDFS42HT.js.map +1 -0
- package/dist/chunk-IISBCCWR.js +52 -0
- package/dist/chunk-IISBCCWR.js.map +1 -0
- package/dist/chunk-JBMSGZEQ.js +441 -0
- package/dist/chunk-JBMSGZEQ.js.map +1 -0
- package/dist/{chunk-J4IYOZZ5.js → chunk-JXS5PDQ7.js} +3 -1
- package/dist/chunk-JXS5PDQ7.js.map +1 -0
- package/dist/chunk-KVBLZUKV.js +173 -0
- package/dist/chunk-KVBLZUKV.js.map +1 -0
- package/dist/{chunk-4LACOVZX.js → chunk-L7IXWRYE.js} +10 -5
- package/dist/chunk-L7IXWRYE.js.map +1 -0
- package/dist/chunk-LBLXEFWK.js +51 -0
- package/dist/chunk-LBLXEFWK.js.map +1 -0
- package/dist/{chunk-WBSAYXVI.js → chunk-LOIMBRDE.js} +201 -45
- package/dist/chunk-LOIMBRDE.js.map +1 -0
- package/dist/{chunk-3WHVNEN7.js → chunk-LTCGGW2D.js} +1 -1
- package/dist/chunk-LTCGGW2D.js.map +1 -0
- package/dist/{chunk-ZVBB3T7V.js → chunk-NBVAS5MT.js} +25 -23
- package/dist/chunk-NBVAS5MT.js.map +1 -0
- package/dist/{chunk-UEYA6UC7.js → chunk-NZLQTHS5.js} +25 -2
- package/dist/chunk-NZLQTHS5.js.map +1 -0
- package/dist/{chunk-NQEVYWX6.js → chunk-OC5OXUQ4.js} +211 -7
- package/dist/chunk-OC5OXUQ4.js.map +1 -0
- package/dist/{chunk-LK6SGL53.js → chunk-OR64ZGRZ.js} +3 -2
- package/dist/chunk-OR64ZGRZ.js.map +1 -0
- package/dist/{chunk-SYUK3VLY.js → chunk-PVICZTKG.js} +117 -5
- package/dist/chunk-PVICZTKG.js.map +1 -0
- package/dist/chunk-PVPWZSSI.js +37 -0
- package/dist/chunk-PVPWZSSI.js.map +1 -0
- package/dist/{chunk-JL2PU6AI.js → chunk-R2XRID2N.js} +2 -2
- package/dist/{chunk-4NRAJUDS.js → chunk-RBBWYEFJ.js} +1 -1
- package/dist/chunk-RFYAYKTD.js +146 -0
- package/dist/chunk-RFYAYKTD.js.map +1 -0
- package/dist/chunk-SOBJ6NEY.js +18 -0
- package/dist/chunk-SOBJ6NEY.js.map +1 -0
- package/dist/{chunk-JIU55F3X.js → chunk-SPI27QT6.js} +2 -2
- package/dist/{chunk-MVTHXUBX.js → chunk-STGWEHYR.js} +479 -20
- package/dist/chunk-STGWEHYR.js.map +1 -0
- package/dist/{chunk-6LX5ORAS.js → chunk-TMYO7B5P.js} +4 -4
- package/dist/chunk-TVVEYCNW.js +65 -0
- package/dist/chunk-TVVEYCNW.js.map +1 -0
- package/dist/chunk-ULYOGL6R.js +322 -0
- package/dist/chunk-ULYOGL6R.js.map +1 -0
- package/dist/{chunk-37UIFYWO.js → chunk-UWB5LMWY.js} +108 -9
- package/dist/chunk-UWB5LMWY.js.map +1 -0
- package/dist/{chunk-47UU5PU2.js → chunk-VBVG2M5G.js} +18 -3
- package/dist/chunk-VBVG2M5G.js.map +1 -0
- package/dist/{chunk-7ECD5ATE.js → chunk-VDX363PS.js} +2 -2
- package/dist/{chunk-O5ETUNBT.js → chunk-VTU2B4VF.js} +7 -3
- package/dist/chunk-VTU2B4VF.js.map +1 -0
- package/dist/{chunk-MTLYEMJB.js → chunk-WCLICCGB.js} +18 -3
- package/dist/chunk-WCLICCGB.js.map +1 -0
- package/dist/chunk-X6GF3FX2.js +26 -0
- package/dist/chunk-X6GF3FX2.js.map +1 -0
- package/dist/{chunk-3QFQGRHO.js → chunk-XMHBH5H6.js} +4 -4
- package/dist/{chunk-DHHP2Z4X.js → chunk-XXVWLXSG.js} +2 -2
- package/dist/{chunk-XZ2TIKGC.js → chunk-Y7R2XJ5Q.js} +25 -9
- package/dist/chunk-Y7R2XJ5Q.js.map +1 -0
- package/dist/{chunk-ALXMCZEU.js → chunk-Z2E7VW55.js} +6 -3
- package/dist/chunk-Z2E7VW55.js.map +1 -0
- package/dist/chunk-ZAIM4TUE.js +488 -0
- package/dist/chunk-ZAIM4TUE.js.map +1 -0
- package/dist/chunk-ZZTOURJI.js +91 -0
- package/dist/chunk-ZZTOURJI.js.map +1 -0
- package/dist/{cli-BneVIEvh.d.ts → cli-BkeRaYfk.d.ts} +2 -2
- package/dist/cli.d.ts +13 -6
- package/dist/cli.js +42 -31
- package/dist/config.js +2 -2
- package/dist/consolidation-operator.d.ts +41 -0
- package/dist/consolidation-operator.js +11 -0
- package/dist/consolidation-operator.js.map +1 -0
- package/dist/consolidation-provenance-check.d.ts +68 -0
- package/dist/consolidation-provenance-check.js +9 -0
- package/dist/consolidation-provenance-check.js.map +1 -0
- package/dist/consolidation-undo.d.ts +123 -0
- package/dist/consolidation-undo.js +426 -0
- package/dist/consolidation-undo.js.map +1 -0
- package/dist/{contradiction-scan-GR33PONM.js → contradiction-scan-E3GJTI4F.js} +43 -7
- package/dist/contradiction-scan-E3GJTI4F.js.map +1 -0
- package/dist/cross-namespace-budget.d.ts +133 -0
- package/dist/cross-namespace-budget.js +9 -0
- package/dist/cross-namespace-budget.js.map +1 -0
- package/dist/direct-answer-wiring.js +5 -70
- package/dist/direct-answer-wiring.js.map +1 -1
- package/dist/embedding-fallback.js +2 -1
- package/dist/{engine-5TIQBYZR.js → engine-72LSIWQP.js} +8 -7
- package/dist/engine-72LSIWQP.js.map +1 -0
- package/dist/entity-retrieval.d.ts +1 -0
- package/dist/entity-retrieval.js +7 -6
- package/dist/explicit-capture.d.ts +6 -3
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-judge-telemetry.d.ts +113 -0
- package/dist/extraction-judge-telemetry.js +14 -0
- package/dist/extraction-judge-telemetry.js.map +1 -0
- package/dist/extraction-judge-training.d.ts +85 -0
- package/dist/extraction-judge-training.js +16 -0
- package/dist/extraction-judge-training.js.map +1 -0
- package/dist/extraction-judge.d.ts +124 -2
- package/dist/extraction-judge.js +11 -1
- package/dist/extraction.js +10 -9
- package/dist/fallback-llm.js +3 -3
- package/dist/graph-recall.d.ts +100 -0
- package/dist/graph-recall.js +8 -0
- package/dist/graph-recall.js.map +1 -0
- package/dist/graph-retrieval.d.ts +271 -0
- package/dist/graph-retrieval.js +21 -0
- package/dist/graph-retrieval.js.map +1 -0
- package/dist/importance.js +1 -1
- package/dist/index.d.ts +585 -20
- package/dist/index.js +542 -344
- package/dist/index.js.map +1 -1
- package/dist/local-llm.js +2 -2
- package/dist/memory-worth-bench.d.ts +51 -0
- package/dist/memory-worth-bench.js +131 -0
- package/dist/memory-worth-bench.js.map +1 -0
- package/dist/memory-worth-filter.d.ts +128 -0
- package/dist/memory-worth-filter.js +10 -0
- package/dist/memory-worth-filter.js.map +1 -0
- package/dist/memory-worth-outcomes.d.ts +118 -0
- package/dist/memory-worth-outcomes.js +9 -0
- package/dist/memory-worth-outcomes.js.map +1 -0
- package/dist/memory-worth.d.ts +102 -0
- package/dist/memory-worth.js +7 -0
- package/dist/memory-worth.js.map +1 -0
- package/dist/operator-toolkit.d.ts +40 -1
- package/dist/operator-toolkit.js +25 -16
- package/dist/{orchestrator-DRYA6_lW.d.ts → orchestrator-CmJ-NTdJ.d.ts} +233 -8
- package/dist/orchestrator.d.ts +6 -3
- package/dist/orchestrator.js +54 -44
- package/dist/page-versioning.d.ts +12 -1
- package/dist/page-versioning.js +5 -3
- package/dist/{port-C1GZFv8h.d.ts → port-BADbLZU5.d.ts} +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +5 -3
- package/dist/qmd.js +3 -3
- package/dist/reasoning-trace-recall.d.ts +90 -0
- package/dist/reasoning-trace-recall.js +13 -0
- package/dist/reasoning-trace-recall.js.map +1 -0
- package/dist/reasoning-trace-types.d.ts +54 -0
- package/dist/reasoning-trace-types.js +17 -0
- package/dist/reasoning-trace-types.js.map +1 -0
- package/dist/recall-audit-anomaly.d.ts +112 -0
- package/dist/recall-audit-anomaly.js +11 -0
- package/dist/recall-audit-anomaly.js.map +1 -0
- package/dist/recall-audit.js +5 -44
- package/dist/recall-audit.js.map +1 -1
- package/dist/recall-explain-renderer.d.ts +49 -0
- package/dist/recall-explain-renderer.js +18 -0
- package/dist/recall-explain-renderer.js.map +1 -0
- package/dist/recall-state.d.ts +12 -1
- package/dist/recall-state.js +1 -1
- package/dist/recall-xray-cli.d.ts +40 -0
- package/dist/recall-xray-cli.js +11 -0
- package/dist/recall-xray-cli.js.map +1 -0
- package/dist/recall-xray-renderer.d.ts +44 -0
- package/dist/recall-xray-renderer.js +18 -0
- package/dist/recall-xray-renderer.js.map +1 -0
- package/dist/recall-xray.d.ts +179 -0
- package/dist/recall-xray.js +13 -0
- package/dist/recall-xray.js.map +1 -0
- package/dist/resolve-provider-secret.d.ts +5 -1
- package/dist/resolve-provider-secret.js +3 -1
- package/dist/resume-bundles.js +6 -6
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +17 -0
- package/dist/retrieval-tiers.js +9 -0
- package/dist/retrieval-tiers.js.map +1 -0
- package/dist/schemas.d.ts +309 -53
- package/dist/schemas.js +1 -1
- package/dist/{semantic-consolidation-DrvSYRdB.d.ts → semantic-consolidation-CxJU6MJk.d.ts} +62 -1
- package/dist/semantic-consolidation.d.ts +2 -1
- package/dist/semantic-consolidation.js +22 -7
- package/dist/semantic-rule-promotion.js +7 -6
- package/dist/semantic-rule-verifier.js +7 -6
- package/dist/storage.d.ts +82 -1
- package/dist/storage.js +6 -5
- package/dist/summarizer.js +6 -6
- package/dist/temporal-supersession.d.ts +1 -0
- package/dist/tier-migration.d.ts +2 -1
- package/dist/tokens.js +2 -1
- package/dist/types.d.ts +276 -2
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +7 -6
- package/package.json +1 -1
- package/dist/chunk-37UIFYWO.js.map +0 -1
- package/dist/chunk-3QHL5ABG.js.map +0 -1
- package/dist/chunk-3SV6CQHO.js.map +0 -1
- package/dist/chunk-3WHVNEN7.js.map +0 -1
- package/dist/chunk-47UU5PU2.js.map +0 -1
- package/dist/chunk-4LACOVZX.js.map +0 -1
- package/dist/chunk-6ZH4TU6I.js.map +0 -1
- package/dist/chunk-ALXMCZEU.js.map +0 -1
- package/dist/chunk-BLKTA7MM.js.map +0 -1
- package/dist/chunk-DEPL3635.js.map +0 -1
- package/dist/chunk-GV6NLQ4X.js.map +0 -1
- package/dist/chunk-J4IYOZZ5.js.map +0 -1
- package/dist/chunk-LAYN4LDC.js +0 -267
- package/dist/chunk-LAYN4LDC.js.map +0 -1
- package/dist/chunk-LK6SGL53.js.map +0 -1
- package/dist/chunk-MBJHSA7F.js.map +0 -1
- package/dist/chunk-MTLYEMJB.js.map +0 -1
- package/dist/chunk-MVTHXUBX.js.map +0 -1
- package/dist/chunk-NQEVYWX6.js.map +0 -1
- package/dist/chunk-O5ETUNBT.js.map +0 -1
- package/dist/chunk-OIT5QGG4.js.map +0 -1
- package/dist/chunk-PAORGQRI.js.map +0 -1
- package/dist/chunk-QDYXG4CS.js.map +0 -1
- package/dist/chunk-QNJMBKFK.js.map +0 -1
- package/dist/chunk-SYUK3VLY.js.map +0 -1
- package/dist/chunk-UEYA6UC7.js.map +0 -1
- package/dist/chunk-UVJFDP7P.js +0 -202
- package/dist/chunk-UVJFDP7P.js.map +0 -1
- package/dist/chunk-WBSAYXVI.js.map +0 -1
- package/dist/chunk-XZ2TIKGC.js.map +0 -1
- package/dist/chunk-ZVBB3T7V.js.map +0 -1
- package/dist/contradiction-scan-GR33PONM.js.map +0 -1
- /package/dist/{engine-5TIQBYZR.js.map → access-audit.js.map} +0 -0
- /package/dist/{chunk-ITRLGI2T.js.map → chunk-3OGMS3PE.js.map} +0 -0
- /package/dist/{chunk-44ICJRF3.js.map → chunk-AYXIPSZO.js.map} +0 -0
- /package/dist/{chunk-6UJ47TVX.js.map → chunk-CUPFXL3J.js.map} +0 -0
- /package/dist/{chunk-N42IWANG.js.map → chunk-DG6YMRDC.js.map} +0 -0
- /package/dist/{chunk-7WQ6SLIE.js.map → chunk-FVA6TGI3.js.map} +0 -0
- /package/dist/{chunk-JL2PU6AI.js.map → chunk-R2XRID2N.js.map} +0 -0
- /package/dist/{chunk-4NRAJUDS.js.map → chunk-RBBWYEFJ.js.map} +0 -0
- /package/dist/{chunk-JIU55F3X.js.map → chunk-SPI27QT6.js.map} +0 -0
- /package/dist/{chunk-6LX5ORAS.js.map → chunk-TMYO7B5P.js.map} +0 -0
- /package/dist/{chunk-7ECD5ATE.js.map → chunk-VDX363PS.js.map} +0 -0
- /package/dist/{chunk-3QFQGRHO.js.map → chunk-XMHBH5H6.js.map} +0 -0
- /package/dist/{chunk-DHHP2Z4X.js.map → chunk-XXVWLXSG.js.map} +0 -0
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
resolveObjectiveStateStoreDir,
|
|
10
10
|
validateObjectiveStateSnapshot
|
|
11
11
|
} from "./chunk-LOBRX7VD.js";
|
|
12
|
+
import {
|
|
13
|
+
parseConfig
|
|
14
|
+
} from "./chunk-BECYBZLX.js";
|
|
12
15
|
import {
|
|
13
16
|
resolveCommitmentLedgerDir,
|
|
14
17
|
validateCommitmentLedgerEntry
|
|
15
18
|
} from "./chunk-FYIYMQ5N.js";
|
|
16
|
-
import {
|
|
17
|
-
parseConfig
|
|
18
|
-
} from "./chunk-MBJHSA7F.js";
|
|
19
19
|
import {
|
|
20
20
|
assertIsoRecordedAt,
|
|
21
21
|
assertSafePathSegment,
|
|
@@ -268,4 +268,4 @@ export {
|
|
|
268
268
|
recordResumeBundle,
|
|
269
269
|
getResumeBundleStatus
|
|
270
270
|
};
|
|
271
|
-
//# sourceMappingURL=chunk-
|
|
271
|
+
//# sourceMappingURL=chunk-TMYO7B5P.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectRecallAnomalies
|
|
3
|
+
} from "./chunk-RFYAYKTD.js";
|
|
4
|
+
import {
|
|
5
|
+
appendRecallAuditEntry
|
|
6
|
+
} from "./chunk-LBLXEFWK.js";
|
|
7
|
+
|
|
8
|
+
// src/access-audit.ts
|
|
9
|
+
var AccessAuditAdapter = class {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
const n = config.trailBufferSize;
|
|
13
|
+
if (typeof n === "number" && Number.isFinite(n) && n > 0) {
|
|
14
|
+
const floored = Math.floor(n);
|
|
15
|
+
this.trailBufferSize = floored >= 1 ? floored : 256;
|
|
16
|
+
} else {
|
|
17
|
+
this.trailBufferSize = 256;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
config;
|
|
21
|
+
trails = /* @__PURE__ */ new Map();
|
|
22
|
+
trailBufferSize;
|
|
23
|
+
/**
|
|
24
|
+
* Record an audit entry and (when enabled) run the anomaly detector
|
|
25
|
+
* over the principal's tail of entries. The principal key is used
|
|
26
|
+
* purely for tail-buffer bucketing — it need not match the production
|
|
27
|
+
* namespace resolver; sessionKey is the safe default.
|
|
28
|
+
*/
|
|
29
|
+
async record(principalKey, entry, now = Date.now()) {
|
|
30
|
+
const result = {};
|
|
31
|
+
if (this.config.detection.enabled) {
|
|
32
|
+
const key = principalKey.length > 0 ? principalKey : "__anonymous__";
|
|
33
|
+
const tail = this.trails.get(key) ?? { entries: [] };
|
|
34
|
+
tail.entries.push(entry);
|
|
35
|
+
if (tail.entries.length > this.trailBufferSize) {
|
|
36
|
+
tail.entries.splice(0, tail.entries.length - this.trailBufferSize);
|
|
37
|
+
}
|
|
38
|
+
this.trails.set(key, tail);
|
|
39
|
+
result.anomalies = detectRecallAnomalies({
|
|
40
|
+
entries: tail.entries,
|
|
41
|
+
now,
|
|
42
|
+
config: this.config.detection
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (this.config.audit.enabled) {
|
|
46
|
+
try {
|
|
47
|
+
result.appendedAt = await appendRecallAuditEntry(
|
|
48
|
+
this.config.audit.rootDir,
|
|
49
|
+
entry
|
|
50
|
+
);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/** Clear all in-memory tail state. Intended for tests / before_reset. */
|
|
57
|
+
reset() {
|
|
58
|
+
this.trails.clear();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
AccessAuditAdapter
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=chunk-TVVEYCNW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/access-audit.ts"],"sourcesContent":["/**\n * Access-layer audit adapter (issue #565 PR 5/5).\n *\n * Wraps `appendRecallAuditEntry` + `detectRecallAnomalies` into a single\n * entry point that MCP (`access-mcp.ts`) and HTTP (`access-http.ts`)\n * surfaces can call once per recall. Closes the gap called out in the\n * memory-extraction threat model §5: recall-audit previously only ran\n * on the Openclaw hook, so MCP/HTTP callers bypassed the trail entirely.\n *\n * The adapter is a per-instance class so the tail-of-trail buffer\n * (used by the anomaly detector) is scoped per-service, not global.\n *\n * No I/O unless `audit.enabled` is true; no detector invocation unless\n * `detection.enabled` is true. Either flag may be enabled independently.\n */\n\nimport {\n appendRecallAuditEntry,\n type RecallAuditEntry,\n} from \"./recall-audit.js\";\nimport {\n detectRecallAnomalies,\n type AnomalyDetectorConfig,\n type AnomalyDetectorResult,\n} from \"./recall-audit-anomaly.js\";\n\nexport interface AccessAuditConfig {\n audit: {\n enabled: boolean;\n /** Root directory the audit adapter writes JSONL shards into. */\n rootDir: string;\n };\n detection: AnomalyDetectorConfig;\n /**\n * How many entries the adapter retains in memory for the detector.\n * Defaults to 256 — enough to cover the threat model's default 5-minute\n * window at high recall rates without unbounded growth.\n */\n trailBufferSize?: number;\n}\n\nexport interface AccessAuditResult {\n /** Path of the JSONL shard the entry was appended to (only when audit enabled). */\n appendedAt?: string;\n /** Result of the anomaly detector (only when detection enabled). */\n anomalies?: AnomalyDetectorResult;\n}\n\n/**\n * Per-principal tail buffer. Distinct principals do not pollute each\n * other's detection windows — otherwise one noisy legitimate client\n * could mask an actual attacker.\n */\ninterface PrincipalTail {\n entries: RecallAuditEntry[];\n}\n\nexport class AccessAuditAdapter {\n private readonly trails = new Map<string, PrincipalTail>();\n private readonly trailBufferSize: number;\n\n constructor(private readonly config: AccessAuditConfig) {\n const n = config.trailBufferSize;\n if (typeof n === \"number\" && Number.isFinite(n) && n > 0) {\n const floored = Math.floor(n);\n // Guard against fractional inputs (e.g. 0.5) that floor to 0.\n this.trailBufferSize = floored >= 1 ? floored : 256;\n } else {\n this.trailBufferSize = 256;\n }\n }\n\n /**\n * Record an audit entry and (when enabled) run the anomaly detector\n * over the principal's tail of entries. The principal key is used\n * purely for tail-buffer bucketing — it need not match the production\n * namespace resolver; sessionKey is the safe default.\n */\n async record(\n principalKey: string,\n entry: RecallAuditEntry,\n now: number = Date.now(),\n ): Promise<AccessAuditResult> {\n const result: AccessAuditResult = {};\n\n // Update the in-memory tail BEFORE the async audit write so a failed\n // append never leaves the tail in a stale state. The detector must see\n // every record regardless of whether the JSONL shard accepted it.\n if (this.config.detection.enabled) {\n const key = principalKey.length > 0 ? principalKey : \"__anonymous__\";\n const tail = this.trails.get(key) ?? { entries: [] };\n tail.entries.push(entry);\n if (tail.entries.length > this.trailBufferSize) {\n tail.entries.splice(0, tail.entries.length - this.trailBufferSize);\n }\n this.trails.set(key, tail);\n\n result.anomalies = detectRecallAnomalies({\n entries: tail.entries,\n now,\n config: this.config.detection,\n });\n }\n\n if (this.config.audit.enabled) {\n try {\n result.appendedAt = await appendRecallAuditEntry(\n this.config.audit.rootDir,\n entry,\n );\n } catch {\n // Audit write failures must never crash the enclosing recall.\n // Swallow — operators can surface the ENOSPC / permission error\n // via the usual filesystem monitoring.\n }\n }\n\n return result;\n }\n\n /** Clear all in-memory tail state. Intended for tests / before_reset. */\n reset(): void {\n this.trails.clear();\n }\n}\n"],"mappings":";;;;;;;;AAyDO,IAAM,qBAAN,MAAyB;AAAA,EAI9B,YAA6B,QAA2B;AAA3B;AAC3B,UAAM,IAAI,OAAO;AACjB,QAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG;AACxD,YAAM,UAAU,KAAK,MAAM,CAAC;AAE5B,WAAK,kBAAkB,WAAW,IAAI,UAAU;AAAA,IAClD,OAAO;AACL,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA,EAT6B;AAAA,EAHZ,SAAS,oBAAI,IAA2B;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBjB,MAAM,OACJ,cACA,OACA,MAAc,KAAK,IAAI,GACK;AAC5B,UAAM,SAA4B,CAAC;AAKnC,QAAI,KAAK,OAAO,UAAU,SAAS;AACjC,YAAM,MAAM,aAAa,SAAS,IAAI,eAAe;AACrD,YAAM,OAAO,KAAK,OAAO,IAAI,GAAG,KAAK,EAAE,SAAS,CAAC,EAAE;AACnD,WAAK,QAAQ,KAAK,KAAK;AACvB,UAAI,KAAK,QAAQ,SAAS,KAAK,iBAAiB;AAC9C,aAAK,QAAQ,OAAO,GAAG,KAAK,QAAQ,SAAS,KAAK,eAAe;AAAA,MACnE;AACA,WAAK,OAAO,IAAI,KAAK,IAAI;AAEzB,aAAO,YAAY,sBAAsB;AAAA,QACvC,SAAS,KAAK;AAAA,QACd;AAAA,QACA,QAAQ,KAAK,OAAO;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,OAAO,MAAM,SAAS;AAC7B,UAAI;AACF,eAAO,aAAa,MAAM;AAAA,UACxB,KAAK,OAAO,MAAM;AAAA,UAClB;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAIR;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,OAAO,MAAM;AAAA,EACpB;AACF;","names":[]}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isConsolidationOperator
|
|
3
|
+
} from "./chunk-X6GF3FX2.js";
|
|
4
|
+
import {
|
|
5
|
+
sidecarKey
|
|
6
|
+
} from "./chunk-FAAFWE4G.js";
|
|
7
|
+
|
|
8
|
+
// src/consolidation-provenance-check.ts
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
11
|
+
var DERIVED_VIA_RAW_RE = /^[\t ]*derived_via:[\t ]*(.*)$/mu;
|
|
12
|
+
var DERIVED_FROM_RAW_RE = /^[\t ]*derived_from:[\t ]*(.*)$/mu;
|
|
13
|
+
function tokenizeRawBlockList(fmSlice, key) {
|
|
14
|
+
const lines = fmSlice.split("\n");
|
|
15
|
+
const keyRe = new RegExp(`^[\\t ]*${key}:[\\t ]*(.*)$`, "u");
|
|
16
|
+
let startIdx = -1;
|
|
17
|
+
for (let i = 0; i < lines.length; i++) {
|
|
18
|
+
const m = lines[i].match(keyRe);
|
|
19
|
+
if (m) {
|
|
20
|
+
if (m[1].trim().length === 0) {
|
|
21
|
+
startIdx = i + 1;
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (startIdx < 0) return null;
|
|
27
|
+
const items = [];
|
|
28
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i];
|
|
30
|
+
if (!/^\s+-/.test(line)) break;
|
|
31
|
+
const m = line.match(/^\s+-\s*(.*)$/u);
|
|
32
|
+
if (!m) break;
|
|
33
|
+
let tok = m[1].trim();
|
|
34
|
+
if (tok.startsWith('"') && tok.endsWith('"') && tok.length >= 2 || tok.startsWith("'") && tok.endsWith("'") && tok.length >= 2) {
|
|
35
|
+
tok = tok.slice(1, -1);
|
|
36
|
+
}
|
|
37
|
+
items.push(tok);
|
|
38
|
+
}
|
|
39
|
+
return items.length > 0 ? items : null;
|
|
40
|
+
}
|
|
41
|
+
function tokenizeRawFlowList(raw) {
|
|
42
|
+
const trimmed = raw.trim();
|
|
43
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
|
|
44
|
+
const inner = trimmed.slice(1, -1);
|
|
45
|
+
const parts = [];
|
|
46
|
+
let current = "";
|
|
47
|
+
let inSingle = false;
|
|
48
|
+
let inDouble = false;
|
|
49
|
+
for (let i = 0; i < inner.length; i++) {
|
|
50
|
+
const ch = inner[i];
|
|
51
|
+
if (inDouble) {
|
|
52
|
+
if (ch === "\\" && i + 1 < inner.length) {
|
|
53
|
+
current += inner[++i];
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (ch === '"') {
|
|
57
|
+
inDouble = false;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
current += ch;
|
|
61
|
+
} else if (inSingle) {
|
|
62
|
+
if (ch === "'" && inner[i + 1] === "'") {
|
|
63
|
+
current += "'";
|
|
64
|
+
i++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (ch === "'") {
|
|
68
|
+
inSingle = false;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
current += ch;
|
|
72
|
+
} else if (ch === '"') {
|
|
73
|
+
inDouble = true;
|
|
74
|
+
} else if (ch === "'") {
|
|
75
|
+
inSingle = true;
|
|
76
|
+
} else if (ch === ",") {
|
|
77
|
+
parts.push(current.trim());
|
|
78
|
+
current = "";
|
|
79
|
+
} else {
|
|
80
|
+
current += ch;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (current.trim().length > 0 || parts.length > 0) {
|
|
84
|
+
parts.push(current.trim());
|
|
85
|
+
}
|
|
86
|
+
return parts;
|
|
87
|
+
}
|
|
88
|
+
var DERIVED_FROM_ENTRY_RE = /^(.+):(\d+)$/;
|
|
89
|
+
function resolveSnapshotPath(memoryDir, sidecarDir, entry) {
|
|
90
|
+
const match = entry.match(DERIVED_FROM_ENTRY_RE);
|
|
91
|
+
if (!match) {
|
|
92
|
+
return { ok: false, reason: `malformed entry (expected "<path>:<version>")` };
|
|
93
|
+
}
|
|
94
|
+
const pagePath = match[1];
|
|
95
|
+
const versionId = match[2];
|
|
96
|
+
const ext = path.extname(pagePath) || ".md";
|
|
97
|
+
const key = sidecarKey(pagePath);
|
|
98
|
+
const snapshotPath = path.join(memoryDir, sidecarDir, key, `${versionId}${ext}`);
|
|
99
|
+
return { ok: true, snapshotPath };
|
|
100
|
+
}
|
|
101
|
+
async function runConsolidationProvenanceCheck(options) {
|
|
102
|
+
const { storage, memoryDir } = options;
|
|
103
|
+
const sidecarDir = options.sidecarDir ?? ".versions";
|
|
104
|
+
const report = {
|
|
105
|
+
scanned: 0,
|
|
106
|
+
withProvenance: 0,
|
|
107
|
+
issues: []
|
|
108
|
+
};
|
|
109
|
+
let memories;
|
|
110
|
+
try {
|
|
111
|
+
memories = await storage.readAllMemories();
|
|
112
|
+
} catch {
|
|
113
|
+
return {
|
|
114
|
+
scanned: 0,
|
|
115
|
+
withProvenance: 0,
|
|
116
|
+
issues: [
|
|
117
|
+
{
|
|
118
|
+
memoryPath: memoryDir,
|
|
119
|
+
memoryId: "(unreadable)",
|
|
120
|
+
kind: "derived_from_malformed_entry",
|
|
121
|
+
detail: "Could not enumerate memory directory to scan provenance."
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
for (const memory of memories) {
|
|
127
|
+
report.scanned += 1;
|
|
128
|
+
const fm = memory.frontmatter;
|
|
129
|
+
const derivedFrom = fm.derived_from;
|
|
130
|
+
const derivedVia = fm.derived_via;
|
|
131
|
+
let rawDerivedVia;
|
|
132
|
+
let rawDerivedFrom;
|
|
133
|
+
let rawDerivedViaKeyPresent = false;
|
|
134
|
+
let rawDerivedFromKeyPresent = false;
|
|
135
|
+
let duplicateViaKeys = false;
|
|
136
|
+
let duplicateFromKeys = false;
|
|
137
|
+
let viaMatchCount = 0;
|
|
138
|
+
let fromMatchCount = 0;
|
|
139
|
+
let fmSlice = "";
|
|
140
|
+
try {
|
|
141
|
+
const raw = await readFile(memory.path, "utf-8");
|
|
142
|
+
const frontmatterEnd = raw.indexOf("\n---", raw.indexOf("---") + 3);
|
|
143
|
+
fmSlice = frontmatterEnd > 0 ? raw.slice(0, frontmatterEnd) : raw;
|
|
144
|
+
const viaMatches = [...fmSlice.matchAll(new RegExp(DERIVED_VIA_RAW_RE.source, DERIVED_VIA_RAW_RE.flags + "g"))];
|
|
145
|
+
viaMatchCount = viaMatches.length;
|
|
146
|
+
duplicateViaKeys = viaMatches.length > 1;
|
|
147
|
+
if (viaMatches.length > 0) {
|
|
148
|
+
rawDerivedViaKeyPresent = true;
|
|
149
|
+
const lastVia = viaMatches[viaMatches.length - 1];
|
|
150
|
+
let val = lastVia[1].trim();
|
|
151
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
152
|
+
val = val.slice(1, -1);
|
|
153
|
+
}
|
|
154
|
+
rawDerivedVia = val;
|
|
155
|
+
}
|
|
156
|
+
const fromMatches = [...fmSlice.matchAll(new RegExp(DERIVED_FROM_RAW_RE.source, DERIVED_FROM_RAW_RE.flags + "g"))];
|
|
157
|
+
fromMatchCount = fromMatches.length;
|
|
158
|
+
duplicateFromKeys = fromMatches.length > 1;
|
|
159
|
+
if (fromMatches.length > 0) {
|
|
160
|
+
rawDerivedFromKeyPresent = true;
|
|
161
|
+
const lastFrom = fromMatches[fromMatches.length - 1];
|
|
162
|
+
rawDerivedFrom = lastFrom[1].trim();
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
const hasFrom = Array.isArray(derivedFrom) && derivedFrom.length > 0;
|
|
167
|
+
const hasVia = derivedVia !== void 0 && derivedVia !== null;
|
|
168
|
+
const hasRawVia = rawDerivedVia !== void 0 && rawDerivedVia.length > 0;
|
|
169
|
+
const hasRawMalformedFrom = rawDerivedFromKeyPresent && !hasFrom;
|
|
170
|
+
const hasBlankRawVia = rawDerivedViaKeyPresent && (rawDerivedVia === void 0 || rawDerivedVia.length === 0) && !hasVia;
|
|
171
|
+
if (!hasFrom && !hasVia && !hasRawVia && !hasRawMalformedFrom && !hasBlankRawVia) continue;
|
|
172
|
+
report.withProvenance += 1;
|
|
173
|
+
if (duplicateViaKeys) {
|
|
174
|
+
report.issues.push({
|
|
175
|
+
memoryPath: memory.path,
|
|
176
|
+
memoryId: fm.id,
|
|
177
|
+
kind: "derived_via_unknown_operator",
|
|
178
|
+
detail: `raw YAML contains ${viaMatchCount} "derived_via" keys; parseFrontmatter uses the last occurrence`
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (duplicateFromKeys) {
|
|
182
|
+
report.issues.push({
|
|
183
|
+
memoryPath: memory.path,
|
|
184
|
+
memoryId: fm.id,
|
|
185
|
+
kind: "derived_from_malformed_entry",
|
|
186
|
+
detail: `raw YAML contains ${fromMatchCount} "derived_from" keys; parseFrontmatter uses the last occurrence`
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (hasRawMalformedFrom) {
|
|
190
|
+
const display = rawDerivedFrom ?? "(blank)";
|
|
191
|
+
report.issues.push({
|
|
192
|
+
memoryPath: memory.path,
|
|
193
|
+
memoryId: fm.id,
|
|
194
|
+
kind: "derived_from_malformed_entry",
|
|
195
|
+
detail: `raw YAML "derived_from: ${display}" could not be parsed as a list`
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (hasFrom && rawDerivedFromKeyPresent) {
|
|
199
|
+
let rawList = null;
|
|
200
|
+
if (rawDerivedFrom && rawDerivedFrom.length > 0) {
|
|
201
|
+
rawList = tokenizeRawFlowList(rawDerivedFrom);
|
|
202
|
+
}
|
|
203
|
+
if (rawList === null) {
|
|
204
|
+
rawList = tokenizeRawBlockList(fmSlice, "derived_from");
|
|
205
|
+
}
|
|
206
|
+
if (rawList !== null && rawList.length > derivedFrom.length) {
|
|
207
|
+
for (const tok of rawList) {
|
|
208
|
+
if (tok.length === 0) {
|
|
209
|
+
report.issues.push({
|
|
210
|
+
memoryPath: memory.path,
|
|
211
|
+
memoryId: fm.id,
|
|
212
|
+
kind: "derived_from_malformed_entry",
|
|
213
|
+
detail: `raw YAML derived_from contains an empty entry (mixed list)`
|
|
214
|
+
});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (!derivedFrom.includes(tok)) {
|
|
218
|
+
if (!/^(.+):(\d+)$/u.test(tok)) {
|
|
219
|
+
report.issues.push({
|
|
220
|
+
memoryPath: memory.path,
|
|
221
|
+
memoryId: fm.id,
|
|
222
|
+
kind: "derived_from_malformed_entry",
|
|
223
|
+
detail: `raw YAML derived_from contains a malformed entry: ${JSON.stringify(tok)}`
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (hasBlankRawVia) {
|
|
231
|
+
report.issues.push({
|
|
232
|
+
memoryPath: memory.path,
|
|
233
|
+
memoryId: fm.id,
|
|
234
|
+
kind: "derived_via_unknown_operator",
|
|
235
|
+
detail: "raw YAML has `derived_via:` key with empty value"
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (hasFrom) {
|
|
239
|
+
for (const entry of derivedFrom) {
|
|
240
|
+
const resolved = resolveSnapshotPath(memoryDir, sidecarDir, entry);
|
|
241
|
+
if (!resolved.ok) {
|
|
242
|
+
report.issues.push({
|
|
243
|
+
memoryPath: memory.path,
|
|
244
|
+
memoryId: fm.id,
|
|
245
|
+
kind: "derived_from_malformed_entry",
|
|
246
|
+
detail: `${JSON.stringify(entry)}: ${resolved.reason}`
|
|
247
|
+
});
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
let snapshotOk = false;
|
|
251
|
+
try {
|
|
252
|
+
const st = await stat(resolved.snapshotPath);
|
|
253
|
+
snapshotOk = st.isFile();
|
|
254
|
+
} catch {
|
|
255
|
+
snapshotOk = false;
|
|
256
|
+
}
|
|
257
|
+
if (!snapshotOk) {
|
|
258
|
+
report.issues.push({
|
|
259
|
+
memoryPath: memory.path,
|
|
260
|
+
memoryId: fm.id,
|
|
261
|
+
kind: "derived_from_missing_snapshot",
|
|
262
|
+
detail: `${entry} \u2192 ${resolved.snapshotPath} (not a regular file)`
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (hasRawVia && !isConsolidationOperator(rawDerivedVia)) {
|
|
268
|
+
report.issues.push({
|
|
269
|
+
memoryPath: memory.path,
|
|
270
|
+
memoryId: fm.id,
|
|
271
|
+
kind: "derived_via_unknown_operator",
|
|
272
|
+
detail: `unknown operator: ${JSON.stringify(rawDerivedVia)}`
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const seenPaths = new Set(memories.map((m) => m.path));
|
|
278
|
+
const scanRoots = ["facts", "corrections", "procedures", "reasoning-traces"];
|
|
279
|
+
for (const rootName of scanRoots) {
|
|
280
|
+
const rootPath = path.join(memoryDir, rootName);
|
|
281
|
+
for await (const file of walkMarkdownFiles(rootPath)) {
|
|
282
|
+
if (seenPaths.has(file)) continue;
|
|
283
|
+
try {
|
|
284
|
+
const raw = await readFile(file, "utf-8");
|
|
285
|
+
if (DERIVED_FROM_RAW_RE.test(raw) || DERIVED_VIA_RAW_RE.test(raw)) {
|
|
286
|
+
report.withProvenance += 1;
|
|
287
|
+
report.issues.push({
|
|
288
|
+
memoryPath: file,
|
|
289
|
+
memoryId: "(parse failed)",
|
|
290
|
+
kind: "derived_from_malformed_entry",
|
|
291
|
+
detail: "frontmatter could not be parsed by storage reader; provenance fields visible in raw YAML"
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
}
|
|
300
|
+
return report;
|
|
301
|
+
}
|
|
302
|
+
async function* walkMarkdownFiles(root) {
|
|
303
|
+
let entries;
|
|
304
|
+
try {
|
|
305
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
306
|
+
} catch {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
for (const entry of entries) {
|
|
310
|
+
const full = path.join(root, entry.name);
|
|
311
|
+
if (entry.isDirectory()) {
|
|
312
|
+
yield* walkMarkdownFiles(full);
|
|
313
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
314
|
+
yield full;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export {
|
|
320
|
+
runConsolidationProvenanceCheck
|
|
321
|
+
};
|
|
322
|
+
//# sourceMappingURL=chunk-ULYOGL6R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/consolidation-provenance-check.ts"],"sourcesContent":["/**\n * Consolidation provenance integrity check (issue #561 PR 4).\n *\n * Validates that every memory carrying consolidation provenance frontmatter\n * (`derived_from`, `derived_via`) resolves to real data:\n *\n * - Each `derived_from` entry `\"<path>:<version>\"` must name a\n * page-version snapshot that exists on disk (via the sidecar layout\n * documented in `page-versioning.ts`).\n * - Each `derived_via` must be one of the known\n * `ConsolidationOperator` values — malformed values are surfaced as\n * warnings rather than crashes so legacy or future operators survive a\n * rollback.\n *\n * Non-fatal: every failure renders a warning with the offending file path\n * and a human-readable reason. Integrity problems are informational for\n * now — we do not auto-heal or archive broken memories.\n */\n\nimport path from \"node:path\";\nimport { access, readdir, readFile, stat } from \"node:fs/promises\";\nimport { constants as fsConstants } from \"node:fs\";\nimport type { StorageManager } from \"./storage.js\";\nimport { isConsolidationOperator } from \"./consolidation-operator.js\";\n// Import the canonical `sidecarKey` from page-versioning (PR #634\n// review, cursor Medium) so a future key-format change stays in\n// lock-step with the doctor scan.\nimport { sidecarKey } from \"./page-versioning.js\";\n\n/**\n * Regex to spot a `derived_via: <value>` line in the raw YAML frontmatter\n * between the opening and first closing `---` delimiters. We use the raw\n * text rather than the parsed `frontmatter.derived_via` because the\n * read-path parser coerces unknown values back to `undefined` — that\n * would silently hide corrupted-or-future operators from the doctor scan\n * (PR #634 review feedback, codex P2).\n */\n// Allow empty capture groups so truncated/blank `derived_via:` and\n// `derived_from:` lines (key present, no value) are distinguishable\n// from \"key missing entirely\" (regex returns null). Optional\n// leading whitespace accepts indented keys which `parseFrontmatter`\n// also accepts (PR #634 round-6 review, codex P2).\nconst DERIVED_VIA_RAW_RE = /^[\\t ]*derived_via:[\\t ]*(.*)$/mu;\nconst DERIVED_FROM_RAW_RE = /^[\\t ]*derived_from:[\\t ]*(.*)$/mu;\n\n/**\n * Tokenize a YAML-block-style list under `key:` in the given\n * frontmatter slice. Looks for lines matching `^ - <value>` after a\n * `key:` line and before the next non-list line. Returns `null` when\n * the key is missing or the value is a scalar / flow list (no block\n * entries found).\n *\n * Only used for the mixed-list malformed-entry detection — it does\n * not try to decode YAML escape sequences since we only need the\n * entry count + raw token text to compare against the parsed array.\n */\nfunction tokenizeRawBlockList(fmSlice: string, key: string): string[] | null {\n const lines = fmSlice.split(\"\\n\");\n // Accept indented keys too — parseFrontmatter does (PR #634 round-7\n // review, codex P2 / cursor Low).\n const keyRe = new RegExp(`^[\\\\t ]*${key}:[\\\\t ]*(.*)$`, \"u\");\n let startIdx = -1;\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(keyRe);\n if (m) {\n if (m[1].trim().length === 0) {\n startIdx = i + 1;\n }\n break;\n }\n }\n if (startIdx < 0) return null;\n const items: string[] = [];\n for (let i = startIdx; i < lines.length; i++) {\n const line = lines[i];\n if (!/^\\s+-/.test(line)) break; // not a block-list entry\n const m = line.match(/^\\s+-\\s*(.*)$/u);\n if (!m) break;\n let tok = m[1].trim();\n if (\n (tok.startsWith('\"') && tok.endsWith('\"') && tok.length >= 2) ||\n (tok.startsWith(\"'\") && tok.endsWith(\"'\") && tok.length >= 2)\n ) {\n tok = tok.slice(1, -1);\n }\n items.push(tok);\n }\n return items.length > 0 ? items : null;\n}\n\n/**\n * Tokenize a YAML-flow-style list (`[\"a\", \"b\", ...]`) into a flat\n * string array. Returns `null` when the input isn't a flow list.\n * Best-effort — we don't implement a full YAML parser, just enough to\n * detect mixed valid/invalid entries for the doctor integrity check.\n */\nfunction tokenizeRawFlowList(raw: string): string[] | null {\n const trimmed = raw.trim();\n if (!trimmed.startsWith(\"[\") || !trimmed.endsWith(\"]\")) return null;\n const inner = trimmed.slice(1, -1);\n const parts: string[] = [];\n let current = \"\";\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (inDouble) {\n if (ch === \"\\\\\" && i + 1 < inner.length) {\n current += inner[++i];\n continue;\n }\n if (ch === '\"') {\n inDouble = false;\n continue;\n }\n current += ch;\n } else if (inSingle) {\n if (ch === \"'\" && inner[i + 1] === \"'\") {\n current += \"'\";\n i++;\n continue;\n }\n if (ch === \"'\") {\n inSingle = false;\n continue;\n }\n current += ch;\n } else if (ch === '\"') {\n inDouble = true;\n } else if (ch === \"'\") {\n inSingle = true;\n } else if (ch === \",\") {\n parts.push(current.trim());\n current = \"\";\n } else {\n current += ch;\n }\n }\n if (current.trim().length > 0 || parts.length > 0) {\n parts.push(current.trim());\n }\n return parts;\n}\n\n/**\n * One integrity warning attached to a specific memory.\n */\nexport interface ConsolidationProvenanceIssue {\n /** Absolute path to the memory markdown file. */\n memoryPath: string;\n /** Memory id from frontmatter. */\n memoryId: string;\n /** Type of integrity issue. */\n kind:\n | \"derived_from_missing_snapshot\"\n | \"derived_from_malformed_entry\"\n | \"derived_via_unknown_operator\";\n /** Human-readable detail — includes the offending value when relevant. */\n detail: string;\n}\n\n/**\n * Summary of a provenance-integrity scan. Used by the operator-doctor\n * report and surfaced in the CLI output.\n */\nexport interface ConsolidationProvenanceReport {\n /** Total memories inspected. */\n scanned: number;\n /** Memories that carry `derived_from` and/or `derived_via`. */\n withProvenance: number;\n /** One entry per problem detected (may be empty). */\n issues: ConsolidationProvenanceIssue[];\n}\n\nconst DERIVED_FROM_ENTRY_RE = /^(.+):(\\d+)$/;\n\n/**\n * Build the on-disk snapshot path for a `\"<relpath>:<version>\"` entry,\n * relative to the given memory directory. Mirrors the layout documented\n * in `page-versioning.ts`:\n *\n * memoryDir/<sidecarDir>/<sidecarKey>/<version><ext>\n */\nfunction resolveSnapshotPath(\n memoryDir: string,\n sidecarDir: string,\n entry: string,\n): { ok: true; snapshotPath: string } | { ok: false; reason: string } {\n const match = entry.match(DERIVED_FROM_ENTRY_RE);\n if (!match) {\n return { ok: false, reason: `malformed entry (expected \"<path>:<version>\")` };\n }\n const pagePath = match[1];\n const versionId = match[2];\n const ext = path.extname(pagePath) || \".md\";\n const key = sidecarKey(pagePath);\n const snapshotPath = path.join(memoryDir, sidecarDir, key, `${versionId}${ext}`);\n return { ok: true, snapshotPath };\n}\n\n/**\n * Scan every memory under `storage` and flag consolidation-provenance\n * problems. Does not throw on individual failures — collects them in the\n * returned report.\n */\nexport async function runConsolidationProvenanceCheck(options: {\n storage: StorageManager;\n memoryDir: string;\n /**\n * Page-versioning sidecar directory name. Defaults to `.versions` —\n * matches the baked-in default used by `setVersioningConfig` when\n * versioning is enabled via config.\n */\n sidecarDir?: string;\n}): Promise<ConsolidationProvenanceReport> {\n const { storage, memoryDir } = options;\n const sidecarDir = options.sidecarDir ?? \".versions\";\n\n const report: ConsolidationProvenanceReport = {\n scanned: 0,\n withProvenance: 0,\n issues: [],\n };\n\n let memories;\n try {\n memories = await storage.readAllMemories();\n } catch {\n // If we can't enumerate memories at all, surface a single synthetic\n // issue rather than throwing — the doctor wrapper treats an empty\n // issues list as \"ok\" and we don't want a filesystem hiccup to crash\n // the whole diagnostic.\n return {\n scanned: 0,\n withProvenance: 0,\n issues: [\n {\n memoryPath: memoryDir,\n memoryId: \"(unreadable)\",\n kind: \"derived_from_malformed_entry\",\n detail: \"Could not enumerate memory directory to scan provenance.\",\n },\n ],\n };\n }\n\n for (const memory of memories) {\n report.scanned += 1;\n const fm = memory.frontmatter;\n const derivedFrom = fm.derived_from;\n const derivedVia = fm.derived_via;\n\n // Raw frontmatter values from disk — the read-path parser coerces\n // malformed `derived_from` and unknown `derived_via` back to\n // `undefined`, which would silently hide on-disk corruption from\n // the doctor scan (PR #634 review feedback, codex P2). We\n // re-extract both via regex so integrity issues are reported even\n // when the parser normalized them away. `rawDerivedVia` /\n // `rawDerivedFrom` being `\"\"` (empty string) represents a\n // corrupted file with the key present but the value truncated —\n // that's distinct from \"key missing entirely\" (undefined).\n let rawDerivedVia: string | undefined;\n let rawDerivedFrom: string | undefined;\n let rawDerivedViaKeyPresent = false;\n let rawDerivedFromKeyPresent = false;\n let duplicateViaKeys = false;\n let duplicateFromKeys = false;\n let viaMatchCount = 0;\n let fromMatchCount = 0;\n let fmSlice = \"\";\n try {\n const raw = await readFile(memory.path, \"utf-8\");\n const frontmatterEnd = raw.indexOf(\"\\n---\", raw.indexOf(\"---\") + 3);\n fmSlice = frontmatterEnd > 0 ? raw.slice(0, frontmatterEnd) : raw;\n // Use matchAll to find ALL occurrences of `derived_via` / `derived_from`\n // in the raw YAML. `parseFrontmatter` keeps the LAST assignment when\n // duplicate keys appear, so the doctor must read the last occurrence\n // to match what the storage reader actually uses (PR #634 review,\n // codex P2 — duplicate `derived_via` keys caused false-clean or\n // false-unknown-operator warnings depending on order).\n const viaMatches = [...fmSlice.matchAll(new RegExp(DERIVED_VIA_RAW_RE.source, DERIVED_VIA_RAW_RE.flags + \"g\"))];\n viaMatchCount = viaMatches.length;\n duplicateViaKeys = viaMatches.length > 1;\n if (viaMatches.length > 0) {\n rawDerivedViaKeyPresent = true;\n // Use the last occurrence — `parseFrontmatter` keeps the last\n // assignment when duplicate keys appear, so the doctor must\n // match that behavior to produce accurate warnings (PR #634\n // review, codex P2).\n const lastVia = viaMatches[viaMatches.length - 1];\n let val = lastVia[1].trim();\n if (\n (val.startsWith('\"') && val.endsWith('\"')) ||\n (val.startsWith(\"'\") && val.endsWith(\"'\"))\n ) {\n val = val.slice(1, -1);\n }\n rawDerivedVia = val;\n }\n const fromMatches = [...fmSlice.matchAll(new RegExp(DERIVED_FROM_RAW_RE.source, DERIVED_FROM_RAW_RE.flags + \"g\"))];\n fromMatchCount = fromMatches.length;\n duplicateFromKeys = fromMatches.length > 1;\n if (fromMatches.length > 0) {\n rawDerivedFromKeyPresent = true;\n const lastFrom = fromMatches[fromMatches.length - 1];\n rawDerivedFrom = lastFrom[1].trim();\n }\n } catch {\n // Fall through to the parsed values.\n }\n\n const hasFrom = Array.isArray(derivedFrom) && derivedFrom.length > 0;\n const hasVia = derivedVia !== undefined && derivedVia !== null;\n const hasRawVia = rawDerivedVia !== undefined && rawDerivedVia.length > 0;\n // A raw `derived_from` that the parser dropped indicates on-disk\n // corruption we must surface. We detect this by: (a) the raw YAML\n // contains a `derived_from:` key, AND (b) the parsed frontmatter\n // has no valid array. A scalar like `derived_from: facts/a.md:7`\n // (list brackets omitted) or a blank `derived_from:` both hit this\n // branch.\n const hasRawMalformedFrom = rawDerivedFromKeyPresent && !hasFrom;\n // A blank `derived_via:` with no value is also corrupt — the\n // parser drops it to undefined, but the raw key is still present\n // on disk (PR #634 round-3 review, codex P2).\n const hasBlankRawVia =\n rawDerivedViaKeyPresent &&\n (rawDerivedVia === undefined || rawDerivedVia.length === 0) &&\n !hasVia;\n if (\n !hasFrom && !hasVia && !hasRawVia &&\n !hasRawMalformedFrom && !hasBlankRawVia\n ) continue;\n report.withProvenance += 1;\n\n // Duplicate-key detection (PR #634 review, codex P2): when the raw\n // YAML contains multiple `derived_via` or `derived_from` lines,\n // `parseFrontmatter` silently uses the last one. Flag this as a\n // malformed entry so operators can inspect and fix the file.\n if (duplicateViaKeys) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: `raw YAML contains ${viaMatchCount} \"derived_via\" keys; parseFrontmatter uses the last occurrence`,\n });\n }\n if (duplicateFromKeys) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML contains ${fromMatchCount} \"derived_from\" keys; parseFrontmatter uses the last occurrence`,\n });\n }\n\n if (hasRawMalformedFrom) {\n const display = rawDerivedFrom ?? \"(blank)\";\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML \"derived_from: ${display}\" could not be parsed as a list`,\n });\n }\n\n // Mixed-list detection (PR #634 round-4 + round-5 review, codex\n // P2): when the parser DID return a valid list but the raw YAML\n // includes additional tokens that got dropped, flag those as\n // malformed. Handles both flow-style (`[\"a\", \"\", \"b\"]`) and\n // block-style (`\\n - a\\n - \\n - b`) YAML lists.\n if (hasFrom && rawDerivedFromKeyPresent) {\n let rawList: string[] | null = null;\n if (rawDerivedFrom && rawDerivedFrom.length > 0) {\n rawList = tokenizeRawFlowList(rawDerivedFrom);\n }\n if (rawList === null) {\n // Fall back to block-list tokenization by re-reading the full\n // frontmatter (already loaded above as `raw`) and scanning\n // the lines following `derived_from:`.\n rawList = tokenizeRawBlockList(fmSlice, \"derived_from\");\n }\n if (rawList !== null && rawList.length > derivedFrom!.length) {\n for (const tok of rawList) {\n if (tok.length === 0) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML derived_from contains an empty entry (mixed list)`,\n });\n continue;\n }\n if (!derivedFrom!.includes(tok)) {\n if (!/^(.+):(\\d+)$/u.test(tok)) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML derived_from contains a malformed entry: ${JSON.stringify(tok)}`,\n });\n }\n }\n }\n }\n }\n if (hasBlankRawVia) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: \"raw YAML has `derived_via:` key with empty value\",\n });\n }\n\n if (hasFrom) {\n for (const entry of derivedFrom!) {\n const resolved = resolveSnapshotPath(memoryDir, sidecarDir, entry);\n if (!resolved.ok) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `${JSON.stringify(entry)}: ${resolved.reason}`,\n });\n continue;\n }\n // Require a regular file at the snapshot path (PR #634\n // round-8 review, codex P2) — a directory or device node at\n // that path means the sidecar was corrupted and the snapshot\n // is effectively missing.\n let snapshotOk = false;\n try {\n const st = await stat(resolved.snapshotPath);\n snapshotOk = st.isFile();\n } catch {\n snapshotOk = false;\n }\n if (!snapshotOk) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_missing_snapshot\",\n detail: `${entry} → ${resolved.snapshotPath} (not a regular file)`,\n });\n }\n }\n }\n\n // Check the RAW YAML value for unknown operators. The parsed value\n // (`fm.derived_via`) is always known-good because the read-path\n // normalizer dropped anything else to undefined.\n if (hasRawVia && !isConsolidationOperator(rawDerivedVia)) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: `unknown operator: ${JSON.stringify(rawDerivedVia)}`,\n });\n }\n }\n\n // Parse-failure detection (PR #634 round-4 review, codex P2):\n // `readAllMemories()` silently drops files whose frontmatter\n // doesn't parse. Walk the facts/ and corrections/ directories for\n // `.md` files that DO reference provenance frontmatter but didn't\n // come back from the reader — those are the corruption cases the\n // doctor is meant to surface.\n try {\n const seenPaths = new Set(memories.map((m) => m.path));\n const scanRoots = [\"facts\", \"corrections\", \"procedures\", \"reasoning-traces\"];\n for (const rootName of scanRoots) {\n const rootPath = path.join(memoryDir, rootName);\n for await (const file of walkMarkdownFiles(rootPath)) {\n if (seenPaths.has(file)) continue;\n try {\n const raw = await readFile(file, \"utf-8\");\n if (\n DERIVED_FROM_RAW_RE.test(raw) ||\n DERIVED_VIA_RAW_RE.test(raw)\n ) {\n report.withProvenance += 1;\n report.issues.push({\n memoryPath: file,\n memoryId: \"(parse failed)\",\n kind: \"derived_from_malformed_entry\",\n detail:\n \"frontmatter could not be parsed by storage reader; provenance fields visible in raw YAML\",\n });\n }\n } catch {\n // Unreadable file — skip.\n }\n }\n }\n } catch {\n // Best-effort; don't fail the whole scan on a filesystem hiccup.\n }\n\n return report;\n}\n\n/**\n * Recursively yield all `.md` file paths under `root`. Silent on\n * missing directories — the facts/corrections dirs may not exist in\n * fresh installs.\n */\nasync function* walkMarkdownFiles(root: string): AsyncGenerator<string> {\n let entries;\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = path.join(root, entry.name);\n if (entry.isDirectory()) {\n yield* walkMarkdownFiles(full);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n yield full;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EngramMcpServer
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import {
|
|
5
|
-
validateRequest
|
|
6
|
-
} from "./chunk-MTLYEMJB.js";
|
|
3
|
+
} from "./chunk-OC5OXUQ4.js";
|
|
7
4
|
import {
|
|
8
5
|
EngramAccessInputError
|
|
9
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-STGWEHYR.js";
|
|
10
7
|
import {
|
|
11
8
|
isTrustZoneName
|
|
12
9
|
} from "./chunk-EQINRHYR.js";
|
|
13
10
|
import {
|
|
14
11
|
log
|
|
15
12
|
} from "./chunk-2ODBA7MQ.js";
|
|
13
|
+
import {
|
|
14
|
+
validateRequest
|
|
15
|
+
} from "./chunk-WCLICCGB.js";
|
|
16
16
|
|
|
17
17
|
// src/access-http.ts
|
|
18
18
|
import { createServer } from "http";
|
|
@@ -402,17 +402,28 @@ var EngramAccessHttpServer = class {
|
|
|
402
402
|
}
|
|
403
403
|
if (req.method === "POST" && pathname === "/engram/v1/recall") {
|
|
404
404
|
const body = await this.readValidatedBody(req, "recall");
|
|
405
|
+
const codingContext = "codingContext" in body ? body.codingContext : void 0;
|
|
405
406
|
const response = await this.service.recall({
|
|
406
407
|
query: body.query ?? "",
|
|
407
408
|
sessionKey: body.sessionKey,
|
|
408
409
|
namespace: this.resolveNamespace(req, body.namespace),
|
|
409
410
|
topK: body.topK,
|
|
410
411
|
mode: body.mode,
|
|
411
|
-
includeDebug: body.includeDebug === true
|
|
412
|
+
includeDebug: body.includeDebug === true,
|
|
413
|
+
codingContext
|
|
412
414
|
});
|
|
413
415
|
this.respondJson(res, 200, response);
|
|
414
416
|
return;
|
|
415
417
|
}
|
|
418
|
+
if (req.method === "POST" && pathname === "/engram/v1/coding-context") {
|
|
419
|
+
const body = await this.readValidatedBody(req, "setCodingContext");
|
|
420
|
+
this.service.setCodingContext({
|
|
421
|
+
sessionKey: body.sessionKey,
|
|
422
|
+
codingContext: body.codingContext
|
|
423
|
+
});
|
|
424
|
+
this.respondJson(res, 200, { ok: true });
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
416
427
|
if (req.method === "POST" && pathname === "/engram/v1/recall/explain") {
|
|
417
428
|
const body = await this.readValidatedBody(req, "recallExplain");
|
|
418
429
|
const response = await this.service.recallExplain({
|
|
@@ -422,6 +433,77 @@ var EngramAccessHttpServer = class {
|
|
|
422
433
|
this.respondJson(res, 200, response);
|
|
423
434
|
return;
|
|
424
435
|
}
|
|
436
|
+
if (req.method === "GET" && pathname === "/engram/v1/recall/tier-explain") {
|
|
437
|
+
const sessionParam = parsed.searchParams.get("session");
|
|
438
|
+
const sessionKey = sessionParam && sessionParam.length > 0 ? sessionParam : void 0;
|
|
439
|
+
const namespaceParam = parsed.searchParams.get("namespace");
|
|
440
|
+
const namespace = this.resolveNamespace(
|
|
441
|
+
req,
|
|
442
|
+
namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0
|
|
443
|
+
);
|
|
444
|
+
const payload = await this.service.recallTierExplain(
|
|
445
|
+
sessionKey,
|
|
446
|
+
namespace,
|
|
447
|
+
this.resolveRequestPrincipal(req)
|
|
448
|
+
);
|
|
449
|
+
this.respondJson(res, 200, payload);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (req.method === "GET" && pathname === "/engram/v1/recall/xray") {
|
|
453
|
+
const queryParam = parsed.searchParams.get("q");
|
|
454
|
+
if (!queryParam || queryParam.trim().length === 0) {
|
|
455
|
+
this.respondJson(res, 400, {
|
|
456
|
+
error: "missing_query",
|
|
457
|
+
code: "missing_query",
|
|
458
|
+
message: "q search parameter is required and must be non-empty"
|
|
459
|
+
});
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const sessionParam = parsed.searchParams.get("session");
|
|
463
|
+
const sessionKey = sessionParam && sessionParam.length > 0 ? sessionParam : void 0;
|
|
464
|
+
const namespaceParam = parsed.searchParams.get("namespace");
|
|
465
|
+
const namespace = this.resolveNamespace(
|
|
466
|
+
req,
|
|
467
|
+
namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0
|
|
468
|
+
);
|
|
469
|
+
const budgetParam = parsed.searchParams.get("budget");
|
|
470
|
+
let budget;
|
|
471
|
+
if (budgetParam !== null && budgetParam !== "") {
|
|
472
|
+
const parsedBudget = Number(budgetParam);
|
|
473
|
+
if (!Number.isFinite(parsedBudget) || parsedBudget <= 0 || !Number.isInteger(parsedBudget)) {
|
|
474
|
+
this.respondJson(res, 400, {
|
|
475
|
+
error: "invalid_budget",
|
|
476
|
+
code: "invalid_budget",
|
|
477
|
+
message: "budget expects a positive integer"
|
|
478
|
+
});
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
budget = parsedBudget;
|
|
482
|
+
}
|
|
483
|
+
let payload;
|
|
484
|
+
try {
|
|
485
|
+
payload = await this.service.recallXray({
|
|
486
|
+
query: queryParam,
|
|
487
|
+
sessionKey,
|
|
488
|
+
namespace,
|
|
489
|
+
budget,
|
|
490
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req)
|
|
491
|
+
});
|
|
492
|
+
} catch (err) {
|
|
493
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
494
|
+
if (message.startsWith("recallXray:")) {
|
|
495
|
+
this.respondJson(res, 400, {
|
|
496
|
+
error: "invalid_request",
|
|
497
|
+
code: "invalid_request",
|
|
498
|
+
message
|
|
499
|
+
});
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
throw err;
|
|
503
|
+
}
|
|
504
|
+
this.respondJson(res, 200, payload);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
425
507
|
if (req.method === "POST" && pathname === "/engram/v1/observe") {
|
|
426
508
|
const body = await this.readValidatedBody(req, "observe");
|
|
427
509
|
this.ensureWriteRateLimitAvailable();
|
|
@@ -588,6 +670,23 @@ var EngramAccessHttpServer = class {
|
|
|
588
670
|
);
|
|
589
671
|
return;
|
|
590
672
|
}
|
|
673
|
+
if (req.method === "GET" && pathname === "/engram/v1/procedural/stats") {
|
|
674
|
+
const namespaceParam = parsed.searchParams.get("namespace");
|
|
675
|
+
this.respondJson(
|
|
676
|
+
res,
|
|
677
|
+
200,
|
|
678
|
+
await this.service.procedureStats(
|
|
679
|
+
{
|
|
680
|
+
namespace: this.resolveNamespace(
|
|
681
|
+
req,
|
|
682
|
+
namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0
|
|
683
|
+
)
|
|
684
|
+
},
|
|
685
|
+
this.resolveRequestPrincipal(req)
|
|
686
|
+
)
|
|
687
|
+
);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
591
690
|
if (req.method === "GET" && pathname === "/engram/v1/trust-zones/records") {
|
|
592
691
|
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "25", 10);
|
|
593
692
|
const offsetRaw = parseInt(parsed.searchParams.get("offset") ?? "0", 10);
|
|
@@ -767,12 +866,12 @@ var EngramAccessHttpServer = class {
|
|
|
767
866
|
}
|
|
768
867
|
if (req.method === "POST" && pathname === "/engram/v1/contradiction-scan") {
|
|
769
868
|
const body = await this.readJsonBody(req);
|
|
770
|
-
const { runContradictionScan } = await import("./contradiction-scan-
|
|
869
|
+
const { runContradictionScan } = await import("./contradiction-scan-E3GJTI4F.js");
|
|
771
870
|
const result = await runContradictionScan({
|
|
772
871
|
storage: this.service.storageRef,
|
|
773
872
|
config: this.service.configRef,
|
|
774
873
|
memoryDir: this.service.memoryDir,
|
|
775
|
-
|
|
874
|
+
embeddingLookupFactory: this.service.embeddingLookupFactoryRef,
|
|
776
875
|
localLlm: this.service.localLlmRef,
|
|
777
876
|
fallbackLlm: this.service.fallbackLlmRef,
|
|
778
877
|
namespace: typeof body.namespace === "string" ? body.namespace : void 0
|
|
@@ -944,4 +1043,4 @@ var EngramAccessHttpServer = class {
|
|
|
944
1043
|
export {
|
|
945
1044
|
EngramAccessHttpServer
|
|
946
1045
|
};
|
|
947
|
-
//# sourceMappingURL=chunk-
|
|
1046
|
+
//# sourceMappingURL=chunk-UWB5LMWY.js.map
|