@remnic/core 9.22.0 → 9.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +15 -12
- package/dist/access-admin-ops-surface.js +25 -24
- package/dist/access-audit.js +2 -2
- package/dist/access-boundary.d.ts +13 -10
- package/dist/access-boundary.js +25 -24
- package/dist/access-cli.js +84 -82
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +16 -9
- package/dist/access-http.js +33 -32
- package/dist/access-identity-continuity-surface.d.ts +9 -7
- package/dist/access-identity-continuity-surface.js +25 -24
- package/dist/access-lcm-surface.d.ts +12 -9
- package/dist/access-lcm-surface.js +25 -24
- package/dist/access-mcp-output-schemas.js +1 -1
- package/dist/access-mcp.d.ts +22 -10
- package/dist/access-mcp.js +31 -30
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-tmwQgCT6.d.ts → access-namespace-preflight-CmErR03h.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +25 -24
- package/dist/access-observe-write-surface.d.ts +12 -9
- package/dist/access-observe-write-surface.js +25 -24
- package/dist/access-operations-batch.js +27 -26
- package/dist/access-operations.d.ts +16 -13
- package/dist/access-operations.js +28 -27
- package/dist/access-recall-concurrency.d.ts +12 -9
- package/dist/access-recall-concurrency.js +25 -24
- package/dist/access-recall-response.d.ts +12 -9
- package/dist/access-recall-response.js +25 -24
- package/dist/access-recall-surface.d.ts +14 -11
- package/dist/access-recall-surface.js +25 -24
- package/dist/access-schema.d.ts +76 -76
- package/dist/{access-service-BtxW52vv.d.ts → access-service-CKEROeFP.d.ts} +22 -11
- package/dist/access-service.d.ts +13 -10
- package/dist/access-service.js +25 -24
- package/dist/access-surface-catalog.d.ts +12 -9
- package/dist/access-surface-catalog.js +6 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-wearables-meetings-surface.d.ts +78 -0
- package/dist/access-wearables-meetings-surface.js +22 -0
- package/dist/access-wearables-meetings-surface.js.map +1 -0
- 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-UPTUVE4X.js → auto-sync-5OE5QOZB.js} +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -7
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +5 -5
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-iWvWz61I.d.ts → catalog-BFTVo2Ez.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +9 -9
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-GF22HWS2.js → chunk-2PHX3I5C.js} +7 -7
- package/dist/{chunk-JFDEYXF7.js → chunk-3NAHZQU3.js} +2 -2
- package/dist/{chunk-XBRPEGXF.js → chunk-3UOSVTFR.js} +5 -5
- package/dist/{chunk-TTKWTT72.js → chunk-3ZW6ZQO6.js} +3 -3
- package/dist/{chunk-UV72DEZD.js → chunk-4B77XSG5.js} +5 -5
- package/dist/{chunk-IC2TCZJ2.js → chunk-57QKLRBV.js} +3 -1
- package/dist/chunk-57QKLRBV.js.map +1 -0
- package/dist/{chunk-LMHDFOVV.js → chunk-5CPI2QWG.js} +3 -3
- package/dist/{chunk-P72WZMWQ.js → chunk-5FZQ74K5.js} +2 -2
- package/dist/{chunk-BRT7KEXU.js → chunk-6TWXQ4E2.js} +5 -5
- package/dist/{chunk-CV6ZYXK3.js → chunk-7I7AYIUQ.js} +3 -3
- package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
- package/dist/{chunk-MONWCSVL.js → chunk-7RAJ3GXO.js} +2 -1
- package/dist/chunk-7RAJ3GXO.js.map +1 -0
- package/dist/{chunk-SQEDDWAM.js → chunk-A2RFKL3V.js} +5 -34
- package/dist/chunk-A2RFKL3V.js.map +1 -0
- package/dist/{chunk-JN57HZCD.js → chunk-ATEAL5EI.js} +6 -6
- package/dist/{chunk-4PYLBXX6.js → chunk-AYAROCWD.js} +2 -2
- package/dist/chunk-BLDBT6HZ.js +39 -0
- package/dist/chunk-BLDBT6HZ.js.map +1 -0
- package/dist/{chunk-NB2P47N7.js → chunk-CUUY5CC4.js} +6 -6
- package/dist/{chunk-KL5Y6PEV.js → chunk-E3ZMFFQZ.js} +2 -2
- package/dist/{chunk-55FDQYPS.js → chunk-ES5IHQ6M.js} +2 -2
- package/dist/{chunk-N47ZWQBO.js → chunk-G2NSSQES.js} +2 -2
- package/dist/{chunk-GSWPU5CR.js → chunk-GD3QOBMB.js} +2 -2
- package/dist/{chunk-6ANPNUVL.js → chunk-IIXLB5E7.js} +58 -36
- package/dist/chunk-IIXLB5E7.js.map +1 -0
- package/dist/{chunk-SACPRJ7F.js → chunk-JNIJBXVK.js} +2 -2
- package/dist/{chunk-W3XT32W7.js → chunk-JVGUY2AU.js} +23 -13
- package/dist/chunk-JVGUY2AU.js.map +1 -0
- package/dist/{chunk-DGLON3AL.js → chunk-KNRG6ZGJ.js} +6 -6
- package/dist/{chunk-GEUDRENF.js → chunk-MMTG3SCJ.js} +2 -2
- package/dist/{chunk-WZFAPHIK.js → chunk-MU7ZHSZE.js} +2 -2
- package/dist/{chunk-W4FBGYGK.js → chunk-NRV4Y3PM.js} +1 -1
- package/dist/{chunk-QTO2FGBT.js → chunk-OUW4PAY4.js} +191 -117
- package/dist/chunk-OUW4PAY4.js.map +1 -0
- package/dist/{chunk-KQ2XCQYN.js → chunk-P2BLHUL2.js} +2 -2
- package/dist/{chunk-ACGGTPU4.js → chunk-QCKQEBIY.js} +17 -9
- package/dist/chunk-QCKQEBIY.js.map +1 -0
- package/dist/{chunk-6L775BUB.js → chunk-QE63OHLB.js} +2 -2
- package/dist/{chunk-ONHRRJZM.js → chunk-QJAQNXWI.js} +6 -6
- package/dist/{chunk-EQYBHXRR.js → chunk-ROUL7RZU.js} +385 -59
- package/dist/chunk-ROUL7RZU.js.map +1 -0
- package/dist/{chunk-N6M7MRVN.js → chunk-RT4TSR2C.js} +9034 -8726
- package/dist/chunk-RT4TSR2C.js.map +1 -0
- package/dist/{chunk-6NZ7U4R4.js → chunk-SCOT5DTA.js} +1 -1
- package/dist/chunk-SCOT5DTA.js.map +1 -0
- package/dist/{chunk-6RYWNBSA.js → chunk-SVZGA2MK.js} +2 -2
- package/dist/chunk-SYOIVK7K.js +63 -0
- package/dist/chunk-SYOIVK7K.js.map +1 -0
- package/dist/{chunk-DM5A4ZFL.js → chunk-TWCOYET2.js} +11 -1
- package/dist/chunk-TWCOYET2.js.map +1 -0
- package/dist/{chunk-7S5OMI76.js → chunk-WS32QRLO.js} +109 -44
- package/dist/chunk-WS32QRLO.js.map +1 -0
- package/dist/{chunk-OCMLGND7.js → chunk-WWZ35GWW.js} +5 -5
- package/dist/{chunk-QMGIQUBG.js → chunk-XWPFZX6S.js} +503 -139
- package/dist/chunk-XWPFZX6S.js.map +1 -0
- package/dist/{chunk-A5N6ISRK.js → chunk-ZN4UZWMT.js} +2 -2
- package/dist/{chunk-7M6APKJL.js → chunk-ZYU7ZFLT.js} +2 -2
- package/dist/{cli-D9vXgWwM.d.ts → cli-BrwT9YMD.d.ts} +5 -5
- package/dist/cli.d.ts +14 -11
- package/dist/cli.js +57 -56
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +5 -5
- 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 +5 -5
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +5 -5
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- 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/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +5 -5
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -7
- package/dist/extraction-error-classification.d.ts +22 -0
- package/dist/extraction-error-classification.js +11 -0
- package/dist/extraction-error-classification.js.map +1 -0
- 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 +3 -17
- package/dist/extraction.js +5 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-LEXL3XYL.js → first-start-migration-BKNGOFED.js} +4 -4
- 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/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +713 -923
- package/dist/index.js +176 -140
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +11 -11
- 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 +6 -6
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +7 -7
- package/dist/maintenance-C98njYb8.d.ts +366 -0
- package/dist/mcp-memory-inspector-app.d.ts +12 -9
- package/dist/mcp-read-only-tools.js +1 -1
- 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 +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +12 -12
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +5 -5
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +2 -2
- package/dist/operator-toolkit.js +18 -18
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +6 -223
- package/dist/orchestration/maintenance.js +12 -12
- package/dist/{orchestrator-DB93S48I.d.ts → orchestrator-C9ZVppNh.d.ts} +12 -481
- package/dist/orchestrator.d.ts +10 -8
- package/dist/orchestrator.js +84 -82
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-DmGc-H66.d.ts → pipeline-id_-sqYm.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-cJT0crkF.d.ts → qmd-R6XaQOZK.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/qmd.js +4 -4
- package/dist/recall-concurrency-config.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-explain-renderer.js +3 -3
- package/dist/recall-memory-map.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-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +4 -4
- package/dist/retrieval-agents.d.ts +2 -2
- 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/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +76 -76
- package/dist/{scope-profiles-KG2nODX7.d.ts → scope-profiles-EhyuAKkd.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +5 -5
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +9 -9
- 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-BaZlg0D8.d.ts → semantic-consolidation-B2xo26UQ.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +7 -7
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +6 -6
- package/dist/service-DgFWlZ-r.d.ts +835 -0
- 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 +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/{storage-BKhHHnRo.d.ts → storage-XGawwqXE.d.ts} +131 -2
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +4 -4
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.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 +2 -2
- 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 +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-GBQZuT7l.d.ts → types-Bbs0qz6b.d.ts} +2 -3
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +6 -6
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-boundary.ts +3 -0
- package/src/access-http.test.ts +63 -0
- package/src/access-http.ts +26 -37
- package/src/access-mcp-output-schemas.ts +10 -0
- package/src/access-mcp.ts +4 -121
- package/src/access-operations-batch.ts +10 -5
- package/src/access-service.ts +43 -46
- package/src/access-surface-catalog.ts +6 -0
- package/src/access-wearables-meetings-surface.ts +139 -0
- package/src/activity/digest.ts +1 -1
- package/src/activity/runner.test.ts +37 -0
- package/src/activity/runner.ts +12 -0
- package/src/activity/scheduler.test.ts +1 -0
- package/src/activity/sync-registration.ts +13 -0
- package/src/cli/meetings-commands.ts +70 -0
- package/src/cli.ts +7 -6
- package/src/extraction-error-classification.ts +52 -0
- package/src/extraction.ts +2 -50
- package/src/mcp-read-only-tools.ts +2 -0
- package/src/meetings/build-memory.test.ts +364 -0
- package/src/meetings/build-scheduler.test.ts +55 -0
- package/src/meetings/build-scheduler.ts +96 -0
- package/src/meetings/day-source.test.ts +275 -0
- package/src/meetings/day-source.ts +352 -0
- package/src/meetings/http-glue.ts +126 -0
- package/src/meetings/index.ts +5 -4
- package/src/meetings/manual-sync-flush.test.ts +72 -0
- package/src/meetings/mcp-tools.ts +65 -0
- package/src/meetings/memory-gen.test.ts +498 -0
- package/src/meetings/memory-gen.ts +549 -0
- package/src/meetings/memory-generator.ts +5 -0
- package/src/meetings/namespace-isolation.test.ts +159 -0
- package/src/meetings/service.test.ts +195 -0
- package/src/meetings/service.ts +157 -0
- package/src/meetings/summary-extractor.test.ts +287 -0
- package/src/meetings/summary-extractor.ts +264 -0
- package/src/orchestration/maintenance.ts +8 -0
- package/src/orchestration/orchestrator-init.ts +6 -1
- package/src/orchestration/workspace-ops.ts +172 -8
- package/src/orchestrator.ts +12 -13
- package/src/storage.ts +21 -0
- package/src/testing/subjects/meetings-namespace-harness.ts +159 -0
- package/src/testing/subjects/meetings-namespace.test.ts +93 -0
- package/src/types.ts +1 -2
- package/src/wearables/cli.ts +2 -1
- package/src/wearables/index.ts +1 -0
- package/src/wearables/mcp-tools.ts +143 -0
- package/src/wearables/registry.test.ts +11 -0
- package/src/wearables/registry.ts +13 -0
- package/src/wearables/service.test.ts +62 -1
- package/src/wearables/service.ts +30 -2
- package/src/wearables/transcript-search-namespace-isolation.test.ts +98 -0
- package/dist/chunk-6ANPNUVL.js.map +0 -1
- package/dist/chunk-6NZ7U4R4.js.map +0 -1
- package/dist/chunk-7S5OMI76.js.map +0 -1
- package/dist/chunk-ACGGTPU4.js.map +0 -1
- package/dist/chunk-DM5A4ZFL.js.map +0 -1
- package/dist/chunk-EQYBHXRR.js.map +0 -1
- package/dist/chunk-IC2TCZJ2.js.map +0 -1
- package/dist/chunk-MONWCSVL.js.map +0 -1
- package/dist/chunk-N6M7MRVN.js.map +0 -1
- package/dist/chunk-QMGIQUBG.js.map +0 -1
- package/dist/chunk-QTO2FGBT.js.map +0 -1
- package/dist/chunk-SQEDDWAM.js.map +0 -1
- package/dist/chunk-W3XT32W7.js.map +0 -1
- /package/dist/{auto-sync-UPTUVE4X.js.map → auto-sync-5OE5QOZB.js.map} +0 -0
- /package/dist/{chunk-GF22HWS2.js.map → chunk-2PHX3I5C.js.map} +0 -0
- /package/dist/{chunk-JFDEYXF7.js.map → chunk-3NAHZQU3.js.map} +0 -0
- /package/dist/{chunk-XBRPEGXF.js.map → chunk-3UOSVTFR.js.map} +0 -0
- /package/dist/{chunk-TTKWTT72.js.map → chunk-3ZW6ZQO6.js.map} +0 -0
- /package/dist/{chunk-UV72DEZD.js.map → chunk-4B77XSG5.js.map} +0 -0
- /package/dist/{chunk-LMHDFOVV.js.map → chunk-5CPI2QWG.js.map} +0 -0
- /package/dist/{chunk-P72WZMWQ.js.map → chunk-5FZQ74K5.js.map} +0 -0
- /package/dist/{chunk-BRT7KEXU.js.map → chunk-6TWXQ4E2.js.map} +0 -0
- /package/dist/{chunk-CV6ZYXK3.js.map → chunk-7I7AYIUQ.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
- /package/dist/{chunk-JN57HZCD.js.map → chunk-ATEAL5EI.js.map} +0 -0
- /package/dist/{chunk-4PYLBXX6.js.map → chunk-AYAROCWD.js.map} +0 -0
- /package/dist/{chunk-NB2P47N7.js.map → chunk-CUUY5CC4.js.map} +0 -0
- /package/dist/{chunk-KL5Y6PEV.js.map → chunk-E3ZMFFQZ.js.map} +0 -0
- /package/dist/{chunk-55FDQYPS.js.map → chunk-ES5IHQ6M.js.map} +0 -0
- /package/dist/{chunk-N47ZWQBO.js.map → chunk-G2NSSQES.js.map} +0 -0
- /package/dist/{chunk-GSWPU5CR.js.map → chunk-GD3QOBMB.js.map} +0 -0
- /package/dist/{chunk-SACPRJ7F.js.map → chunk-JNIJBXVK.js.map} +0 -0
- /package/dist/{chunk-DGLON3AL.js.map → chunk-KNRG6ZGJ.js.map} +0 -0
- /package/dist/{chunk-GEUDRENF.js.map → chunk-MMTG3SCJ.js.map} +0 -0
- /package/dist/{chunk-WZFAPHIK.js.map → chunk-MU7ZHSZE.js.map} +0 -0
- /package/dist/{chunk-W4FBGYGK.js.map → chunk-NRV4Y3PM.js.map} +0 -0
- /package/dist/{chunk-KQ2XCQYN.js.map → chunk-P2BLHUL2.js.map} +0 -0
- /package/dist/{chunk-6L775BUB.js.map → chunk-QE63OHLB.js.map} +0 -0
- /package/dist/{chunk-ONHRRJZM.js.map → chunk-QJAQNXWI.js.map} +0 -0
- /package/dist/{chunk-6RYWNBSA.js.map → chunk-SVZGA2MK.js.map} +0 -0
- /package/dist/{chunk-OCMLGND7.js.map → chunk-WWZ35GWW.js.map} +0 -0
- /package/dist/{chunk-A5N6ISRK.js.map → chunk-ZN4UZWMT.js.map} +0 -0
- /package/dist/{chunk-7M6APKJL.js.map → chunk-ZYU7ZFLT.js.map} +0 -0
- /package/dist/{first-start-migration-LEXL3XYL.js.map → first-start-migration-BKNGOFED.js.map} +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-derived namespace-symmetry isolation tests (issue #2123).
|
|
3
|
+
*
|
|
4
|
+
* Two principals on distinct NON-default namespaces exercise the full
|
|
5
|
+
* wearablesSync -> meetingsBuild path against a real Orchestrator +
|
|
6
|
+
* EngramAccessService. Asserts (mirroring the extraction-run namespace-isolation
|
|
7
|
+
* pattern, keyed on storageForNs(ns).dir):
|
|
8
|
+
* 1. principal A's wearable source transcript, meeting record, and episode
|
|
9
|
+
* memory physically land under A's namespace root;
|
|
10
|
+
* 2. principal B (a different non-default namespace) sees NONE of A's data;
|
|
11
|
+
* 3. a non-default caller receives NEITHER default-ns wearable days NOR the
|
|
12
|
+
* machine-global activity store — a day with only default wearables + global
|
|
13
|
+
* activity yields zero meetings for a non-default caller, while the
|
|
14
|
+
* DEFAULT/machine-owner caller DOES consume both.
|
|
15
|
+
*
|
|
16
|
+
* These fail before the caller-derived namespace fix: with a single
|
|
17
|
+
* machine-default-pinned service, A's writes would land in the default root (not
|
|
18
|
+
* A's), B would observe them, and a non-default caller would fuse default
|
|
19
|
+
* wearables + global activity.
|
|
20
|
+
*/
|
|
21
|
+
import assert from "node:assert/strict";
|
|
22
|
+
import test from "node:test";
|
|
23
|
+
|
|
24
|
+
import { EngramAccessInputError } from "../access-service.js";
|
|
25
|
+
|
|
26
|
+
import { MEETING_SOURCE_PREFIX } from "./memory-gen.js";
|
|
27
|
+
import {
|
|
28
|
+
FAKE_WEARABLE_SOURCE,
|
|
29
|
+
buildMeetingsNamespaceHarness,
|
|
30
|
+
} from "../testing/subjects/meetings-namespace-harness.js";
|
|
31
|
+
|
|
32
|
+
const scope = (namespace: string, principal: string) => ({
|
|
33
|
+
namespace,
|
|
34
|
+
authenticatedPrincipal: principal,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function meetingMemoryCount(memories: ReadonlyArray<{ frontmatter: { tags?: readonly string[] } }>): number {
|
|
38
|
+
return memories.filter((m) => (m.frontmatter.tags ?? []).includes(MEETING_SOURCE_PREFIX)).length;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("namespace isolation: principal A's wearable source, meeting record, and episode memory land under A's namespace root", async () => {
|
|
42
|
+
const h = await buildMeetingsNamespaceHarness();
|
|
43
|
+
const DATE = "2026-03-10";
|
|
44
|
+
try {
|
|
45
|
+
const sync = await h.service.wearablesSync({ date: DATE, ...scope("nsA", "pA") });
|
|
46
|
+
assert.equal(sync[0]?.transcriptsWritten.includes(DATE), true, "sync wrote a transcript for the day");
|
|
47
|
+
|
|
48
|
+
// The manual sync already flushed the debounced tail-step build (P2 fix;
|
|
49
|
+
// see manual-sync-flush.test.ts), so this explicit build is an idempotent
|
|
50
|
+
// rebuild: detection is stable (the audio meeting re-detects) and the
|
|
51
|
+
// unchanged record regenerates no duplicate episode. The record + episode
|
|
52
|
+
// that physically landed under nsA are asserted on storage below.
|
|
53
|
+
const build = await h.service.meetingsBuild(DATE, scope("nsA", "pA"));
|
|
54
|
+
assert.equal(build.enabled, true);
|
|
55
|
+
assert.equal(build.meetings.length, 1, "one audio-only meeting detected from A's wearables");
|
|
56
|
+
assert.equal(build.meetings[0]?.detectionSource, "audio");
|
|
57
|
+
|
|
58
|
+
const storageA = await h.storageForNs("nsA");
|
|
59
|
+
assert.notEqual(
|
|
60
|
+
await storageA.readWearableDayTranscript(FAKE_WEARABLE_SOURCE, DATE),
|
|
61
|
+
null,
|
|
62
|
+
"wearable source transcript physically under nsA root",
|
|
63
|
+
);
|
|
64
|
+
assert.deepEqual(
|
|
65
|
+
await storageA.meetingRecordStore().listMeetingDates(),
|
|
66
|
+
[DATE],
|
|
67
|
+
"meeting record physically under nsA root",
|
|
68
|
+
);
|
|
69
|
+
assert.equal(meetingMemoryCount(await storageA.readAllMemories()), 1, "episode memory under nsA root");
|
|
70
|
+
} finally {
|
|
71
|
+
await h.cleanup();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("namespace isolation: principal B (a different non-default namespace) sees NONE of A's data", async () => {
|
|
76
|
+
const h = await buildMeetingsNamespaceHarness();
|
|
77
|
+
const DATE = "2026-03-10";
|
|
78
|
+
try {
|
|
79
|
+
await h.service.wearablesSync({ date: DATE, ...scope("nsA", "pA") });
|
|
80
|
+
await h.service.meetingsBuild(DATE, scope("nsA", "pA"));
|
|
81
|
+
|
|
82
|
+
const buildB = await h.service.meetingsBuild(DATE, scope("nsB", "pB"));
|
|
83
|
+
assert.equal(buildB.meetings.length, 0, "B builds zero meetings — A's wearables are not visible");
|
|
84
|
+
|
|
85
|
+
const storageB = await h.storageForNs("nsB");
|
|
86
|
+
assert.equal(
|
|
87
|
+
await storageB.readWearableDayTranscript(FAKE_WEARABLE_SOURCE, DATE),
|
|
88
|
+
null,
|
|
89
|
+
"no wearable transcript under nsB root",
|
|
90
|
+
);
|
|
91
|
+
assert.deepEqual(await storageB.meetingRecordStore().listMeetingDates(), [], "no meeting record under nsB root");
|
|
92
|
+
assert.equal(meetingMemoryCount(await storageB.readAllMemories()), 0, "no meeting memory under nsB root");
|
|
93
|
+
} finally {
|
|
94
|
+
await h.cleanup();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("namespace isolation: a non-default caller gets neither default-ns wearables nor global activity; the machine-owner does", async () => {
|
|
99
|
+
const h = await buildMeetingsNamespaceHarness();
|
|
100
|
+
const DATE = "2026-05-20";
|
|
101
|
+
try {
|
|
102
|
+
// Only DEFAULT-ns wearables + the machine-global activity store exist for the day.
|
|
103
|
+
await h.service.wearablesSync({ date: DATE, ...scope("default", "op") });
|
|
104
|
+
h.seedGlobalActivity(DATE);
|
|
105
|
+
|
|
106
|
+
// Non-default caller: strict isolation — no default-ns wearable fallback,
|
|
107
|
+
// no machine-global activity → zero meetings.
|
|
108
|
+
const buildNonDefault = await h.service.meetingsBuild(DATE, scope("nsB", "pB"));
|
|
109
|
+
assert.equal(buildNonDefault.meetings.length, 0, "non-default caller sees no default wearables and no activity");
|
|
110
|
+
|
|
111
|
+
// Machine-owner (default) caller: consumes default wearables AND global activity.
|
|
112
|
+
const buildDefault = await h.service.meetingsBuild(DATE, scope("default", "op"));
|
|
113
|
+
assert.equal(buildDefault.meetings.length, 1, "default caller detects the meeting from default wearables");
|
|
114
|
+
assert.ok(
|
|
115
|
+
(buildDefault.meetings[0]?.snapshotCount ?? 0) > 0,
|
|
116
|
+
"default caller consumed machine-global activity (screen context snapshots)",
|
|
117
|
+
);
|
|
118
|
+
} finally {
|
|
119
|
+
await h.cleanup();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("namespace isolation: a write-only principal (in writePrincipals, not readPrincipals) is DENIED meetingsBuild, while read+write and the machine owner succeed", async () => {
|
|
124
|
+
const h = await buildMeetingsNamespaceHarness();
|
|
125
|
+
const DATE = "2026-07-14";
|
|
126
|
+
try {
|
|
127
|
+
// pWO can WRITE nsWO, so the sync (a pure write) is allowed and physically
|
|
128
|
+
// lands a source transcript under nsWO — the very data meetingsBuild reads
|
|
129
|
+
// back and derives from.
|
|
130
|
+
const sync = await h.service.wearablesSync({ date: DATE, ...scope("nsWO", "pWO") });
|
|
131
|
+
assert.equal(sync[0]?.transcriptsWritten.includes(DATE), true, "write-only principal's sync wrote a transcript");
|
|
132
|
+
|
|
133
|
+
// meetingsBuild READS those transcripts + meeting records to derive the day's
|
|
134
|
+
// meetings. A WRITE grant alone must NOT confer that read: pWO is omitted from
|
|
135
|
+
// nsWO.readPrincipals, so build is DENIED with the shared read-ACL error.
|
|
136
|
+
// (Pre-fix, meetingsBuild authorized via writableNamespaceFor only, so pWO
|
|
137
|
+
// would read nsWO's transcripts back and return meetings — the breach.)
|
|
138
|
+
await assert.rejects(
|
|
139
|
+
() => h.service.meetingsBuild(DATE, scope("nsWO", "pWO")),
|
|
140
|
+
(err: unknown) => err instanceof EngramAccessInputError && /not readable/.test((err as Error).message),
|
|
141
|
+
"write-only principal is denied meetingsBuild on a namespace it cannot read",
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Positive control: a principal with BOTH read and write on its namespace
|
|
145
|
+
// builds successfully.
|
|
146
|
+
await h.service.wearablesSync({ date: DATE, ...scope("nsA", "pA") });
|
|
147
|
+
const buildRW = await h.service.meetingsBuild(DATE, scope("nsA", "pA"));
|
|
148
|
+
assert.equal(buildRW.enabled, true, "read+write principal builds successfully");
|
|
149
|
+
assert.equal(buildRW.meetings.length, 1, "read+write principal derives its meeting");
|
|
150
|
+
|
|
151
|
+
// The default/machine-owner principal is unaffected (full access to default).
|
|
152
|
+
await h.service.wearablesSync({ date: DATE, ...scope("default", "op") });
|
|
153
|
+
const buildDefault = await h.service.meetingsBuild(DATE, scope("default", "op"));
|
|
154
|
+
assert.equal(buildDefault.enabled, true, "machine owner still builds on the default namespace");
|
|
155
|
+
assert.equal(buildDefault.meetings.length, 1, "machine owner derives its meeting");
|
|
156
|
+
} finally {
|
|
157
|
+
await h.cleanup();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import { MeetingsBuilder, type MeetingDayData, type MeetingsDaySource } from "./build.js";
|
|
5
|
+
import { DEFAULT_MEETINGS_CONFIG } from "./config.js";
|
|
6
|
+
import { MeetingRecordStore, type MeetingRecordFileIo } from "./store.js";
|
|
7
|
+
import { MeetingsInputError } from "./errors.js";
|
|
8
|
+
import { MeetingsService } from "./service.js";
|
|
9
|
+
import type { MeetingsConfig } from "./types.js";
|
|
10
|
+
|
|
11
|
+
const MEMORY_DIR = "/mem";
|
|
12
|
+
const DATE = "2026-03-10";
|
|
13
|
+
const START = "2026-03-10T14:00:00.000Z";
|
|
14
|
+
const END = "2026-03-10T15:00:00.000Z";
|
|
15
|
+
|
|
16
|
+
class InMemoryIo implements MeetingRecordFileIo {
|
|
17
|
+
files = new Map<string, string>();
|
|
18
|
+
async writeFile(p: string, c: string): Promise<void> {
|
|
19
|
+
this.files.set(p, c);
|
|
20
|
+
}
|
|
21
|
+
async readFile(p: string): Promise<string> {
|
|
22
|
+
const v = this.files.get(p);
|
|
23
|
+
if (v === undefined) throw enoent();
|
|
24
|
+
return v;
|
|
25
|
+
}
|
|
26
|
+
async readDir(dirPath: string): Promise<string[]> {
|
|
27
|
+
const prefix = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
|
|
28
|
+
const names = new Set<string>();
|
|
29
|
+
let found = false;
|
|
30
|
+
for (const key of this.files.keys()) {
|
|
31
|
+
if (!key.startsWith(prefix)) continue;
|
|
32
|
+
found = true;
|
|
33
|
+
names.add(key.slice(prefix.length).split("/")[0]!);
|
|
34
|
+
}
|
|
35
|
+
if (!found) throw enoent();
|
|
36
|
+
return [...names];
|
|
37
|
+
}
|
|
38
|
+
async deleteFile(p: string): Promise<void> {
|
|
39
|
+
if (!this.files.delete(p)) throw enoent();
|
|
40
|
+
}
|
|
41
|
+
async realpath(p: string): Promise<string> {
|
|
42
|
+
return p;
|
|
43
|
+
}
|
|
44
|
+
async lstat(): Promise<{ isSymbolicLink: boolean }> {
|
|
45
|
+
return { isSymbolicLink: false };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function enoent(): NodeJS.ErrnoException {
|
|
50
|
+
const err = new Error("ENOENT") as NodeJS.ErrnoException;
|
|
51
|
+
err.code = "ENOENT";
|
|
52
|
+
return err;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function config(overrides: Partial<MeetingsConfig> = {}): MeetingsConfig {
|
|
56
|
+
return {
|
|
57
|
+
...DEFAULT_MEETINGS_CONFIG,
|
|
58
|
+
appPatterns: [...DEFAULT_MEETINGS_CONFIG.appPatterns],
|
|
59
|
+
enabled: true,
|
|
60
|
+
...overrides,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A day source that detects one app+audio meeting for DATE. */
|
|
65
|
+
function dayData(): MeetingDayData {
|
|
66
|
+
return {
|
|
67
|
+
detection: {
|
|
68
|
+
date: DATE,
|
|
69
|
+
appSpans: [{ app: "Zoom", startUtc: START, endUtc: END }],
|
|
70
|
+
audioWindows: [{ source: "desktop", startUtc: START, endUtc: END, distinctNonWearerSpeakers: 2 }],
|
|
71
|
+
},
|
|
72
|
+
conversations: [
|
|
73
|
+
{
|
|
74
|
+
source: "desktop",
|
|
75
|
+
conversationId: "c1",
|
|
76
|
+
startIso: START,
|
|
77
|
+
endIso: END,
|
|
78
|
+
segments: [{ speaker: "Jane", isSelf: false, text: "hello", startIso: "2026-03-10T14:05:00.000Z" }],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function makeService(cfg: MeetingsConfig, source: MeetingsDaySource): {
|
|
85
|
+
service: MeetingsService;
|
|
86
|
+
store: MeetingRecordStore;
|
|
87
|
+
} {
|
|
88
|
+
const store = new MeetingRecordStore(MEMORY_DIR, new InMemoryIo());
|
|
89
|
+
const builder = new MeetingsBuilder({ source, store, config: cfg });
|
|
90
|
+
const service = new MeetingsService({ config: cfg, store, builder, buildDebounceMs: 10_000 });
|
|
91
|
+
return { service, store };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
test("meetingsBuild detects + stores, and meetingsList/meetingsGet then surface it", async () => {
|
|
95
|
+
const { service } = makeService(config(), { loadDayData: () => dayData() });
|
|
96
|
+
const built = await service.meetingsBuild(DATE);
|
|
97
|
+
assert.equal(built.enabled, true);
|
|
98
|
+
assert.equal(built.meetings.length, 1);
|
|
99
|
+
const id = built.meetings[0]!.id;
|
|
100
|
+
|
|
101
|
+
const list = await service.meetingsList(DATE);
|
|
102
|
+
assert.equal(list.enabled, true);
|
|
103
|
+
assert.equal(list.days.length, 1);
|
|
104
|
+
assert.equal(list.days[0]?.date, DATE);
|
|
105
|
+
assert.equal(list.days[0]?.meetings[0]?.id, id);
|
|
106
|
+
|
|
107
|
+
const listAll = await service.meetingsList();
|
|
108
|
+
assert.deepEqual(listAll.days.map((d) => d.date), [DATE]);
|
|
109
|
+
|
|
110
|
+
const got = await service.meetingsGet(id);
|
|
111
|
+
assert.equal(got.enabled, true);
|
|
112
|
+
assert.equal(got.found, true);
|
|
113
|
+
assert.equal(got.id, id);
|
|
114
|
+
assert.match(got.record ?? "", /## Transcript/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("meetingsGet reports a missing record without throwing", async () => {
|
|
118
|
+
const { service } = makeService(config(), { loadDayData: () => dayData() });
|
|
119
|
+
const got = await service.meetingsGet("mtg-2026-03-10-deadbeef");
|
|
120
|
+
assert.equal(got.found, false);
|
|
121
|
+
assert.equal(got.record, null);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("disabled mode: every entrypoint is a no-op", async () => {
|
|
125
|
+
const { service, store } = makeService(config({ enabled: false }), { loadDayData: () => dayData() });
|
|
126
|
+
|
|
127
|
+
const built = await service.meetingsBuild(DATE);
|
|
128
|
+
assert.equal(built.enabled, false);
|
|
129
|
+
assert.deepEqual(built.meetings, []);
|
|
130
|
+
assert.deepEqual(await store.listMeetingDates(), [], "disabled build writes nothing");
|
|
131
|
+
|
|
132
|
+
const list = await service.meetingsList(DATE);
|
|
133
|
+
assert.equal(list.enabled, false);
|
|
134
|
+
assert.deepEqual(list.days, []);
|
|
135
|
+
|
|
136
|
+
const got = await service.meetingsGet("mtg-2026-03-10-deadbeef");
|
|
137
|
+
assert.equal(got.enabled, false);
|
|
138
|
+
assert.equal(got.found, false);
|
|
139
|
+
|
|
140
|
+
// requestBuild is a no-op when disabled: flushing must build nothing.
|
|
141
|
+
service.requestBuild(DATE);
|
|
142
|
+
await service.flushBuilds();
|
|
143
|
+
assert.deepEqual(await store.listMeetingDates(), []);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("invalid inputs surface MeetingsInputError (mapped to 400 by the transports)", async () => {
|
|
147
|
+
const { service } = makeService(config(), { loadDayData: () => dayData() });
|
|
148
|
+
await assert.rejects(() => service.meetingsList("2026-13-40"), MeetingsInputError);
|
|
149
|
+
await assert.rejects(() => service.meetingsBuild("not-a-date"), MeetingsInputError);
|
|
150
|
+
await assert.rejects(() => service.meetingsGet("not-a-meeting-id"), MeetingsInputError);
|
|
151
|
+
await assert.rejects(() => service.meetingsGet("mtg-2026-13-40-abcdef01"), MeetingsInputError);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("requestBuild + flush runs the debounced tail-step build when enabled", async () => {
|
|
155
|
+
const { service, store } = makeService(config(), { loadDayData: () => dayData() });
|
|
156
|
+
service.requestBuild(DATE);
|
|
157
|
+
service.requestBuild(DATE); // coalesced
|
|
158
|
+
await service.flushBuilds();
|
|
159
|
+
assert.deepEqual(await store.listMeetingDates(), [DATE], "the coalesced tail-step build persisted the day");
|
|
160
|
+
const summaries = await store.listMeetingSummaries(DATE);
|
|
161
|
+
assert.equal(summaries.length, 1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("requestBuildForActivitySync rebuilds every touched date, not just the sync tick's day (issue #1900)", async () => {
|
|
165
|
+
const source: MeetingsDaySource = {
|
|
166
|
+
loadDayData: (date) => ({
|
|
167
|
+
detection: {
|
|
168
|
+
date,
|
|
169
|
+
appSpans: [{ app: "Zoom", startUtc: `${date}T14:00:00.000Z`, endUtc: `${date}T15:00:00.000Z` }],
|
|
170
|
+
audioWindows: [
|
|
171
|
+
{ source: "desktop", startUtc: `${date}T14:00:00.000Z`, endUtc: `${date}T15:00:00.000Z`, distinctNonWearerSpeakers: 2 },
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
conversations: [
|
|
175
|
+
{
|
|
176
|
+
source: "desktop",
|
|
177
|
+
conversationId: "c1",
|
|
178
|
+
startIso: `${date}T14:00:00.000Z`,
|
|
179
|
+
endIso: `${date}T15:00:00.000Z`,
|
|
180
|
+
segments: [{ speaker: "Jane", isSelf: false, text: "hi", startIso: `${date}T14:05:00.000Z` }],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
const { service, store } = makeService(config(), source);
|
|
186
|
+
// The sync tick ran today (2026-03-10); a rolling/backfill window also changed
|
|
187
|
+
// yesterday's activity. Both days must rebuild — not just the tick's local day.
|
|
188
|
+
service.requestBuildForActivitySync("2026-03-10T14:30:00.000Z", "UTC", ["2026-03-09", "2026-03-10"]);
|
|
189
|
+
await service.flushBuilds();
|
|
190
|
+
assert.deepEqual(
|
|
191
|
+
(await store.listMeetingDates()).sort(),
|
|
192
|
+
["2026-03-09", "2026-03-10"],
|
|
193
|
+
"the backfilled older day is rebuilt too, not only the tick's local day",
|
|
194
|
+
);
|
|
195
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meetings service (issue #1900) — the orchestrator-owned facade every meeting
|
|
3
|
+
* surface (CLI, MCP tools, HTTP routes) shares, so behavior + validation never
|
|
4
|
+
* fork across hosts (the same rule the wearables service follows). It owns the
|
|
5
|
+
* record store, the store-backed builder, and the debounced post-sync build
|
|
6
|
+
* scheduler.
|
|
7
|
+
*
|
|
8
|
+
* `meetings.enabled` is honored at every entrypoint: `meetingsList`/`meetingsGet`
|
|
9
|
+
* short-circuit to an empty/absent result when disabled, `meetingsBuild`
|
|
10
|
+
* delegates to the builder (which already reports `enabled: false` and writes
|
|
11
|
+
* nothing), and the tail-step `requestBuild` is a no-op. Caller-correctable
|
|
12
|
+
* faults (bad dates/ids) throw `MeetingsInputError`, which the transports map to
|
|
13
|
+
* 400-class responses; the CLI reuses the store + builder directly through the
|
|
14
|
+
* shared `runMeetingsCliCommand` runner.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { isValidTranscriptDate } from "../wearables/day-store.js";
|
|
18
|
+
import { formatDateInTimeZone } from "../orchestration/orchestrator-helpers.js";
|
|
19
|
+
import { MeetingsBuildScheduler } from "./build-scheduler.js";
|
|
20
|
+
import type { MeetingsBuilder, MeetingsDayBuildSummary } from "./build.js";
|
|
21
|
+
import { MeetingsInputError } from "./errors.js";
|
|
22
|
+
import { meetingIdDate, type MeetingRecordStore, type MeetingRecordSummary } from "./store.js";
|
|
23
|
+
import type { MeetingsConfig } from "./types.js";
|
|
24
|
+
|
|
25
|
+
/** One day's stored meeting summaries in a list result. */
|
|
26
|
+
export interface MeetingsListDay {
|
|
27
|
+
date: string;
|
|
28
|
+
meetings: MeetingRecordSummary[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Structured result of a meetings list query (all days, or one day). */
|
|
32
|
+
export interface MeetingsListResult {
|
|
33
|
+
/** False when `meetings.enabled` is off (zero behavior). */
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
days: MeetingsListDay[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Structured result of fetching one stored meeting record. */
|
|
39
|
+
export interface MeetingsGetResult {
|
|
40
|
+
/** False when `meetings.enabled` is off (zero behavior). */
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
found: boolean;
|
|
43
|
+
id: string;
|
|
44
|
+
/** Raw persisted markdown, or null when disabled/absent. */
|
|
45
|
+
record: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MeetingsServiceDeps {
|
|
49
|
+
config: MeetingsConfig;
|
|
50
|
+
store: MeetingRecordStore;
|
|
51
|
+
builder: MeetingsBuilder;
|
|
52
|
+
/** Trailing-edge coalescing window for post-sync rebuilds (ms). */
|
|
53
|
+
buildDebounceMs: number;
|
|
54
|
+
/** Observe a tail-step build failure (logging); never called on success. */
|
|
55
|
+
onBuildError?(date: string, err: unknown): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class MeetingsService {
|
|
59
|
+
readonly store: MeetingRecordStore;
|
|
60
|
+
readonly builder: MeetingsBuilder;
|
|
61
|
+
readonly config: MeetingsConfig;
|
|
62
|
+
private readonly scheduler: MeetingsBuildScheduler;
|
|
63
|
+
|
|
64
|
+
constructor(deps: MeetingsServiceDeps) {
|
|
65
|
+
this.config = deps.config;
|
|
66
|
+
this.store = deps.store;
|
|
67
|
+
this.builder = deps.builder;
|
|
68
|
+
this.scheduler = new MeetingsBuildScheduler({
|
|
69
|
+
debounceMs: deps.buildDebounceMs,
|
|
70
|
+
build: (date) => this.builder.buildDay(date),
|
|
71
|
+
...(deps.onBuildError ? { onError: deps.onBuildError } : {}),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** List stored meetings for one day, or across all days when no date given. */
|
|
76
|
+
async meetingsList(date?: string): Promise<MeetingsListResult> {
|
|
77
|
+
if (!this.config.enabled) return { enabled: false, days: [] };
|
|
78
|
+
if (date !== undefined) {
|
|
79
|
+
if (!isValidTranscriptDate(date)) {
|
|
80
|
+
throw new MeetingsInputError(`date must be a real YYYY-MM-DD; got '${date}'`);
|
|
81
|
+
}
|
|
82
|
+
return { enabled: true, days: [{ date, meetings: await this.store.listMeetingSummaries(date) }] };
|
|
83
|
+
}
|
|
84
|
+
const dates = await this.store.listMeetingDates();
|
|
85
|
+
const days: MeetingsListDay[] = [];
|
|
86
|
+
for (const day of dates) {
|
|
87
|
+
days.push({ date: day, meetings: await this.store.listMeetingSummaries(day) });
|
|
88
|
+
}
|
|
89
|
+
return { enabled: true, days };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Fetch one stored meeting record's raw markdown by id. */
|
|
93
|
+
async meetingsGet(id: string): Promise<MeetingsGetResult> {
|
|
94
|
+
if (!this.config.enabled) return { enabled: false, found: false, id, record: null };
|
|
95
|
+
const date = meetingIdDate(id);
|
|
96
|
+
if (date === null) {
|
|
97
|
+
throw new MeetingsInputError(`invalid meeting id '${id}' — expected mtg-YYYY-MM-DD-<hash>`);
|
|
98
|
+
}
|
|
99
|
+
// A syntactically valid id can still embed an impossible calendar date
|
|
100
|
+
// (mtg-2026-13-40-...); surface that as a clean input error, not a 500 from
|
|
101
|
+
// the store's path validator.
|
|
102
|
+
if (!isValidTranscriptDate(date)) {
|
|
103
|
+
throw new MeetingsInputError(`invalid meeting id '${id}' — '${date}' is not a real calendar date`);
|
|
104
|
+
}
|
|
105
|
+
const record = await this.store.readMeetingRecord(date, id);
|
|
106
|
+
return { enabled: true, found: record !== null, id, record };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Detect + fuse + store the day's meetings. Gated inside the builder. */
|
|
110
|
+
async meetingsBuild(date: string): Promise<MeetingsDayBuildSummary> {
|
|
111
|
+
if (!isValidTranscriptDate(date)) {
|
|
112
|
+
throw new MeetingsInputError(`date must be a real YYYY-MM-DD; got '${date}'`);
|
|
113
|
+
}
|
|
114
|
+
return this.builder.buildDay(date);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Tail-step after a sync touched a day: schedule a debounced (re)build. A
|
|
119
|
+
* no-op when the subsystem is disabled, so a sync of other signals never
|
|
120
|
+
* spins up meeting building behind the master gate.
|
|
121
|
+
*/
|
|
122
|
+
requestBuild(date: string): void {
|
|
123
|
+
if (!this.config.enabled) return;
|
|
124
|
+
this.scheduler.requestBuild(date);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Activity tail-step (issue #1900): schedule a debounced (re)build for every
|
|
129
|
+
* local day the sync touched. The tick's own local day (from `ranAtIso`, in
|
|
130
|
+
* the activity timezone) is always eligible; `touchedDates` adds any OTHER
|
|
131
|
+
* days a rolling/backfill sync changed, so a change to yesterday's activity
|
|
132
|
+
* rebuilds yesterday's meetings rather than only today's. No-op when disabled,
|
|
133
|
+
* so a sync of other signals never spins up meeting building behind the gate.
|
|
134
|
+
*/
|
|
135
|
+
requestBuildForActivitySync(
|
|
136
|
+
ranAtIso: string,
|
|
137
|
+
timezone: string,
|
|
138
|
+
touchedDates: readonly string[] = [],
|
|
139
|
+
): void {
|
|
140
|
+
if (!this.config.enabled) return;
|
|
141
|
+
const days = new Set<string>([formatDateInTimeZone(new Date(ranAtIso), timezone)]);
|
|
142
|
+
for (const date of touchedDates) {
|
|
143
|
+
if (isValidTranscriptDate(date)) days.add(date);
|
|
144
|
+
}
|
|
145
|
+
for (const date of days) this.scheduler.requestBuild(date);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Force any pending debounced builds to run now (teardown/tests). */
|
|
149
|
+
flushBuilds(): Promise<void> {
|
|
150
|
+
return this.scheduler.flush();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Cancel armed build timers (orchestrator teardown). */
|
|
154
|
+
dispose(): void {
|
|
155
|
+
this.scheduler.dispose();
|
|
156
|
+
}
|
|
157
|
+
}
|