@remnic/core 9.6.0 → 9.6.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-admin-ops-surface.d.ts +7 -8
- package/dist/access-admin-ops-surface.js +8 -8
- package/dist/access-boundary.d.ts +9 -8
- package/dist/access-boundary.js +9 -9
- package/dist/access-cli.js +40 -40
- package/dist/access-http.d.ts +47 -16
- package/dist/access-http.js +13 -13
- package/dist/access-identity-continuity-surface.d.ts +6 -7
- package/dist/access-identity-continuity-surface.js +8 -8
- package/dist/access-lcm-surface.d.ts +7 -8
- package/dist/access-lcm-surface.js +8 -8
- package/dist/access-mcp.d.ts +13 -8
- package/dist/access-mcp.js +12 -12
- package/dist/access-observe-write-surface.d.ts +7 -8
- package/dist/access-observe-write-surface.js +8 -8
- package/dist/access-operations-batch.js +10 -10
- package/dist/access-operations.d.ts +8 -9
- package/dist/access-operations.js +11 -11
- package/dist/access-recall-surface.d.ts +7 -8
- package/dist/access-recall-surface.js +8 -8
- package/dist/access-schema.d.ts +6 -6
- package/dist/{access-service-CXJaql3A.d.ts → access-service-B_sc1w_k.d.ts} +9 -9
- package/dist/access-service.d.ts +7 -8
- package/dist/access-service.js +8 -8
- package/dist/access-surface-catalog.d.ts +7 -8
- package/dist/action-confidence.d.ts +1 -2
- package/dist/active-memory-bridge.d.ts +1 -2
- package/dist/active-recall.d.ts +1 -2
- package/dist/behavior-learner.d.ts +1 -2
- package/dist/behavior-signals.d.ts +1 -2
- package/dist/bootstrap.d.ts +6 -7
- package/dist/briefing.d.ts +2 -3
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -2
- package/dist/buffer.d.ts +2 -3
- package/dist/buffer.js +1 -1
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/bulk-import/index.js +2 -2
- package/dist/calibration.d.ts +1 -2
- package/dist/capabilities.d.ts +1 -2
- package/dist/{catalog-C9cu67Ig.d.ts → catalog-9WanSGtt.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -2
- package/dist/causal-consolidation.d.ts +1 -2
- package/dist/causal-consolidation.js +4 -4
- package/dist/causal-trajectory-graph.d.ts +1 -2
- package/dist/{chunk-PNLFN2PZ.js → chunk-23G3T2Q5.js} +2 -2
- package/dist/{chunk-SIRPLF54.js → chunk-2K3OSLHE.js} +2 -2
- package/dist/{chunk-JS2HKKOK.js → chunk-3AC5UYKI.js} +1 -1
- package/dist/{chunk-JS2HKKOK.js.map → chunk-3AC5UYKI.js.map} +1 -1
- package/dist/{chunk-FKCRGLRD.js → chunk-4WRKKRTD.js} +266 -160
- package/dist/chunk-4WRKKRTD.js.map +1 -0
- package/dist/{chunk-LQHDIS7L.js → chunk-54BGIBCR.js} +2 -2
- package/dist/{chunk-PVUJ6WVV.js → chunk-5MH5NF3U.js} +5 -5
- package/dist/{chunk-PCLIWBGD.js → chunk-7ARV32PI.js} +8 -6
- package/dist/chunk-7ARV32PI.js.map +1 -0
- package/dist/{chunk-Y7UNNAZD.js → chunk-7ZYBDQ7V.js} +2 -2
- package/dist/{chunk-2PRQG7PV.js → chunk-AJVLDJU6.js} +1 -1
- package/dist/chunk-AJVLDJU6.js.map +1 -0
- package/dist/{chunk-LR5DZ56F.js → chunk-AXALUFAH.js} +2 -2
- package/dist/{chunk-AYZJID4S.js → chunk-B5GBWBCZ.js} +2 -2
- package/dist/{chunk-TLUDFPZL.js → chunk-CAZ3Z5M6.js} +2 -2
- package/dist/{chunk-RGK27ELN.js → chunk-DEKIXXIG.js} +2 -2
- package/dist/{chunk-2KK5WNQB.js → chunk-DQ42IQSK.js} +2 -2
- package/dist/{chunk-UFUKHGZD.js → chunk-F4I5NYBI.js} +89 -33
- package/dist/chunk-F4I5NYBI.js.map +1 -0
- package/dist/{chunk-HOJZMQ4J.js → chunk-FGDKJJZS.js} +2 -2
- package/dist/{chunk-ABP6QMVM.js → chunk-FQ7HAWWC.js} +2 -2
- package/dist/{chunk-CYWA2CXR.js → chunk-FVTLSFWU.js} +2 -2
- package/dist/{chunk-JJEJJ7RK.js → chunk-FXZTL7F7.js} +2 -2
- package/dist/{chunk-LADFWYHY.js → chunk-GOEV7CYF.js} +6 -6
- package/dist/chunk-GOEV7CYF.js.map +1 -0
- package/dist/{chunk-DANV2HTH.js → chunk-GQGDRLDM.js} +3 -3
- package/dist/{chunk-7F7Z6MOS.js → chunk-H4CKS5B2.js} +2 -2
- package/dist/{chunk-SVOHGUMI.js → chunk-H4FHR4UM.js} +174 -197
- package/dist/chunk-H4FHR4UM.js.map +1 -0
- package/dist/{chunk-CKITWVWJ.js → chunk-HSSERO3E.js} +2 -2
- package/dist/{chunk-23JFRB73.js → chunk-JODHPNLN.js} +2 -2
- package/dist/{chunk-DS7MM2ES.js → chunk-JZKR6ZAV.js} +2 -2
- package/dist/{chunk-BEM4D2QG.js → chunk-KJIJNLXT.js} +3 -3
- package/dist/{chunk-JSNVWNGX.js → chunk-KVSVVKHA.js} +2 -2
- package/dist/{chunk-4GNEDXDI.js → chunk-M67TLMP6.js} +2 -2
- package/dist/chunk-M67TLMP6.js.map +1 -0
- package/dist/{chunk-MC4FJXPA.js → chunk-MMACM5G2.js} +2 -2
- package/dist/{chunk-OUELRE5E.js → chunk-NOUJNHWG.js} +1 -1
- package/dist/chunk-NOUJNHWG.js.map +1 -0
- package/dist/{chunk-3XIY7MDQ.js → chunk-O6YI4CVI.js} +2 -2
- package/dist/{chunk-F53UF7CI.js → chunk-QLVLW7UD.js} +6 -2
- package/dist/{chunk-F53UF7CI.js.map → chunk-QLVLW7UD.js.map} +1 -1
- package/dist/{chunk-XCL57M6Q.js → chunk-RRKPLLCC.js} +21 -20
- package/dist/chunk-RRKPLLCC.js.map +1 -0
- package/dist/{chunk-72WG5QKN.js → chunk-SDAXPXER.js} +2 -2
- package/dist/{chunk-4LV23CHK.js → chunk-VHJARF5R.js} +2 -2
- package/dist/{chunk-H2DXDQCQ.js → chunk-VRJG3HIZ.js} +46 -42
- package/dist/chunk-VRJG3HIZ.js.map +1 -0
- package/dist/{chunk-6MMONPHM.js → chunk-W2AHSCKY.js} +2 -2
- package/dist/{chunk-P454KXD6.js → chunk-YAWJMR3Y.js} +21 -21
- package/dist/{chunk-K5LLP4CY.js → chunk-ZGPDSEKG.js} +20 -9
- package/dist/chunk-ZGPDSEKG.js.map +1 -0
- package/dist/{cli-CqdPA6z9.d.ts → cli-CnRsWREp.d.ts} +4 -5
- package/dist/cli.d.ts +9 -10
- package/dist/cli.js +32 -32
- package/dist/compounding/engine.d.ts +2 -3
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -2
- package/dist/compression-optimizer.d.ts +1 -2
- package/dist/config.d.ts +1 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -2
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -2
- package/dist/connectors/index.d.ts +1 -2
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +2 -3
- package/dist/consolidation-undo.d.ts +2 -3
- package/dist/contradiction/index.d.ts +3 -4
- package/dist/conversation-index/backend.d.ts +1 -2
- package/dist/conversation-index/chunker.d.ts +1 -2
- package/dist/conversation-index/faiss-adapter.d.ts +1 -2
- package/dist/conversation-index/indexer.d.ts +1 -2
- package/dist/conversation-index/search.d.ts +1 -2
- package/dist/day-summary.d.ts +1 -2
- package/dist/delinearize.d.ts +1 -2
- package/dist/direct-answer-wiring.d.ts +1 -2
- package/dist/direct-answer.d.ts +1 -2
- package/dist/embedding-fallback.d.ts +1 -2
- package/dist/enrichment/index.d.ts +1 -2
- package/dist/entity-retrieval.d.ts +2 -3
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -2
- package/dist/explicit-capture.d.ts +8 -7
- package/dist/explicit-capture.js +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -2
- package/dist/extraction-judge-telemetry.d.ts +1 -2
- package/dist/extraction-judge-training.d.ts +1 -2
- package/dist/extraction-judge.d.ts +1 -2
- package/dist/extraction.d.ts +1 -2
- package/dist/fallback-llm.d.ts +1 -2
- package/dist/graph-dashboard-diff.d.ts +1 -2
- package/dist/graph-dashboard-key.d.ts +1 -2
- package/dist/graph-dashboard-parser.d.ts +1 -2
- package/dist/graph-edge-reinforcement.d.ts +1 -2
- package/dist/graph-snapshot.d.ts +1 -2
- package/dist/graph.d.ts +1 -2
- package/dist/identity-continuity.d.ts +1 -2
- package/dist/importance.d.ts +1 -2
- package/dist/importers/index.d.ts +1 -1
- package/dist/importers/index.js +2 -2
- package/dist/index.d.ts +14 -15
- package/dist/index.js +40 -40
- package/dist/intent.d.ts +1 -2
- package/dist/lcm/engine.d.ts +1 -2
- package/dist/lcm/index.d.ts +1 -2
- package/dist/lcm/tools.d.ts +1 -2
- package/dist/lifecycle.d.ts +1 -2
- package/dist/live-connectors-runner.d.ts +1 -2
- package/dist/local-llm.d.ts +1 -2
- package/dist/local-model-endpoint.d.ts +1 -2
- package/dist/maintenance/memory-governance.d.ts +1 -2
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +7 -8
- package/dist/memory-action-policy.d.ts +1 -2
- package/dist/memory-cache.d.ts +1 -2
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -2
- package/dist/memory-projection-store.d.ts +1 -2
- package/dist/memory-provenance.d.ts +1 -2
- package/dist/memory-worth-outcomes.d.ts +2 -3
- package/dist/models-json.d.ts +1 -2
- package/dist/namespaces/migrate.d.ts +3 -4
- package/dist/namespaces/migrate.js +4 -4
- package/dist/namespaces/principal.d.ts +1 -2
- package/dist/namespaces/search.d.ts +1 -2
- package/dist/namespaces/storage.d.ts +3 -4
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -2
- package/dist/operator-toolkit.d.ts +2 -3
- package/dist/operator-toolkit.js +7 -7
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -3
- package/dist/orchestration/maintenance.d.ts +2 -3
- package/dist/orchestration/maintenance.js +5 -5
- package/dist/{orchestrator-CWFrT8AF.d.ts → orchestrator-NRLVXPpH.d.ts} +40 -15
- package/dist/orchestrator.d.ts +6 -7
- package/dist/orchestrator.js +40 -40
- package/dist/patterns-cli.d.ts +1 -2
- package/dist/{pipeline-DmqtvION.d.ts → pipeline-CiD86zIk.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -2
- package/dist/provenance.d.ts +1 -2
- package/dist/{qmd--fnPMK60.d.ts → qmd-Bef7DDuQ.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -3
- package/dist/qmd-recall-cache.d.ts +1 -2
- package/dist/qmd.d.ts +2 -3
- package/dist/recall-disclosure-escalation.d.ts +1 -2
- package/dist/recall-explain-renderer.d.ts +1 -2
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -2
- package/dist/recall-state.d.ts +1 -2
- package/dist/recall-tag-filter.d.ts +1 -2
- package/dist/recall-timings.d.ts +1 -2
- package/dist/recall-xray-cli.d.ts +1 -2
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -2
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -2
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -0
- package/dist/replay/normalizers/chatgpt.js +3 -3
- package/dist/replay/normalizers/claude.d.ts +1 -0
- package/dist/replay/normalizers/claude.js +3 -3
- package/dist/replay/normalizers/openclaw.d.ts +1 -0
- package/dist/replay/normalizers/openclaw.js +3 -3
- package/dist/replay/normalizers/shared.d.ts +1 -0
- package/dist/replay/normalizers/shared.js +2 -2
- package/dist/replay/runner.d.ts +1 -0
- package/dist/replay/runner.js +2 -2
- package/dist/replay/types.d.ts +2 -1
- package/dist/replay/types.js +1 -1
- package/dist/resolve-auth-token.d.ts +1 -2
- package/dist/retrieval-agents.d.ts +2 -3
- package/dist/retrieval-tiers.d.ts +1 -2
- package/dist/routing/engine.d.ts +1 -2
- package/dist/routing/store.d.ts +1 -2
- package/dist/schemas.d.ts +44 -44
- package/dist/search/embed-helper.d.ts +1 -2
- package/dist/search/factory.d.ts +1 -2
- package/dist/search/index.d.ts +1 -2
- package/dist/search/lancedb-backend.d.ts +1 -2
- package/dist/search/meilisearch-backend.d.ts +1 -2
- package/dist/search/noop-backend.d.ts +1 -2
- package/dist/search/orama-backend.d.ts +1 -2
- package/dist/search/port.d.ts +1 -2
- package/dist/search/remote-backend.d.ts +1 -2
- package/dist/{semantic-DJR8_DMQ.d.ts → semantic-TddhF0y7.d.ts} +17 -1
- package/dist/{semantic-consolidation-O5lMZ4LG.d.ts → semantic-consolidation-BTShF2Fo.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -3
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -2
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-bands.d.ts +1 -2
- package/dist/session-observer-state.d.ts +1 -2
- package/dist/shared-context/manager.d.ts +3 -4
- package/dist/signal.d.ts +1 -2
- package/dist/{storage-C1maCNbc.d.ts → storage-DxPDAiU9.d.ts} +5 -1
- package/dist/storage.d.ts +2 -3
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -2
- package/dist/summary-snapshot.d.ts +1 -2
- package/dist/temporal-supersession.d.ts +2 -3
- package/dist/temporal-validity.d.ts +1 -2
- package/dist/threading.d.ts +1 -2
- package/dist/tier-migration.d.ts +2 -3
- package/dist/tier-routing.d.ts +1 -2
- package/dist/topics.d.ts +1 -2
- package/dist/transcript.d.ts +1 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/trust-score-stage.d.ts +1 -2
- package/dist/trust-score.d.ts +1 -2
- package/dist/{types-CGgcNWCG.d.ts → types-DDUDgd7T.d.ts} +93 -4
- package/dist/types.d.ts +1 -2
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -2
- package/dist/verified-recall.js +3 -3
- package/package.json +2 -2
- package/src/access-admin-ops-surface.ts +1 -0
- package/src/access-boundary.ts +2 -0
- package/src/access-http.test.ts +129 -0
- package/src/access-http.ts +92 -23
- package/src/access-mcp.ts +11 -2
- package/src/access-observe-write-surface.ts +87 -113
- package/src/access-operations-batch.ts +2 -2
- package/src/access-operations.ts +17 -16
- package/src/access-service.ts +11 -3
- package/src/admin/admin-surfaces.ts +2 -0
- package/src/buffer.ts +6 -1
- package/src/bulk-import/types.ts +2 -1
- package/src/coding/architecture-surfaces.test.ts +79 -5
- package/src/coding/architecture-surfaces.ts +7 -1
- package/src/coding/decision-surfaces.test.ts +1 -0
- package/src/coding/decision-surfaces.ts +4 -0
- package/src/connector-provenance-lifecycle.test.ts +618 -0
- package/src/connector-provenance.test.ts +219 -0
- package/src/dedup/semantic.test.ts +105 -0
- package/src/dedup/semantic.ts +69 -1
- package/src/explicit-capture.ts +21 -4
- package/src/orchestration/extraction-persist.ts +154 -6
- package/src/orchestration/extraction-run.ts +138 -228
- package/src/orchestration/persistence-index.ts +40 -5
- package/src/orchestration/turn-ingestion.ts +16 -9
- package/src/orchestrator-connector-dedup.test.ts +1082 -0
- package/src/orchestrator.ts +7 -11
- package/src/replay/types.ts +2 -1
- package/src/storage.ts +426 -553
- package/src/types.ts +11 -2
- package/src/utils/yaml-scalar.ts +20 -0
- package/dist/chunk-2PRQG7PV.js.map +0 -1
- package/dist/chunk-4GNEDXDI.js.map +0 -1
- package/dist/chunk-FKCRGLRD.js.map +0 -1
- package/dist/chunk-H2DXDQCQ.js.map +0 -1
- package/dist/chunk-K5LLP4CY.js.map +0 -1
- package/dist/chunk-LADFWYHY.js.map +0 -1
- package/dist/chunk-OUELRE5E.js.map +0 -1
- package/dist/chunk-PCLIWBGD.js.map +0 -1
- package/dist/chunk-SVOHGUMI.js.map +0 -1
- package/dist/chunk-UFUKHGZD.js.map +0 -1
- package/dist/chunk-XCL57M6Q.js.map +0 -1
- package/dist/types-gb_SNtjR.d.ts +0 -85
- /package/dist/{chunk-PNLFN2PZ.js.map → chunk-23G3T2Q5.js.map} +0 -0
- /package/dist/{chunk-SIRPLF54.js.map → chunk-2K3OSLHE.js.map} +0 -0
- /package/dist/{chunk-LQHDIS7L.js.map → chunk-54BGIBCR.js.map} +0 -0
- /package/dist/{chunk-PVUJ6WVV.js.map → chunk-5MH5NF3U.js.map} +0 -0
- /package/dist/{chunk-Y7UNNAZD.js.map → chunk-7ZYBDQ7V.js.map} +0 -0
- /package/dist/{chunk-LR5DZ56F.js.map → chunk-AXALUFAH.js.map} +0 -0
- /package/dist/{chunk-AYZJID4S.js.map → chunk-B5GBWBCZ.js.map} +0 -0
- /package/dist/{chunk-TLUDFPZL.js.map → chunk-CAZ3Z5M6.js.map} +0 -0
- /package/dist/{chunk-RGK27ELN.js.map → chunk-DEKIXXIG.js.map} +0 -0
- /package/dist/{chunk-2KK5WNQB.js.map → chunk-DQ42IQSK.js.map} +0 -0
- /package/dist/{chunk-HOJZMQ4J.js.map → chunk-FGDKJJZS.js.map} +0 -0
- /package/dist/{chunk-ABP6QMVM.js.map → chunk-FQ7HAWWC.js.map} +0 -0
- /package/dist/{chunk-CYWA2CXR.js.map → chunk-FVTLSFWU.js.map} +0 -0
- /package/dist/{chunk-JJEJJ7RK.js.map → chunk-FXZTL7F7.js.map} +0 -0
- /package/dist/{chunk-DANV2HTH.js.map → chunk-GQGDRLDM.js.map} +0 -0
- /package/dist/{chunk-7F7Z6MOS.js.map → chunk-H4CKS5B2.js.map} +0 -0
- /package/dist/{chunk-CKITWVWJ.js.map → chunk-HSSERO3E.js.map} +0 -0
- /package/dist/{chunk-23JFRB73.js.map → chunk-JODHPNLN.js.map} +0 -0
- /package/dist/{chunk-DS7MM2ES.js.map → chunk-JZKR6ZAV.js.map} +0 -0
- /package/dist/{chunk-BEM4D2QG.js.map → chunk-KJIJNLXT.js.map} +0 -0
- /package/dist/{chunk-JSNVWNGX.js.map → chunk-KVSVVKHA.js.map} +0 -0
- /package/dist/{chunk-MC4FJXPA.js.map → chunk-MMACM5G2.js.map} +0 -0
- /package/dist/{chunk-3XIY7MDQ.js.map → chunk-O6YI4CVI.js.map} +0 -0
- /package/dist/{chunk-72WG5QKN.js.map → chunk-SDAXPXER.js.map} +0 -0
- /package/dist/{chunk-4LV23CHK.js.map → chunk-VHJARF5R.js.map} +0 -0
- /package/dist/{chunk-6MMONPHM.js.map → chunk-W2AHSCKY.js.map} +0 -0
- /package/dist/{chunk-P454KXD6.js.map → chunk-YAWJMR3Y.js.map} +0 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance lifecycle tests — scenario matrix from the provenance design doc.
|
|
3
|
+
*
|
|
4
|
+
* Covers: deriveSourceConnector mixed-batch attribution, buffer fingerprint
|
|
5
|
+
* dedupe, replay ingestion connector preservation, frontmatter YAML injection
|
|
6
|
+
* guard, and end-to-end extraction→persistExtraction wiring.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import test from "node:test";
|
|
14
|
+
|
|
15
|
+
import { SmartBuffer } from "./buffer.js";
|
|
16
|
+
import {
|
|
17
|
+
ExtractionRunCoordinator,
|
|
18
|
+
type ExtractionRunCoordinatorDeps,
|
|
19
|
+
deriveSourceConnector,
|
|
20
|
+
} from "./orchestration/extraction-run.js";
|
|
21
|
+
import { TurnIngestionCoordinator, type TurnIngestionDeps } from "./orchestration/turn-ingestion.js";
|
|
22
|
+
import { StorageManager } from "./storage.js";
|
|
23
|
+
import { hashAccessIdempotencyPayload } from "./access-idempotency.js";
|
|
24
|
+
import type { ExtractionEngine } from "./extraction.js";
|
|
25
|
+
import type { ThreadingManager } from "./threading.js";
|
|
26
|
+
import type { BufferTurn, ExtractionResult, PluginConfig } from "./types.js";
|
|
27
|
+
import { handleCodingDecision } from "./coding/decision-surfaces.js";
|
|
28
|
+
import type { DecisionSurfaceContext } from "./coding/decision-surfaces.js";
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Helpers
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
function makeMinimalConfig(): PluginConfig {
|
|
35
|
+
return {
|
|
36
|
+
memoryDir: "/tmp/prov-test",
|
|
37
|
+
triggerMode: "every_n",
|
|
38
|
+
bufferMaxTurns: 2,
|
|
39
|
+
bufferMaxMinutes: 5,
|
|
40
|
+
highSignalPatterns: [],
|
|
41
|
+
consolidateEveryN: 999,
|
|
42
|
+
maxMemoryTokens: 2000,
|
|
43
|
+
openaiApiKey: "",
|
|
44
|
+
qmdEnabled: false,
|
|
45
|
+
} as unknown as PluginConfig;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeTurn(overrides: Partial<BufferTurn> = {}): BufferTurn {
|
|
49
|
+
return {
|
|
50
|
+
role: "user",
|
|
51
|
+
content: "test content",
|
|
52
|
+
timestamp: "2026-07-12T00:00:00Z",
|
|
53
|
+
...overrides,
|
|
54
|
+
} as BufferTurn;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// deriveSourceConnector tests (scenarios 1-4)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
test("deriveSourceConnector: all turns same connector → returns that connector", () => {
|
|
62
|
+
const turns = [makeTurn({ sourceConnector: "chatgpt" }), makeTurn({ role: "assistant", sourceConnector: "chatgpt" })];
|
|
63
|
+
assert.equal(deriveSourceConnector(turns), "chatgpt");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("deriveSourceConnector: mixed tagged+untagged → undefined", () => {
|
|
67
|
+
const turns = [makeTurn({ sourceConnector: "chatgpt" }), makeTurn({ role: "assistant" })];
|
|
68
|
+
assert.equal(deriveSourceConnector(turns), undefined);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("deriveSourceConnector: conflicting connectors → undefined", () => {
|
|
72
|
+
const turns = [
|
|
73
|
+
makeTurn({ sourceConnector: "chatgpt" }),
|
|
74
|
+
makeTurn({ role: "assistant", sourceConnector: "codex-cli" }),
|
|
75
|
+
];
|
|
76
|
+
assert.equal(deriveSourceConnector(turns), undefined);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("deriveSourceConnector: all untagged → undefined", () => {
|
|
80
|
+
const turns = [makeTurn(), makeTurn({ role: "assistant" })];
|
|
81
|
+
assert.equal(deriveSourceConnector(turns), undefined);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// bufferTurnsEqual via clearAfterExtraction (scenario 11)
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
test("bufferTurnsEqual via clearAfterExtraction: different fingerprint → turn NOT cleared", async () => {
|
|
89
|
+
StorageManager.clearAllStaticCaches();
|
|
90
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-fp-"));
|
|
91
|
+
try {
|
|
92
|
+
const storage = new StorageManager(baseDir);
|
|
93
|
+
await storage.ensureDirectories();
|
|
94
|
+
const config = makeMinimalConfig();
|
|
95
|
+
const buffer = new SmartBuffer(config, storage);
|
|
96
|
+
await buffer.load();
|
|
97
|
+
|
|
98
|
+
// Buffer contains turnB (fingerprint fp-B). An extraction snapshot
|
|
99
|
+
// claims turnA (same content/timestamp, different fingerprint fp-A)
|
|
100
|
+
// was extracted. Without the turnFingerprint comparison in
|
|
101
|
+
// bufferTurnsEqual, turnA would match turnB and clear it — a live
|
|
102
|
+
// turn that was NOT part of the extraction snapshot.
|
|
103
|
+
const turnA = makeTurn({ content: "hello", turnFingerprint: "fp-A" });
|
|
104
|
+
const turnB = makeTurn({ content: "hello", turnFingerprint: "fp-B" });
|
|
105
|
+
await buffer.addTurn("default", turnB);
|
|
106
|
+
assert.equal(buffer.getTurns("default").length, 1);
|
|
107
|
+
|
|
108
|
+
// Pass turnA (not in buffer) as the extraction snapshot.
|
|
109
|
+
// clearAfterExtraction must NOT clear turnB because fingerprints differ.
|
|
110
|
+
await buffer.clearAfterExtraction("default", [turnA]);
|
|
111
|
+
const remaining = buffer.getTurns("default");
|
|
112
|
+
assert.equal(remaining.length, 1, "turnB must survive — its fingerprint differs from the extraction snapshot");
|
|
113
|
+
assert.equal(remaining[0].turnFingerprint, "fp-B");
|
|
114
|
+
} finally {
|
|
115
|
+
StorageManager.clearAllStaticCaches();
|
|
116
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// StorageManager frontmatter serialization (scenarios 12-13)
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
test("StorageManager.writeMemory: connector ID with newline → quoted, no frontmatter injection", async () => {
|
|
125
|
+
StorageManager.clearAllStaticCaches();
|
|
126
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-inject-"));
|
|
127
|
+
try {
|
|
128
|
+
const storage = new StorageManager(baseDir);
|
|
129
|
+
await storage.ensureDirectories();
|
|
130
|
+
|
|
131
|
+
const malicious = "evil\nstatus: archived";
|
|
132
|
+
const { id } = await storage.writeMemory("fact", "test", {
|
|
133
|
+
sourceConnector: malicious,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const memory = await storage.getMemoryById(id);
|
|
137
|
+
if (!memory) assert.fail("memory must exist");
|
|
138
|
+
const fm = memory.frontmatter;
|
|
139
|
+
assert.equal(fm.sourceConnector, malicious, "round-trip must preserve the exact connector value");
|
|
140
|
+
assert.notEqual(fm.status, "archived", "newline in connector must not inject a status: frontmatter key");
|
|
141
|
+
} finally {
|
|
142
|
+
StorageManager.clearAllStaticCaches();
|
|
143
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("StorageManager.writeMemory: valid connector IDs round-trip correctly", async () => {
|
|
148
|
+
StorageManager.clearAllStaticCaches();
|
|
149
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-valid-"));
|
|
150
|
+
try {
|
|
151
|
+
const storage = new StorageManager(baseDir);
|
|
152
|
+
await storage.ensureDirectories();
|
|
153
|
+
|
|
154
|
+
for (const connector of ["chatgpt", "codex-cli", "omp", "openclaw", "github-copilot"]) {
|
|
155
|
+
const { id } = await storage.writeMemory("fact", `test-${connector}`, {
|
|
156
|
+
sourceConnector: connector,
|
|
157
|
+
});
|
|
158
|
+
const memory = await storage.getMemoryById(id);
|
|
159
|
+
if (!memory) assert.fail(`memory for ${connector} must exist`);
|
|
160
|
+
const fm = memory.frontmatter;
|
|
161
|
+
assert.equal(fm.sourceConnector, connector, `connector '${connector}' must round-trip exactly`);
|
|
162
|
+
}
|
|
163
|
+
} finally {
|
|
164
|
+
StorageManager.clearAllStaticCaches();
|
|
165
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// ingestReplayBatch sourceConnector preservation (scenario 5)
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
test("ingestReplayBatch: sourceConnector preserved through replay ingestion", async () => {
|
|
174
|
+
StorageManager.clearAllStaticCaches();
|
|
175
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-replay-"));
|
|
176
|
+
try {
|
|
177
|
+
const storage = new StorageManager(baseDir);
|
|
178
|
+
await storage.ensureDirectories();
|
|
179
|
+
const config = makeMinimalConfig();
|
|
180
|
+
const buffer = new SmartBuffer(config, storage);
|
|
181
|
+
await buffer.load();
|
|
182
|
+
|
|
183
|
+
let capturedTurns: BufferTurn[] = [];
|
|
184
|
+
const deps: TurnIngestionDeps = {
|
|
185
|
+
buffer,
|
|
186
|
+
config,
|
|
187
|
+
getStorage: async () => storage,
|
|
188
|
+
bulkImportWriteNamespace: () => "default",
|
|
189
|
+
get extractionQueueCoordinator(): never {
|
|
190
|
+
throw new Error("unexpected extractionQueueCoordinator");
|
|
191
|
+
},
|
|
192
|
+
heartbeatObserverChains: new Map(),
|
|
193
|
+
lcmEngine: null,
|
|
194
|
+
passiveCorrectionDedup: new Set(),
|
|
195
|
+
passiveCorrectionService: () => {
|
|
196
|
+
throw new Error("unexpected passiveCorrectionService");
|
|
197
|
+
},
|
|
198
|
+
passiveCorrectionTelemetry: {
|
|
199
|
+
detected: 0,
|
|
200
|
+
queued: 0,
|
|
201
|
+
autoApplied: 0,
|
|
202
|
+
suppressedReasonCounts: {},
|
|
203
|
+
},
|
|
204
|
+
queueBufferedExtraction: async (
|
|
205
|
+
turnsToExtract: BufferTurn[],
|
|
206
|
+
_reason: "trigger_mode" | "heartbeat_observer",
|
|
207
|
+
options?: { onTaskSettled?: (error?: unknown) => void }
|
|
208
|
+
) => {
|
|
209
|
+
capturedTurns = turnsToExtract;
|
|
210
|
+
options?.onTaskSettled?.();
|
|
211
|
+
},
|
|
212
|
+
resolveMemoryIdOrHandle: () => {
|
|
213
|
+
throw new Error("unexpected resolveMemoryIdOrHandle");
|
|
214
|
+
},
|
|
215
|
+
runExtraction: async () => {
|
|
216
|
+
throw new Error("unexpected runExtraction");
|
|
217
|
+
},
|
|
218
|
+
get sessionObserver(): never {
|
|
219
|
+
throw new Error("unexpected sessionObserver");
|
|
220
|
+
},
|
|
221
|
+
shouldQueueExtraction: () => {
|
|
222
|
+
throw new Error("unexpected shouldQueueExtraction");
|
|
223
|
+
},
|
|
224
|
+
get transcript(): never {
|
|
225
|
+
throw new Error("unexpected transcript");
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
const coordinator = new TurnIngestionCoordinator(deps);
|
|
229
|
+
|
|
230
|
+
await coordinator.ingestReplayBatch([
|
|
231
|
+
{
|
|
232
|
+
source: "chatgpt",
|
|
233
|
+
sessionKey: "s1",
|
|
234
|
+
role: "user",
|
|
235
|
+
content: "hello from chatgpt",
|
|
236
|
+
timestamp: "2026-07-12T00:00:00Z",
|
|
237
|
+
sourceConnector: "chatgpt",
|
|
238
|
+
},
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
assert.ok(capturedTurns.length > 0, "ingestReplayBatch must pass turns to queueBufferedExtraction");
|
|
242
|
+
assert.equal(
|
|
243
|
+
capturedTurns[0].sourceConnector,
|
|
244
|
+
"chatgpt",
|
|
245
|
+
"ingestReplayBatch must preserve sourceConnector on rebuilt BufferTurn"
|
|
246
|
+
);
|
|
247
|
+
} finally {
|
|
248
|
+
StorageManager.clearAllStaticCaches();
|
|
249
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// runExtraction end-to-end: deriveSourceConnector → persistExtraction (scenario 3)
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
test("runExtraction: mixed tagged+untagged turns → persistExtraction receives sourceConnector=undefined", async () => {
|
|
258
|
+
StorageManager.clearAllStaticCaches();
|
|
259
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-e2e-"));
|
|
260
|
+
try {
|
|
261
|
+
const storage = new StorageManager(baseDir);
|
|
262
|
+
await storage.ensureDirectories();
|
|
263
|
+
const config = makeMinimalConfig();
|
|
264
|
+
const buffer = new SmartBuffer(config, storage);
|
|
265
|
+
await buffer.load();
|
|
266
|
+
|
|
267
|
+
function makeDeps(
|
|
268
|
+
spy: (sourceContext: { sourceConnector?: string } | undefined) => void
|
|
269
|
+
): ExtractionRunCoordinatorDeps {
|
|
270
|
+
return {
|
|
271
|
+
config,
|
|
272
|
+
getBuffer: () => buffer,
|
|
273
|
+
getExtraction: () => ({
|
|
274
|
+
extract: async (..._args: Parameters<ExtractionEngine["extract"]>): Promise<ExtractionResult> => ({
|
|
275
|
+
facts: [{ content: "test fact", category: "fact", confidence: 0.8, tags: [] }],
|
|
276
|
+
entities: [],
|
|
277
|
+
questions: [],
|
|
278
|
+
profileUpdates: [],
|
|
279
|
+
}),
|
|
280
|
+
}),
|
|
281
|
+
getStorageRouter: () => ({
|
|
282
|
+
storageFor: async () => storage,
|
|
283
|
+
}),
|
|
284
|
+
getThreading: () => ({
|
|
285
|
+
processTurn: async (..._args: Parameters<ThreadingManager["processTurn"]>) => "thread-1",
|
|
286
|
+
updateThreadTitle: async (..._args: Parameters<ThreadingManager["updateThreadTitle"]>) => {},
|
|
287
|
+
}),
|
|
288
|
+
persistExtraction: async (
|
|
289
|
+
_result: ExtractionResult,
|
|
290
|
+
_storage: StorageManager,
|
|
291
|
+
_threadId?: string | null,
|
|
292
|
+
sourceContext?: {
|
|
293
|
+
sessionKey?: string;
|
|
294
|
+
principal?: string;
|
|
295
|
+
validAt?: string;
|
|
296
|
+
sourceConnector?: string;
|
|
297
|
+
}
|
|
298
|
+
): Promise<string[]> => {
|
|
299
|
+
spy(sourceContext);
|
|
300
|
+
return ["fact-1"];
|
|
301
|
+
},
|
|
302
|
+
maybeCapturePassiveCorrections: async () => {},
|
|
303
|
+
resolveSelfNamespace: () => "default",
|
|
304
|
+
getCodingContextForSession: () => null,
|
|
305
|
+
applyCodingNamespaceOverlay: (_sk: string, ns: string) => ns,
|
|
306
|
+
boxBuilderFor: () => {
|
|
307
|
+
throw new Error("unexpected boxBuilderFor");
|
|
308
|
+
},
|
|
309
|
+
appendPersistedThreadEpisodes: async () => {},
|
|
310
|
+
maybeScheduleConsolidation: () => {},
|
|
311
|
+
requestQmdMaintenance: () => {},
|
|
312
|
+
runTierMigrationCycle: async () => {
|
|
313
|
+
throw new Error("unexpected runTierMigrationCycle");
|
|
314
|
+
},
|
|
315
|
+
getLastPersistExtractionDeferredCount: () => 0,
|
|
316
|
+
recordProcessedExtractionFingerprint: async () => {},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Mixed batch: one tagged + one untagged → sourceConnector must be undefined
|
|
321
|
+
let mixedConnector: string | undefined = "SENTINEL";
|
|
322
|
+
const coord1 = new ExtractionRunCoordinator(
|
|
323
|
+
makeDeps((ctx) => {
|
|
324
|
+
mixedConnector = ctx?.sourceConnector;
|
|
325
|
+
})
|
|
326
|
+
);
|
|
327
|
+
await coord1.runExtraction(
|
|
328
|
+
[
|
|
329
|
+
makeTurn({ content: "hello from chatgpt", sourceConnector: "chatgpt" }),
|
|
330
|
+
makeTurn({ role: "assistant", content: "response" }),
|
|
331
|
+
],
|
|
332
|
+
{ skipCharThreshold: true, skipUserTurnThreshold: true }
|
|
333
|
+
);
|
|
334
|
+
assert.equal(mixedConnector, undefined, "mixed batch must produce sourceConnector=undefined");
|
|
335
|
+
|
|
336
|
+
// All-same connector batch → sourceConnector must be "chatgpt"
|
|
337
|
+
let sameConnector: string | undefined = "SENTINEL";
|
|
338
|
+
const coord2 = new ExtractionRunCoordinator(
|
|
339
|
+
makeDeps((ctx) => {
|
|
340
|
+
sameConnector = ctx?.sourceConnector;
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
await coord2.runExtraction(
|
|
344
|
+
[
|
|
345
|
+
makeTurn({ content: "hello from chatgpt", sourceConnector: "chatgpt" }),
|
|
346
|
+
makeTurn({ role: "assistant", content: "response", sourceConnector: "chatgpt" }),
|
|
347
|
+
],
|
|
348
|
+
{ skipCharThreshold: true, skipUserTurnThreshold: true }
|
|
349
|
+
);
|
|
350
|
+
assert.equal(sameConnector, "chatgpt", "all-same batch must produce sourceConnector='chatgpt'");
|
|
351
|
+
} finally {
|
|
352
|
+
StorageManager.clearAllStaticCaches();
|
|
353
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("runExtraction: context-only turns without sourceConnector do not affect attribution", async () => {
|
|
358
|
+
// QMnY3: deriveSourceConnector was called on the full turns array including
|
|
359
|
+
// extractionContextOnly turns. Context-only turns without sourceConnector
|
|
360
|
+
// caused deriveSourceConnector to return undefined (mixed tagged+untagged)
|
|
361
|
+
// even when every contributing turn shared one connector.
|
|
362
|
+
StorageManager.clearAllStaticCaches();
|
|
363
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-ctx-"));
|
|
364
|
+
try {
|
|
365
|
+
const storage = new StorageManager(baseDir);
|
|
366
|
+
await storage.ensureDirectories();
|
|
367
|
+
const config = makeMinimalConfig();
|
|
368
|
+
const buffer = new SmartBuffer(config, storage);
|
|
369
|
+
await buffer.load();
|
|
370
|
+
|
|
371
|
+
let capturedConnector: string | undefined = "SENTINEL";
|
|
372
|
+
const coordinator = new ExtractionRunCoordinator({
|
|
373
|
+
config,
|
|
374
|
+
getBuffer: () => buffer,
|
|
375
|
+
getExtraction: () => ({
|
|
376
|
+
extract: async (..._args: Parameters<ExtractionEngine["extract"]>): Promise<ExtractionResult> => ({
|
|
377
|
+
facts: [{ content: "test fact", category: "fact", confidence: 0.8, tags: [] }],
|
|
378
|
+
entities: [],
|
|
379
|
+
questions: [],
|
|
380
|
+
profileUpdates: [],
|
|
381
|
+
}),
|
|
382
|
+
}),
|
|
383
|
+
getStorageRouter: () => ({
|
|
384
|
+
storageFor: async () => storage,
|
|
385
|
+
}),
|
|
386
|
+
getThreading: () => ({
|
|
387
|
+
processTurn: async (..._args: Parameters<ThreadingManager["processTurn"]>) => "thread-1",
|
|
388
|
+
updateThreadTitle: async (..._args: Parameters<ThreadingManager["updateThreadTitle"]>) => {},
|
|
389
|
+
}),
|
|
390
|
+
persistExtraction: async (
|
|
391
|
+
_result: ExtractionResult,
|
|
392
|
+
_storage: StorageManager,
|
|
393
|
+
_threadId?: string | null,
|
|
394
|
+
sourceContext?: {
|
|
395
|
+
sessionKey?: string;
|
|
396
|
+
principal?: string;
|
|
397
|
+
validAt?: string;
|
|
398
|
+
sourceConnector?: string;
|
|
399
|
+
}
|
|
400
|
+
): Promise<string[]> => {
|
|
401
|
+
capturedConnector = sourceContext?.sourceConnector;
|
|
402
|
+
return ["fact-1"];
|
|
403
|
+
},
|
|
404
|
+
maybeCapturePassiveCorrections: async () => {},
|
|
405
|
+
resolveSelfNamespace: () => "default",
|
|
406
|
+
getCodingContextForSession: () => null,
|
|
407
|
+
applyCodingNamespaceOverlay: (_sk: string, ns: string) => ns,
|
|
408
|
+
boxBuilderFor: () => {
|
|
409
|
+
throw new Error("unexpected boxBuilderFor");
|
|
410
|
+
},
|
|
411
|
+
appendPersistedThreadEpisodes: async () => {},
|
|
412
|
+
maybeScheduleConsolidation: () => {},
|
|
413
|
+
requestQmdMaintenance: () => {},
|
|
414
|
+
runTierMigrationCycle: async () => {
|
|
415
|
+
throw new Error("unexpected runTierMigrationCycle");
|
|
416
|
+
},
|
|
417
|
+
getLastPersistExtractionDeferredCount: () => 0,
|
|
418
|
+
recordProcessedExtractionFingerprint: async () => {},
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Two real turns with the same connector, plus one context-only turn
|
|
422
|
+
// without sourceConnector. Before the fix, deriveSourceConnector saw
|
|
423
|
+
// the context-only turn as "untagged" and returned undefined.
|
|
424
|
+
await coordinator.runExtraction(
|
|
425
|
+
[
|
|
426
|
+
makeTurn({ content: "hello from chatgpt", sourceConnector: "chatgpt" }),
|
|
427
|
+
makeTurn({ role: "assistant", content: "response", sourceConnector: "chatgpt" }),
|
|
428
|
+
makeTurn({
|
|
429
|
+
content: "context-only background",
|
|
430
|
+
extractionContextOnly: true,
|
|
431
|
+
}),
|
|
432
|
+
],
|
|
433
|
+
{ skipCharThreshold: true, skipUserTurnThreshold: true }
|
|
434
|
+
);
|
|
435
|
+
assert.equal(
|
|
436
|
+
capturedConnector,
|
|
437
|
+
"chatgpt",
|
|
438
|
+
"context-only turns must not cause sourceConnector to be undefined when all contributing turns share a connector"
|
|
439
|
+
);
|
|
440
|
+
} finally {
|
|
441
|
+
StorageManager.clearAllStaticCaches();
|
|
442
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("StorageManager.writeChunk: sourceConnector preserved on chunk memories", async () => {
|
|
447
|
+
// QMo7g: chunked writes must carry sourceConnector so independently
|
|
448
|
+
// surfaced chunks preserve provenance.
|
|
449
|
+
StorageManager.clearAllStaticCaches();
|
|
450
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-chunk-"));
|
|
451
|
+
try {
|
|
452
|
+
const storage = new StorageManager(baseDir);
|
|
453
|
+
await storage.ensureDirectories();
|
|
454
|
+
|
|
455
|
+
const parentId = "test-parent-1";
|
|
456
|
+
const chunkId = await storage.writeChunk(parentId, 0, 2, "fact", "First chunk of a large fact", {
|
|
457
|
+
confidence: 0.8,
|
|
458
|
+
tags: ["chunked"],
|
|
459
|
+
source: "chunking",
|
|
460
|
+
sourceConnector: "chatgpt",
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const chunk = await storage.getMemoryById(chunkId);
|
|
464
|
+
if (!chunk) assert.fail("chunk memory must exist");
|
|
465
|
+
assert.equal(
|
|
466
|
+
chunk.frontmatter.sourceConnector,
|
|
467
|
+
"chatgpt",
|
|
468
|
+
"chunk memory must preserve sourceConnector from write options"
|
|
469
|
+
);
|
|
470
|
+
} finally {
|
|
471
|
+
StorageManager.clearAllStaticCaches();
|
|
472
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test("StorageManager.writeMemory: sourceConnector preserved for inline capture path", async () => {
|
|
477
|
+
// QMo7i: OpenClaw inline captures call persistExplicitCapture /
|
|
478
|
+
// queueExplicitCaptureForReview, which both call writeMemory.
|
|
479
|
+
// This test verifies writeMemory accepts and preserves sourceConnector
|
|
480
|
+
// when called with the "openclaw" connector (as the inline capture path does).
|
|
481
|
+
StorageManager.clearAllStaticCaches();
|
|
482
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-inline-"));
|
|
483
|
+
try {
|
|
484
|
+
const storage = new StorageManager(baseDir);
|
|
485
|
+
await storage.ensureDirectories();
|
|
486
|
+
|
|
487
|
+
const { id } = await storage.writeMemory("fact", "inline captured note", {
|
|
488
|
+
sourceConnector: "openclaw",
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const memory = await storage.getMemoryById(id);
|
|
492
|
+
if (!memory) assert.fail("memory must exist");
|
|
493
|
+
assert.equal(
|
|
494
|
+
memory.frontmatter.sourceConnector,
|
|
495
|
+
"openclaw",
|
|
496
|
+
"inline-captured memory must preserve sourceConnector='openclaw'"
|
|
497
|
+
);
|
|
498
|
+
} finally {
|
|
499
|
+
StorageManager.clearAllStaticCaches();
|
|
500
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test("hashAccessIdempotencyPayload: different sourceConnector yields different fingerprint", () => {
|
|
505
|
+
// QMx3S: two writes with identical content but different sourceConnector
|
|
506
|
+
// must produce different idempotency fingerprints so they are not
|
|
507
|
+
// treated as duplicates.
|
|
508
|
+
const base = {
|
|
509
|
+
operation: "memory_store",
|
|
510
|
+
request: {
|
|
511
|
+
schemaVersion: 1,
|
|
512
|
+
content: "test fact",
|
|
513
|
+
category: "fact",
|
|
514
|
+
confidence: 0.8,
|
|
515
|
+
namespace: "default",
|
|
516
|
+
tags: [],
|
|
517
|
+
entityRef: undefined,
|
|
518
|
+
ttl: undefined,
|
|
519
|
+
sourceReason: undefined,
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
const withChatgpt = { ...base, request: { ...base.request, sourceConnector: "chatgpt" } };
|
|
523
|
+
const withCodex = { ...base, request: { ...base.request, sourceConnector: "codex-cli" } };
|
|
524
|
+
const noConnector = { ...base, request: { ...base.request, sourceConnector: undefined } };
|
|
525
|
+
|
|
526
|
+
const hashChatgpt = hashAccessIdempotencyPayload(withChatgpt);
|
|
527
|
+
const hashCodex = hashAccessIdempotencyPayload(withCodex);
|
|
528
|
+
const hashNone = hashAccessIdempotencyPayload(noConnector);
|
|
529
|
+
|
|
530
|
+
assert.notEqual(hashChatgpt, hashCodex, "different connectors must yield different fingerprints");
|
|
531
|
+
assert.notEqual(hashChatgpt, hashNone, "connector vs no-connector must yield different fingerprints");
|
|
532
|
+
assert.notEqual(hashCodex, hashNone, "connector vs no-connector must yield different fingerprints");
|
|
533
|
+
|
|
534
|
+
// Same connector + key order must be stable (stableStringify sorts keys)
|
|
535
|
+
const withChatgptReordered = {
|
|
536
|
+
request: { ...base.request, sourceConnector: "chatgpt" },
|
|
537
|
+
operation: "memory_store",
|
|
538
|
+
};
|
|
539
|
+
const hashReordered = hashAccessIdempotencyPayload(withChatgptReordered);
|
|
540
|
+
assert.equal(hashChatgpt, hashReordered, "key order must not affect fingerprint");
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test("handleCodingDecision: sourceConnector='chatgpt' reaches stored frontmatter", async () => {
|
|
544
|
+
StorageManager.clearAllStaticCaches();
|
|
545
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "prov-decision-"));
|
|
546
|
+
try {
|
|
547
|
+
const storage = new StorageManager(baseDir);
|
|
548
|
+
await storage.ensureDirectories();
|
|
549
|
+
|
|
550
|
+
const ctx: DecisionSurfaceContext = {
|
|
551
|
+
codingKnowledge: {
|
|
552
|
+
enabled: true,
|
|
553
|
+
decisionRecords: true,
|
|
554
|
+
architectureCard: false,
|
|
555
|
+
sessionDelta: false,
|
|
556
|
+
architectureCardLlmSummary: false,
|
|
557
|
+
structuralProvider: "none",
|
|
558
|
+
structuralProviderCommand: "",
|
|
559
|
+
codegraphTools: false,
|
|
560
|
+
codegraphDbDir: "",
|
|
561
|
+
},
|
|
562
|
+
getCodingContext: () => ({
|
|
563
|
+
projectId: "test",
|
|
564
|
+
branch: "main",
|
|
565
|
+
rootPath: baseDir,
|
|
566
|
+
defaultBranch: "main",
|
|
567
|
+
}),
|
|
568
|
+
resolveStorage: async () => Object.assign(storage, { namespace: "default" }),
|
|
569
|
+
throwInputError: (msg: string): never => {
|
|
570
|
+
throw new Error(msg);
|
|
571
|
+
},
|
|
572
|
+
sourceConnector: "chatgpt",
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const result = await handleCodingDecision(
|
|
576
|
+
{ subcommand: "record", title: "Test Decision", decision: "We adopt SQLite for local dev.", status: "accepted", sessionKey: "s1" },
|
|
577
|
+
ctx
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
assert.equal(result.subcommand, "record");
|
|
581
|
+
if (!("memoryId" in result)) assert.fail("expected record result with memoryId");
|
|
582
|
+
const memory = await storage.getMemoryById(result.memoryId);
|
|
583
|
+
if (!memory) assert.fail("decision memory must exist");
|
|
584
|
+
assert.equal(
|
|
585
|
+
memory.frontmatter.sourceConnector,
|
|
586
|
+
"chatgpt",
|
|
587
|
+
"decision record must persist sourceConnector in frontmatter"
|
|
588
|
+
);
|
|
589
|
+
} finally {
|
|
590
|
+
StorageManager.clearAllStaticCaches();
|
|
591
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test("codegraph_manage_adr operation: ctx.sourceConnector forwarded to codegraphTool", async () => {
|
|
596
|
+
// Tests the operation handler directly — no MCP server needed.
|
|
597
|
+
// Proves ctx.sourceConnector reaches service.codegraphTool as the 3rd arg.
|
|
598
|
+
const { codegraphManageAdrOperation } = await import("./access-operations.js");
|
|
599
|
+
let capturedConnector: string | undefined = "SENTINEL";
|
|
600
|
+
const result = await codegraphManageAdrOperation.spec.handler(
|
|
601
|
+
{ tool: "manage_adr", subcommand: "record", title: "Test", status: "accepted", sessionKey: "s1" },
|
|
602
|
+
{
|
|
603
|
+
service: {
|
|
604
|
+
codegraphTool: async (_req: unknown, _principal?: string, sourceConnector?: string) => {
|
|
605
|
+
capturedConnector = sourceConnector;
|
|
606
|
+
return { tool: "manage_adr", ok: true, result: { subcommand: "record" } };
|
|
607
|
+
},
|
|
608
|
+
} as never,
|
|
609
|
+
authenticatedPrincipal: "test-user",
|
|
610
|
+
sourceConnector: "chatgpt",
|
|
611
|
+
} as never
|
|
612
|
+
);
|
|
613
|
+
assert.equal(
|
|
614
|
+
capturedConnector,
|
|
615
|
+
"chatgpt",
|
|
616
|
+
"operation handler must forward ctx.sourceConnector='chatgpt' to codegraphTool"
|
|
617
|
+
);
|
|
618
|
+
});
|