@remnic/core 9.3.729 → 9.3.730
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-boundary.d.ts +6 -5
- package/dist/access-boundary.js +13 -13
- package/dist/access-cli.js +34 -33
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +6 -5
- package/dist/access-http.js +19 -19
- package/dist/access-mcp.d.ts +6 -5
- package/dist/access-mcp.js +18 -18
- package/dist/access-operations-batch.js +16 -16
- package/dist/access-operations.d.ts +11 -10
- package/dist/access-operations.js +17 -17
- package/dist/access-schema.d.ts +42 -42
- package/dist/{access-service-BXMJqkTA.d.ts → access-service-Byoe3HsT.d.ts} +18 -3
- package/dist/access-service.d.ts +6 -5
- package/dist/access-service.js +12 -12
- package/dist/access-surface-catalog.d.ts +6 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +2 -2
- package/dist/{auto-sync-F3KCSQFJ.js → auto-sync-G3V5AN4C.js} +2 -2
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +5 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +55 -2
- package/dist/capabilities.js +5 -1
- package/dist/{catalog-bMczlO2G.d.ts → catalog-DM1nk7cM.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-4NOI577P.js → chunk-24ZLC5QN.js} +2 -2
- package/dist/{chunk-DMQWV6SQ.js → chunk-5RZOFIDL.js} +2 -2
- package/dist/{chunk-UM5AWJNQ.js → chunk-646B5ERJ.js} +62 -37
- package/dist/chunk-646B5ERJ.js.map +1 -0
- package/dist/{chunk-H5BWGCRZ.js → chunk-6TUWSUUD.js} +51 -47
- package/dist/{chunk-H5BWGCRZ.js.map → chunk-6TUWSUUD.js.map} +1 -1
- package/dist/{chunk-36SEDFAU.js → chunk-C5WN74HH.js} +2 -2
- package/dist/{chunk-MLKWJOAJ.js → chunk-CGPHMNL4.js} +2 -2
- package/dist/{chunk-R6OVFAX6.js → chunk-DJECGBBM.js} +7 -2
- package/dist/{chunk-R6OVFAX6.js.map → chunk-DJECGBBM.js.map} +1 -1
- package/dist/{chunk-3RCBIOOJ.js → chunk-ESF7TRYJ.js} +18 -2
- package/dist/{chunk-3RCBIOOJ.js.map → chunk-ESF7TRYJ.js.map} +1 -1
- package/dist/{chunk-EY6MDMHK.js → chunk-F6OEXJQQ.js} +2 -2
- package/dist/{chunk-M4V2JVSZ.js → chunk-FFUCCKPN.js} +2 -2
- package/dist/{chunk-O6YH5F47.js → chunk-FYWUNVFZ.js} +8 -8
- package/dist/{chunk-37436GML.js → chunk-GEXI357K.js} +2 -2
- package/dist/{chunk-YGIQORJ3.js → chunk-GGL34GCW.js} +3 -3
- package/dist/{chunk-4H4F3BFL.js → chunk-GXTYEG7Q.js} +3 -3
- package/dist/{chunk-4H4F3BFL.js.map → chunk-GXTYEG7Q.js.map} +1 -1
- package/dist/{chunk-WAS3NV46.js → chunk-H3MB3F62.js} +2 -2
- package/dist/{chunk-UTHQPSRC.js → chunk-HHNSLPX7.js} +11 -8
- package/dist/chunk-HHNSLPX7.js.map +1 -0
- package/dist/{chunk-76NHR6ZL.js → chunk-HJWQJUKQ.js} +4 -4
- package/dist/{chunk-Y4YD5TYW.js → chunk-IIBFCZA7.js} +3 -3
- package/dist/{chunk-UVPL565C.js → chunk-IQOEW5YV.js} +2 -2
- package/dist/{chunk-V4GNSDFH.js → chunk-KQYGNGON.js} +2 -2
- package/dist/{chunk-CBWI22JN.js → chunk-KSJCXXOU.js} +2 -2
- package/dist/{chunk-M4AYVEWS.js → chunk-LUCARK4W.js} +12 -11
- package/dist/chunk-LUCARK4W.js.map +1 -0
- package/dist/{chunk-I5REUWLH.js → chunk-NIZQH6AU.js} +3 -3
- package/dist/{chunk-4ESAR77N.js → chunk-NOU77OHX.js} +2 -2
- package/dist/{chunk-BQCHEV7O.js → chunk-O4ZIBPIW.js} +3 -3
- package/dist/{chunk-HOHVIVDR.js → chunk-OBJUKYDO.js} +2 -2
- package/dist/{chunk-YKBHOBJK.js → chunk-OEHCMALU.js} +56 -4
- package/dist/chunk-OEHCMALU.js.map +1 -0
- package/dist/{chunk-YOLZS7NY.js → chunk-OTQE66IX.js} +2 -2
- package/dist/{chunk-HEDZUSCS.js → chunk-OUITHS6V.js} +246 -368
- package/dist/chunk-OUITHS6V.js.map +1 -0
- package/dist/{chunk-4T3BMHKD.js → chunk-OZECYVGI.js} +20 -7
- package/dist/chunk-OZECYVGI.js.map +1 -0
- package/dist/{chunk-C2PG7IEH.js → chunk-P3VJCTVE.js} +2 -2
- package/dist/{chunk-EH5ED3C6.js → chunk-PMAQRUN4.js} +4 -4
- package/dist/chunk-PMAQRUN4.js.map +1 -0
- package/dist/{chunk-7RLCCNHU.js → chunk-Q6BAA57B.js} +4 -3
- package/dist/chunk-Q6BAA57B.js.map +1 -0
- package/dist/{chunk-HGNOCPZX.js → chunk-QZHF4BWN.js} +6 -6
- package/dist/chunk-SYX73XD7.js +283 -0
- package/dist/chunk-SYX73XD7.js.map +1 -0
- package/dist/{chunk-ASKXIAHD.js → chunk-WDJCZQB4.js} +2 -2
- package/dist/{chunk-AQLOP5F4.js → chunk-WNGXQMRI.js} +95 -6
- package/dist/chunk-WNGXQMRI.js.map +1 -0
- package/dist/{chunk-JYLFZWUX.js → chunk-XYUFGHUW.js} +2 -2
- package/dist/{chunk-3TSTGI6F.js → chunk-ZQUAVZNG.js} +2 -2
- package/dist/{chunk-4NR2PDZ5.js → chunk-ZVHRSR2R.js} +14 -4
- package/dist/chunk-ZVHRSR2R.js.map +1 -0
- package/dist/{cli-DmAiDd8A.d.ts → cli-Dt-tuiYH.d.ts} +3 -3
- package/dist/cli.d.ts +7 -6
- package/dist/cli.js +35 -35
- package/dist/compounding/engine.d.ts +12 -1
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +2 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/contradiction/index.js +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/embedding-fallback.js +2 -2
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +6 -4
- package/dist/explicit-capture.js +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-22LPFHD5.js → first-start-migration-OOR57AYR.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +296 -11
- package/dist/index.js +359 -47
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- 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 +6 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +9 -9
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +5 -5
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +14 -14
- package/dist/orchestration/compression-guideline-coordinator.d.ts +105 -0
- package/dist/orchestration/compression-guideline-coordinator.js +12 -0
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +5 -5
- package/dist/{orchestrator-BR0sGsME.d.ts → orchestrator-B4KjaOtY.d.ts} +24 -8
- package/dist/orchestrator.d.ts +5 -4
- package/dist/orchestrator.js +28 -26
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/provenance.js +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/{resolution-MN36NW5P.js → resolution-33PIX3TT.js} +2 -2
- package/dist/resolution-33PIX3TT.js.map +1 -0
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +3 -3
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +84 -84
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +4 -4
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +8 -8
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-Cl15ksZy.d.ts → semantic-consolidation-CstTvHPo.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +3 -3
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +22 -3
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +46 -46
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-ChQb9D6s.d.ts → types-VkxElCHR.d.ts} +8 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/package.json +12 -2
- package/src/access-service-coding-write.test.ts +2 -2
- package/src/access-service.ts +33 -14
- package/src/bitemporal-dedup-backfill.test.ts +10 -10
- package/src/bitemporal-frontmatter.test.ts +3 -3
- package/src/capabilities.test.ts +185 -0
- package/src/capabilities.ts +110 -0
- package/src/cli/creation-ledger-commands.ts +22 -21
- package/src/coding/architecture-surfaces.test.ts +25 -1
- package/src/coding/architecture-surfaces.ts +47 -19
- package/src/coding/decision-surfaces.ts +12 -3
- package/src/coding/review-context-structural.test.ts +303 -0
- package/src/coding/review-context.ts +79 -0
- package/src/coding/structural-context.test.ts +259 -0
- package/src/coding/structural-context.ts +315 -0
- package/src/coding/structural-subprocess-provider.test.ts +430 -0
- package/src/coding/structural-subprocess-provider.ts +355 -0
- package/src/compounding/engine.test.ts +90 -0
- package/src/compounding/engine.ts +25 -1
- package/src/contradiction/contradiction.test.ts +1 -1
- package/src/contradiction/resolution.ts +9 -1
- package/src/correction/correction-access-wiring.ts +21 -3
- package/src/correction/correction-contract.ts +7 -0
- package/src/correction/correction-planner.test.ts +231 -0
- package/src/correction/correction-planner.ts +73 -0
- package/src/correction/correction-service.ts +29 -0
- package/src/correction/correction-storage-integration.test.ts +2 -2
- package/src/dedup/semantic.test.ts +1 -1
- package/src/explicit-capture.ts +7 -4
- package/src/extraction-eventtime-wiring.test.ts +3 -3
- package/src/extraction-redaction-rules.test.ts +172 -0
- package/src/index.ts +29 -0
- package/src/maintenance/forget.test.ts +1 -1
- package/src/maintenance/memory-governance.test.ts +1 -1
- package/src/maintenance/pattern-reinforcement.test.ts +2 -2
- package/src/operator-toolkit.ts +4 -3
- package/src/orchestration/compression-guideline-coordinator.ts +449 -0
- package/src/orchestrator.ts +345 -464
- package/src/procedural/procedure-miner.test.ts +2 -2
- package/src/provenance-extraction.test.ts +382 -4
- package/src/provenance.ts +158 -22
- package/src/recall-handles-wiring.test.ts +1 -1
- package/src/search/factory.ts +4 -3
- package/src/semantic-rule-promotion.ts +1 -1
- package/src/source-attribution-roundtrip.test.ts +2 -2
- package/src/storage-contract/atomicity.test.ts +1 -1
- package/src/storage-contract/cache-coherence.test.ts +2 -2
- package/src/storage-contract/content-hash.test.ts +1 -1
- package/src/storage-contract/namespace-routing.test.ts +1 -1
- package/src/storage-contract/round-trip.test.ts +3 -3
- package/src/storage.ts +23 -2
- package/src/temporal-supersession.test.ts +12 -11
- package/src/temporal-validity.test.ts +11 -11
- package/src/wearables/cli.test.ts +30 -0
- package/src/wearables/cli.ts +3 -0
- package/src/wearables/memory-gen.test.ts +57 -1
- package/src/wearables/memory-gen.ts +31 -7
- package/src/wearables/pipeline.test.ts +40 -1
- package/src/wearables/pipeline.ts +4 -0
- package/src/wearables/service.test.ts +2 -2
- package/src/wearables/storage-io.test.ts +3 -3
- package/src/wearables/types.ts +7 -0
- package/dist/chunk-4NR2PDZ5.js.map +0 -1
- package/dist/chunk-4T3BMHKD.js.map +0 -1
- package/dist/chunk-7RLCCNHU.js.map +0 -1
- package/dist/chunk-AQLOP5F4.js.map +0 -1
- package/dist/chunk-EH5ED3C6.js.map +0 -1
- package/dist/chunk-HEDZUSCS.js.map +0 -1
- package/dist/chunk-M4AYVEWS.js.map +0 -1
- package/dist/chunk-UM5AWJNQ.js.map +0 -1
- package/dist/chunk-UTHQPSRC.js.map +0 -1
- package/dist/chunk-YKBHOBJK.js.map +0 -1
- /package/dist/{auto-sync-F3KCSQFJ.js.map → auto-sync-G3V5AN4C.js.map} +0 -0
- /package/dist/{chunk-4NOI577P.js.map → chunk-24ZLC5QN.js.map} +0 -0
- /package/dist/{chunk-DMQWV6SQ.js.map → chunk-5RZOFIDL.js.map} +0 -0
- /package/dist/{chunk-36SEDFAU.js.map → chunk-C5WN74HH.js.map} +0 -0
- /package/dist/{chunk-MLKWJOAJ.js.map → chunk-CGPHMNL4.js.map} +0 -0
- /package/dist/{chunk-EY6MDMHK.js.map → chunk-F6OEXJQQ.js.map} +0 -0
- /package/dist/{chunk-M4V2JVSZ.js.map → chunk-FFUCCKPN.js.map} +0 -0
- /package/dist/{chunk-O6YH5F47.js.map → chunk-FYWUNVFZ.js.map} +0 -0
- /package/dist/{chunk-37436GML.js.map → chunk-GEXI357K.js.map} +0 -0
- /package/dist/{chunk-YGIQORJ3.js.map → chunk-GGL34GCW.js.map} +0 -0
- /package/dist/{chunk-WAS3NV46.js.map → chunk-H3MB3F62.js.map} +0 -0
- /package/dist/{chunk-76NHR6ZL.js.map → chunk-HJWQJUKQ.js.map} +0 -0
- /package/dist/{chunk-Y4YD5TYW.js.map → chunk-IIBFCZA7.js.map} +0 -0
- /package/dist/{chunk-UVPL565C.js.map → chunk-IQOEW5YV.js.map} +0 -0
- /package/dist/{chunk-V4GNSDFH.js.map → chunk-KQYGNGON.js.map} +0 -0
- /package/dist/{chunk-CBWI22JN.js.map → chunk-KSJCXXOU.js.map} +0 -0
- /package/dist/{chunk-I5REUWLH.js.map → chunk-NIZQH6AU.js.map} +0 -0
- /package/dist/{chunk-4ESAR77N.js.map → chunk-NOU77OHX.js.map} +0 -0
- /package/dist/{chunk-BQCHEV7O.js.map → chunk-O4ZIBPIW.js.map} +0 -0
- /package/dist/{chunk-HOHVIVDR.js.map → chunk-OBJUKYDO.js.map} +0 -0
- /package/dist/{chunk-YOLZS7NY.js.map → chunk-OTQE66IX.js.map} +0 -0
- /package/dist/{chunk-C2PG7IEH.js.map → chunk-P3VJCTVE.js.map} +0 -0
- /package/dist/{chunk-HGNOCPZX.js.map → chunk-QZHF4BWN.js.map} +0 -0
- /package/dist/{chunk-ASKXIAHD.js.map → chunk-WDJCZQB4.js.map} +0 -0
- /package/dist/{chunk-JYLFZWUX.js.map → chunk-XYUFGHUW.js.map} +0 -0
- /package/dist/{chunk-3TSTGI6F.js.map → chunk-ZQUAVZNG.js.map} +0 -0
- /package/dist/{first-start-migration-22LPFHD5.js.map → first-start-migration-OOR57AYR.js.map} +0 -0
- /package/dist/{resolution-MN36NW5P.js.map → orchestration/compression-guideline-coordinator.js.map} +0 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in subprocess structural-context provider + doctor probe
|
|
3
|
+
* (issue #1548 Track A PR 5).
|
|
4
|
+
*
|
|
5
|
+
* Shells out to a configured external binary via `execFile` with an argv
|
|
6
|
+
* ARRAY (never a shell string — rule 10). codebase-memory-mcp's CLI mode is
|
|
7
|
+
* the canonical target, but the provider is command-agnostic: any binary
|
|
8
|
+
* that accepts `<symbols-subcommand> <json-arg>` and emits a JSON object
|
|
9
|
+
* `{ symbols: [{ symbol, path?, kind? }] }` satisfies it.
|
|
10
|
+
*
|
|
11
|
+
* Probe is cached once per instance (rule 11). Every failure path is a
|
|
12
|
+
* tagged outcome — `provider_unavailable` / `provider_timeout` /
|
|
13
|
+
* `provider_malformed` / `provider_error` — never a throw (rule 34).
|
|
14
|
+
*
|
|
15
|
+
* The async {@link probeStructuralProviderForDoctor} constructs a one-shot
|
|
16
|
+
* provider from config so `remnic doctor` can render
|
|
17
|
+
* "configured / probed / last error code" without a registered instance.
|
|
18
|
+
*/
|
|
19
|
+
import { execFile } from "node:child_process";
|
|
20
|
+
import { promisify } from "node:util";
|
|
21
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
22
|
+
|
|
23
|
+
import type { PluginConfig } from "../types.js";
|
|
24
|
+
import { expandTildePath } from "../utils/path.js";
|
|
25
|
+
import { isCodingGraphInstalled } from "./optional-coding-graph.js";
|
|
26
|
+
import {
|
|
27
|
+
describeStructuralProviderStatus,
|
|
28
|
+
type ArchitectureHintsResult,
|
|
29
|
+
type StructuralContextProvider,
|
|
30
|
+
type StructuralProviderStatus,
|
|
31
|
+
type StructuralContextErrorCode,
|
|
32
|
+
type StructuralSymbol,
|
|
33
|
+
type SymbolsForDiffResult,
|
|
34
|
+
} from "./structural-context.js";
|
|
35
|
+
|
|
36
|
+
const execFileAsync = promisify(execFile);
|
|
37
|
+
|
|
38
|
+
/** Injectable spawn shape so tests substitute a stub (rule 33). */
|
|
39
|
+
export type StructuralSpawnFn = (
|
|
40
|
+
command: string,
|
|
41
|
+
argv: readonly string[],
|
|
42
|
+
options: { timeout: number; signal?: AbortSignal },
|
|
43
|
+
) => Promise<{ stdout: string; stderr: string }>;
|
|
44
|
+
|
|
45
|
+
export interface SubprocessProviderOptions {
|
|
46
|
+
/** Absolute path to the binary (rule 24 — statSync at probe time). */
|
|
47
|
+
readonly command: string;
|
|
48
|
+
/**
|
|
49
|
+
* Extra argv appended after the symbols-for-diff subcommand
|
|
50
|
+
* (rule 10 — array, never a shell string).
|
|
51
|
+
*/
|
|
52
|
+
readonly args?: readonly string[];
|
|
53
|
+
/** Per-call deadline in ms. Default 5000. */
|
|
54
|
+
readonly timeoutMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Subcommand the provider invokes for symbol expansion.
|
|
57
|
+
* Default `"symbols-for-diff"`.
|
|
58
|
+
*/
|
|
59
|
+
readonly symbolsSubcommand?: string;
|
|
60
|
+
/** Test seam — defaults to promisified `execFile`. */
|
|
61
|
+
readonly spawn?: StructuralSpawnFn;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface ProbeState {
|
|
65
|
+
readonly available: boolean;
|
|
66
|
+
readonly detail?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
70
|
+
const DEFAULT_SYMBOLS_SUBCOMMAND = "symbols-for-diff";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Construct a structural-context provider backed by an external subprocess.
|
|
74
|
+
* The instance is self-contained: callers register it via
|
|
75
|
+
* `registerStructuralContextProvider(scope, provider)` and dispose via the
|
|
76
|
+
* returned unregister handle.
|
|
77
|
+
*/
|
|
78
|
+
export function createSubprocessStructuralProvider(
|
|
79
|
+
options: SubprocessProviderOptions,
|
|
80
|
+
): StructuralContextProvider {
|
|
81
|
+
// Rule 17 — expand a leading ~ so a home-relative binary path
|
|
82
|
+
// (e.g. ~/bin/cbm) resolves instead of degrading to unavailable.
|
|
83
|
+
// Mirrors the codegraph-runtime.ts precedent for operator paths.
|
|
84
|
+
const command = expandTildePath(options.command.trim());
|
|
85
|
+
const extraArgs = options.args ?? [];
|
|
86
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
87
|
+
const symbolsSubcommand = options.symbolsSubcommand ?? DEFAULT_SYMBOLS_SUBCOMMAND;
|
|
88
|
+
const spawn: StructuralSpawnFn = options.spawn ?? defaultSpawn;
|
|
89
|
+
|
|
90
|
+
let probeState: ProbeState | null = null;
|
|
91
|
+
const id = `subprocess(${command})`;
|
|
92
|
+
|
|
93
|
+
async function ensureProbe(): Promise<ProbeState> {
|
|
94
|
+
if (probeState !== null) return probeState;
|
|
95
|
+
if (!command) {
|
|
96
|
+
probeState = { available: false, detail: "structuralProviderCommand is empty" };
|
|
97
|
+
return probeState;
|
|
98
|
+
}
|
|
99
|
+
// Rule 24 — file existence check at probe time. An operator pointing at
|
|
100
|
+
// a missing/renamed binary gets a DISTINCT `provider_unavailable` code,
|
|
101
|
+
// never a silent empty result.
|
|
102
|
+
let statOk = false;
|
|
103
|
+
let notExecutable = false;
|
|
104
|
+
try {
|
|
105
|
+
statOk = statSync(command).isFile();
|
|
106
|
+
} catch {
|
|
107
|
+
statOk = false;
|
|
108
|
+
}
|
|
109
|
+
// P2 hardening: a non-executable regular file would report available
|
|
110
|
+
// here but fail later with EACCES/provider_error. On POSIX, enforce
|
|
111
|
+
// executable access so `remnic doctor` surfaces the real state. Windows
|
|
112
|
+
// has no reliable exec bit (Node X_OK is always true there), so the
|
|
113
|
+
// check is POSIX-only.
|
|
114
|
+
if (statOk && process.platform !== "win32") {
|
|
115
|
+
try {
|
|
116
|
+
accessSync(command, constants.X_OK);
|
|
117
|
+
} catch {
|
|
118
|
+
notExecutable = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
probeState = !statOk
|
|
122
|
+
? { available: false, detail: `binary not found: ${command}` }
|
|
123
|
+
: notExecutable
|
|
124
|
+
? { available: false, detail: `binary not executable: ${command}` }
|
|
125
|
+
: { available: true };
|
|
126
|
+
return probeState;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
id,
|
|
131
|
+
async probe() {
|
|
132
|
+
return ensureProbe();
|
|
133
|
+
},
|
|
134
|
+
async symbolsForDiff(
|
|
135
|
+
diff: string,
|
|
136
|
+
callOpts,
|
|
137
|
+
): Promise<SymbolsForDiffResult> {
|
|
138
|
+
const probe = await ensureProbe();
|
|
139
|
+
if (!probe.available) {
|
|
140
|
+
return { ok: false, code: "provider_unavailable", detail: probe.detail };
|
|
141
|
+
}
|
|
142
|
+
// Rule 10 — argv array; the diff is carried as a JSON argument so the
|
|
143
|
+
// binary never sees a shell and never re-interpolates paths.
|
|
144
|
+
const payload = JSON.stringify({ diff });
|
|
145
|
+
const argv = [symbolsSubcommand, payload, ...extraArgs];
|
|
146
|
+
try {
|
|
147
|
+
const { stdout } = await spawn(command, argv, {
|
|
148
|
+
timeout: timeoutMs,
|
|
149
|
+
signal: callOpts?.signal,
|
|
150
|
+
});
|
|
151
|
+
return parseSymbolJson(stdout);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return classifySpawnError(err);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
async architectureHints(
|
|
157
|
+
root: string,
|
|
158
|
+
callOpts,
|
|
159
|
+
): Promise<ArchitectureHintsResult> {
|
|
160
|
+
const probe = await ensureProbe();
|
|
161
|
+
if (!probe.available) {
|
|
162
|
+
return { ok: false, code: "provider_unavailable", detail: probe.detail };
|
|
163
|
+
}
|
|
164
|
+
const argv = ["architecture-hints", root, ...extraArgs];
|
|
165
|
+
try {
|
|
166
|
+
const { stdout } = await spawn(command, argv, {
|
|
167
|
+
timeout: timeoutMs,
|
|
168
|
+
signal: callOpts?.signal,
|
|
169
|
+
});
|
|
170
|
+
return parseHintsJson(stdout);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return classifySpawnError(err);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
close() {
|
|
176
|
+
probeState = null;
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
182
|
+
// JSON validation — rule 18 (object-not-null), rule 51 (shape), rule 34
|
|
183
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
function parseSymbolJson(stdout: string): SymbolsForDiffResult {
|
|
186
|
+
let value: unknown;
|
|
187
|
+
try {
|
|
188
|
+
value = JSON.parse(stdout);
|
|
189
|
+
} catch {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
code: "provider_malformed",
|
|
193
|
+
detail: "stdout was not valid JSON",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
code: "provider_malformed",
|
|
200
|
+
detail: "provider output was not a JSON object",
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const obj = value as Record<string, unknown>;
|
|
204
|
+
const symbolsRaw = obj.symbols;
|
|
205
|
+
if (!Array.isArray(symbolsRaw)) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
code: "provider_malformed",
|
|
209
|
+
detail: "provider output .symbols is not an array",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const symbols: StructuralSymbol[] = [];
|
|
213
|
+
for (const entry of symbolsRaw) {
|
|
214
|
+
const item = readStructuralSymbol(entry);
|
|
215
|
+
if (item !== null) symbols.push(item);
|
|
216
|
+
}
|
|
217
|
+
return { ok: true, symbols };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function parseHintsJson(stdout: string): ArchitectureHintsResult {
|
|
221
|
+
let value: unknown;
|
|
222
|
+
try {
|
|
223
|
+
value = JSON.parse(stdout);
|
|
224
|
+
} catch {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
code: "provider_malformed",
|
|
228
|
+
detail: "stdout was not valid JSON",
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (!Array.isArray(value)) {
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
code: "provider_malformed",
|
|
235
|
+
detail: "provider hints output was not a JSON array",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const hints = value.filter((h): h is string => typeof h === "string" && h.length > 0);
|
|
239
|
+
return { ok: true, hints };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function readStructuralSymbol(entry: unknown): StructuralSymbol | null {
|
|
243
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const rec = entry as Record<string, unknown>;
|
|
247
|
+
const symbol = typeof rec.symbol === "string" ? rec.symbol.trim() : "";
|
|
248
|
+
if (!symbol) return null;
|
|
249
|
+
const path = typeof rec.path === "string" && rec.path.length > 0 ? rec.path : undefined;
|
|
250
|
+
const kind = typeof rec.kind === "string" && rec.kind.length > 0 ? rec.kind : undefined;
|
|
251
|
+
const item: StructuralSymbol =
|
|
252
|
+
path !== undefined && kind !== undefined
|
|
253
|
+
? { symbol, path, kind }
|
|
254
|
+
: path !== undefined
|
|
255
|
+
? { symbol, path }
|
|
256
|
+
: kind !== undefined
|
|
257
|
+
? { symbol, kind }
|
|
258
|
+
: { symbol };
|
|
259
|
+
return item;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
type StructuralContextError = {
|
|
263
|
+
readonly ok: false;
|
|
264
|
+
readonly code: StructuralContextErrorCode;
|
|
265
|
+
readonly detail?: string;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
function classifySpawnError(err: unknown): StructuralContextError {
|
|
269
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
270
|
+
// Node sets the child's `.killed` flag and surfaces an ETIMEDOUT-style
|
|
271
|
+
// message when `timeout` elapses; the regex keeps this robust to Node
|
|
272
|
+
// version wording differences.
|
|
273
|
+
if (/timeout|timed out|ETIMEDOUT/i.test(message)) {
|
|
274
|
+
return { ok: false, code: "provider_timeout", detail: message };
|
|
275
|
+
}
|
|
276
|
+
return { ok: false, code: "provider_error", detail: message };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function defaultSpawn(
|
|
280
|
+
command: string,
|
|
281
|
+
argv: readonly string[],
|
|
282
|
+
options: { timeout: number; signal?: AbortSignal },
|
|
283
|
+
): Promise<{ stdout: string; stderr: string }> {
|
|
284
|
+
return execFileAsync(command, [...argv], {
|
|
285
|
+
timeout: options.timeout,
|
|
286
|
+
signal: options.signal,
|
|
287
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
292
|
+
// Doctor probe — construct a one-shot provider from config and probe it
|
|
293
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Augment the pure {@link describeStructuralProviderStatus} with a LIVE probe
|
|
297
|
+
* so `remnic doctor` can render "configured / probed / last error code".
|
|
298
|
+
*
|
|
299
|
+
* - `"none"` → inactive (no probe).
|
|
300
|
+
* - `"subprocess"` → builds a throwaway provider from
|
|
301
|
+
* `structuralProviderCommand`, probes it, disposes it.
|
|
302
|
+
* - `"native"` → asks `isCodingGraphInstalled()` whether the optional
|
|
303
|
+
* @remnic/coding-graph peer is present (the adapter that
|
|
304
|
+
* turns the engine into a provider lands in #1551).
|
|
305
|
+
*
|
|
306
|
+
* Never throws — a probe failure populates `probed.available = false`.
|
|
307
|
+
*/
|
|
308
|
+
export async function probeStructuralProviderForDoctor(
|
|
309
|
+
config: PluginConfig,
|
|
310
|
+
): Promise<StructuralProviderStatus> {
|
|
311
|
+
const status = describeStructuralProviderStatus(config);
|
|
312
|
+
if (!status.active) return status;
|
|
313
|
+
|
|
314
|
+
const ck = config.codingKnowledge;
|
|
315
|
+
if (ck.structuralProvider === "subprocess") {
|
|
316
|
+
if (!ck.structuralProviderCommand) {
|
|
317
|
+
return {
|
|
318
|
+
...status,
|
|
319
|
+
probed: { available: false, detail: "structuralProviderCommand is empty" },
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const provider = createSubprocessStructuralProvider({
|
|
323
|
+
command: ck.structuralProviderCommand,
|
|
324
|
+
});
|
|
325
|
+
try {
|
|
326
|
+
const probed = await safeProbe(provider);
|
|
327
|
+
return { ...status, probed };
|
|
328
|
+
} finally {
|
|
329
|
+
void provider.close?.();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (ck.structuralProvider === "native") {
|
|
334
|
+
const installed = await isCodingGraphInstalled().catch(() => false);
|
|
335
|
+
return {
|
|
336
|
+
...status,
|
|
337
|
+
probed: {
|
|
338
|
+
available: installed,
|
|
339
|
+
detail: installed ? undefined : "@remnic/coding-graph not installed",
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return status;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function safeProbe(
|
|
348
|
+
provider: StructuralContextProvider,
|
|
349
|
+
): Promise<{ available: boolean; detail?: string }> {
|
|
350
|
+
try {
|
|
351
|
+
return await provider.probe();
|
|
352
|
+
} catch (err) {
|
|
353
|
+
return { available: false, detail: err instanceof Error ? err.message : String(err) };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
|
|
7
|
+
import { StorageManager, type PluginConfig } from "../index.js";
|
|
8
|
+
import { CompoundingEngine, type CompoundingPromotionReport } from "./engine.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Issue #1645 — a tombstone-blocked promotion write lands pending_review (no
|
|
12
|
+
* active copy). promoteCandidate must divert blocked ids to a distinct
|
|
13
|
+
* `tombstoneBlocked` report field and NEVER report them as promoted — mirroring
|
|
14
|
+
* the postWriteGuard consumption pattern in the orchestrator extraction path.
|
|
15
|
+
*
|
|
16
|
+
* The tombstone chokepoint currently gates on category === "fact", so a
|
|
17
|
+
* rule/principle/preference promotion cannot be blocked by a real tombstone
|
|
18
|
+
* today. The defensive consumption is still correct (the gate may widen), so
|
|
19
|
+
* this test exercises the diversion logic with a storage double that returns
|
|
20
|
+
* tombstoneBlocked: true — pinning the CALLER contract, not the gate.
|
|
21
|
+
*/
|
|
22
|
+
test("#1645: tombstone-blocked promotion is diverted, not reported as promoted", async () => {
|
|
23
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-compounding-"));
|
|
24
|
+
try {
|
|
25
|
+
const weekId = "2026-W27";
|
|
26
|
+
const candidateContent = "Always write tests before shipping code.";
|
|
27
|
+
await mkdir(path.join(dir, "compounding", "weekly"), { recursive: true });
|
|
28
|
+
await writeFile(
|
|
29
|
+
path.join(dir, "compounding", "weekly", `${weekId}.json`),
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
version: 2,
|
|
32
|
+
generatedAt: "2026-07-07T00:00:00Z",
|
|
33
|
+
weekId,
|
|
34
|
+
promotionCandidates: [
|
|
35
|
+
{
|
|
36
|
+
id: "cand-1",
|
|
37
|
+
sourceType: "action-outcome",
|
|
38
|
+
subject: "testing",
|
|
39
|
+
category: "rule",
|
|
40
|
+
content: candidateContent,
|
|
41
|
+
score: 0.9,
|
|
42
|
+
rationale: "high signal",
|
|
43
|
+
outcome: null,
|
|
44
|
+
provenance: [],
|
|
45
|
+
agent: null,
|
|
46
|
+
workflow: null,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}),
|
|
50
|
+
"utf-8",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const config = {
|
|
54
|
+
memoryDir: dir,
|
|
55
|
+
workspaceDir: dir,
|
|
56
|
+
compoundingEnabled: true,
|
|
57
|
+
compoundingSemanticEnabled: true,
|
|
58
|
+
} as unknown as PluginConfig;
|
|
59
|
+
const engine = new CompoundingEngine(config, new StorageManager(dir));
|
|
60
|
+
|
|
61
|
+
// Storage double: writeMemory reports tombstoneBlocked (simulating the
|
|
62
|
+
// chokepoint downgrading the promotion to pending_review).
|
|
63
|
+
let writeCallCount = 0;
|
|
64
|
+
const blockedStorage = {
|
|
65
|
+
dir,
|
|
66
|
+
async readAllMemories() {
|
|
67
|
+
return [];
|
|
68
|
+
},
|
|
69
|
+
async writeMemory() {
|
|
70
|
+
writeCallCount += 1;
|
|
71
|
+
return { id: "blocked-promo-1", tombstoneBlocked: true, blockedBy: "tomb-1" };
|
|
72
|
+
},
|
|
73
|
+
} as unknown as StorageManager;
|
|
74
|
+
|
|
75
|
+
const report: CompoundingPromotionReport = await engine.promoteCandidate({
|
|
76
|
+
weekId,
|
|
77
|
+
candidateId: "cand-1",
|
|
78
|
+
storage: blockedStorage,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
assert.equal(writeCallCount, 1, "the promotion write still happens (pending_review row)");
|
|
82
|
+
assert.equal(report.promoted.length, 0, "blocked write must NOT appear in promoted");
|
|
83
|
+
assert.equal(report.tombstoneBlocked.length, 1, "blocked write diverted to tombstoneBlocked");
|
|
84
|
+
assert.equal(report.tombstoneBlocked[0]?.id, "blocked-promo-1");
|
|
85
|
+
assert.equal(report.tombstoneBlocked[0]?.candidateId, "cand-1");
|
|
86
|
+
assert.equal(report.tombstoneBlocked[0]?.content, candidateContent);
|
|
87
|
+
} finally {
|
|
88
|
+
await rm(dir, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
@@ -154,6 +154,17 @@ export interface CompoundingPromotionReport {
|
|
|
154
154
|
reason: "disabled" | "weekly-artifact-missing" | "candidate-not-found" | "duplicate-guidance";
|
|
155
155
|
existingMemoryId?: string;
|
|
156
156
|
}>;
|
|
157
|
+
/**
|
|
158
|
+
* Promotions the #1579 tombstone chokepoint downgraded to pending_review (no
|
|
159
|
+
* active copy). Reported distinctly so callers never treat a blocked write as
|
|
160
|
+
* a successfully promoted active memory (#1645).
|
|
161
|
+
*/
|
|
162
|
+
tombstoneBlocked: Array<{
|
|
163
|
+
id: string;
|
|
164
|
+
candidateId: string;
|
|
165
|
+
category: "principle" | "rule" | "preference";
|
|
166
|
+
content: string;
|
|
167
|
+
}>;
|
|
157
168
|
}
|
|
158
169
|
|
|
159
170
|
type WeeklyActionEvent = {
|
|
@@ -634,6 +645,7 @@ export class CompoundingEngine {
|
|
|
634
645
|
weekId: opts.weekId,
|
|
635
646
|
promoted: [],
|
|
636
647
|
skipped: [],
|
|
648
|
+
tombstoneBlocked: [],
|
|
637
649
|
};
|
|
638
650
|
if (!report.enabled) {
|
|
639
651
|
report.skipped.push({ weekId: opts.weekId, candidateId: opts.candidateId, reason: "disabled" });
|
|
@@ -695,13 +707,25 @@ export class CompoundingEngine {
|
|
|
695
707
|
return report;
|
|
696
708
|
}
|
|
697
709
|
|
|
698
|
-
const id = await storage.writeMemory(candidate.category, persistedContent, {
|
|
710
|
+
const { id, tombstoneBlocked } = await storage.writeMemory(candidate.category, persistedContent, {
|
|
699
711
|
source: "compounding-promotion",
|
|
700
712
|
tags: uniqueTags,
|
|
701
713
|
confidence,
|
|
702
714
|
lineage,
|
|
703
715
|
memoryKind: "note",
|
|
704
716
|
});
|
|
717
|
+
// #1645: a tombstone-blocked promotion lands pending_review (no active
|
|
718
|
+
// copy) — divert it to the tombstoneBlocked list so the report never
|
|
719
|
+
// claims a blocked write as a successfully promoted active memory.
|
|
720
|
+
if (tombstoneBlocked) {
|
|
721
|
+
report.tombstoneBlocked.push({
|
|
722
|
+
id,
|
|
723
|
+
candidateId: opts.candidateId,
|
|
724
|
+
category: candidate.category,
|
|
725
|
+
content: persistedContent,
|
|
726
|
+
});
|
|
727
|
+
return report;
|
|
728
|
+
}
|
|
705
729
|
report.promoted.push({
|
|
706
730
|
id,
|
|
707
731
|
candidateId: opts.candidateId,
|
|
@@ -131,7 +131,7 @@ function makeResolutionStorage(options: {
|
|
|
131
131
|
memory.frontmatter.derived_from = writeOptions.derivedFrom;
|
|
132
132
|
memory.frontmatter.derived_via = writeOptions.derivedVia as MemoryFrontmatter["derived_via"];
|
|
133
133
|
memories.set(id, memory);
|
|
134
|
-
return id;
|
|
134
|
+
return { id: id, tombstoneBlocked: false };
|
|
135
135
|
},
|
|
136
136
|
async supersedeMemory(oldId: string, newId: string, reason: string) {
|
|
137
137
|
supersedeCalls.push({ oldId, newId, reason });
|
|
@@ -386,7 +386,7 @@ async function prepareMergeReplacement(
|
|
|
386
386
|
const category = options.mergedCategory ?? mergedMemoryCategory(sourceA, sourceB);
|
|
387
387
|
let mergedId: string;
|
|
388
388
|
try {
|
|
389
|
-
|
|
389
|
+
const mergeResult = await storage.writeMemory(category, mergedContent, {
|
|
390
390
|
actor: "contradiction-resolution",
|
|
391
391
|
confidence: Math.min(sourceA.frontmatter.confidence ?? 0.8, sourceB.frontmatter.confidence ?? 0.8),
|
|
392
392
|
tags: ["contradiction-resolution", "merge"],
|
|
@@ -394,6 +394,14 @@ async function prepareMergeReplacement(
|
|
|
394
394
|
lineage: [idA, idB],
|
|
395
395
|
derivedVia: "merge",
|
|
396
396
|
});
|
|
397
|
+
mergedId = mergeResult.id;
|
|
398
|
+
if (mergeResult.tombstoneBlocked) {
|
|
399
|
+
// #1645: merged content matched a tombstone (pending_review). Don't
|
|
400
|
+
// supersede both sources to a non-active replacement — that retires
|
|
401
|
+
// the only active copies. Clean up the pending merge and abort the pair.
|
|
402
|
+
await cleanupMemoryId(storage, mergedId);
|
|
403
|
+
return { ok: false, message: `Merged memory for ${pairId} was tombstone-blocked (pending_review); not resolving — sources kept active` };
|
|
404
|
+
}
|
|
397
405
|
} catch (err) {
|
|
398
406
|
log.warn(
|
|
399
407
|
"[contradiction-resolution] merged memory creation failed for %s: %s",
|
|
@@ -401,7 +401,7 @@ async function writeReplacementMemory(
|
|
|
401
401
|
// writeMemory is the single storage chokepoint — catalog/dedup/reindex fire
|
|
402
402
|
// here (rule 43). Tombstone blocking also fires here (#1579), so a
|
|
403
403
|
// resurrected fact lands as pending_review rather than silently overwriting.
|
|
404
|
-
const id = await storage.writeMemory(
|
|
404
|
+
const { id, tombstoneBlocked } = await storage.writeMemory(
|
|
405
405
|
(draft.category ?? "fact") as Parameters<typeof storage.writeMemory>[0],
|
|
406
406
|
draft.content,
|
|
407
407
|
{
|
|
@@ -415,6 +415,16 @@ async function writeReplacementMemory(
|
|
|
415
415
|
...(draft.supersedes ? { supersedes: draft.supersedes } : {}),
|
|
416
416
|
},
|
|
417
417
|
);
|
|
418
|
+
if (tombstoneBlocked) {
|
|
419
|
+
// #1645 (review thread): the replacement content matched a tombstone, so it
|
|
420
|
+
// landed pending_review (non-active). Returning the id would let the
|
|
421
|
+
// executor record the supersede as applied and Phase 2 retire the loser
|
|
422
|
+
// with `supersededBy` pointing at a non-active replacement — stranding
|
|
423
|
+
// the fact behind a tombstone. Fail the correction cleanly instead.
|
|
424
|
+
throw new CorrectionContractError(
|
|
425
|
+
`correction replacement was tombstone-blocked (pending_review ${id}) — cannot supersede with a non-active replacement`,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
418
428
|
return id;
|
|
419
429
|
}
|
|
420
430
|
|
|
@@ -505,7 +515,7 @@ async function rescopeMemoryFn(
|
|
|
505
515
|
const destContent = fm.structuredAttributes
|
|
506
516
|
? stripAttributesSuffix(memory.content)
|
|
507
517
|
: memory.content;
|
|
508
|
-
const destId = await destStorage.writeMemory(fm.category, destContent, {
|
|
518
|
+
const { id: destId, tombstoneBlocked: destBlocked } = await destStorage.writeMemory(fm.category, destContent, {
|
|
509
519
|
source: `correction:rescope:${namespace}`,
|
|
510
520
|
...(typeof fm.confidence === "number" ? { confidence: fm.confidence } : {}),
|
|
511
521
|
...(Array.isArray(fm.tags) ? { tags: fm.tags } : {}),
|
|
@@ -517,6 +527,14 @@ async function rescopeMemoryFn(
|
|
|
517
527
|
...(Array.isArray(fm.links) ? { links: fm.links } : {}),
|
|
518
528
|
...(fm.intentGoal ? { intentGoal: fm.intentGoal } : {}),
|
|
519
529
|
});
|
|
530
|
+
if (destBlocked) {
|
|
531
|
+
// #1645: destination tombstone-blocked the rescope (pending_review). Don't
|
|
532
|
+
// archive the source — that deletes the only active copy while the
|
|
533
|
+
// replacement sits behind review. Fail the rescope; source stays active.
|
|
534
|
+
throw new CorrectionContractError(
|
|
535
|
+
`rescope of ${memoryId} into "${toNamespace}" was tombstone-blocked (pending_review ${destId}) — keeping source active`,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
520
538
|
// Unlink the source by archiving (non-destructive — rule 25). If the archive
|
|
521
539
|
// fails AFTER the destination write succeeded, compensate by archiving the
|
|
522
540
|
// destination too so no duplicate ACTIVE fact remains, then re-throw so the
|
|
@@ -650,7 +668,7 @@ async function appendAuditRecordFn(
|
|
|
650
668
|
// Corrections are themselves memories, searchable and namespaced (issue
|
|
651
669
|
// #1580 design §4). Write a correction-category memory capturing the
|
|
652
670
|
// plan + outcome as the audit trail.
|
|
653
|
-
const id = await storage.writeMemory("correction", buildAuditBody(record), {
|
|
671
|
+
const { id: id } = await storage.writeMemory("correction", buildAuditBody(record), {
|
|
654
672
|
source: "correction-contract",
|
|
655
673
|
confidence: 1.0,
|
|
656
674
|
tags: ["correction-audit", `plan:${record.planId}`, `classification:${record.classification}`],
|
|
@@ -168,6 +168,13 @@ export interface CorrectionPlan {
|
|
|
168
168
|
* never be re-applied wholesale (it would duplicate succeeded actions).
|
|
169
169
|
*/
|
|
170
170
|
status?: "pending" | "applying" | "applied" | "discarded" | "partial";
|
|
171
|
+
/**
|
|
172
|
+
* ISO timestamp recorded when the plan transitioned to `applying` (#1713
|
|
173
|
+
* Item 2). Startup recovery uses this to detect plans stuck in `applying`
|
|
174
|
+
* past the stale-applying TTL (process died mid-apply) and discard+scrub
|
|
175
|
+
* them. Absent on plans that never entered `applying`.
|
|
176
|
+
*/
|
|
177
|
+
applyingAt?: string;
|
|
171
178
|
}
|
|
172
179
|
|
|
173
180
|
/**
|