@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
|
@@ -62,7 +62,7 @@ test("procedure mining deduplicates by full cluster hash instead of truncated cl
|
|
|
62
62
|
written.push({
|
|
63
63
|
structuredAttributes: options.structuredAttributes ?? {},
|
|
64
64
|
});
|
|
65
|
-
return `procedure-${written.length}
|
|
65
|
+
return { id: `procedure-${written.length}`, tombstoneBlocked: false };
|
|
66
66
|
},
|
|
67
67
|
} as unknown as StorageManager;
|
|
68
68
|
|
|
@@ -123,7 +123,7 @@ test("procedure mining serializes concurrent writes for the same cluster", async
|
|
|
123
123
|
written.push({
|
|
124
124
|
structuredAttributes: options.structuredAttributes ?? {},
|
|
125
125
|
});
|
|
126
|
-
return `procedure-${written.length}
|
|
126
|
+
return { id: `procedure-${written.length}`, tombstoneBlocked: false };
|
|
127
127
|
},
|
|
128
128
|
} as unknown as StorageManager;
|
|
129
129
|
|
|
@@ -224,7 +224,7 @@ test("end-to-end: fact with verified sources survives write → readAllMemories"
|
|
|
224
224
|
},
|
|
225
225
|
];
|
|
226
226
|
|
|
227
|
-
const memoryId = await storage.writeMemory("fact", "Production DB uses pgBouncer.", {
|
|
227
|
+
const { id: memoryId } = await storage.writeMemory("fact", "Production DB uses pgBouncer.", {
|
|
228
228
|
confidence: 0.9,
|
|
229
229
|
tags: ["infra"],
|
|
230
230
|
sources,
|
|
@@ -254,7 +254,7 @@ test("end-to-end: fact without provenance fields is legacy-compatible (undefined
|
|
|
254
254
|
const storage = new StorageManager(dir);
|
|
255
255
|
await storage.ensureDirectories();
|
|
256
256
|
|
|
257
|
-
const memoryId = await storage.writeMemory("fact", "A fact with no provenance.", {
|
|
257
|
+
const { id: memoryId } = await storage.writeMemory("fact", "A fact with no provenance.", {
|
|
258
258
|
confidence: 0.9,
|
|
259
259
|
tags: [],
|
|
260
260
|
});
|
|
@@ -278,7 +278,7 @@ test("end-to-end: verified tag without sources downgrades to none on read", asyn
|
|
|
278
278
|
// Write a fact with provenance="verified" but NO sources. The serialize
|
|
279
279
|
// invariant (PR1) must downgrade to "none" so downstream surfaces never
|
|
280
280
|
// see an ungrounded "verified" tag.
|
|
281
|
-
const memoryId = await storage.writeMemory("fact", "A tagged fact with no evidence.", {
|
|
281
|
+
const { id: memoryId } = await storage.writeMemory("fact", "A tagged fact with no evidence.", {
|
|
282
282
|
confidence: 0.9,
|
|
283
283
|
tags: [],
|
|
284
284
|
provenance: "verified",
|
|
@@ -363,7 +363,7 @@ test("end-to-end: verified source with non-ISO turn timestamp survives write →
|
|
|
363
363
|
);
|
|
364
364
|
assert.equal(built.provenance, "verified");
|
|
365
365
|
|
|
366
|
-
const memoryId = await storage.writeMemory("fact", "Production DB uses pgBouncer.", {
|
|
366
|
+
const { id: memoryId } = await storage.writeMemory("fact", "Production DB uses pgBouncer.", {
|
|
367
367
|
confidence: 0.9,
|
|
368
368
|
tags: ["infra"],
|
|
369
369
|
sources: built.sources,
|
|
@@ -1102,3 +1102,381 @@ test("toStrictIsoTimestamp path: full month name is preserved (issue #1657, code
|
|
|
1102
1102
|
"full-name textual month must not fall back to the epoch (it was accepted, not rejected)",
|
|
1103
1103
|
);
|
|
1104
1104
|
});
|
|
1105
|
+
|
|
1106
|
+
// ---------------------------------------------------------------------------
|
|
1107
|
+
// Regression: issue #1723 — complete-but-unusual-punctuation year-led dates
|
|
1108
|
+
// (e.g. "2026 May, 10", "2026, May 10", "2026 Jan. 15") parse correctly via
|
|
1109
|
+
// Date.parse (correct calendar day, no fabrication), but the strict
|
|
1110
|
+
// single-char separator shape in parseYearLedDatePrefix over-rejected them,
|
|
1111
|
+
// dropping a correctly-located source to the unverified epoch fallback. The
|
|
1112
|
+
// separator shape is relaxed to a run of non-alphanumeric chars so any
|
|
1113
|
+
// complete, calendar-valid date is accepted while the three-component +
|
|
1114
|
+
// valid-day + day-not-the-hour invariants are preserved (#1657 stays green).
|
|
1115
|
+
// ---------------------------------------------------------------------------
|
|
1116
|
+
|
|
1117
|
+
test("toStrictIsoTimestamp path: punctuation-tail 'YYYY Month, DD' is preserved (issue #1723)", () => {
|
|
1118
|
+
// "2026 May, 10 12:00:00" parses to May 10 via Date.parse (correct calendar
|
|
1119
|
+
// day, no fabrication). Pre-fix the comma+space separator shape was rejected
|
|
1120
|
+
// by the single-char guard, dropping a correctly-located source to unverified.
|
|
1121
|
+
const turns = [
|
|
1122
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1123
|
+
timestamp: "2026 May, 10 12:00:00" as unknown as string,
|
|
1124
|
+
}),
|
|
1125
|
+
];
|
|
1126
|
+
const result = buildFactProvenance(
|
|
1127
|
+
"migrated the production database to pgBouncer",
|
|
1128
|
+
turns,
|
|
1129
|
+
DEFAULT_CONFIG,
|
|
1130
|
+
);
|
|
1131
|
+
assert.equal(result.provenance, "verified", "complete punctuation-tail date must stay verified");
|
|
1132
|
+
assert.ok(result.sources);
|
|
1133
|
+
const observedAt = result.sources![0]!.observedAt;
|
|
1134
|
+
assert.match(
|
|
1135
|
+
observedAt,
|
|
1136
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/,
|
|
1137
|
+
"punctuation-tail observedAt must normalize to strict ISO",
|
|
1138
|
+
);
|
|
1139
|
+
assert.notEqual(
|
|
1140
|
+
observedAt,
|
|
1141
|
+
new Date(0).toISOString(),
|
|
1142
|
+
"punctuation-tail date must not fall back to the epoch (it was accepted, not rejected)",
|
|
1143
|
+
);
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
test("toStrictIsoTimestamp path: punctuation-tail 'YYYY, Month DD' is preserved (issue #1723)", () => {
|
|
1147
|
+
// "2026, May 10 12:00:00" — comma+space between year and month. Same class
|
|
1148
|
+
// as the comma-after-month form: complete, calendar-valid, over-rejected.
|
|
1149
|
+
const turns = [
|
|
1150
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1151
|
+
timestamp: "2026, May 10 12:00:00" as unknown as string,
|
|
1152
|
+
}),
|
|
1153
|
+
];
|
|
1154
|
+
const result = buildFactProvenance(
|
|
1155
|
+
"migrated the production database to pgBouncer",
|
|
1156
|
+
turns,
|
|
1157
|
+
DEFAULT_CONFIG,
|
|
1158
|
+
);
|
|
1159
|
+
assert.equal(result.provenance, "verified", "comma-after-year date must stay verified");
|
|
1160
|
+
assert.ok(result.sources);
|
|
1161
|
+
assert.notEqual(
|
|
1162
|
+
result.sources![0]!.observedAt,
|
|
1163
|
+
new Date(0).toISOString(),
|
|
1164
|
+
"comma-after-year date must not fall back to the epoch",
|
|
1165
|
+
);
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
test("toStrictIsoTimestamp path: punctuation-tail 'YYYY Mon. DD' is preserved (issue #1723)", () => {
|
|
1169
|
+
// "2026 Jan. 15 12:00:00" — period+space between month and day. Same class.
|
|
1170
|
+
const turns = [
|
|
1171
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1172
|
+
timestamp: "2026 Jan. 15 12:00:00" as unknown as string,
|
|
1173
|
+
}),
|
|
1174
|
+
];
|
|
1175
|
+
const result = buildFactProvenance(
|
|
1176
|
+
"migrated the production database to pgBouncer",
|
|
1177
|
+
turns,
|
|
1178
|
+
DEFAULT_CONFIG,
|
|
1179
|
+
);
|
|
1180
|
+
assert.equal(result.provenance, "verified", "period-after-month date must stay verified");
|
|
1181
|
+
assert.ok(result.sources);
|
|
1182
|
+
assert.notEqual(
|
|
1183
|
+
result.sources![0]!.observedAt,
|
|
1184
|
+
new Date(0).toISOString(),
|
|
1185
|
+
"period-after-month date must not fall back to the epoch",
|
|
1186
|
+
);
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
test("toStrictIsoTimestamp path: date-only punctuation-tail 'YYYY Month, DD' is preserved (issue #1723, codex)", () => {
|
|
1190
|
+
// "2026 May, 10" (no time component) parses to May 10 via Date.parse but
|
|
1191
|
+
// contains no -/:/T, so pre-fix the bare-number guard dropped it to
|
|
1192
|
+
// unverified before parseYearLedDatePrefix was ever reached. The guard now
|
|
1193
|
+
// admits a complete, calendar-valid year-led date even without a time
|
|
1194
|
+
// separator, so the correctly-located source stays verified.
|
|
1195
|
+
const turns = [
|
|
1196
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1197
|
+
timestamp: "2026 May, 10" as unknown as string,
|
|
1198
|
+
}),
|
|
1199
|
+
];
|
|
1200
|
+
const result = buildFactProvenance(
|
|
1201
|
+
"migrated the production database to pgBouncer",
|
|
1202
|
+
turns,
|
|
1203
|
+
DEFAULT_CONFIG,
|
|
1204
|
+
);
|
|
1205
|
+
assert.equal(result.provenance, "verified", "date-only punctuation-tail date must stay verified");
|
|
1206
|
+
assert.ok(result.sources);
|
|
1207
|
+
assert.notEqual(
|
|
1208
|
+
result.sources![0]!.observedAt,
|
|
1209
|
+
new Date(0).toISOString(),
|
|
1210
|
+
"date-only punctuation-tail date must not fall back to the epoch",
|
|
1211
|
+
);
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
test("toStrictIsoTimestamp path: bare numeric string still rejected after #1723 broadening (non-regression)", () => {
|
|
1215
|
+
// The bare-number guard must still reject "123" / "2026" (Date.parse-able
|
|
1216
|
+
// plain numbers with no date structure) so they never round-trip into a fake
|
|
1217
|
+
// epoch. The #1723 broadening only admits COMPLETE year-led dates.
|
|
1218
|
+
const turns = [
|
|
1219
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1220
|
+
timestamp: "2026" as unknown as string,
|
|
1221
|
+
}),
|
|
1222
|
+
];
|
|
1223
|
+
const result = buildFactProvenance(
|
|
1224
|
+
"migrated the production database to pgBouncer",
|
|
1225
|
+
turns,
|
|
1226
|
+
DEFAULT_CONFIG,
|
|
1227
|
+
);
|
|
1228
|
+
assert.equal(result.provenance, "unverified");
|
|
1229
|
+
assert.equal(
|
|
1230
|
+
result.sources![0]!.observedAt,
|
|
1231
|
+
new Date(0).toISOString(),
|
|
1232
|
+
"bare-year string must fall back to epoch, not round-trip into a fake date",
|
|
1233
|
+
);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
test("toStrictIsoTimestamp path: parenthesized-month fabrication is rejected (issue #1723, codex r2)", () => {
|
|
1237
|
+
// "2026 (May) 10 12:00:00" parses as a valid May 10 PREFIX (year, "May",
|
|
1238
|
+
// day 10) but Date.parse reads it as October 1 — the round-trip would
|
|
1239
|
+
// persist October while the guard "validated" May (fabrication). The
|
|
1240
|
+
// agreement check compares the prefix y/m against Date.parse's own
|
|
1241
|
+
// interpretation and rejects on disagreement.
|
|
1242
|
+
const turns = [
|
|
1243
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1244
|
+
timestamp: "2026 (May) 10 12:00:00" as unknown as string,
|
|
1245
|
+
}),
|
|
1246
|
+
];
|
|
1247
|
+
const result = buildFactProvenance(
|
|
1248
|
+
"migrated the production database to pgBouncer",
|
|
1249
|
+
turns,
|
|
1250
|
+
DEFAULT_CONFIG,
|
|
1251
|
+
);
|
|
1252
|
+
assert.equal(result.provenance, "unverified", "parenthesized-month fabrication must be rejected");
|
|
1253
|
+
assert.ok(result.sources);
|
|
1254
|
+
assert.notEqual(
|
|
1255
|
+
result.sources![0]!.observedAt,
|
|
1256
|
+
"2026-10-01T12:00:00.000Z",
|
|
1257
|
+
"must not persist Date.parse's October interpretation while validating May",
|
|
1258
|
+
);
|
|
1259
|
+
assert.equal(
|
|
1260
|
+
result.sources![0]!.observedAt,
|
|
1261
|
+
new Date(0).toISOString(),
|
|
1262
|
+
"disagreeing punctuation form falls back to epoch",
|
|
1263
|
+
);
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
test("toStrictIsoTimestamp path: parenthesized-day fabrication is rejected (issue #1723, codex r3)", () => {
|
|
1267
|
+
// "2026 May (10) 12:00:00" matches the prefix as May 10 and Date.parse agrees
|
|
1268
|
+
// on year+month (May 2026) but reads day 1 (it ignores the parenthesized
|
|
1269
|
+
// "(10)"), so a month-only check would still fabricate observedAt = May 1.
|
|
1270
|
+
// The agreement check must compare the full (y, m, d) tuple.
|
|
1271
|
+
const turns = [
|
|
1272
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1273
|
+
timestamp: "2026 May (10) 12:00:00" as unknown as string,
|
|
1274
|
+
}),
|
|
1275
|
+
];
|
|
1276
|
+
const result = buildFactProvenance(
|
|
1277
|
+
"migrated the production database to pgBouncer",
|
|
1278
|
+
turns,
|
|
1279
|
+
DEFAULT_CONFIG,
|
|
1280
|
+
);
|
|
1281
|
+
assert.equal(result.provenance, "unverified", "parenthesized-day fabrication must be rejected");
|
|
1282
|
+
assert.ok(result.sources);
|
|
1283
|
+
assert.notEqual(
|
|
1284
|
+
result.sources![0]!.observedAt,
|
|
1285
|
+
"2026-05-01T12:00:00.000Z",
|
|
1286
|
+
"must not persist Date.parse's day-1 interpretation while validating day 10",
|
|
1287
|
+
);
|
|
1288
|
+
assert.equal(
|
|
1289
|
+
result.sources![0]!.observedAt,
|
|
1290
|
+
new Date(0).toISOString(),
|
|
1291
|
+
"day-disagreeing punctuation form falls back to epoch",
|
|
1292
|
+
);
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
test("toStrictIsoTimestamp path: explicit-offset timestamp crossing UTC day boundary stays verified (issue #1723, codex r4)", () => {
|
|
1296
|
+
// "2026-05-10 00:30:00 +05:00" parses to a UTC instant on May 9 (the offset
|
|
1297
|
+
// shifts the instant across the UTC day boundary), but the prefix date is May
|
|
1298
|
+
// 10 in the +05:00 frame. The agreement check must compare in the source's
|
|
1299
|
+
// explicit offset frame, not the UTC/local frame, so this legitimate
|
|
1300
|
+
// offset-bearing timestamp stays verified (pre-fix the strict UTC/local
|
|
1301
|
+
// comparison wrongly rejected it).
|
|
1302
|
+
const turns = [
|
|
1303
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1304
|
+
timestamp: "2026-05-10 00:30:00 +05:00" as unknown as string,
|
|
1305
|
+
}),
|
|
1306
|
+
];
|
|
1307
|
+
const result = buildFactProvenance(
|
|
1308
|
+
"migrated the production database to pgBouncer",
|
|
1309
|
+
turns,
|
|
1310
|
+
DEFAULT_CONFIG,
|
|
1311
|
+
);
|
|
1312
|
+
assert.equal(result.provenance, "verified", "explicit-offset timestamp must stay verified");
|
|
1313
|
+
assert.ok(result.sources);
|
|
1314
|
+
assert.match(
|
|
1315
|
+
result.sources![0]!.observedAt,
|
|
1316
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/,
|
|
1317
|
+
"offset timestamp normalizes to strict ISO",
|
|
1318
|
+
);
|
|
1319
|
+
assert.notEqual(
|
|
1320
|
+
result.sources![0]!.observedAt,
|
|
1321
|
+
new Date(0).toISOString(),
|
|
1322
|
+
"offset timestamp must not fall back to the epoch",
|
|
1323
|
+
);
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
test("toStrictIsoTimestamp path: offset-bearing punctuation fabrication still rejected (issue #1723, codex r4)", () => {
|
|
1327
|
+
// "2026 (May) 10 12:00:00 +05:00" carries an offset AND exotic punctuation.
|
|
1328
|
+
// The agreement check compares in the +05:00 frame: prefix (2026,5,10) vs
|
|
1329
|
+
// Date.parse's October reading shifted to +05:00 — they still disagree, so
|
|
1330
|
+
// the fabrication is rejected even with an offset present.
|
|
1331
|
+
const turns = [
|
|
1332
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1333
|
+
timestamp: "2026 (May) 10 12:00:00 +05:00" as unknown as string,
|
|
1334
|
+
}),
|
|
1335
|
+
];
|
|
1336
|
+
const result = buildFactProvenance(
|
|
1337
|
+
"migrated the production database to pgBouncer",
|
|
1338
|
+
turns,
|
|
1339
|
+
DEFAULT_CONFIG,
|
|
1340
|
+
);
|
|
1341
|
+
assert.equal(result.provenance, "unverified", "offset-bearing fabrication must be rejected");
|
|
1342
|
+
assert.equal(
|
|
1343
|
+
result.sources![0]!.observedAt,
|
|
1344
|
+
new Date(0).toISOString(),
|
|
1345
|
+
"offset-bearing fabrication falls back to epoch",
|
|
1346
|
+
);
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
test("toStrictIsoTimestamp path: single-digit explicit offset stays verified (issue #1723, codex r5)", () => {
|
|
1350
|
+
// "+5:00" (single-digit offset hour) parses to the same instant as "+05:00"
|
|
1351
|
+
// and crosses the UTC day boundary; the offset extractor must recognize the
|
|
1352
|
+
// single-digit form so the agreement check compares in the +05:00 frame.
|
|
1353
|
+
const turns = [
|
|
1354
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1355
|
+
timestamp: "2026-05-10 00:30:00 +5:00" as unknown as string,
|
|
1356
|
+
}),
|
|
1357
|
+
];
|
|
1358
|
+
const result = buildFactProvenance(
|
|
1359
|
+
"migrated the production database to pgBouncer",
|
|
1360
|
+
turns,
|
|
1361
|
+
DEFAULT_CONFIG,
|
|
1362
|
+
);
|
|
1363
|
+
assert.equal(result.provenance, "verified", "single-digit offset timestamp must stay verified");
|
|
1364
|
+
assert.ok(result.sources);
|
|
1365
|
+
assert.notEqual(
|
|
1366
|
+
result.sources![0]!.observedAt,
|
|
1367
|
+
new Date(0).toISOString(),
|
|
1368
|
+
"single-digit offset timestamp must not fall back to the epoch",
|
|
1369
|
+
);
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
test("toStrictIsoTimestamp path: date-only and numeric dates not misread as offsets (issue #1723, codex r5 non-regression)", () => {
|
|
1373
|
+
// The offset extractor must not misread date digits as an offset (e.g. the
|
|
1374
|
+
// "-0510" in "2026-0510" or the "-2026" tail of "05-10-2026"). These forms
|
|
1375
|
+
// have no real offset, so they must be handled by the offset-free path —
|
|
1376
|
+
// a complete valid date stays verified, a bare invalid one is rejected, but
|
|
1377
|
+
// neither is silently mistreated as offset-bearing.
|
|
1378
|
+
const turnsOk = [
|
|
1379
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1380
|
+
timestamp: "2026-05-03" as unknown as string,
|
|
1381
|
+
}),
|
|
1382
|
+
];
|
|
1383
|
+
const resultOk = buildFactProvenance(
|
|
1384
|
+
"migrated the production database to pgBouncer",
|
|
1385
|
+
turnsOk,
|
|
1386
|
+
DEFAULT_CONFIG,
|
|
1387
|
+
);
|
|
1388
|
+
assert.equal(resultOk.provenance, "verified", "plain date must not be misread as offset-bearing");
|
|
1389
|
+
assert.notEqual(resultOk.sources![0]!.observedAt, new Date(0).toISOString());
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
test("toStrictIsoTimestamp path: GMT+HHMM offset minutes preserved (issue #1723, codex r6)", () => {
|
|
1393
|
+
// "GMT+0530" is +5:30, not +5:00 — the extractor must try the 4-digit HHMM
|
|
1394
|
+
// form before the bare-hour form so the minutes are not dropped.
|
|
1395
|
+
const turns = [
|
|
1396
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1397
|
+
timestamp: "2026 May 10 00:15:00 GMT+0530" as unknown as string,
|
|
1398
|
+
}),
|
|
1399
|
+
];
|
|
1400
|
+
const result = buildFactProvenance(
|
|
1401
|
+
"migrated the production database to pgBouncer",
|
|
1402
|
+
turns,
|
|
1403
|
+
DEFAULT_CONFIG,
|
|
1404
|
+
);
|
|
1405
|
+
assert.equal(result.provenance, "verified", "GMT+HHMM offset timestamp must stay verified");
|
|
1406
|
+
assert.ok(result.sources);
|
|
1407
|
+
assert.notEqual(
|
|
1408
|
+
result.sources![0]!.observedAt,
|
|
1409
|
+
new Date(0).toISOString(),
|
|
1410
|
+
"GMT+HHMM offset timestamp must not fall back to the epoch",
|
|
1411
|
+
);
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
test("toStrictIsoTimestamp path: offset glued to seconds stays verified (issue #1723, cursor r6)", () => {
|
|
1415
|
+
// "00:30:00+05:00" — the offset is glued to the seconds field with no space.
|
|
1416
|
+
// The extractor must detect it (via the ":SS" lookbehind) so the agreement
|
|
1417
|
+
// check compares in the +05:00 frame, not UTC/local.
|
|
1418
|
+
const turns = [
|
|
1419
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1420
|
+
timestamp: "2026-05-10 00:30:00+05:00" as unknown as string,
|
|
1421
|
+
}),
|
|
1422
|
+
];
|
|
1423
|
+
const result = buildFactProvenance(
|
|
1424
|
+
"migrated the production database to pgBouncer",
|
|
1425
|
+
turns,
|
|
1426
|
+
DEFAULT_CONFIG,
|
|
1427
|
+
);
|
|
1428
|
+
assert.equal(result.provenance, "verified", "glued-offset timestamp must stay verified");
|
|
1429
|
+
assert.ok(result.sources);
|
|
1430
|
+
assert.notEqual(
|
|
1431
|
+
result.sources![0]!.observedAt,
|
|
1432
|
+
new Date(0).toISOString(),
|
|
1433
|
+
"glued-offset timestamp must not fall back to the epoch",
|
|
1434
|
+
);
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
test("toStrictIsoTimestamp path: glued bare-hour offset stays verified (issue #1723, codex r7)", () => {
|
|
1438
|
+
// "00:15:00+5" — a bare-hour offset glued to the seconds field. The extractor
|
|
1439
|
+
// must detect it (via the ":SS" lookbehind, bare-hour alternative) so the
|
|
1440
|
+
// agreement check compares in the +05:00 frame.
|
|
1441
|
+
const turns = [
|
|
1442
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1443
|
+
timestamp: "2026 May 10 00:15:00+5" as unknown as string,
|
|
1444
|
+
}),
|
|
1445
|
+
];
|
|
1446
|
+
const result = buildFactProvenance(
|
|
1447
|
+
"migrated the production database to pgBouncer",
|
|
1448
|
+
turns,
|
|
1449
|
+
DEFAULT_CONFIG,
|
|
1450
|
+
);
|
|
1451
|
+
assert.equal(result.provenance, "verified", "glued bare-hour offset must stay verified");
|
|
1452
|
+
assert.ok(result.sources);
|
|
1453
|
+
assert.notEqual(
|
|
1454
|
+
result.sources![0]!.observedAt,
|
|
1455
|
+
new Date(0).toISOString(),
|
|
1456
|
+
"glued bare-hour offset must not fall back to the epoch",
|
|
1457
|
+
);
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
test("toStrictIsoTimestamp path: named US timezone abbreviation stays verified (issue #1723, codex r8)", () => {
|
|
1461
|
+
// "2026 May 10 23:30:00 PST" — Date.parse reads PST as -8, so on a UTC
|
|
1462
|
+
// process the instant lands on May 11 UTC. The prefix date is May 10 in the
|
|
1463
|
+
// PST frame; the extractor must recognize the named zone so the agreement
|
|
1464
|
+
// check compares in the -8 frame.
|
|
1465
|
+
const turns = [
|
|
1466
|
+
makeTurn("We migrated the production database to pgBouncer.", {
|
|
1467
|
+
timestamp: "2026 May 10 23:30:00 PST" as unknown as string,
|
|
1468
|
+
}),
|
|
1469
|
+
];
|
|
1470
|
+
const result = buildFactProvenance(
|
|
1471
|
+
"migrated the production database to pgBouncer",
|
|
1472
|
+
turns,
|
|
1473
|
+
DEFAULT_CONFIG,
|
|
1474
|
+
);
|
|
1475
|
+
assert.equal(result.provenance, "verified", "named-zone timestamp must stay verified");
|
|
1476
|
+
assert.ok(result.sources);
|
|
1477
|
+
assert.notEqual(
|
|
1478
|
+
result.sources![0]!.observedAt,
|
|
1479
|
+
new Date(0).toISOString(),
|
|
1480
|
+
"named-zone timestamp must not fall back to the epoch",
|
|
1481
|
+
);
|
|
1482
|
+
});
|
package/src/provenance.ts
CHANGED
|
@@ -197,22 +197,47 @@ function toStrictIsoTimestamp(ts: string | undefined | null): string | undefined
|
|
|
197
197
|
if (isStrictIsoTimestamp(value)) return value;
|
|
198
198
|
const parsed = Date.parse(value);
|
|
199
199
|
if (Number.isNaN(parsed)) return undefined;
|
|
200
|
+
// Year-led date guard (issues #1657 + #1723). `parseYearLedDatePrefix`
|
|
201
|
+
// recognizes a COMPLETE, calendar-valid year-led prefix — numeric OR textual
|
|
202
|
+
// month — with real date-separator RUNS of non-alphanumeric, non-colon chars
|
|
203
|
+
// (so complete-but-unusual-punctuation dates like "2026 May, 10",
|
|
204
|
+
// "2026, May 10", "2026 Jan. 15" are accepted), the day-not-the-hour
|
|
205
|
+
// invariant via the `(?![\d:])` lookahead, and a calendar-valid day. This
|
|
206
|
+
// single rule subsumes the prior partial-numeric ("2026-05"), textual-partial
|
|
207
|
+
// ("2026-Jan"), and calendar-overflow ("2026-02-30", "2026-Feb-30") checks
|
|
208
|
+
// and extends them to textual months — Date.parse silently fills missing
|
|
209
|
+
// components and rolls overflow, fabricating observedAt, so a non-complete
|
|
210
|
+
// or invalid year-led prefix is rejected. Non-year-led shapes
|
|
211
|
+
// ("Jan 15 2026", "02/30/2026") fall through to Date.parse / the mdy check.
|
|
212
|
+
const yearLedPrefix = parseYearLedDatePrefix(value);
|
|
213
|
+
// Agreement check (issue #1723 codex r2/r3/r4): exotic punctuation can make
|
|
214
|
+
// the prefix structure disagree with Date.parse's own interpretation — e.g.
|
|
215
|
+
// "2026 (May) 10" parses as a valid May 10 PREFIX but Date.parse reads
|
|
216
|
+
// October 1; "2026 May (10)" agrees on month but Date.parse reads day 1 — so
|
|
217
|
+
// the round-trip would persist a fabricated observedAt while the guard
|
|
218
|
+
// "validated" a different date. Require Date.parse's interpretation to match
|
|
219
|
+
// the prefix's FULL (y, m, d) tuple. Offset-bearing inputs compare in the
|
|
220
|
+
// source's explicit offset frame (the prefix date is offset-local, so a UTC
|
|
221
|
+
// instant legitimately falls on an adjacent UTC/local day); offset-free
|
|
222
|
+
// inputs compare both the local and UTC frames (Date.parse uses UTC for ISO
|
|
223
|
+
// date-only strings and local for non-ISO — a single frame would wrongly
|
|
224
|
+
// reject legitimate month/day-boundary dates that shift across timezones).
|
|
225
|
+
const yearLedAgrees = yearLedAgreesWithDateParse(parsed, value, yearLedPrefix);
|
|
226
|
+
const validYearLed = yearLedPrefix !== null && yearLedAgrees;
|
|
200
227
|
// Reject bare-year / numeric-only strings that Date.parse accepts (e.g.
|
|
201
228
|
// "123") — isStrictIsoTimestamp already rejected them, and the round-trip
|
|
202
229
|
// below would otherwise resurrect them. Require at least one date/time
|
|
203
|
-
// separator so a plain number never round-trips into a fake epoch
|
|
204
|
-
|
|
230
|
+
// separator so a plain number never round-trips into a fake epoch — UNLESS
|
|
231
|
+
// the value is itself a complete, calendar-valid year-led date that
|
|
232
|
+
// Date.parse agrees with (issue #1723: a date-only punctuated textual form
|
|
233
|
+
// like "2026 May, 10" has no -/:/T yet parses to the correct calendar day).
|
|
234
|
+
if (!/[-:T]/.test(value) && !validYearLed) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
205
237
|
// Reject year-led timestamps that are not a COMPLETE, calendar-valid date
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
// requires a complete three-component date — numeric OR textual month — with
|
|
210
|
-
// real date separators (not T/:, and not the space that precedes a time
|
|
211
|
-
// component, so the hour is never captured as the day) and a calendar-valid
|
|
212
|
-
// day, in one rule (subsumes the prior partial-numeric and numeric-overflow
|
|
213
|
-
// checks, and extends them to textual months). Non-year-led shapes
|
|
214
|
-
// ("Jan 15 2026", "02/30/2026") fall through to Date.parse / the mdy check.
|
|
215
|
-
if (/^\d{4}\D/.test(value) && parseYearLedDatePrefix(value) === null) {
|
|
238
|
+
// Date.parse agrees with (covers partial, overflow, AND the punctuation
|
|
239
|
+
// misinterpretation above).
|
|
240
|
+
if (/^\d{4}\D/.test(value) && !validYearLed) {
|
|
216
241
|
return undefined;
|
|
217
242
|
}
|
|
218
243
|
// Also validate M/D/Y or D/M/Y formats (provider/import common shapes:
|
|
@@ -268,18 +293,22 @@ const MONTH_NAMES: Record<string, number> = {
|
|
|
268
293
|
|
|
269
294
|
/**
|
|
270
295
|
* Parse a complete, calendar-valid year-led date prefix from `s`
|
|
271
|
-
* (issue #1657): `YYYY <sep> (MM | MonthName) <sep> DD`
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
296
|
+
* (issue #1657, relaxed in #1723): `YYYY <sep> (MM | MonthName) <sep> DD`
|
|
297
|
+
* where each separator is a RUN of non-alphanumeric, non-colon chars (so a
|
|
298
|
+
* complete date with any punctuation — "2026 May, 10", "2026, May 10",
|
|
299
|
+
* "2026 Jan. 15" — is accepted; the run excludes letters so a textual month
|
|
300
|
+
* is never consumed by the separator, and excludes the colon so the date/time
|
|
301
|
+
* boundary stays clean) and the day is a true calendar day — not the hour
|
|
302
|
+
* before a `:` time separator (the `(?![\d:])` lookahead rejects
|
|
303
|
+
* "2026-05 10:00" capturing "10" as the day). Returns the numeric components
|
|
304
|
+
* for a complete, valid date; `null` for an incomplete ("2026-05",
|
|
305
|
+
* "2026-Jan"), overflowed ("2026-02-30", "2026-Feb-30"), or non-year-led
|
|
306
|
+
* prefix. Centralizes the fabricated-date rejection for the
|
|
307
|
+
* toStrictIsoTimestamp normalization path across numeric and textual months
|
|
308
|
+
* in one rule.
|
|
280
309
|
*/
|
|
281
310
|
function parseYearLedDatePrefix(s: string): { y: number; mo: number; da: number } | null {
|
|
282
|
-
const m = /^(\d{4})[^0-
|
|
311
|
+
const m = /^(\d{4})[^0-9A-Za-z]+(\d{1,2}|[A-Za-z]{3,})[^0-9A-Za-z:]+(\d{1,2})(?![\d:])/.exec(s);
|
|
283
312
|
if (!m) return null;
|
|
284
313
|
const y = Number(m[1]);
|
|
285
314
|
const da = Number(m[3]);
|
|
@@ -288,6 +317,113 @@ function parseYearLedDatePrefix(s: string): { y: number; mo: number; da: number
|
|
|
288
317
|
return { y, mo, da };
|
|
289
318
|
}
|
|
290
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Extract an explicit timezone offset (ms east of UTC) from a non-strict-ISO
|
|
322
|
+
* timestamp, or `null` if none is present (issue #1723 codex r4). Recognizes
|
|
323
|
+
* the offset forms `Date.parse` accepts for non-ISO year-led dates: a trailing
|
|
324
|
+
* `±HH:MM` / `±HHMM`, and a `GMT`/`UTC` token optionally followed by
|
|
325
|
+
* `±HHMM` (bare `GMT`/`UTC` ⇒ offset 0). Strict ISO `Z` forms are handled
|
|
326
|
+
* by `isStrictIsoTimestamp` before this point, so they never reach here.
|
|
327
|
+
*/
|
|
328
|
+
/**
|
|
329
|
+
* Named timezone abbreviations Node's `Date.parse` accepts for non-ISO dates
|
|
330
|
+
* (issue #1723 codex r8). V8 recognizes a fixed set of US zones; each maps to a
|
|
331
|
+
* fixed offset (the abbreviation itself encodes standard vs daylight, so PST is
|
|
332
|
+
* always -8 and PDT always -7 regardless of the calendar date). Other named
|
|
333
|
+
* zones (CET/CEST/JST/…) return NaN from `Date.parse` and never reach here.
|
|
334
|
+
*/
|
|
335
|
+
const NAMED_ZONE_OFFSETS_MS: Record<string, number> = {
|
|
336
|
+
PST: -8 * 60, PDT: -7 * 60,
|
|
337
|
+
MST: -7 * 60, MDT: -6 * 60,
|
|
338
|
+
CST: -6 * 60, CDT: -5 * 60,
|
|
339
|
+
EST: -5 * 60, EDT: -4 * 60,
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
function explicitOffsetMs(value: string): number | null {
|
|
343
|
+
// Recognize the offset forms Date.parse accepts for non-ISO year-led dates,
|
|
344
|
+
// returning ms east of UTC or null. Tries each shape in order; a stray run
|
|
345
|
+
// of date digits can never pose as an offset because (a) trailing numeric
|
|
346
|
+
// offsets must be preceded by whitespace or glued to a ":SS" seconds field
|
|
347
|
+
// (lookbehind), and (b) every candidate is range-validated (|hh| ≤ 14,
|
|
348
|
+
// mm < 59).
|
|
349
|
+
// Named US zone abbreviation (PST/EST/EDT/…), trailing token (codex r8).
|
|
350
|
+
const nz = /\b([A-Z]{3,4})\s*$/.exec(value);
|
|
351
|
+
if (nz && NAMED_ZONE_OFFSETS_MS[nz[1]] !== undefined) {
|
|
352
|
+
return NAMED_ZONE_OFFSETS_MS[nz[1]] * 60_000;
|
|
353
|
+
}
|
|
354
|
+
const tryOff = (sign: string | undefined, hh: number, mm: number): number | null => {
|
|
355
|
+
if (hh > 14 || mm > 59) return null;
|
|
356
|
+
return (sign === "-" ? -1 : 1) * (hh * 60 + mm) * 60_000;
|
|
357
|
+
};
|
|
358
|
+
let m: RegExpExecArray | null;
|
|
359
|
+
// GMT/UTC token: colon form, then ±HHMM (4-digit, before bare hour so
|
|
360
|
+
// "GMT+0530" is not read as "+05"), then bare hour, then the bare token
|
|
361
|
+
// itself (offset 0).
|
|
362
|
+
if (
|
|
363
|
+
(m = /\b(?:GMT|UTC)\b\s*([+-])?(\d{1,2}):(\d{2})/i.exec(value)) ||
|
|
364
|
+
(m = /\b(?:GMT|UTC)\b\s*([+-])?(\d{2})(\d{2})/i.exec(value))
|
|
365
|
+
) {
|
|
366
|
+
const r = tryOff(m[1], Number(m[2]), Number(m[3]));
|
|
367
|
+
if (r !== null) return r;
|
|
368
|
+
}
|
|
369
|
+
if ((m = /\b(?:GMT|UTC)\b\s*([+-])?(\d{1,2})\b/i.exec(value))) {
|
|
370
|
+
const r = tryOff(m[1], Number(m[2]), 0);
|
|
371
|
+
if (r !== null) return r;
|
|
372
|
+
}
|
|
373
|
+
if (/\b(?:GMT|UTC)\b/i.test(value)) return 0;
|
|
374
|
+
// Trailing numeric offset: whitespace-delimited OR glued to a ":SS"
|
|
375
|
+
// seconds field (lookbehind), so date digits ("-0510" in "2026-0510") are
|
|
376
|
+
// never misread as an offset. Colon form, then ±HHMM, then bare hour — each
|
|
377
|
+
// for BOTH the whitespace and glued contexts.
|
|
378
|
+
if (
|
|
379
|
+
(m = /(?:\s|(?<=:\d{2}))([+-])(\d{1,2}):(\d{2})\s*$/.exec(value)) ||
|
|
380
|
+
(m = /(?:\s|(?<=:\d{2}))([+-])(\d{2})(\d{2})\s*$/.exec(value)) ||
|
|
381
|
+
(m = /(?:\s|(?<=:\d{2}))([+-])(\d{1,2})\s*$/.exec(value))
|
|
382
|
+
) {
|
|
383
|
+
const r = tryOff(m[1], Number(m[2]), m[3] ? Number(m[3]) : 0);
|
|
384
|
+
if (r !== null) return r;
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Verify `Date.parse`'s interpretation of `value` agrees with the year-led
|
|
391
|
+
* `prefix` components `(y, m, d)` (issue #1723 codex r2/r3/r4). Returns
|
|
392
|
+
* `false` when `prefix` is `null` (no complete year-led date). Otherwise
|
|
393
|
+
* compares the FULL tuple so punctuation that shifts the month OR the day is
|
|
394
|
+
* caught. Offset-bearing inputs are compared in the source's explicit offset
|
|
395
|
+
* frame (the prefix date is offset-local, so the same instant legitimately
|
|
396
|
+
* lands on an adjacent UTC/local day); offset-free inputs accept either the
|
|
397
|
+
* local or UTC frame, because `Date.parse` uses UTC for ISO date-only strings
|
|
398
|
+
* and local for non-ISO forms.
|
|
399
|
+
*/
|
|
400
|
+
function yearLedAgreesWithDateParse(
|
|
401
|
+
parsed: number,
|
|
402
|
+
value: string,
|
|
403
|
+
prefix: { y: number; mo: number; da: number } | null,
|
|
404
|
+
): boolean {
|
|
405
|
+
if (prefix === null) return false;
|
|
406
|
+
const off = explicitOffsetMs(value);
|
|
407
|
+
if (off !== null) {
|
|
408
|
+
const shifted = new Date(parsed + off);
|
|
409
|
+
return (
|
|
410
|
+
shifted.getUTCFullYear() === prefix.y &&
|
|
411
|
+
shifted.getUTCMonth() + 1 === prefix.mo &&
|
|
412
|
+
shifted.getUTCDate() === prefix.da
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const pd = new Date(parsed);
|
|
416
|
+
const localMatch =
|
|
417
|
+
pd.getFullYear() === prefix.y &&
|
|
418
|
+
pd.getMonth() + 1 === prefix.mo &&
|
|
419
|
+
pd.getDate() === prefix.da;
|
|
420
|
+
const utcMatch =
|
|
421
|
+
pd.getUTCFullYear() === prefix.y &&
|
|
422
|
+
pd.getUTCMonth() + 1 === prefix.mo &&
|
|
423
|
+
pd.getUTCDate() === prefix.da;
|
|
424
|
+
return localMatch || utcMatch;
|
|
425
|
+
}
|
|
426
|
+
|
|
291
427
|
/**
|
|
292
428
|
* Zod schema for a single `ProvenanceSource` entry (issue #1575). Parsed
|
|
293
429
|
* JSON from frontmatter is external data, so each entry is validated here
|
|
@@ -355,7 +355,7 @@ test("memoryGet resolves a handle to the underlying memory via sessionKey", asyn
|
|
|
355
355
|
// cite its handle. writeMemory returns the persisted id directly.
|
|
356
356
|
const defaultNs = orchestrator.config.defaultNamespace;
|
|
357
357
|
const storage = await orchestrator.getStorage(defaultNs);
|
|
358
|
-
const writtenId = await storage.writeMemory(
|
|
358
|
+
const { id: writtenId } = await storage.writeMemory(
|
|
359
359
|
"fact",
|
|
360
360
|
"The API rate limit is 1000 rpm.",
|
|
361
361
|
{ confidence: 0.9, source: "test" },
|