@remnic/core 10.0.49 → 10.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +49 -48
- package/dist/access-authorization-probe.js +50 -49
- package/dist/access-boundary.d.ts +6 -1
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +49 -48
- package/dist/access-cli.js +120 -119
- package/dist/access-cli.js.map +1 -1
- package/dist/access-errors.d.ts.map +1 -1
- package/dist/access-errors.js +1 -1
- package/dist/access-extraction-force-flush.js +49 -48
- package/dist/access-http-briefing.d.ts +2 -0
- package/dist/access-http-briefing.d.ts.map +1 -1
- package/dist/access-http-briefing.js +50 -49
- package/dist/access-http-query.js +51 -50
- package/dist/access-http.d.ts.map +1 -1
- package/dist/access-http.js +70 -69
- package/dist/access-identity-continuity-surface.js +49 -48
- package/dist/access-lcm-surface.js +49 -48
- package/dist/access-mcp.js +60 -59
- package/dist/access-memory-search-fanout.js +2 -2
- package/dist/access-namespace-preflight.js +49 -48
- package/dist/access-observe-write-category.d.ts +1 -1
- package/dist/access-observe-write-category.d.ts.map +1 -1
- package/dist/access-observe-write-category.js +10 -10
- package/dist/access-observe-write-surface.d.ts.map +1 -1
- package/dist/access-observe-write-surface.js +49 -48
- package/dist/access-offline-manifest.js +6 -6
- package/dist/access-operations-batch.js +50 -49
- package/dist/access-operations.js +57 -56
- package/dist/access-recall-concurrency.js +49 -48
- package/dist/access-recall-diagnostics.js +2 -2
- package/dist/access-recall-response.js +49 -48
- package/dist/access-recall-surface.js +49 -48
- package/dist/access-schema.d.ts +245 -773
- package/dist/access-schema.d.ts.map +1 -1
- package/dist/access-schema.js +6 -6
- package/dist/access-service.d.ts +5 -0
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +49 -48
- package/dist/access-surface-catalog.js +51 -50
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-token-capabilities.js +2 -2
- package/dist/active-recall.js +16 -16
- package/dist/{auto-sync-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
- package/dist/briefing-deadline.d.ts +7 -0
- package/dist/briefing-deadline.d.ts.map +1 -0
- package/dist/briefing-deadline.js +13 -0
- package/dist/briefing-window.d.ts +12 -4
- package/dist/briefing-window.d.ts.map +1 -1
- package/dist/briefing-window.js +3 -1
- package/dist/briefing.d.ts +2 -0
- package/dist/briefing.d.ts.map +1 -1
- package/dist/briefing.js +12 -11
- package/dist/buffer.js +3 -3
- package/dist/calibration.js +2 -2
- package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
- package/dist/causal-consolidation.js +11 -11
- package/dist/{chunk-ESDOQEJM.js → chunk-2AKIVSHP.js} +2 -2
- package/dist/{chunk-BWF3KNSQ.js → chunk-2EXF6IIB.js} +2 -2
- package/dist/{chunk-AGCEJDG2.js → chunk-2ONKXEC7.js} +3 -3
- package/dist/{chunk-3NYIXVCT.js → chunk-2S6JADBB.js} +2 -2
- package/dist/chunk-2S6JADBB.js.map +1 -0
- package/dist/{chunk-ZSEFGDCI.js → chunk-2TGIKMMZ.js} +2 -2
- package/dist/{chunk-MCS4LIXP.js → chunk-43TUWQNR.js} +2 -2
- package/dist/{chunk-Q6PYCPHO.js → chunk-44EUCSG4.js} +2 -2
- package/dist/{chunk-2J6BIOVN.js → chunk-4FM35YIA.js} +3 -3
- package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
- package/dist/{chunk-YY6PWFUC.js → chunk-6ZAU7LAR.js} +3 -3
- package/dist/{chunk-HCPVL6SF.js → chunk-72CBJ5VP.js} +3 -3
- package/dist/{chunk-AF4UIHON.js → chunk-74AY3S7U.js} +9 -9
- package/dist/{chunk-N6EM7ESC.js → chunk-75BK4XGV.js} +27 -19
- package/dist/chunk-75BK4XGV.js.map +1 -0
- package/dist/{chunk-5IDQVE4U.js → chunk-A3UFV4K3.js} +6 -6
- package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
- package/dist/chunk-A5L5KGDI.js.map +1 -0
- package/dist/{chunk-RZDDNH63.js → chunk-A6JDT6QF.js} +27 -15
- package/dist/chunk-A6JDT6QF.js.map +1 -0
- package/dist/{chunk-XFND63OB.js → chunk-AHGOOZUC.js} +2 -2
- package/dist/{chunk-A7AISUJI.js → chunk-AJG2TAMR.js} +2 -2
- package/dist/{chunk-2BY56HE6.js → chunk-AZLD4CKG.js} +36 -36
- package/dist/{chunk-E47UQBYW.js → chunk-B35EQQ7L.js} +3 -3
- package/dist/{chunk-E6UNM67F.js → chunk-B6SPPYGJ.js} +2 -2
- package/dist/{chunk-CMOYD5CA.js → chunk-CCL2OXFO.js} +2 -2
- package/dist/{chunk-SVJ5JVZO.js → chunk-CK6GCGMX.js} +4 -4
- package/dist/{chunk-QZK6HG4J.js → chunk-DF6ZF2BN.js} +3 -3
- package/dist/{chunk-CPBYKOTC.js → chunk-DJFULNXK.js} +4 -4
- package/dist/{chunk-VGRBKEBH.js → chunk-DQOGZZJA.js} +3 -3
- package/dist/{chunk-YB3WN6XA.js → chunk-EC2KVA27.js} +12 -12
- package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
- package/dist/chunk-EKUI7I2X.js.map +1 -0
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-WIBWONL4.js → chunk-EZS6QJNC.js} +2 -2
- package/dist/{chunk-C7WDWD35.js → chunk-FNNGWN3O.js} +5 -5
- package/dist/{chunk-PBWJVRKE.js → chunk-FT75AQ74.js} +3 -3
- package/dist/{chunk-VY6BD5KB.js → chunk-G6A3SGCE.js} +14 -12
- package/dist/chunk-G6A3SGCE.js.map +1 -0
- package/dist/{chunk-O4CA53UY.js → chunk-GBGU5OZP.js} +111 -104
- package/dist/chunk-GBGU5OZP.js.map +1 -0
- package/dist/{chunk-WJCAAVZX.js → chunk-GE3ZX6YK.js} +2 -2
- package/dist/{chunk-3PVKV3XL.js → chunk-HCIRR3BA.js} +3 -3
- package/dist/{chunk-BJTFLP3J.js → chunk-HEF4BRMT.js} +3 -3
- package/dist/{chunk-BMG3SAXA.js → chunk-HQUYENKK.js} +9 -12
- package/dist/chunk-HQUYENKK.js.map +1 -0
- package/dist/{chunk-6UDLN4IA.js → chunk-I6ORDT54.js} +2 -2
- package/dist/{chunk-N5PISU26.js → chunk-IN6U2BS5.js} +2 -2
- package/dist/{chunk-3INLTKTW.js → chunk-IUQXRRJB.js} +4 -4
- package/dist/{chunk-6W3QH3QW.js → chunk-JIJAWTRK.js} +388 -292
- package/dist/chunk-JIJAWTRK.js.map +1 -0
- package/dist/{chunk-4KS4IHKH.js → chunk-JX3E4XCW.js} +2 -2
- package/dist/{chunk-SW3WHVKN.js → chunk-KAJYSO3U.js} +2 -2
- package/dist/{chunk-D76AHITY.js → chunk-KETS5A62.js} +2 -2
- package/dist/{chunk-LCHFINL4.js → chunk-KIWO4327.js} +2 -2
- package/dist/{chunk-FC3ZFEXF.js → chunk-KJ3MDX4V.js} +2 -2
- package/dist/{chunk-7LCGFUMB.js → chunk-KQAFEZQX.js} +4 -4
- package/dist/{chunk-6TIVVOVU.js → chunk-L3EFYRYK.js} +9 -9
- package/dist/{chunk-3MUKQQ7A.js → chunk-LGEH473S.js} +24 -29
- package/dist/chunk-LGEH473S.js.map +1 -0
- package/dist/{chunk-X52SXKVC.js → chunk-LMB47UM6.js} +2 -2
- package/dist/{chunk-A3WXV7YU.js → chunk-LWMLBVGB.js} +2 -2
- package/dist/{chunk-KMTMMBEB.js → chunk-MC7PXJDP.js} +7 -7
- package/dist/{chunk-NMOEOQV4.js → chunk-MUNYUSBU.js} +2 -2
- package/dist/{chunk-VKUMKVEU.js → chunk-OCRLJJOV.js} +5 -5
- package/dist/{chunk-XSJL62LE.js → chunk-ON77FQXH.js} +2 -2
- package/dist/{chunk-C3HRYVGD.js → chunk-POJRFSJM.js} +3 -3
- package/dist/{chunk-LKRXGOQW.js → chunk-QA3Y2O6H.js} +2 -2
- package/dist/{chunk-23ZLN5X6.js → chunk-QT5MGENX.js} +14 -6
- package/dist/chunk-QT5MGENX.js.map +1 -0
- package/dist/{chunk-QFTNWDJ6.js → chunk-RA7YRL6H.js} +2 -2
- package/dist/{chunk-WQFHHJWK.js → chunk-RJDYDF5K.js} +4 -4
- package/dist/{chunk-E3DL3CAL.js → chunk-ROAKLYOL.js} +2 -2
- package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
- package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
- package/dist/{chunk-NWIT3U2N.js → chunk-S5CQI2KF.js} +2 -2
- package/dist/{chunk-P6P35FY4.js → chunk-SDESKFKT.js} +2 -2
- package/dist/{chunk-OD7ZXCUF.js → chunk-SFRBUBUZ.js} +10 -10
- package/dist/{chunk-7XFQSQYS.js → chunk-SHNM7RIT.js} +5 -4
- package/dist/chunk-SHNM7RIT.js.map +1 -0
- package/dist/{chunk-NH37SP6V.js → chunk-SJD334AG.js} +81 -78
- package/dist/chunk-SJD334AG.js.map +1 -0
- package/dist/{chunk-O7GNSHFF.js → chunk-SPTPBKZX.js} +2 -2
- package/dist/{chunk-HI53EK3Y.js → chunk-TC55M5PO.js} +2 -2
- package/dist/{chunk-SA6P2KS3.js → chunk-TEH5MDX5.js} +33 -17
- package/dist/chunk-TEH5MDX5.js.map +1 -0
- package/dist/{chunk-FOKTW6GY.js → chunk-UCLJTGP3.js} +2 -2
- package/dist/{chunk-FYZXZUGU.js → chunk-UQBOPCDK.js} +2 -2
- package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
- package/dist/chunk-UWMZT2TY.js.map +1 -0
- package/dist/{chunk-754XN6LX.js → chunk-V27FK6D6.js} +2 -2
- package/dist/{chunk-DM6SOROF.js → chunk-V6Z4NAJZ.js} +2 -2
- package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
- package/dist/{chunk-NUK5XU3P.js → chunk-VUMWVYMR.js} +28 -13
- package/dist/chunk-VUMWVYMR.js.map +1 -0
- package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
- package/dist/{chunk-VCBS5GRQ.js → chunk-WY66J4PI.js} +2 -2
- package/dist/chunk-X2KO4FK5.js +40 -0
- package/dist/chunk-X2KO4FK5.js.map +1 -0
- package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
- package/dist/{chunk-V4GPFCJ7.js → chunk-XWJUFER3.js} +3 -3
- package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
- package/dist/{chunk-CRJX4OMN.js → chunk-Y3T5KQCR.js} +2 -2
- package/dist/{chunk-KNCR7APE.js → chunk-Y77FXULH.js} +8 -8
- package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
- package/dist/{chunk-IG747LRN.js → chunk-YO2JEX2Z.js} +2 -2
- package/dist/{chunk-HDT3DMUF.js → chunk-ZKK3VMWU.js} +2 -2
- package/dist/cli.js +92 -91
- package/dist/coding/export-okf-codegraph.js +9 -9
- package/dist/compounding/engine.js +11 -11
- package/dist/config.js +16 -16
- package/dist/connectors/codex-materialize-runner.js +10 -10
- package/dist/connectors/index.js +14 -14
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +2 -2
- package/dist/convergence-refresh.js +12 -12
- package/dist/corpus-watermark.js +14 -14
- package/dist/correction/passive-correction-notifications.d.ts +1 -1
- package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-config.js +2 -2
- package/dist/deep-recall-http-glue.js +9 -9
- package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-F3JVG6K6.js} +3 -3
- package/dist/entity-canonical-id-migration-F3JVG6K6.js.map +1 -0
- package/dist/entity-retrieval.js +11 -11
- package/dist/explicit-capture.js +2 -2
- package/dist/external-wiki-access.js +50 -49
- package/dist/extraction-span-wire.d.ts.map +1 -1
- package/dist/extraction-span-wire.js +1 -1
- package/dist/extraction.js +22 -22
- package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
- package/dist/harmonic-retrieval.js +10 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +143 -135
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +16 -16
- package/dist/location/tagging.d.ts +7 -5
- package/dist/location/tagging.d.ts.map +1 -1
- package/dist/maintenance/memory-governance.js +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
- package/dist/maintenance/rebuild-memory-projection-runner.js +12 -12
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- package/dist/memory-browse-http-glue.js +2 -2
- package/dist/memory-browse-operations.js +50 -49
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/storage.js +10 -10
- package/dist/offline-sync-file-io.d.ts +15 -0
- package/dist/offline-sync-file-io.d.ts.map +1 -1
- package/dist/offline-sync-file-io.js +9 -1
- package/dist/offline-sync-impression-drain.js +2 -2
- package/dist/offline-sync-snapshot-stream.js +4 -4
- package/dist/offline-sync-upload-staging.js +2 -2
- package/dist/offline-sync.d.ts.map +1 -1
- package/dist/offline-sync.js +3 -3
- package/dist/operator-doctor-corpus.js +17 -17
- package/dist/operator-doctor-replica.js +4 -4
- package/dist/operator-toolkit.js +29 -29
- package/dist/orchestration/maintenance.js +25 -25
- package/dist/orchestrator.d.ts +1 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +122 -121
- package/dist/page-versioning.js +2 -2
- package/dist/recall-navigation-http-glue.js +2 -2
- package/dist/recall-navigation-operations.js +50 -49
- package/dist/recall-navigation.js +49 -48
- package/dist/relay/mission.d.ts +731 -3403
- package/dist/relay/mission.d.ts.map +1 -1
- package/dist/replica-divergence.js +3 -3
- package/dist/resume-bundles.js +17 -17
- package/dist/schemas.d.ts +346 -1771
- package/dist/schemas.d.ts.map +1 -1
- package/dist/semantic-consolidation.js +15 -15
- package/dist/semantic-rule-promotion.js +10 -10
- package/dist/semantic-rule-verifier.js +10 -10
- package/dist/shared-context/manager.d.ts +13 -33
- package/dist/shared-context/manager.d.ts.map +1 -1
- package/dist/source-agent-qualifier.js +18 -18
- package/dist/standup/build.js +11 -11
- package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
- package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
- package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
- package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
- package/dist/storage/entity-canonical-id-references.d.ts +2 -0
- package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
- package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
- package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
- package/dist/storage/entity-store.d.ts +1 -8
- package/dist/storage/entity-store.d.ts.map +1 -1
- package/dist/storage/memory-read-store.d.ts +3 -2
- package/dist/storage/memory-read-store.d.ts.map +1 -1
- package/dist/storage/windowed-memory-read.d.ts +6 -0
- package/dist/storage/windowed-memory-read.d.ts.map +1 -1
- package/dist/storage.d.ts +2 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +9 -9
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/access-operations.d.ts +51 -51
- package/dist/support-passport/contracts.d.ts +85 -126
- package/dist/support-passport/contracts.d.ts.map +1 -1
- package/dist/support-passport/generated-batch.d.ts +3 -17
- package/dist/support-passport/generated-batch.d.ts.map +1 -1
- package/dist/support-passport/grant-contracts.d.ts +75 -381
- package/dist/support-passport/grant-contracts.d.ts.map +1 -1
- package/dist/support-passport/index.js +52 -51
- package/dist/support-passport/model-audit.d.ts +17 -82
- package/dist/support-passport/model-audit.d.ts.map +1 -1
- package/dist/support-passport/model-bridge.d.ts +12 -51
- package/dist/support-passport/model-bridge.d.ts.map +1 -1
- package/dist/support-passport/model-contracts.d.ts +53 -192
- package/dist/support-passport/model-contracts.d.ts.map +1 -1
- package/dist/tokens.js +3 -3
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +10 -10
- package/dist/transfer/types.d.ts +32 -737
- package/dist/transfer/types.d.ts.map +1 -1
- package/dist/verified-recall.js +10 -10
- package/dist/who-knows-cli.js +12 -12
- package/dist/who-knows.js +11 -11
- package/package.json +3 -4
- package/src/access-boundary.test.ts +14 -0
- package/src/access-boundary.ts +48 -42
- package/src/access-category-alias.test.ts +9 -7
- package/src/access-errors.test.ts +21 -9
- package/src/access-errors.ts +45 -31
- package/src/access-http-briefing.ts +4 -0
- package/src/access-http.test.ts +41 -12
- package/src/access-http.ts +6 -0
- package/src/access-observe-write-category.ts +1 -1
- package/src/access-observe-write-surface.ts +24 -19
- package/src/access-operations-batch.ts +18 -2
- package/src/access-schema.ts +13 -17
- package/src/access-service.ts +5 -0
- package/src/briefing-abort.test.ts +173 -0
- package/src/briefing-deadline.test.ts +84 -0
- package/src/briefing-deadline.ts +33 -0
- package/src/briefing-window-abort.test.ts +47 -0
- package/src/briefing-window.ts +33 -4
- package/src/briefing.ts +23 -16
- package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
- package/src/correction/passive-correction-notifications.ts +4 -0
- package/src/entity-lazy-compatibility.test.ts +229 -0
- package/src/entity-migration-completion-notification.test.ts +148 -0
- package/src/extraction-scope-source-agent.test.ts +6 -6
- package/src/extraction-span-wire.test.ts +28 -0
- package/src/extraction-span-wire.ts +34 -6
- package/src/index.ts +1 -1
- package/src/location/tagging.ts +14 -12
- package/src/offline-sync-file-io.ts +61 -2
- package/src/offline-sync.test.ts +313 -20
- package/src/offline-sync.ts +43 -21
- package/src/orchestrator-init.test.ts +49 -1
- package/src/orchestrator.ts +9 -8
- package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
- package/src/storage/entity-canonical-id-migration.ts +19 -10
- package/src/storage/entity-canonical-id-references.ts +24 -0
- package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
- package/src/storage/entity-migration-completion-cache.ts +153 -0
- package/src/storage/entity-store.ts +58 -86
- package/src/storage/memory-read-store.ts +9 -3
- package/src/storage/windowed-memory-read.ts +8 -0
- package/src/storage-startup-nonblocking-migration.test.ts +183 -0
- package/src/storage.ts +7 -2
- package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
- package/dist/chunk-23ZLN5X6.js.map +0 -1
- package/dist/chunk-3MUKQQ7A.js.map +0 -1
- package/dist/chunk-3NYIXVCT.js.map +0 -1
- package/dist/chunk-6W3QH3QW.js.map +0 -1
- package/dist/chunk-7XFQSQYS.js.map +0 -1
- package/dist/chunk-BMG3SAXA.js.map +0 -1
- package/dist/chunk-FRXS6SC7.js.map +0 -1
- package/dist/chunk-LWRIGKFM.js.map +0 -1
- package/dist/chunk-N6EM7ESC.js.map +0 -1
- package/dist/chunk-NH37SP6V.js.map +0 -1
- package/dist/chunk-NUK5XU3P.js.map +0 -1
- package/dist/chunk-O4CA53UY.js.map +0 -1
- package/dist/chunk-RZDDNH63.js.map +0 -1
- package/dist/chunk-SA6P2KS3.js.map +0 -1
- package/dist/chunk-SUKPM6DV.js.map +0 -1
- package/dist/chunk-VY6BD5KB.js.map +0 -1
- /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
- /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → briefing-deadline.js.map} +0 -0
- /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
- /package/dist/{chunk-ESDOQEJM.js.map → chunk-2AKIVSHP.js.map} +0 -0
- /package/dist/{chunk-BWF3KNSQ.js.map → chunk-2EXF6IIB.js.map} +0 -0
- /package/dist/{chunk-AGCEJDG2.js.map → chunk-2ONKXEC7.js.map} +0 -0
- /package/dist/{chunk-ZSEFGDCI.js.map → chunk-2TGIKMMZ.js.map} +0 -0
- /package/dist/{chunk-MCS4LIXP.js.map → chunk-43TUWQNR.js.map} +0 -0
- /package/dist/{chunk-Q6PYCPHO.js.map → chunk-44EUCSG4.js.map} +0 -0
- /package/dist/{chunk-2J6BIOVN.js.map → chunk-4FM35YIA.js.map} +0 -0
- /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
- /package/dist/{chunk-YY6PWFUC.js.map → chunk-6ZAU7LAR.js.map} +0 -0
- /package/dist/{chunk-HCPVL6SF.js.map → chunk-72CBJ5VP.js.map} +0 -0
- /package/dist/{chunk-AF4UIHON.js.map → chunk-74AY3S7U.js.map} +0 -0
- /package/dist/{chunk-5IDQVE4U.js.map → chunk-A3UFV4K3.js.map} +0 -0
- /package/dist/{chunk-XFND63OB.js.map → chunk-AHGOOZUC.js.map} +0 -0
- /package/dist/{chunk-A7AISUJI.js.map → chunk-AJG2TAMR.js.map} +0 -0
- /package/dist/{chunk-2BY56HE6.js.map → chunk-AZLD4CKG.js.map} +0 -0
- /package/dist/{chunk-E47UQBYW.js.map → chunk-B35EQQ7L.js.map} +0 -0
- /package/dist/{chunk-E6UNM67F.js.map → chunk-B6SPPYGJ.js.map} +0 -0
- /package/dist/{chunk-CMOYD5CA.js.map → chunk-CCL2OXFO.js.map} +0 -0
- /package/dist/{chunk-SVJ5JVZO.js.map → chunk-CK6GCGMX.js.map} +0 -0
- /package/dist/{chunk-QZK6HG4J.js.map → chunk-DF6ZF2BN.js.map} +0 -0
- /package/dist/{chunk-CPBYKOTC.js.map → chunk-DJFULNXK.js.map} +0 -0
- /package/dist/{chunk-VGRBKEBH.js.map → chunk-DQOGZZJA.js.map} +0 -0
- /package/dist/{chunk-YB3WN6XA.js.map → chunk-EC2KVA27.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-WIBWONL4.js.map → chunk-EZS6QJNC.js.map} +0 -0
- /package/dist/{chunk-C7WDWD35.js.map → chunk-FNNGWN3O.js.map} +0 -0
- /package/dist/{chunk-PBWJVRKE.js.map → chunk-FT75AQ74.js.map} +0 -0
- /package/dist/{chunk-WJCAAVZX.js.map → chunk-GE3ZX6YK.js.map} +0 -0
- /package/dist/{chunk-3PVKV3XL.js.map → chunk-HCIRR3BA.js.map} +0 -0
- /package/dist/{chunk-BJTFLP3J.js.map → chunk-HEF4BRMT.js.map} +0 -0
- /package/dist/{chunk-6UDLN4IA.js.map → chunk-I6ORDT54.js.map} +0 -0
- /package/dist/{chunk-N5PISU26.js.map → chunk-IN6U2BS5.js.map} +0 -0
- /package/dist/{chunk-3INLTKTW.js.map → chunk-IUQXRRJB.js.map} +0 -0
- /package/dist/{chunk-4KS4IHKH.js.map → chunk-JX3E4XCW.js.map} +0 -0
- /package/dist/{chunk-SW3WHVKN.js.map → chunk-KAJYSO3U.js.map} +0 -0
- /package/dist/{chunk-D76AHITY.js.map → chunk-KETS5A62.js.map} +0 -0
- /package/dist/{chunk-LCHFINL4.js.map → chunk-KIWO4327.js.map} +0 -0
- /package/dist/{chunk-FC3ZFEXF.js.map → chunk-KJ3MDX4V.js.map} +0 -0
- /package/dist/{chunk-7LCGFUMB.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-6TIVVOVU.js.map → chunk-L3EFYRYK.js.map} +0 -0
- /package/dist/{chunk-X52SXKVC.js.map → chunk-LMB47UM6.js.map} +0 -0
- /package/dist/{chunk-A3WXV7YU.js.map → chunk-LWMLBVGB.js.map} +0 -0
- /package/dist/{chunk-KMTMMBEB.js.map → chunk-MC7PXJDP.js.map} +0 -0
- /package/dist/{chunk-NMOEOQV4.js.map → chunk-MUNYUSBU.js.map} +0 -0
- /package/dist/{chunk-VKUMKVEU.js.map → chunk-OCRLJJOV.js.map} +0 -0
- /package/dist/{chunk-XSJL62LE.js.map → chunk-ON77FQXH.js.map} +0 -0
- /package/dist/{chunk-C3HRYVGD.js.map → chunk-POJRFSJM.js.map} +0 -0
- /package/dist/{chunk-LKRXGOQW.js.map → chunk-QA3Y2O6H.js.map} +0 -0
- /package/dist/{chunk-QFTNWDJ6.js.map → chunk-RA7YRL6H.js.map} +0 -0
- /package/dist/{chunk-WQFHHJWK.js.map → chunk-RJDYDF5K.js.map} +0 -0
- /package/dist/{chunk-E3DL3CAL.js.map → chunk-ROAKLYOL.js.map} +0 -0
- /package/dist/{chunk-NWIT3U2N.js.map → chunk-S5CQI2KF.js.map} +0 -0
- /package/dist/{chunk-P6P35FY4.js.map → chunk-SDESKFKT.js.map} +0 -0
- /package/dist/{chunk-OD7ZXCUF.js.map → chunk-SFRBUBUZ.js.map} +0 -0
- /package/dist/{chunk-O7GNSHFF.js.map → chunk-SPTPBKZX.js.map} +0 -0
- /package/dist/{chunk-HI53EK3Y.js.map → chunk-TC55M5PO.js.map} +0 -0
- /package/dist/{chunk-FOKTW6GY.js.map → chunk-UCLJTGP3.js.map} +0 -0
- /package/dist/{chunk-FYZXZUGU.js.map → chunk-UQBOPCDK.js.map} +0 -0
- /package/dist/{chunk-754XN6LX.js.map → chunk-V27FK6D6.js.map} +0 -0
- /package/dist/{chunk-DM6SOROF.js.map → chunk-V6Z4NAJZ.js.map} +0 -0
- /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
- /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
- /package/dist/{chunk-VCBS5GRQ.js.map → chunk-WY66J4PI.js.map} +0 -0
- /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
- /package/dist/{chunk-V4GPFCJ7.js.map → chunk-XWJUFER3.js.map} +0 -0
- /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
- /package/dist/{chunk-CRJX4OMN.js.map → chunk-Y3T5KQCR.js.map} +0 -0
- /package/dist/{chunk-KNCR7APE.js.map → chunk-Y77FXULH.js.map} +0 -0
- /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
- /package/dist/{chunk-IG747LRN.js.map → chunk-YO2JEX2Z.js.map} +0 -0
- /package/dist/{chunk-HDT3DMUF.js.map → chunk-ZKK3VMWU.js.map} +0 -0
- /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
package/src/briefing-window.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { log } from "./logger.js";
|
|
2
|
-
import type { MemoryFile } from "./types.js";
|
|
2
|
+
import type { EntityFile, MemoryFile } from "./types.js";
|
|
3
3
|
import { isAbortError } from "./abort-error.js";
|
|
4
4
|
import type { CorpusReadOptions } from "./corpus-read-cancellation.js";
|
|
5
5
|
import type { ParsedBriefingWindow } from "./briefing.js";
|
|
6
|
+
import type { WindowedMemoryReadOptions } from "./storage/windowed-memory-read.js";
|
|
6
7
|
|
|
7
8
|
type BriefingMemoryReader = {
|
|
8
|
-
readMemoriesWindow?: (options:
|
|
9
|
+
readMemoriesWindow?: (options: WindowedMemoryReadOptions) => Promise<{ memories: MemoryFile[] }>;
|
|
9
10
|
readAllMemories: ((options?: CorpusReadOptions) => Promise<MemoryFile[]>) & {
|
|
10
11
|
supportsAbortSignal?: boolean;
|
|
11
12
|
};
|
|
@@ -30,6 +31,12 @@ export const BRIEFING_FULL_READ_FALLBACK_MS = 30_000;
|
|
|
30
31
|
export interface SafeReadMemoriesOptions {
|
|
31
32
|
/** Test seam overriding {@link BRIEFING_FULL_READ_FALLBACK_MS}. */
|
|
32
33
|
fallbackDeadlineMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Caller cancellation. The signal reaches the storage read so it stops at
|
|
36
|
+
* its next boundary, and a cancelled read is rethrown rather than degraded
|
|
37
|
+
* into an empty corpus (issue #3139).
|
|
38
|
+
*/
|
|
39
|
+
signal?: AbortSignal;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
type BriefingReadMode = "windowed" | "full-read-fallback";
|
|
@@ -138,6 +145,12 @@ export async function safeReadMemories(
|
|
|
138
145
|
resultCount = memories.length;
|
|
139
146
|
return memories;
|
|
140
147
|
} catch (err) {
|
|
148
|
+
if (isAbortError(err) && options.signal?.aborted) {
|
|
149
|
+
// Caller cancellation is not a read failure: rethrow it so the briefing
|
|
150
|
+
// rejects instead of degrading into an empty corpus (issue #3139). The
|
|
151
|
+
// own fallback deadline is reported as BriefingReadTimedOut below.
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
141
154
|
if (err instanceof BriefingReadTimedOut) {
|
|
142
155
|
outcome = "timeout";
|
|
143
156
|
bestEffortLog(
|
|
@@ -174,7 +187,9 @@ async function readBriefingMemories(
|
|
|
174
187
|
// the full corpus on cache misses; keep the full-read fallback for custom
|
|
175
188
|
// StorageManager-compatible callers that predate readMemoriesWindow().
|
|
176
189
|
if (mode === "windowed") {
|
|
177
|
-
const
|
|
190
|
+
const readOptions: WindowedMemoryReadOptions = { updatedAfter: window.from };
|
|
191
|
+
if (options.signal) readOptions.abortSignal = options.signal;
|
|
192
|
+
const result = await storage.readMemoriesWindow!(readOptions);
|
|
178
193
|
return result.memories;
|
|
179
194
|
}
|
|
180
195
|
// Refuse legacy adapters that do not explicitly declare cancellation support (#2827).
|
|
@@ -195,12 +210,13 @@ async function readBriefingMemories(
|
|
|
195
210
|
deadlineFired = true;
|
|
196
211
|
controller.abort();
|
|
197
212
|
}, deadlineMs);
|
|
213
|
+
const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
198
214
|
try {
|
|
199
215
|
// Direct await, no Promise.race: the signal IS the deadline. A
|
|
200
216
|
// signal-aware read settles on abort (AbortError at its next scan
|
|
201
217
|
// boundary, #2307), so nothing is left scanning in the background once
|
|
202
218
|
// this function returns.
|
|
203
|
-
const memories = await storage.readAllMemories({ abortSignal:
|
|
219
|
+
const memories = await storage.readAllMemories({ abortSignal: signal });
|
|
204
220
|
if (deadlineFired) {
|
|
205
221
|
// A double that resolves partial rows instead of rejecting on abort
|
|
206
222
|
// must not have them served as a complete corpus.
|
|
@@ -214,3 +230,16 @@ async function readBriefingMemories(
|
|
|
214
230
|
clearTimeout(timer);
|
|
215
231
|
}
|
|
216
232
|
}
|
|
233
|
+
|
|
234
|
+
export async function safeReadEntities(
|
|
235
|
+
storage: { readAllEntityFiles(options?: CorpusReadOptions): Promise<EntityFile[]> },
|
|
236
|
+
signal: AbortSignal,
|
|
237
|
+
): Promise<EntityFile[]> {
|
|
238
|
+
try {
|
|
239
|
+
return await storage.readAllEntityFiles({ abortSignal: signal });
|
|
240
|
+
} catch (err) {
|
|
241
|
+
if (isAbortError(err) && signal.aborted) throw err;
|
|
242
|
+
log.warn(`briefing: readAllEntityFiles failed: ${err}`);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
}
|
package/src/briefing.ts
CHANGED
|
@@ -19,12 +19,14 @@ import { readFile } from "node:fs/promises";
|
|
|
19
19
|
import os from "node:os";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { log } from "./logger.js";
|
|
22
|
+
import { isAbortError, throwIfAborted } from "./abort-error.js";
|
|
23
|
+
import { withBriefingDeadline } from "./briefing-deadline.js";
|
|
22
24
|
import { withBriefingFollowupTimeout } from "./briefing-followup-timeout.js";
|
|
23
25
|
import { extractJsonCandidates } from "./json-extract.js";
|
|
24
26
|
import { drainPassiveCorrectionNotifications } from "./correction/passive-correction-notifications.js";
|
|
25
27
|
import { excludeSupportPassportPrivateMemories } from "./support-passport/card-projection.js";
|
|
26
28
|
import { normalizeEntityName, StorageManager } from "./storage.js";
|
|
27
|
-
import { safeReadMemories } from "./briefing-window.js";
|
|
29
|
+
import { safeReadEntities, safeReadMemories } from "./briefing-window.js";
|
|
28
30
|
import { readEnvVar, resolveHomeDir } from "./runtime/env.js";
|
|
29
31
|
import type {
|
|
30
32
|
BriefingActiveThread,
|
|
@@ -817,6 +819,8 @@ export interface BuildBriefingOptions {
|
|
|
817
819
|
followupGenerator?: BriefingFollowupGenerator;
|
|
818
820
|
/** Injected "now" — makes tests deterministic. */
|
|
819
821
|
now?: Date;
|
|
822
|
+
/** Caller cancellation must not consume correction notifications. */
|
|
823
|
+
abortSignal?: AbortSignal;
|
|
820
824
|
}
|
|
821
825
|
|
|
822
826
|
const MAX_ACTIVE_THREADS = 8;
|
|
@@ -830,15 +834,23 @@ const MAX_OPEN_COMMITMENTS = 8;
|
|
|
830
834
|
* omitted and `followupsUnavailableReason` is set.
|
|
831
835
|
*/
|
|
832
836
|
export async function buildBriefing(options: BuildBriefingOptions): Promise<BriefingResult> {
|
|
837
|
+
return withBriefingDeadline(
|
|
838
|
+
(signal) => buildBriefingWithSignal(options, signal),
|
|
839
|
+
options.abortSignal,
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
async function buildBriefingWithSignal(options: BuildBriefingOptions, signal: AbortSignal): Promise<BriefingResult> {
|
|
833
844
|
const now = options.now ?? new Date();
|
|
834
845
|
const window = options.window ?? defaultWindow(now);
|
|
835
846
|
const maxFollowups = clampFollowups(options.maxFollowups);
|
|
836
847
|
const focus = options.focus ?? null;
|
|
837
848
|
|
|
838
849
|
const [allMemories, allEntities] = await Promise.all([
|
|
839
|
-
safeReadMemories(options.storage, window).then(excludeSupportPassportPrivateMemories),
|
|
840
|
-
safeReadEntities(options.storage),
|
|
850
|
+
safeReadMemories(options.storage, window, { signal }).then(excludeSupportPassportPrivateMemories),
|
|
851
|
+
safeReadEntities(options.storage, signal),
|
|
841
852
|
]);
|
|
853
|
+
throwIfAborted(signal, "briefing aborted");
|
|
842
854
|
|
|
843
855
|
const memoriesInWindow = filterMemoriesByWindow(allMemories, window);
|
|
844
856
|
const focusedMemories = focus
|
|
@@ -852,6 +864,7 @@ export async function buildBriefing(options: BuildBriefingOptions): Promise<Brie
|
|
|
852
864
|
const calendarLoadResult = options.calendarSource
|
|
853
865
|
? await loadTodayCalendar(options.calendarSource, now)
|
|
854
866
|
: undefined;
|
|
867
|
+
throwIfAborted(signal, "briefing aborted");
|
|
855
868
|
|
|
856
869
|
const calendarSourceErrors: BriefingCalendarSourceError[] =
|
|
857
870
|
calendarLoadResult?.error ? [calendarLoadResult.error] : [];
|
|
@@ -904,18 +917,20 @@ export async function buildBriefing(options: BuildBriefingOptions): Promise<Brie
|
|
|
904
917
|
log.warn(`briefing: ${followupsUnavailableReason}`);
|
|
905
918
|
}
|
|
906
919
|
}
|
|
920
|
+
throwIfAborted(signal, "briefing aborted");
|
|
907
921
|
|
|
908
922
|
const sections: BriefingSections = {
|
|
909
923
|
...sectionsBase,
|
|
910
924
|
suggestedFollowups: followups,
|
|
911
925
|
};
|
|
912
926
|
|
|
913
|
-
// Drain auto-applied passive-correction notifications (issue #1581)
|
|
914
|
-
//
|
|
915
|
-
//
|
|
927
|
+
// Drain auto-applied passive-correction notifications (issue #1581): cleared on read,
|
|
928
|
+
// so each surfaces in exactly one briefing. Fail-open except cancellation, which must
|
|
929
|
+
// not consume notifications nobody receives.
|
|
930
|
+
throwIfAborted(signal, "briefing aborted");
|
|
916
931
|
let correctionNotifications: BriefingResult["correctionNotifications"];
|
|
917
932
|
try {
|
|
918
|
-
const drained = await drainPassiveCorrectionNotifications(options.storage.dir);
|
|
933
|
+
const drained = await drainPassiveCorrectionNotifications(options.storage.dir, signal);
|
|
919
934
|
if (drained.length > 0) {
|
|
920
935
|
correctionNotifications = drained.map((n) => ({
|
|
921
936
|
planId: n.planId,
|
|
@@ -925,6 +940,7 @@ export async function buildBriefing(options: BuildBriefingOptions): Promise<Brie
|
|
|
925
940
|
}));
|
|
926
941
|
}
|
|
927
942
|
} catch (err) {
|
|
943
|
+
if (isAbortError(err)) throw err;
|
|
928
944
|
log.debug(`briefing: correction notification drain failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
929
945
|
}
|
|
930
946
|
|
|
@@ -977,15 +993,6 @@ function defaultWindow(now: Date): ParsedBriefingWindow {
|
|
|
977
993
|
return { from: new Date(now.getTime() - DAY_MS), to: now, label: "yesterday" };
|
|
978
994
|
}
|
|
979
995
|
|
|
980
|
-
async function safeReadEntities(storage: StorageManager): Promise<EntityFile[]> {
|
|
981
|
-
try {
|
|
982
|
-
return await storage.readAllEntityFiles();
|
|
983
|
-
} catch (err) {
|
|
984
|
-
log.warn(`briefing: readAllEntityFiles failed: ${err}`);
|
|
985
|
-
return [];
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
996
|
function memoryTimestamp(memory: MemoryFile): number {
|
|
990
997
|
const raw = memory.frontmatter.updated || memory.frontmatter.created;
|
|
991
998
|
if (!raw) return 0;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import {
|
|
8
|
+
drainPassiveCorrectionNotifications,
|
|
9
|
+
enqueuePassiveCorrectionNotification,
|
|
10
|
+
} from "./passive-correction-notifications.js";
|
|
11
|
+
|
|
12
|
+
// Issue #3139 review: a briefing cancelled mid-drain must leave the queue
|
|
13
|
+
// intact, because the caller never receives what the drain consumed.
|
|
14
|
+
test("a drain cancelled after reading keeps the notification queue", async (t) => {
|
|
15
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "remnic-3139-drain-"));
|
|
16
|
+
t.after(() => fs.rm(dir, { recursive: true, force: true }));
|
|
17
|
+
await enqueuePassiveCorrectionNotification(dir, {
|
|
18
|
+
planId: "plan-1",
|
|
19
|
+
summary: "kept",
|
|
20
|
+
undoCommand: "undo",
|
|
21
|
+
createdAt: new Date(0).toISOString(),
|
|
22
|
+
});
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const read: (file: string, encoding: "utf-8") => Promise<string> = fs.readFile;
|
|
25
|
+
t.mock.method(fs, "readFile", async (file: string) => {
|
|
26
|
+
const content = await read(file, "utf-8");
|
|
27
|
+
controller.abort(new Error("deadline"));
|
|
28
|
+
return content;
|
|
29
|
+
});
|
|
30
|
+
syncBuiltinESMExports();
|
|
31
|
+
t.after(() => {
|
|
32
|
+
t.mock.restoreAll();
|
|
33
|
+
syncBuiltinESMExports();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await assert.rejects(drainPassiveCorrectionNotifications(dir, controller.signal), { name: "AbortError" });
|
|
37
|
+
|
|
38
|
+
t.mock.restoreAll();
|
|
39
|
+
syncBuiltinESMExports();
|
|
40
|
+
const kept = await drainPassiveCorrectionNotifications(dir);
|
|
41
|
+
assert.equal(kept.length, 1, "the cancelled drain must not consume the queue");
|
|
42
|
+
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { mkdir, readFile, rename, writeFile, unlink } from "node:fs/promises";
|
|
16
16
|
import path from "node:path";
|
|
17
|
+
import { throwIfAborted } from "../abort-error.js";
|
|
17
18
|
import { serializeMutations } from "../utils/serialize-mutations.js";
|
|
18
19
|
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
@@ -84,6 +85,7 @@ export async function enqueuePassiveCorrectionNotification(
|
|
|
84
85
|
*/
|
|
85
86
|
export async function drainPassiveCorrectionNotifications(
|
|
86
87
|
storageDir: string,
|
|
88
|
+
signal?: AbortSignal,
|
|
87
89
|
): Promise<PassiveCorrectionNotification[]> {
|
|
88
90
|
const filePath = notificationsPath(storageDir);
|
|
89
91
|
return serializeMutations(`passive-notification:${filePath}`, async () => {
|
|
@@ -96,6 +98,8 @@ export async function drainPassiveCorrectionNotifications(
|
|
|
96
98
|
throw err;
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
// Cancellation after the read must keep the queue: the caller never receives these.
|
|
102
|
+
throwIfAborted(signal, "briefing aborted");
|
|
99
103
|
const notifications: PassiveCorrectionNotification[] = [];
|
|
100
104
|
for (const line of raw.split("\n")) {
|
|
101
105
|
const trimmed = line.trim();
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rename, 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
|
+
import { StorageManager, parseEntityFile } from "./storage.js";
|
|
7
|
+
import { sleep } from "./utils/sleep.js";
|
|
8
|
+
|
|
9
|
+
async function legacyStore() {
|
|
10
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-lazy-compat-"));
|
|
11
|
+
const seed = new StorageManager(dir);
|
|
12
|
+
const id = await seed.writeEntity("Legacy Person", "person", ["Keeps a synthetic task."]);
|
|
13
|
+
await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-person.md"));
|
|
14
|
+
const journal = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
|
|
15
|
+
await rm(journal, { force: true });
|
|
16
|
+
const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
|
|
17
|
+
await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
|
|
18
|
+
const storage = new StorageManager(dir);
|
|
19
|
+
await storage.ensureDirectories({ waitForEntityMigration: false });
|
|
20
|
+
return { dir, id, journal, lock, storage };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test("canonical reads find a legacy file without a journal or migration", async () => {
|
|
24
|
+
const fixture = await legacyStore();
|
|
25
|
+
try {
|
|
26
|
+
assert.match(await fixture.storage.readEntity(fixture.id), /Keeps a synthetic task/);
|
|
27
|
+
await assert.rejects(() => readFile(fixture.journal), { code: "ENOENT" });
|
|
28
|
+
assert.ok(await readFile(fixture.lock));
|
|
29
|
+
} finally {
|
|
30
|
+
StorageManager.clearAllStaticCaches();
|
|
31
|
+
await rm(fixture.dir, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("ambiguous legacy canonical matches are refused rather than picking a claimant", async () => {
|
|
36
|
+
const fixture = await legacyStore();
|
|
37
|
+
try {
|
|
38
|
+
const raw = await readFile(path.join(fixture.dir, "entities", "legacy-person.md"));
|
|
39
|
+
await writeFile(path.join(fixture.dir, "entities", "other-legacy-person.md"), raw);
|
|
40
|
+
await assert.rejects(() => fixture.storage.readEntity(fixture.id), /ambiguous/i);
|
|
41
|
+
} finally {
|
|
42
|
+
StorageManager.clearAllStaticCaches();
|
|
43
|
+
await rm(fixture.dir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("completed migration skips corpus reads on repeated misses in a fresh read-only store", async () => {
|
|
48
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-miss-"));
|
|
49
|
+
try {
|
|
50
|
+
const seed = new StorageManager(dir);
|
|
51
|
+
await seed.writeEntity("Existing Person", "person", ["Synthetic fact."]);
|
|
52
|
+
await seed.ensureDirectories();
|
|
53
|
+
const reader = new StorageManager(dir);
|
|
54
|
+
await reader.ensureDirectories({ waitForEntityMigration: false });
|
|
55
|
+
// Bracket access is TypeScript's typed escape hatch for the private reader.
|
|
56
|
+
const originalRead = reader["readStorageSecureFile"].bind(reader);
|
|
57
|
+
const reads: string[] = [];
|
|
58
|
+
reader["readStorageSecureFile"] = async (filePath: string) => {
|
|
59
|
+
reads.push(path.basename(filePath));
|
|
60
|
+
return originalRead(filePath);
|
|
61
|
+
};
|
|
62
|
+
assert.equal(await reader.readEntity("person-missing"), "");
|
|
63
|
+
assert.equal(await reader.readEntity("person-missing"), "");
|
|
64
|
+
assert.deepEqual(reads, ["person-missing.md", "person-missing.md"], "completed-store misses must not scan entity contents");
|
|
65
|
+
} finally {
|
|
66
|
+
StorageManager.clearAllStaticCaches();
|
|
67
|
+
await rm(dir, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("an external entities change invalidates the amortized completion verdict on the same reader", async () => {
|
|
72
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-cache-"));
|
|
73
|
+
try {
|
|
74
|
+
const seed = new StorageManager(dir);
|
|
75
|
+
const id = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
|
|
76
|
+
await seed.ensureDirectories();
|
|
77
|
+
const reader = new StorageManager(dir);
|
|
78
|
+
await reader.ensureDirectories({ waitForEntityMigration: false });
|
|
79
|
+
const canonical = reader.normalizeEntityName("Café", "project");
|
|
80
|
+
// First miss computes and caches the completion verdict.
|
|
81
|
+
assert.equal(await reader.readEntity("project-missing"), "");
|
|
82
|
+
// An external rename changes the entities directory; the cached verdict
|
|
83
|
+
// must not keep hiding the now-legacy file from the same reader.
|
|
84
|
+
await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-cafe.md"));
|
|
85
|
+
assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
|
|
86
|
+
} finally {
|
|
87
|
+
StorageManager.clearAllStaticCaches();
|
|
88
|
+
await rm(dir, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("an external aliases change invalidates the amortized completion verdict on the same reader", async () => {
|
|
93
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-alias-cache-"));
|
|
94
|
+
try {
|
|
95
|
+
const seed = new StorageManager(dir);
|
|
96
|
+
const previousId = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
|
|
97
|
+
await seed.ensureDirectories();
|
|
98
|
+
const journalPath = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
|
|
99
|
+
const journal = await readFile(journalPath, "utf8");
|
|
100
|
+
assert.equal(JSON.parse(journal).complete, true);
|
|
101
|
+
|
|
102
|
+
const reader = new StorageManager(dir);
|
|
103
|
+
await reader.ensureDirectories({ waitForEntityMigration: false });
|
|
104
|
+
// Warm amortized completion (and any same-reader completion memo) on a miss.
|
|
105
|
+
assert.equal(await reader.readEntity("project-missing"), "");
|
|
106
|
+
|
|
107
|
+
// Block migration repair so fingerprint drift stays visible to reads.
|
|
108
|
+
const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
|
|
109
|
+
await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
|
|
110
|
+
|
|
111
|
+
// External aliases.json is a getFingerprint input. It must invalidate a
|
|
112
|
+
// warm completed verdict without entities membership or mutation-version change.
|
|
113
|
+
// The entity file stays at previousId; only the alias-derived canonical id moves.
|
|
114
|
+
await mkdir(path.join(dir, "config"), { recursive: true });
|
|
115
|
+
await writeFile(path.join(dir, "config", "aliases.json"), JSON.stringify({ café: "coffee" }));
|
|
116
|
+
await reader.loadAliases();
|
|
117
|
+
const canonical = reader.normalizeEntityName("Café", "project");
|
|
118
|
+
assert.notEqual(canonical, previousId);
|
|
119
|
+
|
|
120
|
+
assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
|
|
121
|
+
assert.equal(await readFile(journalPath, "utf8"), journal, "read compatibility must not replay migration");
|
|
122
|
+
assert.ok(await readFile(lock));
|
|
123
|
+
} finally {
|
|
124
|
+
StorageManager.clearAllStaticCaches();
|
|
125
|
+
await rm(dir, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
for (const change of ["aliases", "raw legacy write"]) {
|
|
131
|
+
test(`stale completed journals do not hide canonical reads after ${change}`, async () => {
|
|
132
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-stale-completion-"));
|
|
133
|
+
try {
|
|
134
|
+
const seed = new StorageManager(dir);
|
|
135
|
+
const previousId = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
|
|
136
|
+
await seed.ensureDirectories();
|
|
137
|
+
const journalPath = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
|
|
138
|
+
const journal = await readFile(journalPath, "utf8");
|
|
139
|
+
assert.equal(JSON.parse(journal).complete, true);
|
|
140
|
+
if (change === "aliases") {
|
|
141
|
+
await mkdir(path.join(dir, "config"), { recursive: true });
|
|
142
|
+
await writeFile(path.join(dir, "config", "aliases.json"), JSON.stringify({ café: "coffee" }));
|
|
143
|
+
} else {
|
|
144
|
+
await rename(path.join(dir, "entities", `${previousId}.md`), path.join(dir, "entities", "legacy-cafe.md"));
|
|
145
|
+
await writeFile(path.join(dir, "state", "entity-canonical-id-reconcile.pending"), "synthetic raw writer\n");
|
|
146
|
+
}
|
|
147
|
+
const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
|
|
148
|
+
await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
|
|
149
|
+
const reader = new StorageManager(dir);
|
|
150
|
+
await reader.ensureDirectories({ waitForEntityMigration: false });
|
|
151
|
+
const canonical = reader.normalizeEntityName("Café", "project");
|
|
152
|
+
if (change === "aliases") assert.notEqual(canonical, previousId);
|
|
153
|
+
assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
|
|
154
|
+
assert.equal(await readFile(journalPath, "utf8"), journal, "read compatibility must not replay migration");
|
|
155
|
+
assert.ok(await readFile(lock));
|
|
156
|
+
} finally {
|
|
157
|
+
StorageManager.clearAllStaticCaches();
|
|
158
|
+
await rm(dir, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const mutations = [
|
|
164
|
+
{
|
|
165
|
+
name: "relationship",
|
|
166
|
+
run: (s: StorageManager, id: string) => s.addEntityRelationship(id, { target: "person-peer", label: "knows" }),
|
|
167
|
+
check: (raw: string) => assert.ok(parseEntityFile(raw).relationships.some((r) => r.label === "knows")),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "activity",
|
|
171
|
+
run: (s: StorageManager, id: string) => s.addEntityActivity(id, { date: "2026-01-01", note: "Synthetic activity" }, 10),
|
|
172
|
+
check: (raw: string) => assert.ok(parseEntityFile(raw).activity.some((a) => a.note === "Synthetic activity")),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "alias",
|
|
176
|
+
run: (s: StorageManager, id: string) => s.addEntityAlias(id, "synthetic-alias"),
|
|
177
|
+
check: (raw: string) => assert.ok(parseEntityFile(raw).aliases.includes("synthetic-alias")),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "synthesis",
|
|
181
|
+
run: (s: StorageManager, id: string) => s.updateEntitySynthesis(id, "Synthetic synthesis"),
|
|
182
|
+
check: (raw: string) => assert.equal(parseEntityFile(raw).synthesis, "Synthetic synthesis"),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "fragment merge",
|
|
186
|
+
run: (s: StorageManager) => s.mergeFragmentedEntities(),
|
|
187
|
+
check: (raw: string) => assert.match(raw, /Keeps a synthetic task/),
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "timeline migration",
|
|
191
|
+
run: (s: StorageManager) => s.migrateEntityFilesToCompiledTruthTimeline(),
|
|
192
|
+
check: (raw: string) => assert.match(raw, /Keeps a synthetic task/),
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
for (const caller of ["canonical", "legacy"]) {
|
|
197
|
+
for (const mutation of mutations) {
|
|
198
|
+
test(`${mutation.name} awaits migration for a ${caller} caller`, async () => {
|
|
199
|
+
const fixture = await legacyStore();
|
|
200
|
+
let settled = false;
|
|
201
|
+
let failure: Error | undefined;
|
|
202
|
+
const operation = mutation.run(fixture.storage, caller === "legacy" ? "legacy-person" : fixture.id).then(
|
|
203
|
+
() => { settled = true; },
|
|
204
|
+
(error: Error) => { failure = error; settled = true; },
|
|
205
|
+
);
|
|
206
|
+
let probeFailure: unknown;
|
|
207
|
+
try {
|
|
208
|
+
await sleep(100);
|
|
209
|
+
assert.equal(settled, false, "a public entity mutation must wait behind the migration lock");
|
|
210
|
+
} catch (error) {
|
|
211
|
+
probeFailure = error;
|
|
212
|
+
} finally {
|
|
213
|
+
await rm(fixture.lock, { force: true });
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
await operation;
|
|
217
|
+
if (probeFailure) throw probeFailure;
|
|
218
|
+
assert.equal(failure, undefined);
|
|
219
|
+
const raw = await readFile(path.join(fixture.dir, "entities", `${fixture.id}.md`), "utf8");
|
|
220
|
+
mutation.check(raw);
|
|
221
|
+
const journal = JSON.parse(await readFile(fixture.journal, "utf8"));
|
|
222
|
+
assert.equal(journal.complete, true);
|
|
223
|
+
} finally {
|
|
224
|
+
StorageManager.clearAllStaticCaches();
|
|
225
|
+
await rm(fixture.dir, { recursive: true, force: true });
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, rename, rm } 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 { parseConfig } from "./config.js";
|
|
8
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
9
|
+
import { StorageManager } from "./storage.js";
|
|
10
|
+
|
|
11
|
+
// #3240 follow-up: the host's onEntityMigrationComplete slot must fire once
|
|
12
|
+
// per migration pass that rewrote entities (never on no-op replays or ensure() no-ops), fail open on a
|
|
13
|
+
// throwing hook, reach router-created storages independent of auto-embed, and
|
|
14
|
+
// be inert after Orchestrator.destroy(). No direct QMD calls; the re-arm is
|
|
15
|
+
// observed through a requestQmdMaintenance spy.
|
|
16
|
+
|
|
17
|
+
async function legacyEntityStore(prefix: string): Promise<string> {
|
|
18
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), prefix));
|
|
19
|
+
const seed = new StorageManager(dir);
|
|
20
|
+
const id = await seed.writeEntity("Legacy Person", "person", ["Keeps a synthetic task."]);
|
|
21
|
+
await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-person.md"));
|
|
22
|
+
await rm(path.join(dir, "state", "entity-canonical-id-migration-v1.json"), { force: true });
|
|
23
|
+
return dir;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test("real storage fires the completion slot only for a pass that rewrote entities, and fails open on a throwing hook", async () => {
|
|
27
|
+
const dir = await legacyEntityStore("remnic-migration-complete-");
|
|
28
|
+
const emptyDir = await mkdtemp(path.join(os.tmpdir(), "remnic-migration-noop-"));
|
|
29
|
+
const throwingDir = await legacyEntityStore("remnic-migration-throwing-");
|
|
30
|
+
try {
|
|
31
|
+
const storage = new StorageManager(dir);
|
|
32
|
+
let completions = 0;
|
|
33
|
+
storage.onEntityMigrationComplete = () => {
|
|
34
|
+
completions += 1;
|
|
35
|
+
};
|
|
36
|
+
await storage.ensureDirectories();
|
|
37
|
+
assert.equal(completions, 1, "a migration pass that renamed a legacy entity must fire the completion slot");
|
|
38
|
+
await storage.ensureDirectories();
|
|
39
|
+
assert.equal(completions, 1, "a second ensureDirectories must not re-fire the slot");
|
|
40
|
+
|
|
41
|
+
const noop = new StorageManager(emptyDir);
|
|
42
|
+
let noopCompletions = 0;
|
|
43
|
+
noop.onEntityMigrationComplete = () => {
|
|
44
|
+
noopCompletions += 1;
|
|
45
|
+
};
|
|
46
|
+
await noop.ensureDirectories();
|
|
47
|
+
assert.equal(noopCompletions, 0, "a no-op replay must not ask search maintenance to run");
|
|
48
|
+
|
|
49
|
+
const throwing = new StorageManager(throwingDir);
|
|
50
|
+
let hookCalls = 0;
|
|
51
|
+
throwing.onEntityMigrationComplete = () => {
|
|
52
|
+
hookCalls += 1;
|
|
53
|
+
throw new Error("consumer hook exploded");
|
|
54
|
+
};
|
|
55
|
+
await throwing.ensureDirectories();
|
|
56
|
+
assert.equal(hookCalls, 1, "the throwing hook must still be invoked");
|
|
57
|
+
} finally {
|
|
58
|
+
StorageManager.clearAllStaticCaches();
|
|
59
|
+
await rm(dir, { recursive: true, force: true });
|
|
60
|
+
await rm(emptyDir, { recursive: true, force: true });
|
|
61
|
+
await rm(throwingDir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("orchestrator wires completion with auto-embed off and blocks it from teardown admission", async () => {
|
|
66
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-migration-complete-orch-"));
|
|
67
|
+
const orchestrator = new Orchestrator(
|
|
68
|
+
parseConfig({
|
|
69
|
+
memoryDir: dir,
|
|
70
|
+
workspaceDir: path.join(dir, "workspace"),
|
|
71
|
+
qmdEnabled: false,
|
|
72
|
+
qmdAutoEmbedEnabled: false,
|
|
73
|
+
namespacesEnabled: true,
|
|
74
|
+
namespaceCatalogEnabled: true,
|
|
75
|
+
defaultNamespace: "default",
|
|
76
|
+
sharedNamespace: "shared",
|
|
77
|
+
namespacePolicies: [],
|
|
78
|
+
embeddingFallbackEnabled: false,
|
|
79
|
+
identityContinuityEnabled: false,
|
|
80
|
+
transcriptEnabled: false,
|
|
81
|
+
hourlySummariesEnabled: false,
|
|
82
|
+
compoundingEnabled: false,
|
|
83
|
+
knowledgeIndexEnabled: false,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
// Test-only access to the Orchestrator's private wiring fields: bracket access
|
|
87
|
+
// is TypeScript's typed escape hatch for private members, so no assertion.
|
|
88
|
+
const internals = {
|
|
89
|
+
get storage() { return orchestrator["storage"]; },
|
|
90
|
+
get storageRouter() { return orchestrator["storageRouter"]; },
|
|
91
|
+
get maintenanceScheduler() { return orchestrator["maintenanceScheduler"]; },
|
|
92
|
+
set requestQmdMaintenance(fn: () => void) { orchestrator["requestQmdMaintenance"] = fn; },
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
// Wiring must exist independent of auto-embed.
|
|
96
|
+
const onMigrationComplete = internals.storage.onEntityMigrationComplete;
|
|
97
|
+
assert.ok(
|
|
98
|
+
onMigrationComplete instanceof Function,
|
|
99
|
+
"the default storage must carry the migration completion slot without auto-embed",
|
|
100
|
+
);
|
|
101
|
+
const namespaceStorage = await internals.storageRouter.storageFor("proj-a");
|
|
102
|
+
const onNamespaceMigrationComplete = namespaceStorage.onEntityMigrationComplete;
|
|
103
|
+
assert.ok(
|
|
104
|
+
onNamespaceMigrationComplete instanceof Function,
|
|
105
|
+
"a router-created namespace storage must carry the slot without auto-embed",
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const rearmRequests: string[] = [];
|
|
109
|
+
internals.requestQmdMaintenance = () => {
|
|
110
|
+
rearmRequests.push("rearm");
|
|
111
|
+
};
|
|
112
|
+
assert.ok(onMigrationComplete);
|
|
113
|
+
onMigrationComplete();
|
|
114
|
+
assert.ok(onNamespaceMigrationComplete);
|
|
115
|
+
onNamespaceMigrationComplete();
|
|
116
|
+
assert.deepEqual(rearmRequests, ["rearm", "rearm"], "a completion must re-arm QMD maintenance");
|
|
117
|
+
|
|
118
|
+
const { promise: disposeGate, resolve: releaseDispose } = Promise.withResolvers<void>();
|
|
119
|
+
const { promise: disposing, resolve: signalDispose } = Promise.withResolvers<void>();
|
|
120
|
+
const originalDispose = internals.maintenanceScheduler.dispose.bind(internals.maintenanceScheduler);
|
|
121
|
+
internals.maintenanceScheduler.dispose = async () => {
|
|
122
|
+
signalDispose();
|
|
123
|
+
await disposeGate;
|
|
124
|
+
await originalDispose();
|
|
125
|
+
};
|
|
126
|
+
const teardown = orchestrator.destroy();
|
|
127
|
+
try {
|
|
128
|
+
await disposing;
|
|
129
|
+
onMigrationComplete();
|
|
130
|
+
onNamespaceMigrationComplete();
|
|
131
|
+
assert.deepEqual(rearmRequests, ["rearm", "rearm"], "teardown admission must stop completion re-arms before drains");
|
|
132
|
+
} finally {
|
|
133
|
+
releaseDispose();
|
|
134
|
+
await teardown;
|
|
135
|
+
}
|
|
136
|
+
onMigrationComplete();
|
|
137
|
+
onNamespaceMigrationComplete();
|
|
138
|
+
assert.deepEqual(
|
|
139
|
+
rearmRequests,
|
|
140
|
+
["rearm", "rearm"],
|
|
141
|
+
"a completion after destroy must not re-arm maintenance",
|
|
142
|
+
);
|
|
143
|
+
} finally {
|
|
144
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
145
|
+
StorageManager.clearAllStaticCaches();
|
|
146
|
+
await rm(dir, { recursive: true, force: true });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
@@ -92,13 +92,13 @@ test("scope prompt qualifies tool knowledge as project, requires naming the agen
|
|
|
92
92
|
assert.match(captured.system, /"`git status --short` emits compact output" -> "global"/);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
//
|
|
96
|
-
//
|
|
95
|
+
// zod 4: superRefine attaches checks to the object itself — the schema stays
|
|
96
|
+
// a ZodObject, so the scope field's describe text is on `.shape` directly.
|
|
97
97
|
function scopeDescribe(): string {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
};
|
|
101
|
-
return
|
|
98
|
+
// SAFETY: ExtractedFactSchema is a ZodObject in zod 4 (superRefine attaches
|
|
99
|
+
// checks in place); its `.shape` maps field names to schema nodes.
|
|
100
|
+
const schema = ExtractedFactSchema as { shape: Record<string, { description?: string }> };
|
|
101
|
+
return schema.shape.scope?.description ?? "";
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
test("scope field schema describe no longer lists tool configs and qualifies by agent", () => {
|