hippo-memory 1.60.0 → 1.62.0
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 +29 -54
- package/dist/ablation.js +9 -27
- package/dist/agent-memories/apply.d.ts +1 -1
- package/dist/agent-memories/apply.js +4 -1
- package/dist/agent-memories/claude-code.d.ts +3 -1
- package/dist/agent-memories/claude-code.js +54 -10
- package/dist/agent-memories/codex.js +1 -1
- package/dist/agent-memories/gemini.js +1 -1
- package/dist/agent-memories/legacy.js +1 -1
- package/dist/agent-memories/source.js +1 -1
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +22 -9
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/ambient-store.d.ts +14 -0
- package/dist/ambient-store.js +90 -0
- package/dist/ambient.d.ts +23 -0
- package/dist/ambient.js +72 -50
- package/dist/api/assemble.d.ts +93 -0
- package/dist/api/assemble.js +153 -0
- package/dist/api/audit.d.ts +17 -0
- package/dist/api/audit.js +23 -0
- package/dist/api/auth.d.ts +79 -0
- package/dist/api/auth.js +178 -0
- package/dist/api/context-select.d.ts +49 -0
- package/dist/api/context-select.js +344 -0
- package/dist/api/context-types.d.ts +105 -0
- package/dist/api/context-types.js +3 -0
- package/dist/api/context.d.ts +31 -0
- package/dist/api/context.js +378 -0
- package/dist/api/dormant.d.ts +30 -0
- package/dist/api/dormant.js +140 -0
- package/dist/api/drill-down.d.ts +84 -0
- package/dist/api/drill-down.js +126 -0
- package/dist/api/forget.d.ts +57 -0
- package/dist/api/forget.js +87 -0
- package/dist/api/goals.d.ts +18 -0
- package/dist/api/goals.js +33 -0
- package/dist/api/learn.d.ts +31 -0
- package/dist/api/learn.js +88 -0
- package/dist/api/outcome.d.ts +61 -0
- package/dist/api/outcome.js +66 -0
- package/dist/api/promote.d.ts +54 -0
- package/dist/api/promote.js +192 -0
- package/dist/api/quarantine.d.ts +24 -0
- package/dist/api/quarantine.js +121 -0
- package/dist/api/recall-types.d.ts +390 -0
- package/dist/api/recall-types.js +3 -0
- package/dist/api/recall.d.ts +36 -0
- package/dist/api/recall.js +499 -0
- package/dist/api/remember.d.ts +35 -0
- package/dist/api/remember.js +43 -0
- package/dist/api/sleep.d.ts +136 -0
- package/dist/api/sleep.js +256 -0
- package/dist/api/tokens.d.ts +26 -0
- package/dist/api/tokens.js +60 -0
- package/dist/api/types.d.ts +56 -0
- package/dist/api/types.js +38 -0
- package/dist/api.d.ts +20 -1262
- package/dist/api.js +25 -2727
- package/dist/audit.d.ts +3 -0
- package/dist/audit.js +6 -3
- package/dist/auth.d.ts +45 -4
- package/dist/auth.js +125 -48
- package/dist/autolearn.js +2 -2
- package/dist/capture/command.d.ts +33 -0
- package/dist/capture/command.js +264 -0
- package/dist/capture/compact.d.ts +44 -0
- package/dist/capture/compact.js +354 -0
- package/dist/capture/extract.d.ts +21 -0
- package/dist/capture/extract.js +464 -0
- package/dist/capture/transcript.d.ts +40 -0
- package/dist/capture/transcript.js +193 -0
- package/dist/capture-error.js +2 -1
- package/dist/churn-git.js +4 -2
- package/dist/cli/audit.d.ts +3 -0
- package/dist/cli/audit.js +159 -0
- package/dist/cli/auth.d.ts +2 -0
- package/dist/cli/auth.js +171 -0
- package/dist/cli/briefs.d.ts +4 -0
- package/dist/cli/briefs.js +453 -0
- package/dist/cli/card.d.ts +3 -0
- package/dist/cli/card.js +333 -0
- package/dist/cli/context.d.ts +15 -0
- package/dist/cli/context.js +376 -0
- package/dist/cli/continuity.d.ts +6 -0
- package/dist/cli/continuity.js +457 -0
- package/dist/cli/curate.d.ts +19 -0
- package/dist/cli/curate.js +556 -0
- package/dist/cli/dag.d.ts +5 -0
- package/dist/cli/dag.js +177 -0
- package/dist/cli/decisions.d.ts +4 -0
- package/dist/cli/decisions.js +528 -0
- package/dist/cli/eval.d.ts +5 -0
- package/dist/cli/eval.js +213 -0
- package/dist/cli/explain.d.ts +4 -0
- package/dist/cli/explain.js +156 -0
- package/dist/cli/goals.d.ts +2 -0
- package/dist/cli/goals.js +196 -0
- package/dist/cli/hook-blocks.d.ts +18 -0
- package/dist/cli/hook-blocks.js +233 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.js +305 -0
- package/dist/cli/maintenance.d.ts +4 -0
- package/dist/cli/maintenance.js +190 -0
- package/dist/cli/playbooks.d.ts +4 -0
- package/dist/cli/playbooks.js +573 -0
- package/dist/cli/projects.js +9 -6
- package/dist/cli/recall.d.ts +7 -0
- package/dist/cli/recall.js +582 -0
- package/dist/cli/remember.d.ts +5 -0
- package/dist/cli/remember.js +485 -0
- package/dist/cli/serve.d.ts +5 -0
- package/dist/cli/serve.js +40 -0
- package/dist/cli/session-hooks.d.ts +29 -0
- package/dist/cli/session-hooks.js +647 -0
- package/dist/cli/setup.d.ts +4 -0
- package/dist/cli/setup.js +397 -0
- package/dist/cli/shared.d.ts +15 -19
- package/dist/cli/shared.js +50 -341
- package/dist/cli/slack.d.ts +2 -0
- package/dist/cli/slack.js +171 -0
- package/dist/cli/status.d.ts +18 -0
- package/dist/cli/status.js +409 -0
- package/dist/cli/transfer.d.ts +10 -0
- package/dist/cli/transfer.js +424 -0
- package/dist/cli/usage.d.ts +85 -0
- package/dist/cli/usage.js +741 -0
- package/dist/cli.d.ts +71 -120
- package/dist/cli.js +170 -8469
- package/dist/client.js +15 -8
- package/dist/compaction-record.d.ts +0 -2
- package/dist/compaction-record.js +7 -5
- package/dist/connectors/github/backfill.js +94 -87
- package/dist/connectors/github/cli-impl.js +4 -3
- package/dist/connectors/github/dlq.js +67 -54
- package/dist/connectors/github/ingest.js +34 -35
- package/dist/connectors/github/tenant-routing.js +3 -2
- package/dist/connectors/github/webhook.js +135 -216
- package/dist/connectors/slack/dlq.js +49 -61
- package/dist/connectors/slack/ingest.js +55 -48
- package/dist/connectors/slack/tenant-routing.js +4 -3
- package/dist/connectors/slack/webhook.js +72 -74
- package/dist/consolidate/conflicts.d.ts +10 -0
- package/dist/consolidate/conflicts.js +178 -0
- package/dist/consolidate/decay.d.ts +12 -0
- package/dist/consolidate/decay.js +145 -0
- package/dist/consolidate/llm-passes.d.ts +3 -0
- package/dist/consolidate/llm-passes.js +141 -0
- package/dist/consolidate/merge.d.ts +7 -0
- package/dist/consolidate/merge.js +251 -0
- package/dist/consolidate/physics-pass.d.ts +3 -0
- package/dist/consolidate/physics-pass.js +60 -0
- package/dist/consolidate/run.d.ts +69 -0
- package/dist/consolidate/run.js +76 -0
- package/dist/consolidate/sleep.d.ts +18 -0
- package/dist/consolidate/sleep.js +209 -0
- package/dist/consolidate/traces.d.ts +4 -0
- package/dist/consolidate/traces.js +178 -0
- package/dist/context-auto.js +7 -11
- package/dist/context-render.d.ts +1 -1
- package/dist/context-render.js +1 -1
- package/dist/customer-notes.d.ts +3 -0
- package/dist/customer-notes.js +83 -72
- package/dist/dag.js +225 -188
- package/dist/dashboard-actions.d.ts +20 -0
- package/dist/dashboard-actions.js +88 -0
- package/dist/dashboard-params.d.ts +45 -0
- package/dist/dashboard-params.js +127 -0
- package/dist/dashboard-queries.d.ts +15 -0
- package/dist/dashboard-queries.js +355 -0
- package/dist/dashboard-snapshot.d.ts +138 -0
- package/dist/dashboard-snapshot.js +308 -0
- package/dist/dashboard-types.d.ts +163 -0
- package/dist/dashboard-types.js +3 -0
- package/dist/dashboard.d.ts +6 -7
- package/dist/dashboard.js +228 -202
- package/dist/db/busy.d.ts +5 -0
- package/dist/db/busy.js +24 -0
- package/dist/db/continuity.d.ts +5 -0
- package/dist/db/continuity.js +145 -0
- package/dist/db/meta.d.ts +8 -0
- package/dist/db/meta.js +35 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +138 -0
- package/dist/db/migrations/index.d.ts +5 -0
- package/dist/db/migrations/index.js +109 -0
- package/dist/db/migrations/types.d.ts +14 -0
- package/dist/db/migrations/types.js +2 -0
- package/dist/db/migrations/v01.d.ts +3 -0
- package/dist/db/migrations/v01.js +38 -0
- package/dist/db/migrations/v02.d.ts +3 -0
- package/dist/db/migrations/v02.js +21 -0
- package/dist/db/migrations/v03.d.ts +3 -0
- package/dist/db/migrations/v03.js +22 -0
- package/dist/db/migrations/v04.d.ts +3 -0
- package/dist/db/migrations/v04.js +28 -0
- package/dist/db/migrations/v05.d.ts +3 -0
- package/dist/db/migrations/v05.js +21 -0
- package/dist/db/migrations/v06.d.ts +3 -0
- package/dist/db/migrations/v06.js +25 -0
- package/dist/db/migrations/v07.d.ts +3 -0
- package/dist/db/migrations/v07.js +13 -0
- package/dist/db/migrations/v08.d.ts +3 -0
- package/dist/db/migrations/v08.js +8 -0
- package/dist/db/migrations/v09.d.ts +3 -0
- package/dist/db/migrations/v09.js +13 -0
- package/dist/db/migrations/v10.d.ts +3 -0
- package/dist/db/migrations/v10.js +17 -0
- package/dist/db/migrations/v11.d.ts +3 -0
- package/dist/db/migrations/v11.js +15 -0
- package/dist/db/migrations/v12.d.ts +3 -0
- package/dist/db/migrations/v12.js +11 -0
- package/dist/db/migrations/v13.d.ts +3 -0
- package/dist/db/migrations/v13.js +15 -0
- package/dist/db/migrations/v14.d.ts +3 -0
- package/dist/db/migrations/v14.js +66 -0
- package/dist/db/migrations/v15.d.ts +3 -0
- package/dist/db/migrations/v15.js +42 -0
- package/dist/db/migrations/v16.d.ts +3 -0
- package/dist/db/migrations/v16.js +61 -0
- package/dist/db/migrations/v17.d.ts +3 -0
- package/dist/db/migrations/v17.js +46 -0
- package/dist/db/migrations/v18.d.ts +3 -0
- package/dist/db/migrations/v18.js +60 -0
- package/dist/db/migrations/v19.d.ts +3 -0
- package/dist/db/migrations/v19.js +28 -0
- package/dist/db/migrations/v20.d.ts +3 -0
- package/dist/db/migrations/v20.js +42 -0
- package/dist/db/migrations/v21.d.ts +3 -0
- package/dist/db/migrations/v21.js +16 -0
- package/dist/db/migrations/v22.d.ts +3 -0
- package/dist/db/migrations/v22.js +82 -0
- package/dist/db/migrations/v23.d.ts +3 -0
- package/dist/db/migrations/v23.js +48 -0
- package/dist/db/migrations/v24.d.ts +3 -0
- package/dist/db/migrations/v24.js +72 -0
- package/dist/db/migrations/v25.d.ts +3 -0
- package/dist/db/migrations/v25.js +46 -0
- package/dist/db/migrations/v26.d.ts +3 -0
- package/dist/db/migrations/v26.js +23 -0
- package/dist/db/migrations/v27.d.ts +3 -0
- package/dist/db/migrations/v27.js +57 -0
- package/dist/db/migrations/v28.d.ts +3 -0
- package/dist/db/migrations/v28.js +38 -0
- package/dist/db/migrations/v29.d.ts +3 -0
- package/dist/db/migrations/v29.js +78 -0
- package/dist/db/migrations/v30.d.ts +3 -0
- package/dist/db/migrations/v30.js +92 -0
- package/dist/db/migrations/v31.d.ts +3 -0
- package/dist/db/migrations/v31.js +74 -0
- package/dist/db/migrations/v32.d.ts +3 -0
- package/dist/db/migrations/v32.js +102 -0
- package/dist/db/migrations/v33.d.ts +3 -0
- package/dist/db/migrations/v33.js +104 -0
- package/dist/db/migrations/v34.d.ts +3 -0
- package/dist/db/migrations/v34.js +93 -0
- package/dist/db/migrations/v35.d.ts +3 -0
- package/dist/db/migrations/v35.js +98 -0
- package/dist/db/migrations/v36.d.ts +3 -0
- package/dist/db/migrations/v36.js +98 -0
- package/dist/db/migrations/v37.d.ts +3 -0
- package/dist/db/migrations/v37.js +219 -0
- package/dist/db/migrations/v38.d.ts +3 -0
- package/dist/db/migrations/v38.js +277 -0
- package/dist/db/migrations/v39.d.ts +3 -0
- package/dist/db/migrations/v39.js +59 -0
- package/dist/db/migrations/v40.d.ts +3 -0
- package/dist/db/migrations/v40.js +74 -0
- package/dist/db/migrations/v41.d.ts +3 -0
- package/dist/db/migrations/v41.js +43 -0
- package/dist/db/migrations/v42.d.ts +3 -0
- package/dist/db/migrations/v42.js +41 -0
- package/dist/db/migrations/v43.d.ts +3 -0
- package/dist/db/migrations/v43.js +67 -0
- package/dist/db/migrations/v44.d.ts +3 -0
- package/dist/db/migrations/v44.js +28 -0
- package/dist/db/migrations/v45.d.ts +3 -0
- package/dist/db/migrations/v45.js +30 -0
- package/dist/db/migrations/v46.d.ts +3 -0
- package/dist/db/migrations/v46.js +25 -0
- package/dist/db/migrations/v47.d.ts +3 -0
- package/dist/db/migrations/v47.js +17 -0
- package/dist/db/migrations/v48.d.ts +3 -0
- package/dist/db/migrations/v48.js +10 -0
- package/dist/db/migrations/v49.d.ts +3 -0
- package/dist/db/migrations/v49.js +31 -0
- package/dist/db/migrations/v50.d.ts +3 -0
- package/dist/db/migrations/v50.js +67 -0
- package/dist/db/migrations/v51.d.ts +3 -0
- package/dist/db/migrations/v51.js +14 -0
- package/dist/db/migrations/v52.d.ts +3 -0
- package/dist/db/migrations/v52.js +7 -0
- package/dist/db/open.d.ts +23 -0
- package/dist/db/open.js +146 -0
- package/dist/db/sqlite.d.ts +21 -0
- package/dist/db/sqlite.js +8 -0
- package/dist/db/tables.d.ts +7 -0
- package/dist/db/tables.js +38 -0
- package/dist/db.d.ts +6 -46
- package/dist/db.js +5 -3049
- package/dist/decisions.d.ts +4 -1
- package/dist/decisions.js +102 -83
- package/dist/dedupe.js +3 -2
- package/dist/delivery-recorder.js +4 -1
- package/dist/doctor.js +14 -10
- package/dist/embedding-provider.d.ts +1 -1
- package/dist/embedding-provider.js +4 -3
- package/dist/embeddings.d.ts +9 -52
- package/dist/embeddings.js +43 -297
- package/dist/env.d.ts +75 -0
- package/dist/env.js +119 -0
- package/dist/eval-suite.js +1 -1
- package/dist/eval.js +2 -2
- package/dist/extract.js +1 -1
- package/dist/gated-write.js +3 -1
- package/dist/goals.d.ts +3 -1
- package/dist/goals.js +117 -86
- package/dist/graph/read.d.ts +73 -0
- package/dist/graph/read.js +325 -0
- package/dist/graph/rows.d.ts +45 -0
- package/dist/graph/rows.js +51 -0
- package/dist/graph/types.d.ts +83 -0
- package/dist/graph/types.js +11 -0
- package/dist/graph/write.d.ts +93 -0
- package/dist/{graph.js → graph/write.js} +6 -384
- package/dist/graph-extract.js +2 -1
- package/dist/graph-recall.d.ts +1 -1
- package/dist/graph-recall.js +2 -2
- package/dist/graph-stream.js +1 -1
- package/dist/graph-view.d.ts +1 -1
- package/dist/graph-view.js +1 -1
- package/dist/half-life-migration.d.ts +1 -1
- package/dist/half-life-migration.js +2 -1
- package/dist/hooks/codex-session.d.ts +8 -0
- package/dist/hooks/codex-session.js +76 -0
- package/dist/hooks/codex-wrapper.d.ts +55 -0
- package/dist/hooks/codex-wrapper.js +288 -0
- package/dist/hooks/json-hooks.d.ts +63 -0
- package/dist/hooks/json-hooks.js +356 -0
- package/dist/hooks/opencode.d.ts +50 -0
- package/dist/hooks/opencode.js +202 -0
- package/dist/hooks/shared.d.ts +54 -0
- package/dist/hooks/shared.js +77 -0
- package/dist/http-retry.d.ts +2 -0
- package/dist/http-retry.js +4 -3
- package/dist/http-util.d.ts +3 -0
- package/dist/http-util.js +10 -0
- package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
- package/dist/importers/core.js +141 -0
- package/dist/importers/markdown-parse.d.ts +41 -0
- package/dist/importers/markdown-parse.js +132 -0
- package/dist/importers/markdown.d.ts +3 -0
- package/dist/importers/markdown.js +92 -0
- package/dist/importers/sources.d.ts +7 -0
- package/dist/importers/sources.js +229 -0
- package/dist/importers/vault.d.ts +11 -0
- package/dist/importers/vault.js +352 -0
- package/dist/incidents.d.ts +3 -0
- package/dist/incidents.js +52 -43
- package/dist/index.d.ts +25 -6
- package/dist/index.js +23 -6
- package/dist/invalidation.js +2 -1
- package/dist/judgment.js +2 -1
- package/dist/keyset.d.ts +13 -0
- package/dist/keyset.js +8 -0
- package/dist/local-embedding.d.ts +13 -0
- package/dist/local-embedding.js +165 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +19 -1
- package/dist/mcp/admin-tools.d.ts +8 -0
- package/dist/mcp/admin-tools.js +116 -0
- package/dist/mcp/format.d.ts +27 -0
- package/dist/mcp/format.js +135 -0
- package/dist/mcp/memory-tools.d.ts +5 -0
- package/dist/mcp/memory-tools.js +83 -0
- package/dist/mcp/protocol.d.ts +83 -0
- package/dist/mcp/protocol.js +55 -0
- package/dist/mcp/recall-tools.d.ts +6 -0
- package/dist/mcp/recall-tools.js +320 -0
- package/dist/mcp/request.d.ts +10 -0
- package/dist/mcp/request.js +163 -0
- package/dist/mcp/server.d.ts +4 -75
- package/dist/mcp/server.js +7 -1190
- package/dist/mcp/session-state.d.ts +11 -0
- package/dist/mcp/session-state.js +28 -0
- package/dist/mcp/stdio.d.ts +8 -0
- package/dist/mcp/stdio.js +79 -0
- package/dist/mcp/tools.d.ts +11 -0
- package/dist/mcp/tools.js +247 -0
- package/dist/memory.d.ts +3 -0
- package/dist/memory.js +27 -1
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +2 -1
- package/dist/owner-validation.js +2 -1
- package/dist/physics-state.js +10 -7
- package/dist/policies.d.ts +3 -0
- package/dist/policies.js +93 -74
- package/dist/postinstall.js +3 -2
- package/dist/predictions/planning-fallacy.d.ts +100 -0
- package/dist/predictions/planning-fallacy.js +190 -0
- package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
- package/dist/predictions/store.js +434 -0
- package/dist/processes.d.ts +3 -0
- package/dist/processes.js +94 -76
- package/dist/project-briefs.d.ts +3 -0
- package/dist/project-briefs.js +142 -113
- package/dist/project-identity.js +3 -2
- package/dist/project-merge.d.ts +12 -4
- package/dist/project-merge.js +132 -45
- package/dist/quarantine.d.ts +2 -1
- package/dist/quarantine.js +8 -5
- package/dist/raw-archive.js +1 -1
- package/dist/recall-history.js +3 -2
- package/dist/recall-pipeline.d.ts +2 -2
- package/dist/recall-pipeline.js +16 -8
- package/dist/recall-scope.js +1 -1
- package/dist/recall-trace.d.ts +1 -1
- package/dist/refine-llm.js +2 -1
- package/dist/reject-flow.js +6 -1
- package/dist/rerankers/clef.js +6 -5
- package/dist/rerankers/jev.d.ts +1 -1
- package/dist/rerankers/jev.js +4 -4
- package/dist/rerankers/llm.d.ts +4 -2
- package/dist/rerankers/llm.js +58 -38
- package/dist/rerankers/types.d.ts +1 -1
- package/dist/salience.js +1 -1
- package/dist/scheduler.d.ts +1 -0
- package/dist/scheduler.js +26 -4
- package/dist/scope.js +4 -3
- package/dist/search/as-of.d.ts +10 -0
- package/dist/search/as-of.js +22 -0
- package/dist/search/bm25-search.d.ts +14 -0
- package/dist/search/bm25-search.js +43 -0
- package/dist/search/bm25.d.ts +15 -0
- package/dist/search/bm25.js +54 -0
- package/dist/search/boosts.d.ts +54 -0
- package/dist/search/boosts.js +94 -0
- package/dist/search/breakdown.d.ts +7 -0
- package/dist/search/breakdown.js +20 -0
- package/dist/search/explain.d.ts +25 -0
- package/dist/search/explain.js +31 -0
- package/dist/search/finalize.d.ts +8 -0
- package/dist/search/finalize.js +52 -0
- package/dist/search/fusion.d.ts +27 -0
- package/dist/search/fusion.js +42 -0
- package/dist/search/hybrid-score.d.ts +20 -0
- package/dist/search/hybrid-score.js +73 -0
- package/dist/search/hybrid.d.ts +46 -0
- package/dist/search/hybrid.js +64 -0
- package/dist/search/physics-search.d.ts +29 -0
- package/dist/search/physics-search.js +162 -0
- package/dist/search/rerank.d.ts +10 -0
- package/dist/search/rerank.js +72 -0
- package/dist/search/temporal.d.ts +15 -0
- package/dist/search/temporal.js +45 -0
- package/dist/search/types.d.ts +91 -0
- package/dist/search/types.js +2 -0
- package/dist/search/vector.d.ts +30 -0
- package/dist/search/vector.js +71 -0
- package/dist/secret-detect.d.ts +2 -0
- package/dist/secret-detect.js +2 -1
- package/dist/server/auth.d.ts +52 -0
- package/dist/server/auth.js +221 -0
- package/dist/server/client-ip.d.ts +25 -0
- package/dist/server/client-ip.js +92 -0
- package/dist/server/cursor.d.ts +23 -0
- package/dist/server/cursor.js +58 -0
- package/dist/server/lifecycle.d.ts +7 -0
- package/dist/server/lifecycle.js +29 -0
- package/dist/server/mcp-http.d.ts +5 -0
- package/dist/server/mcp-http.js +199 -0
- package/dist/server/request.d.ts +33 -0
- package/dist/server/request.js +103 -0
- package/dist/server/routes/admin.d.ts +9 -0
- package/dist/server/routes/admin.js +158 -0
- package/dist/server/routes/customer-notes.d.ts +7 -0
- package/dist/server/routes/customer-notes.js +112 -0
- package/dist/server/routes/decisions.d.ts +7 -0
- package/dist/server/routes/decisions.js +133 -0
- package/dist/server/routes/incidents.d.ts +7 -0
- package/dist/server/routes/incidents.js +126 -0
- package/dist/server/routes/memories.d.ts +10 -0
- package/dist/server/routes/memories.js +179 -0
- package/dist/server/routes/policies.d.ts +8 -0
- package/dist/server/routes/policies.js +151 -0
- package/dist/server/routes/predictions.d.ts +7 -0
- package/dist/server/routes/predictions.js +164 -0
- package/dist/server/routes/processes.d.ts +7 -0
- package/dist/server/routes/processes.js +161 -0
- package/dist/server/routes/project-briefs.d.ts +8 -0
- package/dist/server/routes/project-briefs.js +136 -0
- package/dist/server/routes/recall.d.ts +8 -0
- package/dist/server/routes/recall.js +340 -0
- package/dist/server/routes/skills.d.ts +8 -0
- package/dist/server/routes/skills.js +150 -0
- package/dist/server/types.d.ts +56 -0
- package/dist/server/types.js +2 -0
- package/dist/server/validation.d.ts +14 -0
- package/dist/server/validation.js +91 -0
- package/dist/server.d.ts +7 -61
- package/dist/server.js +69 -2362
- package/dist/session-digest.d.ts +1 -1
- package/dist/session-digest.js +9 -2
- package/dist/shared.d.ts +12 -1
- package/dist/shared.js +33 -27
- package/dist/skills.d.ts +3 -0
- package/dist/skills.js +88 -70
- package/dist/stdin.js +2 -2
- package/dist/store/audit-event.d.ts +19 -0
- package/dist/store/audit-event.js +33 -0
- package/dist/store/candidates.d.ts +42 -0
- package/dist/store/candidates.js +152 -0
- package/dist/store/conflicts.d.ts +44 -0
- package/dist/store/conflicts.js +444 -0
- package/dist/store/delete-and-batch.d.ts +63 -0
- package/dist/store/delete-and-batch.js +310 -0
- package/dist/store/entry-reads.d.ts +92 -0
- package/dist/store/entry-reads.js +255 -0
- package/dist/store/entry-row.d.ts +67 -0
- package/dist/store/entry-row.js +208 -0
- package/dist/store/entry-writes.d.ts +46 -0
- package/dist/store/entry-writes.js +148 -0
- package/dist/store/handoffs.d.ts +26 -0
- package/dist/store/handoffs.js +184 -0
- package/dist/store/index-and-stats.d.ts +52 -0
- package/dist/store/index-and-stats.js +214 -0
- package/dist/store/markdown.d.ts +10 -0
- package/dist/store/markdown.js +108 -0
- package/dist/store/mirrors.d.ts +49 -0
- package/dist/store/mirrors.js +312 -0
- package/dist/store/open.d.ts +15 -0
- package/dist/store/open.js +223 -0
- package/dist/store/rows.d.ts +178 -0
- package/dist/store/rows.js +158 -0
- package/dist/store/search-rows.d.ts +86 -0
- package/dist/store/search-rows.js +260 -0
- package/dist/store/sessions.d.ts +83 -0
- package/dist/store/sessions.js +272 -0
- package/dist/store/summaries.d.ts +94 -0
- package/dist/store/summaries.js +377 -0
- package/dist/store/tenant-lookup.d.ts +12 -0
- package/dist/store/tenant-lookup.js +16 -0
- package/dist/store-cards.js +2 -1
- package/dist/summary-dirty.d.ts +4 -0
- package/dist/summary-dirty.js +32 -0
- package/dist/support-bundle.js +4 -3
- package/dist/tenant.js +2 -2
- package/dist/tokenize.d.ts +2 -0
- package/dist/tokenize.js +16 -0
- package/dist/transcript-tail.d.ts +7 -0
- package/dist/transcript-tail.js +48 -0
- package/dist/vector-store.d.ts +27 -0
- package/dist/vector-store.js +210 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/working-memory.js +1 -1
- package/dist/yaml.js +36 -11
- package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist-ui/assets/index-DPN7cP19.js +33 -0
- package/dist-ui/assets/index-dFloKRVr.css +1 -0
- package/dist-ui/index.html +3 -25
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/capture.d.ts +0 -155
- package/dist/capture.js +0 -1295
- package/dist/consolidate.d.ts +0 -58
- package/dist/consolidate.js +0 -1124
- package/dist/graph.d.ts +0 -245
- package/dist/hooks.d.ts +0 -208
- package/dist/hooks.js +0 -1076
- package/dist/importers.js +0 -900
- package/dist/predictions.js +0 -620
- package/dist/search.d.ts +0 -320
- package/dist/search.js +0 -970
- package/dist/store.d.ts +0 -776
- package/dist/store.js +0 -3473
- package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
- package/dist-ui/assets/index-BhT8RvO6.js +0 -61
- package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
- package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
package/dist/customer-notes.js
CHANGED
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { BadRequestError, ConflictError, NotFoundError } from './api-errors.js';
|
|
23
23
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
24
|
-
import { writeEntry } from './store.js';
|
|
24
|
+
import { writeEntry } from './store/entry-writes.js';
|
|
25
25
|
import { assertTenantId } from './tenant.js';
|
|
26
|
-
import { markGraphDirty, removeGraphEntitiesForObject } from './graph.js';
|
|
26
|
+
import { markGraphDirty, removeGraphEntitiesForObject } from './graph/write.js';
|
|
27
27
|
import { createMemory, Layer } from './memory.js';
|
|
28
28
|
import { appendAuditEvent } from './audit.js';
|
|
29
29
|
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
30
|
+
import { keysetAfter } from './keyset.js';
|
|
30
31
|
export const VALID_NOTE_STATES = new Set([
|
|
31
32
|
'active',
|
|
32
33
|
'superseded',
|
|
@@ -93,9 +94,79 @@ const NOTE_COLS = `
|
|
|
93
94
|
function buildNoteContent(customer, note) {
|
|
94
95
|
return `${customer}\n\n${note}`;
|
|
95
96
|
}
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
97
|
+
// Preflight the supersede target BEFORE inserting the new row (so the new
|
|
98
|
+
// autoincrement id can never be its own supersede target); read the
|
|
99
|
+
// predecessor version in the same SELECT for server-derived versioning.
|
|
100
|
+
// Mirrors saveProjectBrief / saveSkill (codex P1 2026-05-28).
|
|
101
|
+
function preflightNoteSupersede(db, tenantId, supersedesId) {
|
|
102
|
+
// SAFETY: SELECT projects exactly status, version; .get() returns that
|
|
103
|
+
// shape for the matching row, or undefined when no note/tenant pair matches.
|
|
104
|
+
const pred = db.prepare(`SELECT status, version FROM customer_notes WHERE id = ? AND tenant_id = ?`).get(supersedesId, tenantId);
|
|
105
|
+
if (!pred) {
|
|
106
|
+
throw new NotFoundError(`saveCustomerNote: note ${supersedesId} to supersede not found for tenant ${tenantId}`);
|
|
107
|
+
}
|
|
108
|
+
if (pred.status !== 'active') {
|
|
109
|
+
throw new ConflictError(`saveCustomerNote: note ${supersedesId} is not active (status='${pred.status}'); only active notes can be superseded.`);
|
|
110
|
+
}
|
|
111
|
+
return pred.version + 1;
|
|
112
|
+
}
|
|
113
|
+
function insertNoteRow(db, memoryId, w, version) {
|
|
114
|
+
const result = db.prepare(`
|
|
115
|
+
INSERT INTO customer_notes(
|
|
116
|
+
memory_id, tenant_id, customer, note, version,
|
|
117
|
+
status, superseded_by, superseded_at, change_summary, closed_at, created_at
|
|
118
|
+
) VALUES (?, ?, ?, ?, ?, 'active', NULL, NULL, ?, NULL, ?)
|
|
119
|
+
`).run(memoryId, w.tenantId, w.customer, w.note, version, w.changeSummary, w.now);
|
|
120
|
+
return Number(result.lastInsertRowid ?? 0);
|
|
121
|
+
}
|
|
122
|
+
function supersedeNoteRow(db, w, supersedesId, noteId, version) {
|
|
123
|
+
const sup = db.prepare(`
|
|
124
|
+
UPDATE customer_notes
|
|
125
|
+
SET status = 'superseded', superseded_by = ?, superseded_at = ?
|
|
126
|
+
WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
|
|
127
|
+
`).run(noteId, w.now, supersedesId, w.tenantId, noteId);
|
|
128
|
+
if (sup.changes === 0) {
|
|
129
|
+
throw new ConflictError(`saveCustomerNote: note ${supersedesId} could not be superseded (no longer active or self-reference).`);
|
|
130
|
+
}
|
|
131
|
+
appendAuditEvent(db, {
|
|
132
|
+
tenantId: w.tenantId,
|
|
133
|
+
actor: w.actor,
|
|
134
|
+
op: 'customer_note_supersede',
|
|
135
|
+
targetId: String(supersedesId),
|
|
136
|
+
metadata: {
|
|
137
|
+
note_id: supersedesId,
|
|
138
|
+
superseded_by: noteId,
|
|
139
|
+
new_version: version,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/** The afterWrite body: preflight, INSERT, supersede, reload, create audit, all in one SAVEPOINT. */
|
|
144
|
+
function writeNoteRow(db, memoryId, w) {
|
|
145
|
+
const version = w.supersedesId !== undefined ? preflightNoteSupersede(db, w.tenantId, w.supersedesId) : 1;
|
|
146
|
+
const noteId = insertNoteRow(db, memoryId, w, version);
|
|
147
|
+
if (w.supersedesId !== undefined)
|
|
148
|
+
supersedeNoteRow(db, w, w.supersedesId, noteId, version);
|
|
149
|
+
// SAFETY: SELECT ${NOTE_COLS} projects exactly the CustomerNoteRow columns;
|
|
150
|
+
// .get() returns that row, or undefined only if the just-inserted id can't
|
|
151
|
+
// be found.
|
|
152
|
+
const row = db.prepare(`SELECT ${NOTE_COLS} FROM customer_notes WHERE id = ?`)
|
|
153
|
+
.get(noteId);
|
|
154
|
+
if (!row)
|
|
155
|
+
throw new Error('saveCustomerNote: failed to reload saved note row');
|
|
156
|
+
// GDPR-light metadata: ids + flags only, no note text.
|
|
157
|
+
appendAuditEvent(db, {
|
|
158
|
+
tenantId: w.tenantId,
|
|
159
|
+
actor: w.actor,
|
|
160
|
+
op: 'customer_note_create',
|
|
161
|
+
targetId: String(noteId),
|
|
162
|
+
metadata: {
|
|
163
|
+
note_id: noteId,
|
|
164
|
+
customer: w.customer,
|
|
165
|
+
version,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
return row;
|
|
169
|
+
}
|
|
99
170
|
/**
|
|
100
171
|
* Create a customer_note (or a new version that supersedes an existing one). Writes
|
|
101
172
|
* the memory mirror + the customer_notes row atomically inside writeEntry's SAVEPOINT.
|
|
@@ -124,75 +195,14 @@ export function saveCustomerNote(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
124
195
|
baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
|
|
125
196
|
tenantId,
|
|
126
197
|
});
|
|
198
|
+
const w = {
|
|
199
|
+
tenantId, actor, customer, note: opts.note, changeSummary, supersedesId: opts.supersedesNoteId, now,
|
|
200
|
+
};
|
|
127
201
|
let savedRow;
|
|
128
202
|
writeEntry(hippoRoot, mem, {
|
|
129
203
|
actor,
|
|
130
204
|
afterWrite: (db, memoryId) => {
|
|
131
|
-
|
|
132
|
-
// autoincrement id can never be its own supersede target); read the
|
|
133
|
-
// predecessor version in the same SELECT for server-derived versioning.
|
|
134
|
-
// Mirrors saveProjectBrief / saveSkill (codex P1 2026-05-28).
|
|
135
|
-
let version = 1;
|
|
136
|
-
if (opts.supersedesNoteId !== undefined) {
|
|
137
|
-
// SAFETY: SELECT projects exactly status, version; .get() returns that
|
|
138
|
-
// shape for the matching row, or undefined when no note/tenant pair matches.
|
|
139
|
-
const pred = db.prepare(`SELECT status, version FROM customer_notes WHERE id = ? AND tenant_id = ?`).get(opts.supersedesNoteId, tenantId);
|
|
140
|
-
if (!pred) {
|
|
141
|
-
throw new NotFoundError(`saveCustomerNote: note ${opts.supersedesNoteId} to supersede not found for tenant ${tenantId}`);
|
|
142
|
-
}
|
|
143
|
-
if (pred.status !== 'active') {
|
|
144
|
-
throw new ConflictError(`saveCustomerNote: note ${opts.supersedesNoteId} is not active (status='${pred.status}'); only active notes can be superseded.`);
|
|
145
|
-
}
|
|
146
|
-
version = pred.version + 1;
|
|
147
|
-
}
|
|
148
|
-
const result = db.prepare(`
|
|
149
|
-
INSERT INTO customer_notes(
|
|
150
|
-
memory_id, tenant_id, customer, note, version,
|
|
151
|
-
status, superseded_by, superseded_at, change_summary, closed_at, created_at
|
|
152
|
-
) VALUES (?, ?, ?, ?, ?, 'active', NULL, NULL, ?, NULL, ?)
|
|
153
|
-
`).run(memoryId, tenantId, customer, opts.note, version, changeSummary, now);
|
|
154
|
-
const noteId = Number(result.lastInsertRowid ?? 0);
|
|
155
|
-
if (opts.supersedesNoteId !== undefined) {
|
|
156
|
-
const sup = db.prepare(`
|
|
157
|
-
UPDATE customer_notes
|
|
158
|
-
SET status = 'superseded', superseded_by = ?, superseded_at = ?
|
|
159
|
-
WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
|
|
160
|
-
`).run(noteId, now, opts.supersedesNoteId, tenantId, noteId);
|
|
161
|
-
if (sup.changes === 0) {
|
|
162
|
-
throw new ConflictError(`saveCustomerNote: note ${opts.supersedesNoteId} could not be superseded (no longer active or self-reference).`);
|
|
163
|
-
}
|
|
164
|
-
appendAuditEvent(db, {
|
|
165
|
-
tenantId,
|
|
166
|
-
actor,
|
|
167
|
-
op: 'customer_note_supersede',
|
|
168
|
-
targetId: String(opts.supersedesNoteId),
|
|
169
|
-
metadata: {
|
|
170
|
-
note_id: opts.supersedesNoteId,
|
|
171
|
-
superseded_by: noteId,
|
|
172
|
-
new_version: version,
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
// SAFETY: SELECT ${NOTE_COLS} projects exactly the CustomerNoteRow columns;
|
|
177
|
-
// .get() returns that row, or undefined only if the just-inserted id can't
|
|
178
|
-
// be found.
|
|
179
|
-
const row = db.prepare(`SELECT ${NOTE_COLS} FROM customer_notes WHERE id = ?`)
|
|
180
|
-
.get(noteId);
|
|
181
|
-
if (!row)
|
|
182
|
-
throw new Error('saveCustomerNote: failed to reload saved note row');
|
|
183
|
-
savedRow = row;
|
|
184
|
-
// GDPR-light metadata: ids + flags only, no note text.
|
|
185
|
-
appendAuditEvent(db, {
|
|
186
|
-
tenantId,
|
|
187
|
-
actor,
|
|
188
|
-
op: 'customer_note_create',
|
|
189
|
-
targetId: String(noteId),
|
|
190
|
-
metadata: {
|
|
191
|
-
note_id: noteId,
|
|
192
|
-
customer,
|
|
193
|
-
version,
|
|
194
|
-
},
|
|
195
|
-
});
|
|
205
|
+
savedRow = writeNoteRow(db, memoryId, w);
|
|
196
206
|
},
|
|
197
207
|
afterCommit: () => markGraphDirty(hippoRoot, tenantId, mem.id),
|
|
198
208
|
});
|
|
@@ -284,6 +294,7 @@ export function loadCustomerNoteById(hippoRoot, tenantId, id) {
|
|
|
284
294
|
export function loadCustomerNotes(hippoRoot, tenantId, opts = {}) {
|
|
285
295
|
assertTenantId('loadCustomerNotes', tenantId);
|
|
286
296
|
const limit = opts.limit ?? 100;
|
|
297
|
+
const after = keysetAfter('created_at', 'id', opts.after);
|
|
287
298
|
if (opts.status && !VALID_NOTE_STATES.has(opts.status)) {
|
|
288
299
|
throw new BadRequestError(`loadCustomerNotes: status must be one of ${Array.from(VALID_NOTE_STATES).join('|')}; got ${opts.status}`);
|
|
289
300
|
}
|
|
@@ -299,13 +310,13 @@ export function loadCustomerNotes(hippoRoot, tenantId, opts = {}) {
|
|
|
299
310
|
clauses.push('customer = ?');
|
|
300
311
|
params.push(opts.customer);
|
|
301
312
|
}
|
|
302
|
-
params.push(limit);
|
|
313
|
+
params.push(...after.params, limit);
|
|
303
314
|
// SAFETY: SELECT ${NOTE_COLS} projects exactly the CustomerNoteRow columns
|
|
304
315
|
// regardless of the dynamic WHERE clause built above; .all() returns rows
|
|
305
316
|
// in that shape.
|
|
306
317
|
const rows = db.prepare(`
|
|
307
318
|
SELECT ${NOTE_COLS} FROM customer_notes
|
|
308
|
-
WHERE ${clauses.join(' AND ')}
|
|
319
|
+
WHERE ${clauses.join(' AND ')}${after.sql}
|
|
309
320
|
ORDER BY created_at DESC, id DESC
|
|
310
321
|
LIMIT ?
|
|
311
322
|
`).all(...params);
|
package/dist/dag.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createMemory, Layer } from './memory.js';
|
|
2
|
-
import { writeEntry
|
|
2
|
+
import { writeEntry } from './store/entry-writes.js';
|
|
3
|
+
import { loadAllDirtySummaries, loadChildrenOfSummary, applyRebuildResult, clearSummaryDirtyAfterBuild, } from './store/summaries.js';
|
|
3
4
|
import { RejectedValueError } from './rejection.js';
|
|
4
5
|
import { redactSecretsStrict } from './secret-detect.js';
|
|
5
6
|
import { fetchWithRetry, llmTimeoutMs } from './http-retry.js';
|
|
@@ -86,17 +87,14 @@ export async function generateDagSummary(label, factContents, opts) {
|
|
|
86
87
|
return null;
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// single-tenant stores (every row 'default') get exactly one partition
|
|
98
|
-
// and iterate in the same order as before this fix — byte-identical
|
|
99
|
-
// behavior there.
|
|
90
|
+
// Hardening follow-up (mirrors consolidate.ts's mergeCandidatesByTenant,
|
|
91
|
+
// T1): partition unparented facts by tenantId BEFORE clustering so a
|
|
92
|
+
// cluster can never mix facts from different tenants into one
|
|
93
|
+
// LLM-synthesized summary. Map preserves insertion order, so
|
|
94
|
+
// single-tenant stores (every row 'default') get exactly one partition
|
|
95
|
+
// and iterate in the same order as before this fix — byte-identical
|
|
96
|
+
// behavior there.
|
|
97
|
+
function partitionFactsByTenant(unparented) {
|
|
100
98
|
const unparentedByTenant = new Map();
|
|
101
99
|
for (const fact of unparented) {
|
|
102
100
|
const key = derivationPartitionKey(fact.tenantId, fact.scope, fact.origin_project);
|
|
@@ -106,79 +104,149 @@ export async function buildDag(hippoRoot, facts, opts) {
|
|
|
106
104
|
else
|
|
107
105
|
unparentedByTenant.set(key, [fact]);
|
|
108
106
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
return unparentedByTenant;
|
|
108
|
+
}
|
|
109
|
+
/** The L2 summary entry for one cluster, landing in the facts' own tenant and scope. */
|
|
110
|
+
function createClusterSummaryEntry(summary, cluster, home) {
|
|
111
|
+
const memberCreatedAts = cluster.members.map((m) => m.created).sort();
|
|
112
|
+
// Every member of `cluster` shares home.tenantId by construction (the
|
|
113
|
+
// tenant partition above), so the summary lands in the same tenant
|
|
114
|
+
// as the facts it summarizes instead of always 'default'
|
|
115
|
+
// (memory.ts:535 defaults tenantId when the option is omitted).
|
|
116
|
+
const summaryEntry = createMemory(summary, {
|
|
117
|
+
layer: Layer.Semantic,
|
|
118
|
+
tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-summary'],
|
|
119
|
+
confidence: 'inferred',
|
|
120
|
+
dag_level: 2,
|
|
121
|
+
tenantId: home.tenantId,
|
|
122
|
+
scope: home.scope,
|
|
123
|
+
baseHalfLifeDays: home.baseHalfLifeDays,
|
|
124
|
+
});
|
|
125
|
+
summaryEntry.origin_project = home.originProject;
|
|
126
|
+
// Schema v25: cache descendant_count + earliest/latest_at on the summary
|
|
127
|
+
// row so DAG-aware recall (docs/plans/2026-05-05-dag-recall.md Task 2)
|
|
128
|
+
// can reason about scope without walking the children.
|
|
129
|
+
summaryEntry.descendant_count = cluster.members.length;
|
|
130
|
+
summaryEntry.earliest_at = memberCreatedAts[0];
|
|
131
|
+
summaryEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
|
|
132
|
+
return summaryEntry;
|
|
133
|
+
}
|
|
134
|
+
/** Summarize one eligible cluster, write the summary, then re-parent its members under it. */
|
|
135
|
+
async function summarizeCluster(hippoRoot, cluster, home, opts, result) {
|
|
136
|
+
const summary = await generateDagSummary(cluster.label, cluster.members.map((m) => m.content), opts);
|
|
137
|
+
if (!summary)
|
|
138
|
+
return;
|
|
139
|
+
const summaryEntry = createClusterSummaryEntry(summary, cluster, home);
|
|
140
|
+
// AT1 (plan §3 containment): a refused LLM-synthesized summary skips
|
|
141
|
+
// ONLY this cluster — the sleep cycle continues to the next one. The
|
|
142
|
+
// member re-parenting writes below never run for a skipped cluster
|
|
143
|
+
// (there is no summary id to parent them under).
|
|
144
|
+
//
|
|
145
|
+
// The tombstone check itself is tenant-scoped for free: writeEntry ->
|
|
146
|
+
// writeEntryDbOnly -> upsertEntryRow calls
|
|
147
|
+
// checkRejectionGuard(db, entry.tenantId ?? 'default', ...)
|
|
148
|
+
// (store/entry-writes.ts), reading tenantId off the entry being written. Now
|
|
149
|
+
// that summaryEntry carries home.tenantId instead of the implicit
|
|
150
|
+
// 'default', the guard consults that tenant's tombstones — no
|
|
151
|
+
// separate check needed here (unlike consolidate.ts's merge pass,
|
|
152
|
+
// which pre-checks via findRejectedValue because it writes through
|
|
153
|
+
// batchWriteAndDelete's bypassRejectionGuard path instead of
|
|
154
|
+
// writeEntry).
|
|
155
|
+
try {
|
|
156
|
+
writeEntry(hippoRoot, summaryEntry);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (err instanceof RejectedValueError) {
|
|
160
|
+
result.rejected++;
|
|
161
|
+
log.warn(`buildDag: cluster "${cluster.label}" skipped: summary matches a rejected value`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
result.summariesCreated++;
|
|
167
|
+
for (const member of cluster.members) {
|
|
168
|
+
const updated = { ...member, dag_parent_id: summaryEntry.id };
|
|
169
|
+
writeEntry(hippoRoot, updated);
|
|
170
|
+
result.factsLinked++;
|
|
171
|
+
}
|
|
172
|
+
// v0.30 / E3 — cancel the cascade of dirty-marks fired by member
|
|
173
|
+
// writeEntry calls (E2 hook on writeEntryDbOnly in store/entry-writes.ts).
|
|
174
|
+
// The summary we just built IS fresh, no rebuild needed. Without this,
|
|
175
|
+
// E3 in the SAME sleep cycle would re-rebuild every new summary
|
|
176
|
+
// (2x LLM cost). plan-eng-r1 HIGH must-fix.
|
|
177
|
+
clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.tenantId, 'buildDag');
|
|
178
|
+
}
|
|
179
|
+
export async function buildDag(hippoRoot, facts, opts) {
|
|
180
|
+
const result = { candidateClusters: 0, summariesCreated: 0, factsLinked: 0, rejected: 0 };
|
|
181
|
+
const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
182
|
+
const unparented = facts.filter((f) => f.dag_level === 1 && !f.dag_parent_id && f.tags.includes('extracted'));
|
|
183
|
+
for (const [, tenantFacts] of partitionFactsByTenant(unparented)) {
|
|
184
|
+
const home = {
|
|
185
|
+
tenantId: tenantFacts[0].tenantId,
|
|
186
|
+
scope: derivationScope(tenantFacts[0].scope),
|
|
187
|
+
originProject: tenantFacts[0].origin_project,
|
|
188
|
+
baseHalfLifeDays,
|
|
189
|
+
};
|
|
112
190
|
const clusters = clusterFacts(tenantFacts);
|
|
113
191
|
const eligibleClusters = clusters.filter((c) => c.members.length >= 3);
|
|
114
192
|
result.candidateClusters += eligibleClusters.length;
|
|
115
193
|
for (const cluster of eligibleClusters) {
|
|
116
|
-
|
|
117
|
-
if (!summary)
|
|
118
|
-
continue;
|
|
119
|
-
const memberCreatedAts = cluster.members.map((m) => m.created).sort();
|
|
120
|
-
// Every member of `cluster` shares factTenant by construction (the
|
|
121
|
-
// tenant partition above), so the summary lands in the same tenant
|
|
122
|
-
// as the facts it summarizes instead of always 'default'
|
|
123
|
-
// (memory.ts:535 defaults tenantId when the option is omitted).
|
|
124
|
-
const summaryEntry = createMemory(summary, {
|
|
125
|
-
layer: Layer.Semantic,
|
|
126
|
-
tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-summary'],
|
|
127
|
-
confidence: 'inferred',
|
|
128
|
-
dag_level: 2,
|
|
129
|
-
tenantId: factTenant,
|
|
130
|
-
scope: factScope,
|
|
131
|
-
baseHalfLifeDays,
|
|
132
|
-
});
|
|
133
|
-
summaryEntry.origin_project = tenantFacts[0].origin_project;
|
|
134
|
-
// Schema v25: cache descendant_count + earliest/latest_at on the summary
|
|
135
|
-
// row so DAG-aware recall (docs/plans/2026-05-05-dag-recall.md Task 2)
|
|
136
|
-
// can reason about scope without walking the children.
|
|
137
|
-
summaryEntry.descendant_count = cluster.members.length;
|
|
138
|
-
summaryEntry.earliest_at = memberCreatedAts[0];
|
|
139
|
-
summaryEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
|
|
140
|
-
// AT1 (plan §3 containment): a refused LLM-synthesized summary skips
|
|
141
|
-
// ONLY this cluster — the sleep cycle continues to the next one. The
|
|
142
|
-
// member re-parenting writes below never run for a skipped cluster
|
|
143
|
-
// (there is no summary id to parent them under).
|
|
144
|
-
//
|
|
145
|
-
// The tombstone check itself is tenant-scoped for free: writeEntry ->
|
|
146
|
-
// writeEntryDbOnly -> upsertEntryRow calls
|
|
147
|
-
// checkRejectionGuard(db, entry.tenantId ?? 'default', ...)
|
|
148
|
-
// (store.ts:1172), reading tenantId off the entry being written. Now
|
|
149
|
-
// that summaryEntry carries factTenant instead of the implicit
|
|
150
|
-
// 'default', the guard consults that tenant's tombstones — no
|
|
151
|
-
// separate check needed here (unlike consolidate.ts's merge pass,
|
|
152
|
-
// which pre-checks via findRejectedValue because it writes through
|
|
153
|
-
// batchWriteAndDelete's bypassRejectionGuard path instead of
|
|
154
|
-
// writeEntry).
|
|
155
|
-
try {
|
|
156
|
-
writeEntry(hippoRoot, summaryEntry);
|
|
157
|
-
}
|
|
158
|
-
catch (err) {
|
|
159
|
-
if (err instanceof RejectedValueError) {
|
|
160
|
-
result.rejected++;
|
|
161
|
-
log.warn(`buildDag: cluster "${cluster.label}" skipped: summary matches a rejected value`);
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
throw err;
|
|
165
|
-
}
|
|
166
|
-
result.summariesCreated++;
|
|
167
|
-
for (const member of cluster.members) {
|
|
168
|
-
const updated = { ...member, dag_parent_id: summaryEntry.id };
|
|
169
|
-
writeEntry(hippoRoot, updated);
|
|
170
|
-
result.factsLinked++;
|
|
171
|
-
}
|
|
172
|
-
// v0.30 / E3 — cancel the cascade of dirty-marks fired by member
|
|
173
|
-
// writeEntry calls (E2 hook on writeEntryDbOnly at store.ts:1214).
|
|
174
|
-
// The summary we just built IS fresh, no rebuild needed. Without this,
|
|
175
|
-
// E3 in the SAME sleep cycle would re-rebuild every new summary
|
|
176
|
-
// (2x LLM cost). plan-eng-r1 HIGH must-fix.
|
|
177
|
-
clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.tenantId, 'buildDag');
|
|
194
|
+
await summarizeCluster(hippoRoot, cluster, home, opts, result);
|
|
178
195
|
}
|
|
179
196
|
}
|
|
180
197
|
return result;
|
|
181
198
|
}
|
|
199
|
+
/** Regenerate one dirty summary from its children and tally the outcome; throws reach the caller's isolation. */
|
|
200
|
+
async function rebuildOneSummary(hippoRoot, summary, opts, result) {
|
|
201
|
+
const children = loadChildrenOfSummary(hippoRoot, summary.id, summary.tenantId);
|
|
202
|
+
if (children.length === 0) {
|
|
203
|
+
// Zero-child case: clear dirty + zero counts, no LLM call, no rebuild_count bump.
|
|
204
|
+
const { changed } = applyRebuildResult(hippoRoot, summary, {
|
|
205
|
+
content: summary.content,
|
|
206
|
+
descendant_count: 0,
|
|
207
|
+
earliest_at: null,
|
|
208
|
+
latest_at: null,
|
|
209
|
+
bumpRebuildCount: false,
|
|
210
|
+
zeroChildren: true,
|
|
211
|
+
actor: 'sleep',
|
|
212
|
+
});
|
|
213
|
+
if (changed)
|
|
214
|
+
result.zeroChildSkipped++;
|
|
215
|
+
// changed=false → race lost / row vanished; silently skip. `refused`
|
|
216
|
+
// is always false here — applyRebuildResult only checks the
|
|
217
|
+
// tombstone when bumpRebuildCount is true (store/summaries.ts).
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Derive label from summary's existing entity tags (mirrors clusterFacts)
|
|
221
|
+
const entityTags = summary.tags.filter((t) => t.startsWith('speaker:') || t.startsWith('topic:'));
|
|
222
|
+
const label = entityTags.length > 0
|
|
223
|
+
? entityTags.map((t) => t.split(':')[1]).join(': ')
|
|
224
|
+
: summary.content.slice(0, 40);
|
|
225
|
+
const newContent = await generateDagSummary(label, children.map((c) => c.content), opts);
|
|
226
|
+
if (!newContent) {
|
|
227
|
+
// LLM null / fetch error → leave dirty for next cycle
|
|
228
|
+
result.failed++;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const childCreatedAts = children.map((c) => c.created).sort();
|
|
232
|
+
const { changed, refused } = applyRebuildResult(hippoRoot, summary, {
|
|
233
|
+
content: newContent,
|
|
234
|
+
descendant_count: children.length,
|
|
235
|
+
earliest_at: childCreatedAts[0],
|
|
236
|
+
latest_at: childCreatedAts[childCreatedAts.length - 1],
|
|
237
|
+
bumpRebuildCount: true,
|
|
238
|
+
zeroChildren: false,
|
|
239
|
+
actor: 'sleep',
|
|
240
|
+
});
|
|
241
|
+
if (refused) {
|
|
242
|
+
result.refused++;
|
|
243
|
+
}
|
|
244
|
+
else if (changed) {
|
|
245
|
+
result.rebuilt++;
|
|
246
|
+
}
|
|
247
|
+
// changed=false (refused also false) → race lost; not failure, not
|
|
248
|
+
// success, silently skip
|
|
249
|
+
}
|
|
182
250
|
/**
|
|
183
251
|
* v0.30 / E3 — sleep-cycle phase that drains the dirty L2 summary queue.
|
|
184
252
|
* Thin orchestrator; the heavy lifting lives in store.ts (load + apply)
|
|
@@ -220,59 +288,12 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
|
|
|
220
288
|
await new Promise((resolve) => setImmediate(resolve));
|
|
221
289
|
}
|
|
222
290
|
try {
|
|
223
|
-
|
|
224
|
-
if (children.length === 0) {
|
|
225
|
-
// Zero-child case: clear dirty + zero counts, no LLM call, no rebuild_count bump.
|
|
226
|
-
const { changed } = applyRebuildResult(hippoRoot, summary, {
|
|
227
|
-
content: summary.content,
|
|
228
|
-
descendant_count: 0,
|
|
229
|
-
earliest_at: null,
|
|
230
|
-
latest_at: null,
|
|
231
|
-
bumpRebuildCount: false,
|
|
232
|
-
zeroChildren: true,
|
|
233
|
-
actor: 'sleep',
|
|
234
|
-
});
|
|
235
|
-
if (changed)
|
|
236
|
-
result.zeroChildSkipped++;
|
|
237
|
-
// changed=false → race lost / row vanished; silently skip. `refused`
|
|
238
|
-
// is always false here — applyRebuildResult only checks the
|
|
239
|
-
// tombstone when bumpRebuildCount is true (store.ts:3398).
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
// Derive label from summary's existing entity tags (mirrors clusterFacts)
|
|
243
|
-
const entityTags = summary.tags.filter((t) => t.startsWith('speaker:') || t.startsWith('topic:'));
|
|
244
|
-
const label = entityTags.length > 0
|
|
245
|
-
? entityTags.map((t) => t.split(':')[1]).join(': ')
|
|
246
|
-
: summary.content.slice(0, 40);
|
|
247
|
-
const newContent = await generateDagSummary(label, children.map((c) => c.content), opts);
|
|
248
|
-
if (!newContent) {
|
|
249
|
-
// LLM null / fetch error → leave dirty for next cycle
|
|
250
|
-
result.failed++;
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
const childCreatedAts = children.map((c) => c.created).sort();
|
|
254
|
-
const { changed, refused } = applyRebuildResult(hippoRoot, summary, {
|
|
255
|
-
content: newContent,
|
|
256
|
-
descendant_count: children.length,
|
|
257
|
-
earliest_at: childCreatedAts[0],
|
|
258
|
-
latest_at: childCreatedAts[childCreatedAts.length - 1],
|
|
259
|
-
bumpRebuildCount: true,
|
|
260
|
-
zeroChildren: false,
|
|
261
|
-
actor: 'sleep',
|
|
262
|
-
});
|
|
263
|
-
if (refused) {
|
|
264
|
-
result.refused++;
|
|
265
|
-
}
|
|
266
|
-
else if (changed) {
|
|
267
|
-
result.rebuilt++;
|
|
268
|
-
}
|
|
269
|
-
// changed=false (refused also false) → race lost; not failure, not
|
|
270
|
-
// success, silently skip
|
|
291
|
+
await rebuildOneSummary(hippoRoot, summary, opts, result);
|
|
271
292
|
}
|
|
272
293
|
catch (err) {
|
|
273
294
|
// Per-summary failure isolation — one throw doesn't abort the queue.
|
|
274
295
|
// independent-review MED #2 fold: log enough to triage in production
|
|
275
|
-
// (audit() wraps its own writes try/catch per store.ts
|
|
296
|
+
// (audit() wraps its own writes try/catch per store/audit-event.ts, so a
|
|
276
297
|
// throw here is exotic: SQLite I/O error, prepare failure, etc).
|
|
277
298
|
result.failed++;
|
|
278
299
|
log.error(`rebuildDirtySummaries: summary ${summary.id} (tenant ${summary.tenantId}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -280,6 +301,76 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
|
|
|
280
301
|
}
|
|
281
302
|
return result;
|
|
282
303
|
}
|
|
304
|
+
// independent-review HIGH #1 fold: cluster ONLY within-tenant.
|
|
305
|
+
// clusterFacts has no tenant awareness; without this partition step a
|
|
306
|
+
// multi-tenant host could form a cluster spanning tenants and produce
|
|
307
|
+
// a single L3 with tenantId='default' that doesn't belong to either
|
|
308
|
+
// child tenant. Fix: bucket by tenantId, run clusterFacts per-tenant,
|
|
309
|
+
// pass tenantId to createMemory.
|
|
310
|
+
function partitionL2sByTenant(unparented) {
|
|
311
|
+
const byTenant = new Map();
|
|
312
|
+
for (const l2 of unparented) {
|
|
313
|
+
const tid = l2.tenantId ?? 'default';
|
|
314
|
+
const key = derivationPartitionKey(tid, l2.scope, l2.origin_project);
|
|
315
|
+
const list = byTenant.get(key) ?? [];
|
|
316
|
+
list.push(l2);
|
|
317
|
+
byTenant.set(key, list);
|
|
318
|
+
}
|
|
319
|
+
return byTenant;
|
|
320
|
+
}
|
|
321
|
+
/** The L3 profile entry for one cluster of L2 summaries. */
|
|
322
|
+
function createProfileEntry(summary, cluster, home) {
|
|
323
|
+
const memberCreatedAts = cluster.members.map((m) => m.created).sort();
|
|
324
|
+
const nowIso = new Date().toISOString();
|
|
325
|
+
const profileEntry = createMemory(summary, {
|
|
326
|
+
layer: Layer.Semantic,
|
|
327
|
+
tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-entity-profile'],
|
|
328
|
+
confidence: 'inferred',
|
|
329
|
+
dag_level: 3,
|
|
330
|
+
tenantId: home.tenantId, // HIGH #1 fold: thread tenant explicitly
|
|
331
|
+
scope: home.scope,
|
|
332
|
+
baseHalfLifeDays: home.baseHalfLifeDays,
|
|
333
|
+
});
|
|
334
|
+
profileEntry.origin_project = home.originProject;
|
|
335
|
+
profileEntry.descendant_count = cluster.members.length;
|
|
336
|
+
profileEntry.earliest_at = memberCreatedAts[0];
|
|
337
|
+
profileEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
|
|
338
|
+
profileEntry.dag_level_3_built_at = nowIso;
|
|
339
|
+
return profileEntry;
|
|
340
|
+
}
|
|
341
|
+
/** Profile one eligible cluster of L2s, write it, then re-link the L2s under it. */
|
|
342
|
+
async function profileCluster(hippoRoot, cluster, home, opts, result) {
|
|
343
|
+
const summary = await generateDagSummary(cluster.label, cluster.members.map((m) => m.content), opts);
|
|
344
|
+
if (!summary) {
|
|
345
|
+
result.failed++;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const profileEntry = createProfileEntry(summary, cluster, home);
|
|
349
|
+
// AT1 (plan §3 containment): per-cluster catch — skip this cluster,
|
|
350
|
+
// count, log once. The member re-linking writes below never run for a
|
|
351
|
+
// skipped cluster (mirrors buildDag above).
|
|
352
|
+
try {
|
|
353
|
+
writeEntry(hippoRoot, profileEntry);
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
if (err instanceof RejectedValueError) {
|
|
357
|
+
result.rejected++;
|
|
358
|
+
log.warn(`buildEntityProfiles: cluster "${cluster.label}" skipped: profile matches a rejected value`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
363
|
+
result.profilesCreated++;
|
|
364
|
+
for (const member of cluster.members) {
|
|
365
|
+
const updated = { ...member, dag_parent_id: profileEntry.id };
|
|
366
|
+
writeEntry(hippoRoot, updated);
|
|
367
|
+
result.l2sLinked++;
|
|
368
|
+
}
|
|
369
|
+
// E3 born-dirty cancellation, same dance as buildDag L161-168 but for
|
|
370
|
+
// L3. Pass source='buildEntityProfiles-clean' to distinguish in audit.
|
|
371
|
+
// Args: (root, id, tenantId, actor, source).
|
|
372
|
+
clearSummaryDirtyAfterBuild(hippoRoot, profileEntry.id, home.tenantId, 'buildEntityProfiles', 'buildEntityProfiles-clean');
|
|
373
|
+
}
|
|
283
374
|
/**
|
|
284
375
|
* v0.30 / E5 — build L3 entity profiles by clustering L2 summaries with
|
|
285
376
|
* shared entity tags. Threshold 2+ L2s per entity. Mirrors buildDag L1->L2
|
|
@@ -302,72 +393,18 @@ export async function buildEntityProfiles(hippoRoot, l2Summaries, opts) {
|
|
|
302
393
|
const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
303
394
|
// Only L2 with no L3 parent yet (avoid re-clustering already-profiled L2s).
|
|
304
395
|
const unparented = l2Summaries.filter((s) => s.dag_level === 2 && !s.dag_parent_id);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
for (const l2 of unparented) {
|
|
313
|
-
const tid = l2.tenantId ?? 'default';
|
|
314
|
-
const key = derivationPartitionKey(tid, l2.scope, l2.origin_project);
|
|
315
|
-
const list = byTenant.get(key) ?? [];
|
|
316
|
-
list.push(l2);
|
|
317
|
-
byTenant.set(key, list);
|
|
318
|
-
}
|
|
319
|
-
for (const [, tenantL2s] of byTenant) {
|
|
320
|
-
const tenantId = tenantL2s[0].tenantId ?? 'default';
|
|
321
|
-
const scope = derivationScope(tenantL2s[0].scope);
|
|
396
|
+
for (const [, tenantL2s] of partitionL2sByTenant(unparented)) {
|
|
397
|
+
const home = {
|
|
398
|
+
tenantId: tenantL2s[0].tenantId ?? 'default',
|
|
399
|
+
scope: derivationScope(tenantL2s[0].scope),
|
|
400
|
+
originProject: tenantL2s[0].origin_project,
|
|
401
|
+
baseHalfLifeDays,
|
|
402
|
+
};
|
|
322
403
|
const clusters = clusterFacts(tenantL2s);
|
|
323
404
|
const eligible = clusters.filter((c) => c.members.length >= 2);
|
|
324
405
|
result.candidateClusters += eligible.length;
|
|
325
406
|
for (const cluster of eligible) {
|
|
326
|
-
|
|
327
|
-
if (!summary) {
|
|
328
|
-
result.failed++;
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
const memberCreatedAts = cluster.members.map((m) => m.created).sort();
|
|
332
|
-
const nowIso = new Date().toISOString();
|
|
333
|
-
const profileEntry = createMemory(summary, {
|
|
334
|
-
layer: Layer.Semantic,
|
|
335
|
-
tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-entity-profile'],
|
|
336
|
-
confidence: 'inferred',
|
|
337
|
-
dag_level: 3,
|
|
338
|
-
tenantId, // HIGH #1 fold: thread tenant explicitly
|
|
339
|
-
scope,
|
|
340
|
-
baseHalfLifeDays,
|
|
341
|
-
});
|
|
342
|
-
profileEntry.origin_project = tenantL2s[0].origin_project;
|
|
343
|
-
profileEntry.descendant_count = cluster.members.length;
|
|
344
|
-
profileEntry.earliest_at = memberCreatedAts[0];
|
|
345
|
-
profileEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
|
|
346
|
-
profileEntry.dag_level_3_built_at = nowIso;
|
|
347
|
-
// AT1 (plan §3 containment): per-cluster catch — skip this cluster,
|
|
348
|
-
// count, log once. The member re-linking writes below never run for a
|
|
349
|
-
// skipped cluster (mirrors buildDag above).
|
|
350
|
-
try {
|
|
351
|
-
writeEntry(hippoRoot, profileEntry);
|
|
352
|
-
}
|
|
353
|
-
catch (err) {
|
|
354
|
-
if (err instanceof RejectedValueError) {
|
|
355
|
-
result.rejected++;
|
|
356
|
-
log.warn(`buildEntityProfiles: cluster "${cluster.label}" skipped: profile matches a rejected value`);
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
throw err;
|
|
360
|
-
}
|
|
361
|
-
result.profilesCreated++;
|
|
362
|
-
for (const member of cluster.members) {
|
|
363
|
-
const updated = { ...member, dag_parent_id: profileEntry.id };
|
|
364
|
-
writeEntry(hippoRoot, updated);
|
|
365
|
-
result.l2sLinked++;
|
|
366
|
-
}
|
|
367
|
-
// E3 born-dirty cancellation, same dance as buildDag L161-168 but for
|
|
368
|
-
// L3. Pass source='buildEntityProfiles-clean' to distinguish in audit.
|
|
369
|
-
// Args: (root, id, tenantId, actor, source).
|
|
370
|
-
clearSummaryDirtyAfterBuild(hippoRoot, profileEntry.id, tenantId, 'buildEntityProfiles', 'buildEntityProfiles-clean');
|
|
407
|
+
await profileCluster(hippoRoot, cluster, home, opts, result);
|
|
371
408
|
}
|
|
372
409
|
}
|
|
373
410
|
return result;
|