nlm-memory 0.21.0 → 0.21.13
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/README.md +67 -12
- package/assets/replay-eval-dark.svg +26 -0
- package/assets/replay-eval-light.svg +26 -0
- package/dist/cli/digest.js +34 -7
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/nlm.d.ts +14 -0
- package/dist/cli/nlm.js +450 -65
- package/dist/cli/nlm.js.map +1 -1
- package/dist/cli/reprocess-job-adapters.d.ts +35 -0
- package/dist/cli/reprocess-job-adapters.js +80 -0
- package/dist/cli/reprocess-job-adapters.js.map +1 -0
- package/dist/cli/scope-coverage.js +4 -1
- package/dist/cli/scope-coverage.js.map +1 -1
- package/dist/cli/supersede.js +8 -7
- package/dist/cli/supersede.js.map +1 -1
- package/dist/core/actions/actions-log.d.ts +65 -8
- package/dist/core/actions/actions-log.js +134 -17
- package/dist/core/actions/actions-log.js.map +1 -1
- package/dist/core/actions/overlay.d.ts +11 -2
- package/dist/core/actions/overlay.js +18 -6
- package/dist/core/actions/overlay.js.map +1 -1
- package/dist/core/adapters/from-source.js +11 -3
- package/dist/core/adapters/from-source.js.map +1 -1
- package/dist/core/adapters/fs-wiki-writer.d.ts +44 -0
- package/dist/core/adapters/fs-wiki-writer.js +132 -0
- package/dist/core/adapters/fs-wiki-writer.js.map +1 -0
- package/dist/core/adapters/hermes-agent.d.ts +15 -2
- package/dist/core/adapters/hermes-agent.js +57 -11
- package/dist/core/adapters/hermes-agent.js.map +1 -1
- package/dist/core/adapters/memory-wiki-writer.d.ts +8 -0
- package/dist/core/adapters/memory-wiki-writer.js +16 -0
- package/dist/core/adapters/memory-wiki-writer.js.map +1 -0
- package/dist/core/adapters/muse.d.ts +51 -0
- package/dist/core/adapters/muse.js +271 -0
- package/dist/core/adapters/muse.js.map +1 -0
- package/dist/core/adapters/opencode.d.ts +11 -4
- package/dist/core/adapters/opencode.js +41 -7
- package/dist/core/adapters/opencode.js.map +1 -1
- package/dist/core/alerts/alert-state.d.ts +17 -0
- package/dist/core/alerts/alert-state.js +45 -0
- package/dist/core/alerts/alert-state.js.map +1 -0
- package/dist/core/alerts/check-and-alert.d.ts +28 -0
- package/dist/core/alerts/check-and-alert.js +53 -0
- package/dist/core/alerts/check-and-alert.js.map +1 -0
- package/dist/core/alerts/drift-transition.d.ts +27 -0
- package/dist/core/alerts/drift-transition.js +56 -0
- package/dist/core/alerts/drift-transition.js.map +1 -0
- package/dist/core/alerts/embedder-transition.d.ts +23 -0
- package/dist/core/alerts/embedder-transition.js +40 -0
- package/dist/core/alerts/embedder-transition.js.map +1 -0
- package/dist/core/alerts/fire-alert.d.ts +24 -0
- package/dist/core/alerts/fire-alert.js +72 -0
- package/dist/core/alerts/fire-alert.js.map +1 -0
- package/dist/core/alerts/job-alert.d.ts +12 -0
- package/dist/core/alerts/job-alert.js +37 -0
- package/dist/core/alerts/job-alert.js.map +1 -0
- package/dist/core/alerts/types.d.ts +104 -0
- package/dist/core/alerts/types.js +11 -0
- package/dist/core/alerts/types.js.map +1 -0
- package/dist/core/alerts/wiki-alert.d.ts +15 -0
- package/dist/core/alerts/wiki-alert.js +31 -0
- package/dist/core/alerts/wiki-alert.js.map +1 -0
- package/dist/core/classifier/prompt.d.ts +13 -0
- package/dist/core/classifier/prompt.js +52 -2
- package/dist/core/classifier/prompt.js.map +1 -1
- package/dist/core/dataset/build-dataset.d.ts +16 -1
- package/dist/core/dataset/build-dataset.js +105 -45
- package/dist/core/dataset/build-dataset.js.map +1 -1
- package/dist/core/digest/compose.d.ts +8 -0
- package/dist/core/digest/compose.js +54 -0
- package/dist/core/digest/compose.js.map +1 -1
- package/dist/core/digest/hook-injection.d.ts +1 -1
- package/dist/core/digest/hook-injection.js +9 -1
- package/dist/core/digest/hook-injection.js.map +1 -1
- package/dist/core/digest/hook-version-parity.d.ts +31 -0
- package/dist/core/digest/hook-version-parity.js +56 -0
- package/dist/core/digest/hook-version-parity.js.map +1 -0
- package/dist/core/embedding/pg-embed-backfill.js +1 -1
- package/dist/core/embedding/pg-embed-backfill.js.map +1 -1
- package/dist/core/exemplars/capture-from-session.d.ts +1 -1
- package/dist/core/exemplars/capture-from-session.js +3 -3
- package/dist/core/exemplars/capture-from-session.js.map +1 -1
- package/dist/core/exemplars/embed-backfill.d.ts +9 -0
- package/dist/core/exemplars/embed-backfill.js +9 -1
- package/dist/core/exemplars/embed-backfill.js.map +1 -1
- package/dist/core/exemplars/recall-code.d.ts +1 -1
- package/dist/core/exemplars/recall-code.js +2 -2
- package/dist/core/exemplars/recall-code.js.map +1 -1
- package/dist/core/facts/backfill-facts.d.ts +1 -1
- package/dist/core/facts/backfill-facts.js +4 -4
- package/dist/core/facts/backfill-facts.js.map +1 -1
- package/dist/core/facts/embed-backfill.d.ts +54 -0
- package/dist/core/facts/embed-backfill.js +109 -0
- package/dist/core/facts/embed-backfill.js.map +1 -0
- package/dist/core/facts/reconcile-facts.d.ts +42 -0
- package/dist/core/facts/reconcile-facts.js +133 -0
- package/dist/core/facts/reconcile-facts.js.map +1 -0
- package/dist/core/health/embed-failure-state.d.ts +4 -0
- package/dist/core/health/embed-failure-state.js +12 -0
- package/dist/core/health/embed-failure-state.js.map +1 -0
- package/dist/core/health/warmup-retry.d.ts +27 -0
- package/dist/core/health/warmup-retry.js +53 -0
- package/dist/core/health/warmup-retry.js.map +1 -0
- package/dist/core/health/warmup-state.d.ts +15 -0
- package/dist/core/health/warmup-state.js +15 -1
- package/dist/core/health/warmup-state.js.map +1 -1
- package/dist/core/hook/cite-memo.d.ts +11 -7
- package/dist/core/hook/cite-memo.js +25 -17
- package/dist/core/hook/cite-memo.js.map +1 -1
- package/dist/core/hook/hook-log.d.ts +13 -2
- package/dist/core/hook/hook-log.js +16 -8
- package/dist/core/hook/hook-log.js.map +1 -1
- package/dist/core/hook/memo.d.ts +10 -6
- package/dist/core/hook/memo.js +25 -17
- package/dist/core/hook/memo.js.map +1 -1
- package/dist/core/hook/pointer-block.d.ts +8 -1
- package/dist/core/hook/pointer-block.js +17 -4
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/hook/resolve-conversation-by-query.d.ts +6 -5
- package/dist/core/hook/resolve-conversation-by-query.js +24 -12
- package/dist/core/hook/resolve-conversation-by-query.js.map +1 -1
- package/dist/core/ingest/ingest-session.d.ts +1 -1
- package/dist/core/ingest/ingest-session.js +3 -3
- package/dist/core/ingest/ingest-session.js.map +1 -1
- package/dist/core/ingest/reclassify-oversized.d.ts +1 -1
- package/dist/core/ingest/reclassify-oversized.js +2 -2
- package/dist/core/ingest/reclassify-oversized.js.map +1 -1
- package/dist/core/ingest/reprocess.d.ts +1 -1
- package/dist/core/ingest/reprocess.js +2 -2
- package/dist/core/ingest/reprocess.js.map +1 -1
- package/dist/core/jobs/job-supervisor.d.ts +168 -0
- package/dist/core/jobs/job-supervisor.js +335 -0
- package/dist/core/jobs/job-supervisor.js.map +1 -0
- package/dist/core/learner/model.d.ts +63 -0
- package/dist/core/learner/model.js +30 -0
- package/dist/core/learner/model.js.map +1 -0
- package/dist/core/learner/service.d.ts +42 -0
- package/dist/core/learner/service.js +74 -0
- package/dist/core/learner/service.js.map +1 -0
- package/dist/core/learner/summary.d.ts +28 -0
- package/dist/core/learner/summary.js +70 -0
- package/dist/core/learner/summary.js.map +1 -0
- package/dist/core/learner/validate.d.ts +18 -0
- package/dist/core/learner/validate.js +95 -0
- package/dist/core/learner/validate.js.map +1 -0
- package/dist/core/metrics/re-derivation.d.ts +31 -2
- package/dist/core/metrics/re-derivation.js +36 -4
- package/dist/core/metrics/re-derivation.js.map +1 -1
- package/dist/core/outcome/coverage.d.ts +1 -1
- package/dist/core/outcome/coverage.js +2 -2
- package/dist/core/outcome/coverage.js.map +1 -1
- package/dist/core/outcome/rollup.d.ts +1 -1
- package/dist/core/outcome/rollup.js +4 -4
- package/dist/core/outcome/rollup.js.map +1 -1
- package/dist/core/providers/provider-registry.d.ts +40 -23
- package/dist/core/providers/provider-registry.js +99 -81
- package/dist/core/providers/provider-registry.js.map +1 -1
- package/dist/core/recall/citation-log.d.ts +10 -5
- package/dist/core/recall/citation-log.js +21 -10
- package/dist/core/recall/citation-log.js.map +1 -1
- package/dist/core/recall/hook-recall-log.d.ts +5 -2
- package/dist/core/recall/hook-recall-log.js +12 -6
- package/dist/core/recall/hook-recall-log.js.map +1 -1
- package/dist/core/recall/miss-log.d.ts +7 -4
- package/dist/core/recall/miss-log.js +18 -11
- package/dist/core/recall/miss-log.js.map +1 -1
- package/dist/core/recall/query-log.d.ts +8 -5
- package/dist/core/recall/query-log.js +16 -9
- package/dist/core/recall/query-log.js.map +1 -1
- package/dist/core/recall/recall-service.d.ts +10 -1
- package/dist/core/recall/recall-service.js +31 -12
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall/recent-log.d.ts +6 -1
- package/dist/core/recall/recent-log.js +13 -5
- package/dist/core/recall/recent-log.js.map +1 -1
- package/dist/core/recall/related-concepts.d.ts +29 -0
- package/dist/core/recall/related-concepts.js +67 -0
- package/dist/core/recall/related-concepts.js.map +1 -0
- package/dist/core/recall/related-exemplars.d.ts +1 -1
- package/dist/core/recall/related-exemplars.js +2 -2
- package/dist/core/recall/related-exemplars.js.map +1 -1
- package/dist/core/recall/related-facts.d.ts +1 -1
- package/dist/core/recall/related-facts.js +3 -3
- package/dist/core/recall/related-facts.js.map +1 -1
- package/dist/core/recall-facts/fact-query-log.d.ts +8 -4
- package/dist/core/recall-facts/fact-query-log.js +16 -8
- package/dist/core/recall-facts/fact-query-log.js.map +1 -1
- package/dist/core/recall-facts/fact-recall-service.d.ts +2 -2
- package/dist/core/recall-facts/fact-recall-service.js +12 -12
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scan-once.js +15 -14
- package/dist/core/scheduler/scan-once.js.map +1 -1
- package/dist/core/scheduler/scheduler.d.ts +3 -0
- package/dist/core/scheduler/scheduler.js +15 -11
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/signals/failure-mode-recall.d.ts +1 -1
- package/dist/core/signals/failure-mode-recall.js +2 -2
- package/dist/core/signals/failure-mode-recall.js.map +1 -1
- package/dist/core/signals/stamp-scope.d.ts +2 -2
- package/dist/core/signals/stamp-scope.js +2 -2
- package/dist/core/signals/stamp-scope.js.map +1 -1
- package/dist/core/sources/source-registry.d.ts +49 -28
- package/dist/core/sources/source-registry.js +113 -70
- package/dist/core/sources/source-registry.js.map +1 -1
- package/dist/core/storage/pg-code-exemplar-store.d.ts +19 -10
- package/dist/core/storage/pg-code-exemplar-store.js +58 -27
- package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/pg-entity-store.d.ts +6 -1
- package/dist/core/storage/pg-entity-store.js +33 -18
- package/dist/core/storage/pg-entity-store.js.map +1 -1
- package/dist/core/storage/pg-fact-ingest.d.ts +1 -1
- package/dist/core/storage/pg-fact-ingest.js +10 -7
- package/dist/core/storage/pg-fact-ingest.js.map +1 -1
- package/dist/core/storage/pg-fact-store.d.ts +29 -17
- package/dist/core/storage/pg-fact-store.js +99 -46
- package/dist/core/storage/pg-fact-store.js.map +1 -1
- package/dist/core/storage/pg-learner-store.d.ts +27 -0
- package/dist/core/storage/pg-learner-store.js +174 -0
- package/dist/core/storage/pg-learner-store.js.map +1 -0
- package/dist/core/storage/pg-session-store.d.ts +24 -20
- package/dist/core/storage/pg-session-store.js +162 -90
- package/dist/core/storage/pg-session-store.js.map +1 -1
- package/dist/core/storage/pg-signal-store.d.ts +10 -5
- package/dist/core/storage/pg-signal-store.js +24 -12
- package/dist/core/storage/pg-signal-store.js.map +1 -1
- package/dist/core/storage/pg-storage.d.ts +4 -0
- package/dist/core/storage/pg-storage.js +6 -0
- package/dist/core/storage/pg-storage.js.map +1 -1
- package/dist/core/storage/pg-workstream-store.d.ts +16 -11
- package/dist/core/storage/pg-workstream-store.js +46 -29
- package/dist/core/storage/pg-workstream-store.js.map +1 -1
- package/dist/core/storage/sqlite-code-exemplar-store.d.ts +24 -10
- package/dist/core/storage/sqlite-code-exemplar-store.js +84 -40
- package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/sqlite-entity-store.d.ts +8 -1
- package/dist/core/storage/sqlite-entity-store.js +46 -27
- package/dist/core/storage/sqlite-entity-store.js.map +1 -1
- package/dist/core/storage/sqlite-fact-store.d.ts +45 -19
- package/dist/core/storage/sqlite-fact-store.js +172 -64
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-learner-store.d.ts +33 -0
- package/dist/core/storage/sqlite-learner-store.js +178 -0
- package/dist/core/storage/sqlite-learner-store.js.map +1 -0
- package/dist/core/storage/sqlite-outcome-store.d.ts +37 -22
- package/dist/core/storage/sqlite-outcome-store.js +90 -37
- package/dist/core/storage/sqlite-outcome-store.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +29 -20
- package/dist/core/storage/sqlite-session-store.js +207 -121
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-signal-store.d.ts +12 -5
- package/dist/core/storage/sqlite-signal-store.js +27 -15
- package/dist/core/storage/sqlite-signal-store.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +4 -0
- package/dist/core/storage/sqlite-storage.js +10 -2
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/core/storage/sqlite-workstream-store.d.ts +16 -11
- package/dist/core/storage/sqlite-workstream-store.js +52 -31
- package/dist/core/storage/sqlite-workstream-store.js.map +1 -1
- package/dist/core/storage/supersedence-log.d.ts +10 -7
- package/dist/core/storage/supersedence-log.js +19 -14
- package/dist/core/storage/supersedence-log.js.map +1 -1
- package/dist/core/tenancy/default-team.d.ts +6 -0
- package/dist/core/tenancy/default-team.js +7 -0
- package/dist/core/tenancy/default-team.js.map +1 -0
- package/dist/core/tenancy/hosted-mode.d.ts +9 -0
- package/dist/core/tenancy/hosted-mode.js +12 -0
- package/dist/core/tenancy/hosted-mode.js.map +1 -0
- package/dist/core/tenancy/team-auth.d.ts +16 -0
- package/dist/core/tenancy/team-auth.js +22 -0
- package/dist/core/tenancy/team-auth.js.map +1 -0
- package/dist/core/tenancy/team-token-store.d.ts +45 -0
- package/dist/core/tenancy/team-token-store.js +52 -0
- package/dist/core/tenancy/team-token-store.js.map +1 -0
- package/dist/core/tenancy/tenant-clause.d.ts +37 -0
- package/dist/core/tenancy/tenant-clause.js +11 -0
- package/dist/core/tenancy/tenant-clause.js.map +1 -0
- package/dist/core/tenancy/tenant-state-path.d.ts +28 -0
- package/dist/core/tenancy/tenant-state-path.js +49 -0
- package/dist/core/tenancy/tenant-state-path.js.map +1 -0
- package/dist/core/wiki/project.d.ts +17 -0
- package/dist/core/wiki/project.js +37 -0
- package/dist/core/wiki/project.js.map +1 -0
- package/dist/core/wiki/reconcile.d.ts +15 -0
- package/dist/core/wiki/reconcile.js +14 -0
- package/dist/core/wiki/reconcile.js.map +1 -0
- package/dist/core/wiki/render.d.ts +5 -0
- package/dist/core/wiki/render.js +84 -0
- package/dist/core/wiki/render.js.map +1 -0
- package/dist/core/wiki/rollup.d.ts +28 -0
- package/dist/core/wiki/rollup.js +91 -0
- package/dist/core/wiki/rollup.js.map +1 -0
- package/dist/core/wiki/select.d.ts +8 -0
- package/dist/core/wiki/select.js +7 -0
- package/dist/core/wiki/select.js.map +1 -0
- package/dist/core/wiki/slug.d.ts +36 -0
- package/dist/core/wiki/slug.js +71 -0
- package/dist/core/wiki/slug.js.map +1 -0
- package/dist/core/wiki/types.d.ts +37 -0
- package/dist/core/wiki/types.js +2 -0
- package/dist/core/wiki/types.js.map +1 -0
- package/dist/core/work-digest/build-work-digest.d.ts +1 -1
- package/dist/core/work-digest/build-work-digest.js +3 -3
- package/dist/core/work-digest/build-work-digest.js.map +1 -1
- package/dist/core/workstream/bind.d.ts +1 -1
- package/dist/core/workstream/bind.js +10 -10
- package/dist/core/workstream/bind.js.map +1 -1
- package/dist/core/workstream/rollup.d.ts +1 -1
- package/dist/core/workstream/rollup.js +6 -6
- package/dist/core/workstream/rollup.js.map +1 -1
- package/dist/hook/hook-helpers.d.ts +14 -0
- package/dist/hook/hook-helpers.js +29 -2
- package/dist/hook/hook-helpers.js.map +1 -1
- package/dist/hook/pre-compact-hook.js +6 -4
- package/dist/hook/pre-compact-hook.js.map +1 -1
- package/dist/hook/prompt-recall-hook.d.ts +5 -2
- package/dist/hook/prompt-recall-hook.js +28 -17
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +27 -3
- package/dist/hook/recall-over-http.js +44 -11
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/session-end-hook.js +7 -5
- package/dist/hook/session-end-hook.js.map +1 -1
- package/dist/hook/session-start-hook.d.ts +14 -1
- package/dist/hook/session-start-hook.js +55 -15
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +9 -7
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +41 -3
- package/dist/http/app.js +355 -96
- package/dist/http/app.js.map +1 -1
- package/dist/install/claude-code.d.ts +17 -0
- package/dist/install/claude-code.js +44 -5
- package/dist/install/claude-code.js.map +1 -1
- package/dist/install/codex.d.ts +23 -2
- package/dist/install/codex.js +111 -14
- package/dist/install/codex.js.map +1 -1
- package/dist/install/cursor.d.ts +2 -2
- package/dist/install/cursor.js +7 -7
- package/dist/install/cursor.js.map +1 -1
- package/dist/install/hermes.d.ts +8 -0
- package/dist/install/hermes.js +28 -5
- package/dist/install/hermes.js.map +1 -1
- package/dist/install/local-source.d.ts +20 -0
- package/dist/install/local-source.js +28 -0
- package/dist/install/local-source.js.map +1 -0
- package/dist/install/mcp-block.d.ts +77 -0
- package/dist/install/mcp-block.js +99 -0
- package/dist/install/mcp-block.js.map +1 -0
- package/dist/install/pi.d.ts +26 -0
- package/dist/install/pi.js +43 -0
- package/dist/install/pi.js.map +1 -1
- package/dist/install/setup.js +6 -0
- package/dist/install/setup.js.map +1 -1
- package/dist/install/windsurf.d.ts +2 -2
- package/dist/install/windsurf.js +7 -7
- package/dist/install/windsurf.js.map +1 -1
- package/dist/llm/classifier-box.d.ts +5 -0
- package/dist/llm/classifier-box.js +5 -0
- package/dist/llm/classifier-box.js.map +1 -1
- package/dist/mcp/server.d.ts +73 -16
- package/dist/mcp/server.js +306 -83
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/clock.d.ts +12 -0
- package/dist/ports/clock.js +10 -0
- package/dist/ports/clock.js.map +1 -0
- package/dist/ports/code-exemplar-store.d.ts +13 -13
- package/dist/ports/entity-store.d.ts +5 -1
- package/dist/ports/fact-store.d.ts +0 -0
- package/dist/ports/fact-store.js +0 -0
- package/dist/ports/fact-store.js.map +1 -1
- package/dist/ports/learner-store.d.ts +80 -0
- package/dist/ports/learner-store.js +19 -0
- package/dist/ports/learner-store.js.map +1 -0
- package/dist/ports/outcome.d.ts +3 -3
- package/dist/ports/session-store.d.ts +20 -14
- package/dist/ports/session-store.js +5 -0
- package/dist/ports/session-store.js.map +1 -1
- package/dist/ports/signal-store.d.ts +12 -8
- package/dist/ports/spawn-child.d.ts +14 -0
- package/dist/ports/spawn-child.js +10 -0
- package/dist/ports/spawn-child.js.map +1 -0
- package/dist/ports/storage.d.ts +6 -0
- package/dist/ports/transcript-adapter.d.ts +11 -0
- package/dist/ports/wiki-writer.d.ts +19 -0
- package/dist/ports/wiki-writer.js +12 -0
- package/dist/ports/wiki-writer.js.map +1 -0
- package/dist/ports/workstream-store.d.ts +19 -12
- package/dist/shared/types.d.ts +22 -0
- package/dist/shared/types.js.map +1 -1
- package/dist/shared/version.d.ts +9 -0
- package/dist/shared/version.js +11 -0
- package/dist/shared/version.js.map +1 -0
- package/dist/ui/assets/index-D6F3xAis.js +11 -0
- package/dist/ui/index.html +1 -1
- package/migrations/033_actions_kind_check.sql +58 -0
- package/migrations/034_tenancy_teams_and_stamps.sql +40 -0
- package/migrations/035_tenancy_entity_rekey.sql +75 -0
- package/migrations/036_tenant_name_uniqueness.sql +59 -0
- package/migrations/037_sessions_covering_index.sql +25 -0
- package/migrations/038_sources_muse.sql +33 -0
- package/migrations/039_learner_evidence.sql +51 -0
- package/migrations/pg/033_actions_kind_check.sql +17 -0
- package/migrations/pg/034_tenancy.sql +77 -0
- package/migrations/pg/035_tenant_name_uniqueness.sql +15 -0
- package/migrations/pg/036_sources_muse.sql +12 -0
- package/migrations/pg/037_learner_evidence.sql +55 -0
- package/nlm/index.js +246 -55
- package/package.json +10 -5
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/scripts/prompt-recall-hook.mjs +244 -55
- package/plugin/scripts/session-start-hook.mjs +273 -53
- package/plugin/scripts/stop-hook.mjs +195 -37
- package/dist/ui/assets/index-CSaBtgXD.js +0 -11
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* Constructor takes the Pool from PgStorage. Also exposes recentWrites()
|
|
5
5
|
* and recentMarkers() for the /live HTTP endpoints, and insertSession() +
|
|
6
6
|
* insertSessionForTest() for ingest and test seeding.
|
|
7
|
+
*
|
|
8
|
+
* Tenancy: mirrors SqliteSessionStore — every method takes `tenantId` as its
|
|
9
|
+
* non-optional first parameter and routes STAMP-table WHERE fragments
|
|
10
|
+
* through `tenantClausePg`. See that file's header comment for the full
|
|
11
|
+
* per-table rationale.
|
|
7
12
|
*/
|
|
8
13
|
import type { Pool } from "pg";
|
|
9
14
|
import type { KeywordNeighbor, SearchOptions, SemanticNeighbor, SessionStore } from "../../ports/session-store.js";
|
|
@@ -14,35 +19,34 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
14
19
|
readonly pool: Pool;
|
|
15
20
|
constructor(pool: Pool);
|
|
16
21
|
private overlayCache;
|
|
17
|
-
private overlayCacheAt;
|
|
18
22
|
invalidateOverlayCache(): void;
|
|
19
23
|
private overlay;
|
|
20
|
-
getById(sessionId: string): Promise<Session | null>;
|
|
21
|
-
getByIds(ids: ReadonlyArray<string>): Promise<ReadonlyArray<Session>>;
|
|
22
|
-
listByDateRange(fromIso: string, toIso: string): Promise<ReadonlyArray<Session>>;
|
|
23
|
-
semanticSearch(queryVector: Float32Array, limit: number, opts?: SearchOptions): Promise<ReadonlyArray<SemanticNeighbor>>;
|
|
24
|
-
keywordSearch(query: string, limit: number, opts?: SearchOptions): Promise<ReadonlyArray<KeywordNeighbor>>;
|
|
25
|
-
resolveSuccessors(ids: ReadonlyArray<string>): Promise<Map<string, string>>;
|
|
26
|
-
updateStatus(sessionId: string, status: SessionStatus): Promise<void>;
|
|
27
|
-
markSuperseded(predecessorId: string, successorId: string): Promise<void>;
|
|
28
|
-
getSessionScopeById(id: string): Promise<string | null>;
|
|
29
|
-
setWorkstreamBinding(sessionId: string, workstreamId: string | null, source: import("../workstream/model.js").BindingSource | null, confidence: number | null): Promise<void>;
|
|
30
|
-
listSessionIdsByWorkstreams(workstreamIds: ReadonlyArray<string>): Promise<ReadonlyArray<string>>;
|
|
31
|
-
getEntities(sessionId: string): Promise<ReadonlyArray<string>>;
|
|
32
|
-
getWorkstreamIds(sessionIds: ReadonlyArray<string>): Promise<Map<string, string | null>>;
|
|
33
|
-
recentWrites(limit: number): Promise<RecentWrite[]>;
|
|
34
|
-
recentMarkers(limit: number): Promise<RecentMarker[]>;
|
|
35
|
-
insertSession(record: IngestRecord, embedder?: import("../../ports/llm-client.js").LLMClient | null, supersedes?: Supersedes | null, factSink?: {
|
|
24
|
+
getById(tenantId: string, sessionId: string): Promise<Session | null>;
|
|
25
|
+
getByIds(tenantId: string, ids: ReadonlyArray<string>): Promise<ReadonlyArray<Session>>;
|
|
26
|
+
listByDateRange(tenantId: string, fromIso: string, toIso: string): Promise<ReadonlyArray<Session>>;
|
|
27
|
+
semanticSearch(tenantId: string, queryVector: Float32Array, limit: number, opts?: SearchOptions): Promise<ReadonlyArray<SemanticNeighbor>>;
|
|
28
|
+
keywordSearch(tenantId: string, query: string, limit: number, opts?: SearchOptions): Promise<ReadonlyArray<KeywordNeighbor>>;
|
|
29
|
+
resolveSuccessors(tenantId: string, ids: ReadonlyArray<string>): Promise<Map<string, string>>;
|
|
30
|
+
updateStatus(tenantId: string, sessionId: string, status: SessionStatus): Promise<void>;
|
|
31
|
+
markSuperseded(tenantId: string, predecessorId: string, successorId: string): Promise<void>;
|
|
32
|
+
getSessionScopeById(tenantId: string, id: string): Promise<string | null>;
|
|
33
|
+
setWorkstreamBinding(tenantId: string, sessionId: string, workstreamId: string | null, source: import("../workstream/model.js").BindingSource | null, confidence: number | null): Promise<void>;
|
|
34
|
+
listSessionIdsByWorkstreams(tenantId: string, workstreamIds: ReadonlyArray<string>): Promise<ReadonlyArray<string>>;
|
|
35
|
+
getEntities(tenantId: string, sessionId: string): Promise<ReadonlyArray<string>>;
|
|
36
|
+
getWorkstreamIds(tenantId: string, sessionIds: ReadonlyArray<string>): Promise<Map<string, string | null>>;
|
|
37
|
+
recentWrites(tenantId: string, limit: number): Promise<RecentWrite[]>;
|
|
38
|
+
recentMarkers(tenantId: string, limit: number): Promise<RecentMarker[]>;
|
|
39
|
+
insertSession(tenantId: string, record: IngestRecord, embedder?: import("../../ports/llm-client.js").LLMClient | null, supersedes?: Supersedes | null, factSink?: {
|
|
36
40
|
factStore: PgFactStore;
|
|
37
41
|
facts: ReadonlyArray<Fact>;
|
|
38
42
|
} | null): Promise<void>;
|
|
39
|
-
insertSessionForTest(session: Session): Promise<void>;
|
|
43
|
+
insertSessionForTest(session: Session, tenantId?: string): Promise<void>;
|
|
40
44
|
/** PG counterpart of SqliteSessionStore.listBackfillCandidates. */
|
|
41
|
-
listBackfillCandidates(filter: BackfillCandidateFilter): Promise<BackfillCandidate[]>;
|
|
45
|
+
listBackfillCandidates(tenantId: string, filter: BackfillCandidateFilter): Promise<BackfillCandidate[]>;
|
|
42
46
|
/** PG counterpart of SqliteSessionStore.insertFactsForSession — writes facts
|
|
43
47
|
* for an EXISTING session row (deterministic supersedence + best-effort
|
|
44
48
|
* embeddings) in one transaction. Used by the fact backfill. */
|
|
45
|
-
insertFactsForSession(sessionId: string, factStore: PgFactStore, facts: ReadonlyArray<Fact>, embedder?: import("../../ports/llm-client.js").LLMClient | null): Promise<void>;
|
|
49
|
+
insertFactsForSession(tenantId: string, sessionId: string, factStore: PgFactStore, facts: ReadonlyArray<Fact>, embedder?: import("../../ports/llm-client.js").LLMClient | null): Promise<void>;
|
|
46
50
|
private loadEntities;
|
|
47
51
|
private loadMarkers;
|
|
48
52
|
private loadEdges;
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* Constructor takes the Pool from PgStorage. Also exposes recentWrites()
|
|
5
5
|
* and recentMarkers() for the /live HTTP endpoints, and insertSession() +
|
|
6
6
|
* insertSessionForTest() for ingest and test seeding.
|
|
7
|
+
*
|
|
8
|
+
* Tenancy: mirrors SqliteSessionStore — every method takes `tenantId` as its
|
|
9
|
+
* non-optional first parameter and routes STAMP-table WHERE fragments
|
|
10
|
+
* through `tenantClausePg`. See that file's header comment for the full
|
|
11
|
+
* per-table rationale.
|
|
7
12
|
*/
|
|
8
13
|
import { tokenize } from "../recall/tokenize.js";
|
|
9
14
|
import { ingestSessionFactsOnClient } from "./pg-fact-ingest.js";
|
|
@@ -11,28 +16,39 @@ import { chunkSessionText } from "../embedding/chunk-body.js";
|
|
|
11
16
|
import { batchWinners } from "./fact-batch.js";
|
|
12
17
|
import { loadActionOverlayPg, openQuestionId } from "../actions/overlay.js";
|
|
13
18
|
import { liveSessionStatus } from "./live-status.js";
|
|
19
|
+
import { tenantClausePg } from "../tenancy/tenant-clause.js";
|
|
20
|
+
import { DEFAULT_TEAM_ID } from "../tenancy/default-team.js";
|
|
21
|
+
import { recordEmbedFailure } from "../health/embed-failure-state.js";
|
|
14
22
|
/**
|
|
15
23
|
* PG mirror of SQLite findContinuesPredecessor: the most recent prior session
|
|
16
24
|
* whose distinct entity-set is identical to the new session's. Runs on the
|
|
17
25
|
* ingest transaction's own client. Returns null when there is no entity-set or
|
|
18
26
|
* no exact-set match, leaving the pair unlinked for the re_derivation_rate metric.
|
|
27
|
+
* Scoped to the same tenant as the new session.
|
|
19
28
|
*/
|
|
20
|
-
async function findContinuesPredecessorPg(client, newId, rawEntities) {
|
|
29
|
+
async function findContinuesPredecessorPg(client, tenantId, newId, rawEntities) {
|
|
21
30
|
const entities = [...new Set(rawEntities.map((e) => e.trim()).filter(Boolean))];
|
|
22
31
|
if (entities.length === 0)
|
|
23
32
|
return null;
|
|
24
|
-
const placeholders = entities.map((_, i) => `$${i +
|
|
33
|
+
const placeholders = entities.map((_, i) => `$${i + 3}`).join(",");
|
|
34
|
+
// session_entities rows are always written with the same tenant_id as their
|
|
35
|
+
// owning session (every write site stamps both together), so scoping the
|
|
36
|
+
// outer `sessions` row by tenant is already sufficient. The `se.tenant_id =
|
|
37
|
+
// s.tenant_id` join condition below is defense in depth (guard test C4
|
|
38
|
+
// explicitly permits column-to-column tenant equality as a join form).
|
|
39
|
+
const tc = tenantClausePg(tenantId, 2, "s.tenant_id");
|
|
25
40
|
const res = await client.query(`SELECT s.id AS id
|
|
26
41
|
FROM sessions s
|
|
27
|
-
JOIN session_entities se ON se.session_id = s.id
|
|
42
|
+
JOIN session_entities se ON se.session_id = s.id AND se.tenant_id = s.tenant_id
|
|
28
43
|
WHERE s.id != $1
|
|
44
|
+
AND ${tc.sql}
|
|
29
45
|
AND se.entity_canonical IN (${placeholders})
|
|
30
46
|
GROUP BY s.id
|
|
31
|
-
HAVING COUNT(DISTINCT se.entity_canonical) = $${entities.length +
|
|
47
|
+
HAVING COUNT(DISTINCT se.entity_canonical) = $${entities.length + 3}
|
|
32
48
|
AND COUNT(DISTINCT se.entity_canonical)
|
|
33
49
|
= (SELECT COUNT(*) FROM session_entities x WHERE x.session_id = s.id)
|
|
34
50
|
ORDER BY s.started_at DESC, s.id DESC
|
|
35
|
-
LIMIT 1`, [newId, ...entities, entities.length]);
|
|
51
|
+
LIMIT 1`, [newId, tc.param, ...entities, entities.length]);
|
|
36
52
|
return res.rows[0]?.id ?? null;
|
|
37
53
|
}
|
|
38
54
|
export class PgSessionStore {
|
|
@@ -42,74 +58,79 @@ export class PgSessionStore {
|
|
|
42
58
|
constructor(pool) {
|
|
43
59
|
this.pool = pool;
|
|
44
60
|
}
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
// Keyed by tenantId — see SqliteSessionStore's identical cache for the
|
|
62
|
+
// tenancy rationale (program spec §4.6 hardening 1).
|
|
63
|
+
overlayCache = new Map();
|
|
47
64
|
invalidateOverlayCache() {
|
|
48
|
-
this.overlayCache
|
|
65
|
+
this.overlayCache.clear();
|
|
49
66
|
}
|
|
50
|
-
async overlay() {
|
|
67
|
+
async overlay(tenantId) {
|
|
51
68
|
// TTL backstop: explicit invalidation covers the daemon's own writers; the
|
|
52
69
|
// 30s expiry bounds staleness if another process ever writes actions to
|
|
53
70
|
// this database.
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
const cached = this.overlayCache.get(tenantId);
|
|
72
|
+
if (cached !== undefined && Date.now() - cached.at < 30_000) {
|
|
73
|
+
return cached.overlay;
|
|
56
74
|
}
|
|
57
|
-
|
|
58
|
-
this.
|
|
59
|
-
return
|
|
75
|
+
const overlay = await loadActionOverlayPg(this.pool, tenantId);
|
|
76
|
+
this.overlayCache.set(tenantId, { overlay, at: Date.now() });
|
|
77
|
+
return overlay;
|
|
60
78
|
}
|
|
61
|
-
async getById(sessionId) {
|
|
79
|
+
async getById(tenantId, sessionId) {
|
|
80
|
+
const tc = tenantClausePg(tenantId, 2);
|
|
62
81
|
const result = await this.pool.query(`SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
63
82
|
label, summary, status, transcript_kind, transcript_path, body,
|
|
64
83
|
classifier_provider, classifier_model, classifier_confidence,
|
|
65
84
|
agent_persona, parent_session_id,
|
|
66
85
|
primary_model, total_tokens, skill
|
|
67
|
-
FROM sessions WHERE id = $1`, [sessionId]);
|
|
86
|
+
FROM sessions WHERE id = $1 AND ${tc.sql}`, [sessionId, tc.param]);
|
|
68
87
|
if (!result.rows[0])
|
|
69
88
|
return null;
|
|
70
89
|
const [entitiesMap, markersMap, edgesMap, overlay] = await Promise.all([
|
|
71
|
-
this.loadEntities([sessionId]),
|
|
90
|
+
this.loadEntities(tenantId, [sessionId]),
|
|
72
91
|
this.loadMarkers([sessionId]),
|
|
73
92
|
this.loadEdges([sessionId]),
|
|
74
|
-
this.overlay(),
|
|
93
|
+
this.overlay(tenantId),
|
|
75
94
|
]);
|
|
76
95
|
const edges = edgesMap.get(sessionId);
|
|
77
96
|
return rowToSession(result.rows[0], entitiesMap, markersMap, overlay, edges);
|
|
78
97
|
}
|
|
79
|
-
async getByIds(ids) {
|
|
98
|
+
async getByIds(tenantId, ids) {
|
|
80
99
|
if (ids.length === 0)
|
|
81
100
|
return [];
|
|
82
101
|
const placeholders = ids.map((_, i) => `$${i + 1}`).join(",");
|
|
102
|
+
const tc = tenantClausePg(tenantId, ids.length + 1);
|
|
83
103
|
const result = await this.pool.query(`SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
84
104
|
label, summary, status, transcript_kind, transcript_path
|
|
85
|
-
FROM sessions WHERE id IN (${placeholders})`, [...ids]);
|
|
105
|
+
FROM sessions WHERE id IN (${placeholders}) AND ${tc.sql}`, [...ids, tc.param]);
|
|
86
106
|
if (result.rows.length === 0)
|
|
87
107
|
return [];
|
|
88
108
|
const foundIds = result.rows.map((r) => r.id);
|
|
89
109
|
const [entitiesMap, markersMap, overlay] = await Promise.all([
|
|
90
|
-
this.loadEntities(foundIds),
|
|
110
|
+
this.loadEntities(tenantId, foundIds),
|
|
91
111
|
this.loadMarkers(foundIds),
|
|
92
|
-
this.overlay(),
|
|
112
|
+
this.overlay(tenantId),
|
|
93
113
|
]);
|
|
94
114
|
return result.rows.map((r) => rowToSession({ ...r, body: null }, entitiesMap, markersMap, overlay));
|
|
95
115
|
}
|
|
96
|
-
async listByDateRange(fromIso, toIso) {
|
|
116
|
+
async listByDateRange(tenantId, fromIso, toIso) {
|
|
117
|
+
const tc = tenantClausePg(tenantId, 3);
|
|
97
118
|
const result = await this.pool.query(`SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
98
119
|
label, summary, status, transcript_kind, transcript_path, workstream_id
|
|
99
120
|
FROM sessions
|
|
100
|
-
WHERE started_at < $1 AND (ended_at IS NULL OR ended_at >= $2)
|
|
101
|
-
ORDER BY started_at ASC`, [toIso, fromIso]);
|
|
121
|
+
WHERE started_at < $1 AND (ended_at IS NULL OR ended_at >= $2) AND ${tc.sql}
|
|
122
|
+
ORDER BY started_at ASC`, [toIso, fromIso, tc.param]);
|
|
102
123
|
if (result.rows.length === 0)
|
|
103
124
|
return [];
|
|
104
125
|
const ids = result.rows.map((r) => r.id);
|
|
105
126
|
const [entitiesMap, markersMap, overlay] = await Promise.all([
|
|
106
|
-
this.loadEntities(ids),
|
|
127
|
+
this.loadEntities(tenantId, ids),
|
|
107
128
|
this.loadMarkers(ids),
|
|
108
|
-
this.overlay(),
|
|
129
|
+
this.overlay(tenantId),
|
|
109
130
|
]);
|
|
110
131
|
return result.rows.map((r) => rowToSession({ ...r, body: null }, entitiesMap, markersMap, overlay));
|
|
111
132
|
}
|
|
112
|
-
async semanticSearch(queryVector, limit, opts) {
|
|
133
|
+
async semanticSearch(tenantId, queryVector, limit, opts) {
|
|
113
134
|
const wsIds = opts?.workstreamIds;
|
|
114
135
|
if (wsIds !== undefined && wsIds.length === 0)
|
|
115
136
|
return [];
|
|
@@ -124,19 +145,24 @@ export class PgSessionStore {
|
|
|
124
145
|
params.push(wsIds);
|
|
125
146
|
wsClause = `AND s.workstream_id = ANY($${params.length}::text[])`;
|
|
126
147
|
}
|
|
148
|
+
// Tenant filter is applied in the id-resolution join against `sessions`
|
|
149
|
+
// (program spec §4.3, vector-path rule) — the `<->` KNN scan over
|
|
150
|
+
// session_embedding_chunks has no tenant column of its own.
|
|
151
|
+
const tc = tenantClausePg(tenantId, params.length + 1, "s.tenant_id");
|
|
152
|
+
params.push(tc.param);
|
|
153
|
+
const limitParam = `$${params.length + 1}`;
|
|
127
154
|
params.push(k);
|
|
128
|
-
const limitParam = `$${params.length}`;
|
|
129
155
|
const result = await this.pool.query(`SELECT sec.session_id, MIN(sec.embedding <-> $1::vector) AS distance, s.status
|
|
130
156
|
FROM session_embedding_chunks sec
|
|
131
157
|
JOIN sessions s ON s.id = sec.session_id
|
|
132
|
-
WHERE
|
|
158
|
+
WHERE ${tc.sql} ${wsClause}
|
|
133
159
|
GROUP BY sec.session_id, s.status
|
|
134
160
|
HAVING ${statusFilter}
|
|
135
161
|
ORDER BY distance
|
|
136
162
|
LIMIT ${limitParam}`, params);
|
|
137
163
|
return result.rows.map((r) => ({ sessionId: r.session_id, distance: r.distance }));
|
|
138
164
|
}
|
|
139
|
-
async keywordSearch(query, limit, opts) {
|
|
165
|
+
async keywordSearch(tenantId, query, limit, opts) {
|
|
140
166
|
const wsIds = opts?.workstreamIds;
|
|
141
167
|
if (wsIds !== undefined && wsIds.length === 0)
|
|
142
168
|
return [];
|
|
@@ -154,6 +180,8 @@ export class PgSessionStore {
|
|
|
154
180
|
params.push(wsIds);
|
|
155
181
|
wsClause = `AND workstream_id = ANY($${params.length}::text[])`;
|
|
156
182
|
}
|
|
183
|
+
const tc = tenantClausePg(tenantId, params.length + 1);
|
|
184
|
+
params.push(tc.param);
|
|
157
185
|
params.push(k);
|
|
158
186
|
const limitParam = `$${params.length}`;
|
|
159
187
|
const result = await this.pool.query(`SELECT id AS session_id,
|
|
@@ -162,39 +190,46 @@ export class PgSessionStore {
|
|
|
162
190
|
WHERE fts_vector @@ websearch_to_tsquery('english', $1)
|
|
163
191
|
AND ${statusFilter}
|
|
164
192
|
${wsClause}
|
|
193
|
+
AND ${tc.sql}
|
|
165
194
|
ORDER BY score DESC
|
|
166
195
|
LIMIT ${limitParam}`, params);
|
|
167
196
|
return result.rows.map((r) => ({ sessionId: r.session_id, score: r.score }));
|
|
168
197
|
}
|
|
169
|
-
async resolveSuccessors(ids) {
|
|
198
|
+
async resolveSuccessors(tenantId, ids) {
|
|
170
199
|
if (ids.length === 0)
|
|
171
200
|
return new Map();
|
|
172
201
|
const placeholders = ids.map((_, i) => `$${i + 1}`).join(",");
|
|
173
|
-
const
|
|
174
|
-
|
|
202
|
+
const tc = tenantClausePg(tenantId, ids.length + 1, "s.tenant_id");
|
|
203
|
+
const result = await this.pool.query(`SELECT se.from_session, se.to_session
|
|
204
|
+
FROM session_edges se
|
|
205
|
+
JOIN sessions s ON s.id = se.to_session
|
|
206
|
+
WHERE se.kind = 'supersedes' AND se.to_session IN (${placeholders}) AND ${tc.sql}`, [...ids, tc.param]);
|
|
175
207
|
const out = new Map();
|
|
176
208
|
for (const r of result.rows)
|
|
177
209
|
out.set(r.to_session, r.from_session);
|
|
178
210
|
return out;
|
|
179
211
|
}
|
|
180
|
-
async updateStatus(sessionId, status) {
|
|
212
|
+
async updateStatus(tenantId, sessionId, status) {
|
|
181
213
|
if (status === "idle") {
|
|
182
214
|
throw new Error("Cannot persist derived status 'idle' — only active/closed/superseded");
|
|
183
215
|
}
|
|
184
|
-
|
|
216
|
+
const tc = tenantClausePg(tenantId, 3);
|
|
217
|
+
await this.pool.query(`UPDATE sessions SET status = $1, updated_at = NOW() WHERE id = $2 AND ${tc.sql}`, [status, sessionId, tc.param]);
|
|
185
218
|
}
|
|
186
|
-
async markSuperseded(predecessorId, successorId) {
|
|
219
|
+
async markSuperseded(tenantId, predecessorId, successorId) {
|
|
187
220
|
if (predecessorId === successorId) {
|
|
188
221
|
throw new Error("A session cannot supersede itself");
|
|
189
222
|
}
|
|
190
223
|
const client = await this.pool.connect();
|
|
191
224
|
try {
|
|
192
225
|
await client.query("BEGIN");
|
|
193
|
-
const
|
|
226
|
+
const predExistsTc = tenantClausePg(tenantId, 2);
|
|
227
|
+
const predExists = await client.query(`SELECT COUNT(*) AS c FROM sessions WHERE id = $1 AND ${predExistsTc.sql}`, [predecessorId, predExistsTc.param]);
|
|
194
228
|
if (Number(predExists.rows[0]?.c) === 0) {
|
|
195
229
|
throw new Error(`predecessor session ${predecessorId} not found`);
|
|
196
230
|
}
|
|
197
|
-
const
|
|
231
|
+
const succExistsTc = tenantClausePg(tenantId, 2);
|
|
232
|
+
const succExists = await client.query(`SELECT COUNT(*) AS c FROM sessions WHERE id = $1 AND ${succExistsTc.sql}`, [successorId, succExistsTc.param]);
|
|
198
233
|
if (Number(succExists.rows[0]?.c) === 0) {
|
|
199
234
|
throw new Error(`successor session ${successorId} not found`);
|
|
200
235
|
}
|
|
@@ -223,8 +258,12 @@ export class PgSessionStore {
|
|
|
223
258
|
await client.query(`INSERT INTO session_edges (from_session, to_session, kind)
|
|
224
259
|
VALUES ($1, $2, 'supersedes')
|
|
225
260
|
ON CONFLICT DO NOTHING`, [successorId, predecessorId]);
|
|
226
|
-
|
|
227
|
-
|
|
261
|
+
const statusTc = tenantClausePg(tenantId, 2);
|
|
262
|
+
await client.query(`UPDATE sessions SET status = 'superseded', updated_at = NOW() WHERE id = $1 AND ${statusTc.sql}`, [predecessorId, statusTc.param]);
|
|
263
|
+
// Cascade supersedence to facts in a single correlated UPDATE, scoped to
|
|
264
|
+
// the caller's tenant on both sides of the join.
|
|
265
|
+
const sTc = tenantClausePg(tenantId, 3, "s.tenant_id");
|
|
266
|
+
const pTc = tenantClausePg(tenantId, 3, "p.tenant_id");
|
|
228
267
|
const cascadeSQL = `
|
|
229
268
|
UPDATE facts AS p
|
|
230
269
|
SET superseded_by = (
|
|
@@ -233,18 +272,21 @@ export class PgSessionStore {
|
|
|
233
272
|
AND s.subject = p.subject
|
|
234
273
|
AND s.predicate = p.predicate
|
|
235
274
|
AND s.superseded_by IS NULL
|
|
275
|
+
AND ${sTc.sql}
|
|
236
276
|
LIMIT 1
|
|
237
277
|
)
|
|
238
278
|
WHERE p.source_session_id = $1
|
|
279
|
+
AND ${pTc.sql}
|
|
239
280
|
AND EXISTS (
|
|
240
281
|
SELECT 1 FROM facts s
|
|
241
282
|
WHERE s.source_session_id = $2
|
|
242
283
|
AND s.subject = p.subject
|
|
243
284
|
AND s.predicate = p.predicate
|
|
244
285
|
AND s.superseded_by IS NULL
|
|
286
|
+
AND ${sTc.sql}
|
|
245
287
|
)
|
|
246
288
|
`;
|
|
247
|
-
const cascaded = await client.query(cascadeSQL + " RETURNING p.id", [predecessorId, successorId]);
|
|
289
|
+
const cascaded = await client.query(cascadeSQL + " RETURNING p.id", [predecessorId, successorId, sTc.param]);
|
|
248
290
|
const cascadedIds = cascaded.rows.map((r) => r.id);
|
|
249
291
|
if (cascadedIds.length > 0) {
|
|
250
292
|
await client.query("DELETE FROM fact_embeddings WHERE fact_id = ANY($1)", [cascadedIds]);
|
|
@@ -259,43 +301,49 @@ export class PgSessionStore {
|
|
|
259
301
|
client.release();
|
|
260
302
|
}
|
|
261
303
|
}
|
|
262
|
-
async getSessionScopeById(id) {
|
|
263
|
-
const
|
|
304
|
+
async getSessionScopeById(tenantId, id) {
|
|
305
|
+
const tc = tenantClausePg(tenantId, 2);
|
|
306
|
+
const r = await this.pool.query(`SELECT scope FROM sessions WHERE id = $1 AND ${tc.sql}`, [id, tc.param]);
|
|
264
307
|
return r.rows[0]?.scope ?? null;
|
|
265
308
|
}
|
|
266
|
-
async setWorkstreamBinding(sessionId, workstreamId, source, confidence) {
|
|
267
|
-
|
|
309
|
+
async setWorkstreamBinding(tenantId, sessionId, workstreamId, source, confidence) {
|
|
310
|
+
const tc = tenantClausePg(tenantId, 5);
|
|
311
|
+
await this.pool.query(`UPDATE sessions SET workstream_id = $1, binding_source = $2, binding_confidence = $3, updated_at = NOW() WHERE id = $4 AND ${tc.sql}`, [workstreamId, source, confidence, sessionId, tc.param]);
|
|
268
312
|
}
|
|
269
|
-
async listSessionIdsByWorkstreams(workstreamIds) {
|
|
313
|
+
async listSessionIdsByWorkstreams(tenantId, workstreamIds) {
|
|
270
314
|
if (workstreamIds.length === 0)
|
|
271
315
|
return [];
|
|
272
316
|
const ph = workstreamIds.map((_, i) => `$${i + 1}`).join(",");
|
|
273
|
-
const
|
|
317
|
+
const tc = tenantClausePg(tenantId, workstreamIds.length + 1);
|
|
318
|
+
const r = await this.pool.query(`SELECT id FROM sessions WHERE workstream_id IN (${ph}) AND ${tc.sql} ORDER BY started_at ASC`, [...workstreamIds, tc.param]);
|
|
274
319
|
return r.rows.map((row) => row.id);
|
|
275
320
|
}
|
|
276
|
-
async getEntities(sessionId) {
|
|
277
|
-
return (await this.loadEntities([sessionId])).get(sessionId) ?? [];
|
|
321
|
+
async getEntities(tenantId, sessionId) {
|
|
322
|
+
return (await this.loadEntities(tenantId, [sessionId])).get(sessionId) ?? [];
|
|
278
323
|
}
|
|
279
|
-
async getWorkstreamIds(sessionIds) {
|
|
324
|
+
async getWorkstreamIds(tenantId, sessionIds) {
|
|
280
325
|
const out = new Map();
|
|
281
326
|
if (sessionIds.length === 0)
|
|
282
327
|
return out;
|
|
283
328
|
const ph = sessionIds.map((_, i) => `$${i + 1}`).join(",");
|
|
284
|
-
const
|
|
329
|
+
const tc = tenantClausePg(tenantId, sessionIds.length + 1);
|
|
330
|
+
const result = await this.pool.query(`SELECT id, workstream_id FROM sessions WHERE id IN (${ph}) AND ${tc.sql}`, [...sessionIds, tc.param]);
|
|
285
331
|
for (const r of result.rows)
|
|
286
332
|
out.set(r.id, r.workstream_id);
|
|
287
333
|
return out;
|
|
288
334
|
}
|
|
289
|
-
async recentWrites(limit) {
|
|
335
|
+
async recentWrites(tenantId, limit) {
|
|
336
|
+
const tc = tenantClausePg(tenantId, 2);
|
|
290
337
|
const result = await this.pool.query(`SELECT id, runtime, label, summary, created_at AS "createdAt"
|
|
291
|
-
FROM sessions ORDER BY created_at DESC LIMIT $1`, [limit]);
|
|
338
|
+
FROM sessions WHERE ${tc.sql} ORDER BY created_at DESC LIMIT $1`, [limit, tc.param]);
|
|
292
339
|
if (result.rows.length === 0)
|
|
293
340
|
return [];
|
|
294
341
|
const ids = result.rows.map((r) => r.id);
|
|
342
|
+
const entTc = tenantClausePg(tenantId, 2);
|
|
295
343
|
const entityResult = await this.pool.query(`SELECT session_id, entity_canonical
|
|
296
344
|
FROM session_entities
|
|
297
|
-
WHERE session_id = ANY($1)
|
|
298
|
-
ORDER BY entity_canonical`, [ids]);
|
|
345
|
+
WHERE session_id = ANY($1) AND ${entTc.sql}
|
|
346
|
+
ORDER BY entity_canonical`, [ids, entTc.param]);
|
|
299
347
|
const byId = new Map();
|
|
300
348
|
for (const e of entityResult.rows) {
|
|
301
349
|
const list = byId.get(e.session_id);
|
|
@@ -306,15 +354,17 @@ export class PgSessionStore {
|
|
|
306
354
|
}
|
|
307
355
|
return result.rows.map((r) => ({ ...r, entities: byId.get(r.id) ?? [] }));
|
|
308
356
|
}
|
|
309
|
-
async recentMarkers(limit) {
|
|
357
|
+
async recentMarkers(tenantId, limit) {
|
|
358
|
+
const tc = tenantClausePg(tenantId, 2, "s.tenant_id");
|
|
310
359
|
const result = await this.pool.query(`SELECT m.session_id AS "sessionId", m.kind, m.text, s.label, s.created_at AS "createdAt"
|
|
311
360
|
FROM markers m
|
|
312
361
|
JOIN sessions s ON s.id = m.session_id
|
|
362
|
+
WHERE ${tc.sql}
|
|
313
363
|
ORDER BY s.created_at DESC, m.position ASC
|
|
314
|
-
LIMIT $1`, [limit]);
|
|
364
|
+
LIMIT $1`, [limit, tc.param]);
|
|
315
365
|
return result.rows;
|
|
316
366
|
}
|
|
317
|
-
async insertSession(record, embedder = null, supersedes = null, factSink = null) {
|
|
367
|
+
async insertSession(tenantId, record, embedder = null, supersedes = null, factSink = null) {
|
|
318
368
|
const client = await this.pool.connect();
|
|
319
369
|
try {
|
|
320
370
|
await client.query("BEGIN");
|
|
@@ -324,8 +374,8 @@ export class PgSessionStore {
|
|
|
324
374
|
transcript_offset, transcript_length,
|
|
325
375
|
classifier_provider, classifier_model, classifier_confidence,
|
|
326
376
|
scope, agent_persona, parent_session_id,
|
|
327
|
-
primary_model, total_tokens, skill
|
|
328
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)
|
|
377
|
+
primary_model, total_tokens, skill, tenant_id
|
|
378
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
|
|
329
379
|
ON CONFLICT (id) DO UPDATE SET
|
|
330
380
|
ended_at = EXCLUDED.ended_at,
|
|
331
381
|
duration_min = EXCLUDED.duration_min,
|
|
@@ -358,6 +408,7 @@ export class PgSessionStore {
|
|
|
358
408
|
record.primaryModel ?? null,
|
|
359
409
|
record.totalTokens ?? null,
|
|
360
410
|
record.skill ?? null,
|
|
411
|
+
tenantId,
|
|
361
412
|
]);
|
|
362
413
|
await client.query("DELETE FROM markers WHERE session_id = $1", [record.id]);
|
|
363
414
|
for (let i = 0; i < record.decisions.length; i++) {
|
|
@@ -373,36 +424,43 @@ export class PgSessionStore {
|
|
|
373
424
|
const rawNewEntities = [...new Set(record.entities.map((e) => e.trim()).filter(Boolean))];
|
|
374
425
|
// Resolve each extracted entity through entity_variants so merged surface
|
|
375
426
|
// forms bind to the canonical instead of resurrecting the retired source.
|
|
376
|
-
const
|
|
427
|
+
const variantTc = tenantClausePg(tenantId, 1);
|
|
428
|
+
const variantRes = await client.query(`SELECT variant, canonical FROM entity_variants WHERE ${variantTc.sql} AND variant = ANY($2)`, [variantTc.param, rawNewEntities]);
|
|
377
429
|
const variantMap = new Map(variantRes.rows.map((r) => [r.variant, r.canonical]));
|
|
378
430
|
const newEntities = rawNewEntities.map((name) => variantMap.get(name) ?? name);
|
|
379
|
-
const
|
|
431
|
+
const oldTc = tenantClausePg(tenantId, 1);
|
|
432
|
+
const oldRes = await client.query(`SELECT entity_canonical FROM session_entities WHERE ${oldTc.sql} AND session_id = $2`, [oldTc.param, record.id]);
|
|
380
433
|
const oldEntities = new Set(oldRes.rows.map((r) => r.entity_canonical));
|
|
381
|
-
|
|
434
|
+
const deleteTc = tenantClausePg(tenantId, 1);
|
|
435
|
+
await client.query(`DELETE FROM session_entities WHERE ${deleteTc.sql} AND session_id = $2`, [deleteTc.param, record.id]);
|
|
382
436
|
for (const name of newEntities) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
437
|
+
const insertTc = tenantClausePg(tenantId, 1);
|
|
438
|
+
await client.query(`INSERT INTO entities (tenant_id, canonical, type, status, source, first_seen_session, last_seen_session, session_count)
|
|
439
|
+
VALUES ($1, $2, 'candidate', 'candidate', 'auto-detected', $3, $3, 0)
|
|
440
|
+
ON CONFLICT (tenant_id, canonical) DO NOTHING`, [insertTc.param, name, record.id]);
|
|
386
441
|
// Update last_seen for entities newly added to this session; matches prior touch semantics.
|
|
387
442
|
if (!oldEntities.has(name)) {
|
|
388
|
-
|
|
443
|
+
const touchTc = tenantClausePg(tenantId, 2);
|
|
444
|
+
await client.query(`UPDATE entities SET last_seen_session = $1, updated_at = NOW() WHERE ${touchTc.sql} AND canonical = $3`, [record.id, touchTc.param, name]);
|
|
389
445
|
}
|
|
390
|
-
await client.query("INSERT INTO session_entities (session_id, entity_canonical) VALUES ($1, $2)", [record.id, name]);
|
|
446
|
+
await client.query("INSERT INTO session_entities (tenant_id, session_id, entity_canonical) VALUES ($1, $2, $3)", [tenantId, record.id, name]);
|
|
391
447
|
}
|
|
392
448
|
// Recompute session_count exactly for every entity in (old union new) so
|
|
393
449
|
// counts reflect reality regardless of prior drift.
|
|
394
450
|
const allTouched = [...new Set([...oldEntities, ...newEntities])];
|
|
395
451
|
for (const name of allTouched) {
|
|
396
|
-
|
|
452
|
+
const recomputeTc = tenantClausePg(tenantId, 2);
|
|
453
|
+
await client.query(`UPDATE entities SET session_count = (SELECT COUNT(*) FROM session_entities WHERE ${recomputeTc.sql} AND entity_canonical = $1), updated_at = NOW() WHERE ${recomputeTc.sql} AND canonical = $1`, [name, recomputeTc.param]);
|
|
397
454
|
}
|
|
398
455
|
if (supersedes && supersedes.priorSessionId !== record.id) {
|
|
399
456
|
const predecessorStatus = supersedes.kind === "replaces" ? "replaced" : "superseded";
|
|
400
457
|
await client.query(`INSERT INTO session_edges (from_session, to_session, kind)
|
|
401
458
|
VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`, [record.id, supersedes.priorSessionId, supersedes.kind]);
|
|
402
|
-
|
|
459
|
+
const predStatusTc = tenantClausePg(tenantId, 3);
|
|
460
|
+
await client.query(`UPDATE sessions SET status = $1, updated_at = NOW() WHERE id = $2 AND ${predStatusTc.sql}`, [predecessorStatus, supersedes.priorSessionId, predStatusTc.param]);
|
|
403
461
|
}
|
|
404
462
|
else {
|
|
405
|
-
const priorId = await findContinuesPredecessorPg(client, record.id, record.entities);
|
|
463
|
+
const priorId = await findContinuesPredecessorPg(client, tenantId, record.id, record.entities);
|
|
406
464
|
if (priorId !== null) {
|
|
407
465
|
await client.query(`INSERT INTO session_edges (from_session, to_session, kind)
|
|
408
466
|
VALUES ($1, $2, 'continues') ON CONFLICT DO NOTHING`, [record.id, priorId]);
|
|
@@ -410,7 +468,7 @@ export class PgSessionStore {
|
|
|
410
468
|
}
|
|
411
469
|
// Atomic session+facts ingest on the session's own client. Single source of truth: pg-fact-ingest.ts.
|
|
412
470
|
if (factSink !== null) {
|
|
413
|
-
await ingestSessionFactsOnClient(client, record.id, factSink.facts, record.scope);
|
|
471
|
+
await ingestSessionFactsOnClient(client, tenantId, record.id, factSink.facts, record.scope);
|
|
414
472
|
}
|
|
415
473
|
await client.query("COMMIT");
|
|
416
474
|
}
|
|
@@ -437,6 +495,7 @@ export class PgSessionStore {
|
|
|
437
495
|
await this.pool.query("INSERT INTO session_chunk_map (chunk_id, session_id, chunk_idx) VALUES ($1, $2, $3)", [chunkId, record.id, chunkIdx]);
|
|
438
496
|
}
|
|
439
497
|
catch (err) {
|
|
498
|
+
recordEmbedFailure("chunk");
|
|
440
499
|
process.stderr.write(`[nlm] embedding chunk failed session=${record.id} chunk=${chunkIdx}: ${String(err)}\n`);
|
|
441
500
|
}
|
|
442
501
|
}
|
|
@@ -450,27 +509,30 @@ export class PgSessionStore {
|
|
|
450
509
|
continue;
|
|
451
510
|
try {
|
|
452
511
|
const { vector } = await embedder.embed(factText, "document");
|
|
453
|
-
await factSink.factStore.upsertEmbedding(fact.id, vector);
|
|
512
|
+
await factSink.factStore.upsertEmbedding(tenantId, fact.id, vector);
|
|
454
513
|
}
|
|
455
|
-
catch {
|
|
456
|
-
// Tolerated;
|
|
514
|
+
catch (err) {
|
|
515
|
+
// Tolerated; the fact row stays current, semantic recall misses
|
|
516
|
+
// it until a future re-ingest.
|
|
517
|
+
recordEmbedFailure("fact");
|
|
518
|
+
process.stderr.write(`[nlm] embedding fact failed fact=${fact.id}: ${String(err)}\n`);
|
|
457
519
|
}
|
|
458
520
|
}
|
|
459
521
|
}
|
|
460
522
|
}
|
|
461
523
|
}
|
|
462
|
-
async insertSessionForTest(session) {
|
|
524
|
+
async insertSessionForTest(session, tenantId = DEFAULT_TEAM_ID) {
|
|
463
525
|
const status = session.status === "idle" ? "active" : session.status;
|
|
464
526
|
await this.pool.query(`INSERT INTO sessions (id, runtime, runtime_session_id, started_at, ended_at,
|
|
465
|
-
duration_min, label, summary, body, status, transcript_kind, transcript_path)
|
|
466
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
|
|
527
|
+
duration_min, label, summary, body, status, transcript_kind, transcript_path, tenant_id)
|
|
528
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [
|
|
467
529
|
session.id, session.runtime, session.runtimeSessionId, session.startedAt,
|
|
468
530
|
session.endedAt, session.durationMin, session.label, session.summary,
|
|
469
|
-
session.body, status, session.transcriptKind, session.transcriptPath,
|
|
531
|
+
session.body, status, session.transcriptKind, session.transcriptPath, tenantId,
|
|
470
532
|
]);
|
|
471
533
|
for (const e of session.entities) {
|
|
472
|
-
await this.pool.query("INSERT INTO entities (canonical, type, status) VALUES ($1, 'candidate', 'active') ON CONFLICT DO NOTHING", [e]);
|
|
473
|
-
await this.pool.query("INSERT INTO session_entities (session_id, entity_canonical) VALUES ($1, $2) ON CONFLICT DO NOTHING", [session.id, e]);
|
|
534
|
+
await this.pool.query("INSERT INTO entities (tenant_id, canonical, type, status) VALUES ($1, $2, 'candidate', 'active') ON CONFLICT DO NOTHING", [tenantId, e]);
|
|
535
|
+
await this.pool.query("INSERT INTO session_entities (tenant_id, session_id, entity_canonical) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", [tenantId, session.id, e]);
|
|
474
536
|
}
|
|
475
537
|
for (let i = 0; i < session.decisions.length; i++) {
|
|
476
538
|
await this.pool.query("INSERT INTO markers (session_id, kind, text, position) VALUES ($1, 'decision', $2, $3)", [session.id, session.decisions[i], i]);
|
|
@@ -480,32 +542,41 @@ export class PgSessionStore {
|
|
|
480
542
|
}
|
|
481
543
|
}
|
|
482
544
|
/** PG counterpart of SqliteSessionStore.listBackfillCandidates. */
|
|
483
|
-
async listBackfillCandidates(filter) {
|
|
545
|
+
async listBackfillCandidates(tenantId, filter) {
|
|
484
546
|
const params = [filter.cutoff];
|
|
485
547
|
let fromClause = "";
|
|
486
548
|
if (filter.from) {
|
|
487
549
|
params.push(filter.from);
|
|
488
550
|
fromClause = `AND s.id > $${params.length}`;
|
|
489
551
|
}
|
|
552
|
+
// facts.tenant_id is always stamped to match its source_session_id's
|
|
553
|
+
// session tenant (every write site stamps both together), so scoping by
|
|
554
|
+
// source_session_id = s.id against an already tenant-filtered `s` is
|
|
555
|
+
// already sufficient. The f.tenant_id = s.tenant_id condition below is
|
|
556
|
+
// defense in depth (guard test C4 permits column-to-column tenant
|
|
557
|
+
// equality as a join form).
|
|
490
558
|
const existingFactsClause = filter.reprocess
|
|
491
559
|
? ""
|
|
492
|
-
: "AND NOT EXISTS (SELECT 1 FROM facts f WHERE f.source_session_id = s.id)";
|
|
560
|
+
: "AND NOT EXISTS (SELECT 1 FROM facts f WHERE f.source_session_id = s.id AND f.tenant_id = s.tenant_id)";
|
|
561
|
+
const tc = tenantClausePg(tenantId, params.length + 1, "s.tenant_id");
|
|
562
|
+
params.push(tc.param);
|
|
493
563
|
const result = await this.pool.query(`SELECT s.id, s.started_at, s.body FROM sessions s
|
|
494
564
|
WHERE s.started_at < $1 AND s.body IS NOT NULL AND length(s.body) > 0
|
|
495
565
|
${existingFactsClause}
|
|
496
566
|
${fromClause}
|
|
567
|
+
AND ${tc.sql}
|
|
497
568
|
ORDER BY s.started_at ASC, s.id ASC`, params);
|
|
498
569
|
return result.rows.map((r) => ({ id: r.id, startedAt: r.started_at, body: r.body }));
|
|
499
570
|
}
|
|
500
571
|
/** PG counterpart of SqliteSessionStore.insertFactsForSession — writes facts
|
|
501
572
|
* for an EXISTING session row (deterministic supersedence + best-effort
|
|
502
573
|
* embeddings) in one transaction. Used by the fact backfill. */
|
|
503
|
-
async insertFactsForSession(sessionId, factStore, facts, embedder = null) {
|
|
504
|
-
const sessionScope = await this.getSessionScopeById(sessionId);
|
|
574
|
+
async insertFactsForSession(tenantId, sessionId, factStore, facts, embedder = null) {
|
|
575
|
+
const sessionScope = await this.getSessionScopeById(tenantId, sessionId);
|
|
505
576
|
const client = await this.pool.connect();
|
|
506
577
|
try {
|
|
507
578
|
await client.query("BEGIN");
|
|
508
|
-
await ingestSessionFactsOnClient(client, sessionId, facts, sessionScope);
|
|
579
|
+
await ingestSessionFactsOnClient(client, tenantId, sessionId, facts, sessionScope);
|
|
509
580
|
await client.query("COMMIT");
|
|
510
581
|
}
|
|
511
582
|
catch (err) {
|
|
@@ -522,7 +593,7 @@ export class PgSessionStore {
|
|
|
522
593
|
continue;
|
|
523
594
|
try {
|
|
524
595
|
const { vector } = await embedder.embed(factText, "document");
|
|
525
|
-
await factStore.upsertEmbedding(fact.id, vector);
|
|
596
|
+
await factStore.upsertEmbedding(tenantId, fact.id, vector);
|
|
526
597
|
}
|
|
527
598
|
catch {
|
|
528
599
|
// Best-effort; a per-fact embed failure leaves the fact current but
|
|
@@ -531,12 +602,13 @@ export class PgSessionStore {
|
|
|
531
602
|
}
|
|
532
603
|
}
|
|
533
604
|
}
|
|
534
|
-
async loadEntities(ids) {
|
|
605
|
+
async loadEntities(tenantId, ids) {
|
|
535
606
|
if (ids.length === 0)
|
|
536
607
|
return new Map();
|
|
537
608
|
const placeholders = ids.map((_, i) => `$${i + 1}`).join(",");
|
|
609
|
+
const tc = tenantClausePg(tenantId, ids.length + 1);
|
|
538
610
|
const result = await this.pool.query(`SELECT session_id, entity_canonical FROM session_entities
|
|
539
|
-
WHERE session_id IN (${placeholders}) ORDER BY session_id`, [...ids]);
|
|
611
|
+
WHERE session_id IN (${placeholders}) AND ${tc.sql} ORDER BY session_id`, [...ids, tc.param]);
|
|
540
612
|
const out = new Map();
|
|
541
613
|
for (const r of result.rows) {
|
|
542
614
|
const list = out.get(r.session_id);
|