hippo-memory 1.61.0 → 1.63.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 +37 -53
- package/dist/agent-memories/apply.d.ts +3 -1
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +4 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +46 -24
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/ambient-store.d.ts +4 -4
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +7 -10
- package/dist/api/assemble.js +62 -66
- package/dist/api/audit.d.ts +2 -2
- package/dist/api/audit.js +2 -2
- package/dist/api/auth.d.ts +6 -6
- package/dist/api/auth.js +5 -7
- package/dist/api/context-select.d.ts +50 -0
- package/dist/api/context-select.js +342 -0
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +5 -4
- package/dist/api/context.js +208 -535
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +56 -41
- package/dist/api/outcome.d.ts +8 -13
- package/dist/api/outcome.js +13 -17
- package/dist/api/promote.d.ts +5 -8
- package/dist/api/promote.js +55 -66
- package/dist/api/quarantine.js +3 -2
- package/dist/api/recall-types.d.ts +43 -55
- package/dist/api/recall.d.ts +3 -3
- package/dist/api/recall.js +310 -456
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +206 -220
- package/dist/api/tokens.d.ts +2 -2
- package/dist/api/tokens.js +2 -2
- package/dist/api/types.d.ts +6 -10
- package/dist/api/types.js +2 -4
- package/dist/audit-prune.d.ts +4 -6
- package/dist/audit-prune.js +3 -5
- package/dist/audit.js +11 -33
- package/dist/auth.d.ts +8 -9
- package/dist/auth.js +5 -7
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +1 -1
- package/dist/availability.js +3 -5
- package/dist/capture/command.d.ts +5 -4
- package/dist/capture/command.js +8 -22
- package/dist/capture/compact.d.ts +2 -2
- package/dist/capture/compact.js +11 -15
- package/dist/capture/extract.js +37 -115
- package/dist/capture-error.d.ts +1 -1
- package/dist/capture-error.js +1 -1
- package/dist/churn-git.d.ts +1 -1
- package/dist/churn-git.js +1 -1
- package/dist/cli/audit.js +3 -4
- package/dist/cli/auth.js +3 -6
- package/dist/cli/briefs.js +324 -306
- package/dist/cli/context.js +44 -34
- package/dist/cli/continuity.js +283 -271
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +43 -58
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +334 -345
- package/dist/cli/explain.js +68 -61
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +391 -379
- package/dist/cli/projects.js +11 -6
- package/dist/cli/recall.js +30 -44
- package/dist/cli/remember.js +118 -87
- package/dist/cli/session-hooks.js +106 -115
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +267 -250
- package/dist/cli/shared.js +3 -3
- package/dist/cli/slack.js +1 -1
- package/dist/cli/sleep.js +27 -1
- package/dist/cli/status.d.ts +4 -4
- package/dist/cli/status.js +80 -76
- package/dist/cli/transfer.js +88 -107
- package/dist/cli/usage.js +9 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -9
- package/dist/compaction-record.d.ts +2 -2
- package/dist/compaction-record.js +89 -68
- package/dist/compare.d.ts +11 -16
- package/dist/compare.js +11 -16
- package/dist/config.d.ts +19 -18
- package/dist/config.js +90 -67
- package/dist/connectors/github/backfill.d.ts +2 -2
- package/dist/connectors/github/backfill.js +8 -15
- package/dist/connectors/github/cli-impl.js +3 -8
- package/dist/connectors/github/deletion.d.ts +5 -12
- package/dist/connectors/github/deletion.js +5 -12
- package/dist/connectors/github/dlq.d.ts +6 -9
- package/dist/connectors/github/dlq.js +2 -3
- package/dist/connectors/github/ingest.d.ts +5 -7
- package/dist/connectors/github/ingest.js +8 -12
- package/dist/connectors/github/octokit-client.d.ts +3 -5
- package/dist/connectors/github/octokit-client.js +5 -6
- package/dist/connectors/github/signature.d.ts +9 -39
- package/dist/connectors/github/signature.js +9 -39
- package/dist/connectors/github/tenant-routing.d.ts +1 -1
- package/dist/connectors/github/tenant-routing.js +1 -1
- package/dist/connectors/github/transform.js +2 -2
- package/dist/connectors/github/types.d.ts +2 -10
- package/dist/connectors/github/types.js +1 -3
- package/dist/connectors/slack/deletion.d.ts +3 -8
- package/dist/connectors/slack/deletion.js +3 -8
- package/dist/connectors/slack/dlq.d.ts +1 -1
- package/dist/connectors/slack/ingest.d.ts +1 -1
- package/dist/connectors/slack/ingest.js +7 -16
- package/dist/connectors/slack/signature.d.ts +1 -1
- package/dist/connectors/slack/tenant-routing.d.ts +3 -5
- package/dist/connectors/slack/tenant-routing.js +3 -5
- package/dist/connectors/slack/transform.d.ts +5 -6
- package/dist/connectors/slack/transform.js +5 -6
- package/dist/connectors/slack/types.d.ts +2 -6
- package/dist/connectors/slack/types.js +1 -3
- package/dist/connectors/slack/web-client.js +10 -3
- package/dist/connectors/slack/workspaces.d.ts +3 -5
- package/dist/connectors/slack/workspaces.js +3 -5
- package/dist/consolidate/conflicts.js +3 -14
- package/dist/consolidate/decay.js +9 -29
- package/dist/consolidate/llm-passes.js +4 -5
- package/dist/consolidate/merge.js +8 -23
- package/dist/consolidate/run.d.ts +1 -8
- package/dist/consolidate/run.js +3 -25
- package/dist/consolidate/sleep.js +5 -17
- package/dist/consolidate/traces.js +9 -21
- package/dist/customer-notes.d.ts +5 -7
- package/dist/customer-notes.js +82 -76
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +189 -203
- package/dist/db/continuity.js +2 -2
- package/dist/db/migrations/v14.js +1 -1
- package/dist/db/migrations/v15.js +1 -2
- package/dist/db/migrations/v16.js +3 -4
- package/dist/db/migrations/v17.js +2 -3
- package/dist/db/migrations/v19.js +1 -1
- package/dist/db/migrations/v20.js +1 -1
- package/dist/db/migrations/v21.js +2 -6
- package/dist/db/migrations/v22.js +2 -4
- package/dist/db/migrations/v23.js +1 -1
- package/dist/db/migrations/v24.js +4 -6
- package/dist/db/migrations/v25.js +2 -3
- package/dist/db/migrations/v26.js +3 -3
- package/dist/db/migrations/v27.js +2 -10
- package/dist/db/migrations/v28.js +5 -8
- package/dist/db/migrations/v29.js +3 -4
- package/dist/db/migrations/v30.js +2 -2
- package/dist/db/migrations/v31.js +1 -1
- package/dist/db/migrations/v32.js +1 -1
- package/dist/db/migrations/v33.js +3 -3
- package/dist/db/migrations/v34.js +1 -1
- package/dist/db/migrations/v35.js +3 -4
- package/dist/db/migrations/v36.js +3 -4
- package/dist/db/migrations/v37.js +5 -5
- package/dist/db/migrations/v38.js +7 -8
- package/dist/db/migrations/v39.js +1 -1
- package/dist/db/migrations/v40.js +4 -16
- package/dist/db/migrations/v41.js +3 -4
- package/dist/db/migrations/v42.js +3 -4
- package/dist/db/migrations/v45.js +1 -1
- package/dist/db/migrations/v46.js +1 -1
- package/dist/db/migrations/v47.js +1 -1
- package/dist/db/migrations/v48.js +1 -1
- package/dist/decisions.d.ts +2 -2
- package/dist/decisions.js +97 -80
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +129 -110
- package/dist/dormant.js +1 -4
- package/dist/embedding-provider.d.ts +4 -8
- package/dist/embedding-provider.js +4 -8
- package/dist/embeddings.js +55 -47
- package/dist/env.d.ts +1 -1
- package/dist/env.js +12 -12
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +10 -0
- package/dist/eval-stats.d.ts +1 -2
- package/dist/eval-stats.js +1 -2
- package/dist/eval-suite.js +27 -21
- package/dist/extract.js +4 -9
- package/dist/failure-log.d.ts +3 -3
- package/dist/failure-log.js +1 -1
- package/dist/forward-claim-detector.d.ts +2 -4
- package/dist/forward-claim-detector.js +6 -11
- package/dist/goals.d.ts +3 -3
- package/dist/goals.js +103 -91
- package/dist/graph/read.d.ts +2 -2
- package/dist/graph/read.js +5 -6
- package/dist/graph/types.d.ts +8 -8
- package/dist/graph/write.d.ts +7 -14
- package/dist/graph/write.js +16 -23
- package/dist/graph-extract.d.ts +7 -8
- package/dist/graph-extract.js +62 -72
- package/dist/graph-recall.d.ts +2 -2
- package/dist/graph-recall.js +55 -49
- package/dist/graph-stream.d.ts +5 -6
- package/dist/graph-stream.js +66 -57
- package/dist/graph-view.d.ts +2 -2
- package/dist/graph-view.js +7 -7
- package/dist/half-life-migration.d.ts +1 -2
- package/dist/half-life-migration.js +2 -3
- package/dist/hooks/codex-session.js +1 -1
- package/dist/hooks/codex-wrapper.d.ts +1 -1
- package/dist/hooks/codex-wrapper.js +3 -2
- package/dist/hooks/json-hooks.d.ts +2 -2
- package/dist/hooks/json-hooks.js +5 -4
- package/dist/hooks/opencode.d.ts +1 -1
- package/dist/hooks/opencode.js +5 -4
- package/dist/hooks/shared.d.ts +3 -7
- package/dist/hooks/shared.js +1 -8
- package/dist/http-util.d.ts +2 -3
- package/dist/http-util.js +3 -0
- package/dist/importers/core.d.ts +2 -9
- package/dist/importers/core.js +15 -30
- package/dist/importers/sources.js +2 -1
- package/dist/importers/vault.js +2 -20
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +46 -39
- package/dist/instruction-detect.d.ts +1 -1
- package/dist/instruction-detect.js +1 -1
- package/dist/invalidation.d.ts +3 -0
- package/dist/invalidation.js +160 -114
- package/dist/json.d.ts +5 -0
- package/dist/json.js +4 -0
- package/dist/judgment.js +1 -2
- package/dist/local-embedding.js +1 -1
- package/dist/mcp/admin-tools.js +7 -17
- package/dist/mcp/format.js +1 -1
- package/dist/mcp/framing.js +3 -6
- package/dist/mcp/protocol.d.ts +2 -5
- package/dist/mcp/protocol.js +1 -3
- package/dist/mcp/recall-tools.js +12 -15
- package/dist/mcp/request.js +4 -3
- package/dist/mcp/session-state.js +2 -3
- package/dist/mcp/stdio.js +2 -1
- package/dist/mcp/tools.js +9 -6
- package/dist/memory-value-weights.d.ts +5 -8
- package/dist/memory-value-weights.js +5 -8
- package/dist/memory-value.d.ts +13 -13
- package/dist/memory-value.js +26 -37
- package/dist/memory.d.ts +20 -22
- package/dist/memory.js +24 -48
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +3 -2
- package/dist/owner-validation.d.ts +4 -5
- package/dist/owner-validation.js +4 -5
- package/dist/physics.d.ts +4 -4
- package/dist/physics.js +7 -9
- package/dist/policies.d.ts +9 -10
- package/dist/policies.js +96 -81
- package/dist/postinstall.js +3 -6
- package/dist/predictions/planning-fallacy.d.ts +9 -14
- package/dist/predictions/planning-fallacy.js +10 -16
- package/dist/predictions/store.d.ts +15 -23
- package/dist/predictions/store.js +36 -33
- package/dist/processes.d.ts +2 -7
- package/dist/processes.js +88 -72
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +141 -118
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +28 -5
- package/dist/project-merge.js +213 -46
- package/dist/project-remote.d.ts +12 -0
- package/dist/project-remote.js +138 -0
- package/dist/prompt-recall.js +1 -2
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.js +1 -1
- package/dist/raw-archive.d.ts +9 -0
- package/dist/raw-archive.js +70 -53
- package/dist/recall-history.d.ts +19 -20
- package/dist/recall-history.js +24 -42
- package/dist/recall-pipeline.js +4 -28
- package/dist/recall-scope.d.ts +7 -8
- package/dist/recall-scope.js +7 -8
- package/dist/recall-trace.d.ts +5 -9
- package/dist/recall-trace.js +6 -10
- package/dist/refine-llm.d.ts +1 -1
- package/dist/refine-llm.js +2 -2
- package/dist/reject-flow.d.ts +3 -4
- package/dist/reject-flow.js +122 -117
- package/dist/rejection.d.ts +5 -6
- package/dist/rejection.js +7 -15
- package/dist/rerankers/clef.d.ts +1 -1
- package/dist/rerankers/jev.d.ts +1 -2
- package/dist/rerankers/jev.js +4 -5
- package/dist/rerankers/llm.d.ts +1 -2
- package/dist/rerankers/llm.js +1 -2
- package/dist/rerankers/types.d.ts +1 -2
- package/dist/rrf.d.ts +2 -2
- package/dist/rrf.js +2 -2
- package/dist/search/bm25-search.d.ts +1 -1
- package/dist/search/bm25-search.js +2 -1
- package/dist/search/boosts.js +2 -1
- package/dist/search/hybrid.d.ts +1 -1
- package/dist/search/hybrid.js +2 -1
- package/dist/search/physics-search.d.ts +1 -1
- package/dist/search/physics-search.js +2 -1
- package/dist/search/types.d.ts +2 -0
- package/dist/search/types.js +3 -1
- package/dist/secret-detect.d.ts +4 -5
- package/dist/secret-detect.js +6 -10
- package/dist/server/auth.js +5 -5
- package/dist/server/client-ip.js +1 -1
- package/dist/server/cursor.js +2 -1
- package/dist/server/mcp-http.js +4 -4
- package/dist/server/request.d.ts +3 -6
- package/dist/server/request.js +6 -7
- package/dist/server/routes/admin.js +5 -4
- package/dist/server/routes/customer-notes.js +6 -5
- package/dist/server/routes/decisions.js +4 -3
- package/dist/server/routes/incidents.js +7 -5
- package/dist/server/routes/memories.js +7 -7
- package/dist/server/routes/policies.js +3 -2
- package/dist/server/routes/predictions.js +12 -15
- package/dist/server/routes/processes.js +3 -2
- package/dist/server/routes/project-briefs.js +8 -7
- package/dist/server/routes/recall.js +95 -93
- package/dist/server/routes/skills.js +6 -5
- package/dist/server/types.d.ts +1 -1
- package/dist/server/validation.d.ts +1 -2
- package/dist/server/validation.js +7 -14
- package/dist/server-detect.js +72 -58
- package/dist/server.d.ts +2 -2
- package/dist/server.js +131 -117
- package/dist/shared.d.ts +26 -17
- package/dist/shared.js +102 -104
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +88 -72
- package/dist/store/audit-event.d.ts +2 -2
- package/dist/store/audit-event.js +1 -1
- package/dist/store/candidates.d.ts +2 -2
- package/dist/store/candidates.js +4 -3
- package/dist/store/conflicts.js +30 -22
- package/dist/store/delete-and-batch.d.ts +11 -14
- package/dist/store/delete-and-batch.js +40 -91
- package/dist/store/entry-reads.d.ts +17 -25
- package/dist/store/entry-reads.js +59 -40
- package/dist/store/entry-row.d.ts +6 -24
- package/dist/store/entry-row.js +6 -24
- package/dist/store/entry-writes.d.ts +6 -7
- package/dist/store/entry-writes.js +13 -11
- package/dist/store/handoffs.d.ts +1 -1
- package/dist/store/handoffs.js +7 -10
- package/dist/store/index-and-stats.d.ts +2 -6
- package/dist/store/index-and-stats.js +4 -10
- package/dist/store/mirrors.d.ts +6 -19
- package/dist/store/mirrors.js +14 -39
- package/dist/store/open.js +9 -31
- package/dist/store/rows.d.ts +5 -11
- package/dist/store/rows.js +6 -11
- package/dist/store/search-rows.d.ts +17 -34
- package/dist/store/search-rows.js +34 -56
- package/dist/store/sessions.d.ts +4 -5
- package/dist/store/sessions.js +5 -6
- package/dist/store/summaries.d.ts +13 -17
- package/dist/store/summaries.js +26 -70
- package/dist/support-bundle.js +4 -8
- package/dist/tenant.d.ts +1 -5
- package/dist/token-ledger.d.ts +1 -1
- package/dist/token-ledger.js +3 -5
- package/dist/trace.js +1 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +1 -1
- 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
|
@@ -2,14 +2,9 @@ import { archiveRaw } from '../../api.js';
|
|
|
2
2
|
import { openHippoDb, closeHippoDb } from '../../db.js';
|
|
3
3
|
import { markEventSeen, hasSeenEvent } from './idempotency.js';
|
|
4
4
|
/**
|
|
5
|
-
* Handle Slack `message_deleted`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* hit a now-archived row and returned `not_found` instead of `duplicate`.
|
|
9
|
-
*
|
|
10
|
-
* Fix: pass `afterArchive` to `archiveRaw`, which runs inside the same
|
|
11
|
-
* SAVEPOINT as the archive itself. The slack_event_log row commits with the
|
|
12
|
-
* archive or not at all.
|
|
5
|
+
* Handle Slack `message_deleted`. `afterArchive` runs inside the archive's own
|
|
6
|
+
* SAVEPOINT, so the slack_event_log row commits with the archive or not at all
|
|
7
|
+
* and a crash cannot leave a retry hitting `not_found` instead of `duplicate`.
|
|
13
8
|
*/
|
|
14
9
|
export function handleMessageDeleted(ctx, input) {
|
|
15
10
|
const db = openHippoDb(ctx.hippoRoot);
|
|
@@ -15,7 +15,7 @@ export interface DlqItem {
|
|
|
15
15
|
slackTimestamp: string | null;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* writeToDlq is bucket-aware. `bucket` defaults to
|
|
19
19
|
* 'parse_error' to preserve the legacy single-arg call sites while letting
|
|
20
20
|
* new callers tag rows for `hippo slack dlq replay` triage.
|
|
21
21
|
*
|
|
@@ -25,7 +25,7 @@ export interface IngestResult {
|
|
|
25
25
|
* and the SAVEPOINT, we throw `DuplicateEventError` to roll back the memory
|
|
26
26
|
* write. The pre-check `hasSeenEvent` stays as a fast path for the common
|
|
27
27
|
* already-seen case, but the afterWrite throw is what makes idempotency
|
|
28
|
-
* correct under two-worker concurrency
|
|
28
|
+
* correct under two-worker concurrency.
|
|
29
29
|
* - Empty-body messages return 'skipped' but still mark seen so a replay
|
|
30
30
|
* returns 'duplicate' rather than re-running the transform.
|
|
31
31
|
*/
|
|
@@ -15,7 +15,7 @@ import { messageToRememberOpts } from './transform.js';
|
|
|
15
15
|
* and the SAVEPOINT, we throw `DuplicateEventError` to roll back the memory
|
|
16
16
|
* write. The pre-check `hasSeenEvent` stays as a fast path for the common
|
|
17
17
|
* already-seen case, but the afterWrite throw is what makes idempotency
|
|
18
|
-
* correct under two-worker concurrency
|
|
18
|
+
* correct under two-worker concurrency.
|
|
19
19
|
* - Empty-body messages return 'skipped' but still mark seen so a replay
|
|
20
20
|
* returns 'duplicate' rather than re-running the transform.
|
|
21
21
|
*/
|
|
@@ -25,12 +25,8 @@ export function ingestMessage(ctx, input) {
|
|
|
25
25
|
const db = openHippoDb(ctx.hippoRoot);
|
|
26
26
|
try {
|
|
27
27
|
if (hasSeenEvent(db, input.eventId)) {
|
|
28
|
-
//
|
|
29
|
-
// first
|
|
30
|
-
// same 'skipped' status they originally returned, not 'duplicate' —
|
|
31
|
-
// the asymmetry was a paper-cut for callers that switch/case on
|
|
32
|
-
// status. memory_id=NULL is the discriminator. Non-NULL memory_id
|
|
33
|
-
// means an actual memory was ingested before, so 'duplicate' is correct.
|
|
28
|
+
// Empty-body events are marked seen with memory_id=NULL, so their replay returns the same 'skipped'
|
|
29
|
+
// they first returned; a non-NULL memory_id means a memory was ingested, so 'duplicate'.
|
|
34
30
|
const cachedId = lookupMemoryByEvent(db, input.eventId);
|
|
35
31
|
return { status: cachedId === null ? 'skipped' : 'duplicate', memoryId: cachedId };
|
|
36
32
|
}
|
|
@@ -65,11 +61,8 @@ export function ingestMessage(ctx, input) {
|
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
63
|
function rememberWithEventLog(ctx, eventId, opts) {
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// Under the new object-shaped Context.actor, an OR-fallback would evaluate
|
|
71
|
-
// an object as truthy and skip the fallback anyway (logic bug if ctx were
|
|
72
|
-
// ever missing); explicit reliance on the caller is safer.
|
|
64
|
+
// No `|| 'connector:slack'` fallback: the caller always builds ctx with the connector subject, and with
|
|
65
|
+
// an object-shaped Context.actor an OR-fallback would never fire anyway.
|
|
73
66
|
const result = remember(ctx, {
|
|
74
67
|
...opts,
|
|
75
68
|
untrusted: true,
|
|
@@ -101,10 +94,8 @@ function lostRaceResult(ctx, eventId) {
|
|
|
101
94
|
}
|
|
102
95
|
}
|
|
103
96
|
function rejectedValueResult(ctx, eventId) {
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
// the same refusal forever). Mark the event seen exactly like the
|
|
107
|
-
// empty-body branch above so a Slack retry acks as done, not error.
|
|
97
|
+
// A tombstone hit is a PERMANENT skip: a DLQ retry would hit the same refusal forever, so mark the
|
|
98
|
+
// event seen like the empty-body branch above and let a Slack retry ack as done, not error.
|
|
108
99
|
const db4 = openHippoDb(ctx.hippoRoot);
|
|
109
100
|
try {
|
|
110
101
|
markEventSeen(db4, eventId, null);
|
|
@@ -4,7 +4,7 @@ export interface VerifyOpts {
|
|
|
4
4
|
signature: string;
|
|
5
5
|
signingSecret: string;
|
|
6
6
|
/**
|
|
7
|
-
* Previous signing secret during a rotation.
|
|
7
|
+
* Previous signing secret during a rotation. Deploy with
|
|
8
8
|
* both `SLACK_SIGNING_SECRET` (new) and `SLACK_SIGNING_SECRET_PREVIOUS` (old)
|
|
9
9
|
* set, verify both work, drop previous after rollover. The verifier tries
|
|
10
10
|
* `signingSecret` first, then `previousSecret` if that fails.
|
|
@@ -11,11 +11,9 @@ import type { DatabaseSyncLike } from '../../db.js';
|
|
|
11
11
|
* deployment's tenant). Escape hatch: SLACK_ALLOW_UNKNOWN_TEAM_FALLBACK=1
|
|
12
12
|
* restores the env fallback for emergency rollback only.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* tenant. The fail-closed contract lives here so every caller (route handler,
|
|
18
|
-
* CLI replay, future MCP) gets the same protection.
|
|
14
|
+
* The fail-closed contract lives here so every caller (route handler, CLI
|
|
15
|
+
* replay, future MCP) gets the same protection against routing a foreign
|
|
16
|
+
* workspace's events into the deployment tenant.
|
|
19
17
|
*/
|
|
20
18
|
export declare function resolveTenantForTeam(db: DatabaseSyncLike, teamId: string): string | null;
|
|
21
19
|
//# sourceMappingURL=tenant-routing.d.ts.map
|
|
@@ -11,11 +11,9 @@ import { envSlackAllowUnknownTeamFallback, envTenant } from '../../env.js';
|
|
|
11
11
|
* deployment's tenant). Escape hatch: SLACK_ALLOW_UNKNOWN_TEAM_FALLBACK=1
|
|
12
12
|
* restores the env fallback for emergency rollback only.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* tenant. The fail-closed contract lives here so every caller (route handler,
|
|
18
|
-
* CLI replay, future MCP) gets the same protection.
|
|
14
|
+
* The fail-closed contract lives here so every caller (route handler, CLI
|
|
15
|
+
* replay, future MCP) gets the same protection against routing a foreign
|
|
16
|
+
* workspace's events into the deployment tenant.
|
|
19
17
|
*/
|
|
20
18
|
export function resolveTenantForTeam(db, teamId) {
|
|
21
19
|
// SAFETY: query selects only `tenant_id`, so a returned row has that shape;
|
|
@@ -12,18 +12,17 @@ export interface TransformInput {
|
|
|
12
12
|
* without text). Caller treats null as "skip but mark idempotency seen".
|
|
13
13
|
*
|
|
14
14
|
* Contract:
|
|
15
|
-
* - kind is the literal 'raw' (
|
|
15
|
+
* - kind is the literal 'raw' (connector boundary, see src/importers.ts).
|
|
16
16
|
* - artifact_ref format MUST be exactly `slack://${teamId}/${channelId}/${ts}`;
|
|
17
|
-
* the deletion path
|
|
18
|
-
* - owner is non-null whenever a row is written. Required by the
|
|
17
|
+
* the deletion path looks up by this string.
|
|
18
|
+
* - owner is non-null whenever a row is written. Required by the
|
|
19
19
|
* provenance gate (`hippo provenance --strict`).
|
|
20
20
|
* - `user:<slack_user_id>` when the event carries a `user`.
|
|
21
21
|
* - `bot:<bot_id>` for the `bot_message` subtype (or any userless+text event
|
|
22
22
|
* that supplies bot_id).
|
|
23
23
|
* - `bot:unknown` only as a last-resort sentinel so the gate never sees null.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* "skipped but seen" path at ingest.ts:54-65.
|
|
24
|
+
* Skipping userless messages instead would silently drop bot ingestion
|
|
25
|
+
* via the "skipped but seen" path in ingest.ts.
|
|
27
26
|
*/
|
|
28
27
|
export declare function messageToRememberOpts(input: TransformInput): RememberOpts | null;
|
|
29
28
|
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -5,18 +5,17 @@ import { scopeFromChannel } from './scope.js';
|
|
|
5
5
|
* without text). Caller treats null as "skip but mark idempotency seen".
|
|
6
6
|
*
|
|
7
7
|
* Contract:
|
|
8
|
-
* - kind is the literal 'raw' (
|
|
8
|
+
* - kind is the literal 'raw' (connector boundary, see src/importers.ts).
|
|
9
9
|
* - artifact_ref format MUST be exactly `slack://${teamId}/${channelId}/${ts}`;
|
|
10
|
-
* the deletion path
|
|
11
|
-
* - owner is non-null whenever a row is written. Required by the
|
|
10
|
+
* the deletion path looks up by this string.
|
|
11
|
+
* - owner is non-null whenever a row is written. Required by the
|
|
12
12
|
* provenance gate (`hippo provenance --strict`).
|
|
13
13
|
* - `user:<slack_user_id>` when the event carries a `user`.
|
|
14
14
|
* - `bot:<bot_id>` for the `bot_message` subtype (or any userless+text event
|
|
15
15
|
* that supplies bot_id).
|
|
16
16
|
* - `bot:unknown` only as a last-resort sentinel so the gate never sees null.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* "skipped but seen" path at ingest.ts:54-65.
|
|
17
|
+
* Skipping userless messages instead would silently drop bot ingestion
|
|
18
|
+
* via the "skipped but seen" path in ingest.ts.
|
|
20
19
|
*/
|
|
21
20
|
export function messageToRememberOpts(input) {
|
|
22
21
|
const text = input.message.text?.trim();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Slack Events API envelope shapes used by the ingestion connector.
|
|
3
3
|
* Spec: https://api.slack.com/events-api
|
|
4
4
|
*/
|
|
5
|
+
import { type JsonValue } from '../../json.js';
|
|
5
6
|
export interface SlackUrlVerification {
|
|
6
7
|
type: 'url_verification';
|
|
7
8
|
challenge: string;
|
|
@@ -18,18 +19,13 @@ export interface SlackMessageEvent {
|
|
|
18
19
|
thread_ts?: string;
|
|
19
20
|
/**
|
|
20
21
|
* Slack `bot_message` subtype carries `bot_id` instead of `user`. The
|
|
21
|
-
*
|
|
22
|
+
* provenance gate requires a non-null `owner`, so transform.ts
|
|
22
23
|
* derives `owner: bot:<bot_id>` when `user` is absent.
|
|
23
24
|
*/
|
|
24
25
|
bot_id?: string;
|
|
25
26
|
/** Present on subtype='message_deleted'. */
|
|
26
27
|
deleted_ts?: string;
|
|
27
28
|
}
|
|
28
|
-
/** JSON value shape for the parts of an inbound Slack payload not yet
|
|
29
|
-
* narrowed to a known envelope/event shape. */
|
|
30
|
-
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
31
|
-
[key: string]: JsonValue;
|
|
32
|
-
};
|
|
33
29
|
export interface SlackEventEnvelope {
|
|
34
30
|
type: 'event_callback';
|
|
35
31
|
team_id: string;
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* Slack Events API envelope shapes used by the ingestion connector.
|
|
3
3
|
* Spec: https://api.slack.com/events-api
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
return typeof value === 'string';
|
|
7
|
-
}
|
|
5
|
+
import { isJsonString } from '../../json.js';
|
|
8
6
|
function isJsonNumber(value) {
|
|
9
7
|
return typeof value === 'number';
|
|
10
8
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { fetchWithRetry, isRetryableStatus } from '../../http-retry.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { isJsonString } from '../../json.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build a SlackHistoryFetcher that pages `conversations.history` over real
|
|
5
|
+
* HTTP. Wraps `fetchWithRetry` so 429 and 5xx handling is automatic. The returned
|
|
6
|
+
* fetcher is the one `backfillChannel` consumes.
|
|
7
|
+
*
|
|
8
|
+
* Slack omits `channel` from messages in the history response, so we stamp
|
|
9
|
+
* the request channel id onto each parsed message — downstream ingest needs
|
|
10
|
+
* it on every event.
|
|
11
|
+
*/
|
|
5
12
|
const SLACK_TIMEOUT_MS = 30_000;
|
|
6
13
|
export function slackHistoryFetcher(token, fetchImpl) {
|
|
7
14
|
return async ({ channelId, cursor, oldest }) => {
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slack workspace registration helpers
|
|
2
|
+
* Slack workspace registration helpers.
|
|
3
3
|
*
|
|
4
4
|
* The `slack_workspaces` table maps Slack `team_id` → hippo `tenant_id`.
|
|
5
5
|
* Empty table = single-tenant install (HIPPO_TENANT fallback).
|
|
6
6
|
* Non-empty = multi-workspace install (fail-closed routing via
|
|
7
7
|
* `resolveTenantForTeam`).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* workspaces. These helpers give the CLI (`hippo slack workspaces
|
|
12
|
-
* add|list|remove`) a clean surface.
|
|
9
|
+
* These helpers give the CLI (`hippo slack workspaces add|list|remove`) a
|
|
10
|
+
* surface, so operators with multiple workspaces need no direct SQL.
|
|
13
11
|
*
|
|
14
12
|
* Design choices:
|
|
15
13
|
* - `add` is an upsert (ON CONFLICT UPDATE). Re-registering an
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slack workspace registration helpers
|
|
2
|
+
* Slack workspace registration helpers.
|
|
3
3
|
*
|
|
4
4
|
* The `slack_workspaces` table maps Slack `team_id` → hippo `tenant_id`.
|
|
5
5
|
* Empty table = single-tenant install (HIPPO_TENANT fallback).
|
|
6
6
|
* Non-empty = multi-workspace install (fail-closed routing via
|
|
7
7
|
* `resolveTenantForTeam`).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* workspaces. These helpers give the CLI (`hippo slack workspaces
|
|
12
|
-
* add|list|remove`) a clean surface.
|
|
9
|
+
* These helpers give the CLI (`hippo slack workspaces add|list|remove`) a
|
|
10
|
+
* surface, so operators with multiple workspaces need no direct SQL.
|
|
13
11
|
*
|
|
14
12
|
* Design choices:
|
|
15
13
|
* - `add` is an upsert (ON CONFLICT UPDATE). Re-registering an
|
|
@@ -28,15 +28,11 @@ const CONFLICT_STOPWORDS = new Set([
|
|
|
28
28
|
]);
|
|
29
29
|
/** Pairs of live non-semantic memories that contradict each other, in survivor order. */
|
|
30
30
|
export function detectConflicts(entries, now, decayOpts = {},
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// filter independently of the decay pass above; without this bypass,
|
|
34
|
-
// rescued entries would be silently re-excluded from conflict detection
|
|
35
|
-
// every cycle even though the decay pass just decided to keep them.
|
|
36
|
-
// Default empty set: flag-off behavior is unchanged.
|
|
31
|
+
// Ids this cycle's decay pass rescued: the survivor filter below is recomputed independently, so
|
|
32
|
+
// without this bypass rescued entries would be silently re-excluded from conflict detection.
|
|
37
33
|
rescuedIds = new Set()) {
|
|
38
34
|
const survivors = entries.filter((entry) => entry.layer !== Layer.Semantic
|
|
39
|
-
//
|
|
35
|
+
// An unreviewed quarantined row must not taint a visible memory as conflicted.
|
|
40
36
|
&& !isQuarantineScope(entry.scope ?? null)
|
|
41
37
|
&& (rescuedIds.has(entry.id) || calculateStrength(entry, now, decayOpts) >= DECAY_THRESHOLD));
|
|
42
38
|
const detected = [];
|
|
@@ -165,13 +161,6 @@ function inferConflictPolarity(text) {
|
|
|
165
161
|
return 'positive';
|
|
166
162
|
return 'neutral';
|
|
167
163
|
}
|
|
168
|
-
function stripConflictPolarity(text) {
|
|
169
|
-
return text
|
|
170
|
-
.toLowerCase()
|
|
171
|
-
.replace(/\b(?:not|never|no|don['’]?t|do\s+not|doesn['’]?t|does\s+not|can['’]?t|cannot|shouldn['’]?t|should\s+not|enabled|enable|disabled|disable|on|off|true|false|always|must|must\s+not|works?|working|missing|broken|failed|available|present)\b/g, ' ')
|
|
172
|
-
.replace(/\s+/g, ' ')
|
|
173
|
-
.trim();
|
|
174
|
-
}
|
|
175
164
|
function containsAny(text, needles) {
|
|
176
165
|
return needles.some((needle) => text.includes(needle));
|
|
177
166
|
}
|
|
@@ -38,17 +38,8 @@ function keepSurvivor(run, entry, strength) {
|
|
|
38
38
|
// -------------------------------------------------------------------------
|
|
39
39
|
// 1. Decay pass
|
|
40
40
|
// -------------------------------------------------------------------------
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// behavior (pre-registered gate G2). Flag ON restructures into two phases:
|
|
44
|
-
// phase 1 classifies every entry (condemned vs survivor) with ZERO
|
|
45
|
-
// commits; phase 2 runs rescueSet over the per-tenant candidate groups,
|
|
46
|
-
// then commits — rescued entries get the standard survivor bookkeeping
|
|
47
|
-
// refresh (stored strength + effective confidence; no half-life edits, no
|
|
48
|
-
// rank-derived writes) and are pushed to survivors so they fully
|
|
49
|
-
// participate in this cycle's merge/physics/conflict passes; non-rescued
|
|
50
|
-
// condemned entries follow the existing pendingDeletes/result.removed/
|
|
51
|
-
// details path.
|
|
41
|
+
// Memory-value flag OFF runs the single-phase loop below. Flag ON classifies every entry with ZERO commits,
|
|
42
|
+
// runs rescueSet per tenant, then commits; rescued entries stay full survivors for this cycle's later passes.
|
|
52
43
|
export function decayPass(run) {
|
|
53
44
|
if (run.config.memoryValue.enabled)
|
|
54
45
|
return decayWithMemoryValue(run);
|
|
@@ -76,10 +67,8 @@ function reportNonFiniteScores(result, rankById) {
|
|
|
76
67
|
}
|
|
77
68
|
function decayWithMemoryValue(run) {
|
|
78
69
|
const { all, now, result } = run;
|
|
79
|
-
// Carried forward to logRun, where the mv_rescue audit rows are
|
|
80
|
-
//
|
|
81
|
-
// would assert rescues for a cycle whose effects might never land if a
|
|
82
|
-
// later phase throws).
|
|
70
|
+
// Carried forward to logRun, where the mv_rescue audit rows are written: writing them here, before
|
|
71
|
+
// batchWriteAndDelete, would assert rescues whose effects might never land if a later phase throws.
|
|
83
72
|
const rescuedEntries = [];
|
|
84
73
|
let rescuedIds = new Set();
|
|
85
74
|
let rankById = new Map();
|
|
@@ -98,31 +87,22 @@ function decayWithMemoryValue(run) {
|
|
|
98
87
|
// logRun stay !dryRun-gated), so the preview matches what a
|
|
99
88
|
// real run would decide.
|
|
100
89
|
const condemnedIds = new Set(condemned.map((e) => e.id));
|
|
101
|
-
// Fail-loud must not depend on condemnation traffic
|
|
102
|
-
// P2-2): validate the frozen weights constant unconditionally, even on a
|
|
103
|
-
// sleep with nothing condemned.
|
|
90
|
+
// Fail-loud must not depend on condemnation traffic: validate the frozen weights even when nothing is condemned.
|
|
104
91
|
validateWeights();
|
|
105
92
|
if (condemnedIds.size > 0) {
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// skipping its own internal rankNonPinnedByTenant call) and the
|
|
109
|
-
// detail/audit rank context below, so the whole-store ranking pass
|
|
110
|
-
// runs a single time per sleep instead of twice, and only when there
|
|
111
|
-
// is actually something condemned to rank against.
|
|
93
|
+
// Rank per tenant ONCE: rankById feeds both rescueSet (via precomputedRanks) and the detail/audit
|
|
94
|
+
// context below, so the whole-store ranking runs once per sleep and only when something is condemned.
|
|
112
95
|
rankById = rankNonPinnedByTenant(all, now);
|
|
113
96
|
rescuedIds = rescueSet(all, condemnedIds, now, MEMORY_VALUE_WEIGHTS, SOURCE_ARTIFACT_SHA256, rankById);
|
|
114
97
|
reportNonFiniteScores(result, rankById);
|
|
115
98
|
}
|
|
116
99
|
// --- Phase 2b: commit, one pass over `all` in ITS ORIGINAL ORDER ---
|
|
117
|
-
//
|
|
118
|
-
// survivors, systematically starving them in downstream order-sensitive
|
|
119
|
-
// passes like extraction's slice(0,20) — a single pass over `all`
|
|
120
|
-
// preserves flag-off's ordering semantics exactly.)
|
|
100
|
+
// Rescued entries appended at the tail would be starved by order-sensitive passes like extraction's slice(0,20).
|
|
121
101
|
for (const entry of all) {
|
|
122
102
|
const strength = strengthById.get(entry.id);
|
|
123
103
|
if (run.retirable(entry) && strength < DECAY_THRESHOLD) {
|
|
124
104
|
if (rescuedIds.has(entry.id)) {
|
|
125
|
-
// Rescued
|
|
105
|
+
// Rescued: standard survivor stored-strength refresh.
|
|
126
106
|
// Confidence is left alone here: it is an epistemic tier, not a
|
|
127
107
|
// cached computation, so resolveConfidence derives it on read.
|
|
128
108
|
rescuedEntries.push(keepSurvivor(run, entry, strength));
|
|
@@ -72,9 +72,9 @@ async function dagBuildPass(run, { apiKey, llmError, llmOpts }) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
// -------------------------------------------------------------------------
|
|
75
|
-
// 1.8. DAG summary rebuild — drain dirty queue from
|
|
75
|
+
// 1.8. DAG summary rebuild — drain dirty queue from the child-write hooks
|
|
76
76
|
// -------------------------------------------------------------------------
|
|
77
|
-
// Consumer of
|
|
77
|
+
// Consumer of the summary_dirty flag. Walks dirty L2 summaries, regenerates
|
|
78
78
|
// each via generateDagSummary, atomically refreshes content + 6 metadata
|
|
79
79
|
// columns + clears summary_dirty (with FTS sync). Same apiKey/dryRun gate
|
|
80
80
|
// as buildDag above. Cap HIPPO_DAG_REBUILD_CAP (default 20, hard ceiling
|
|
@@ -84,8 +84,7 @@ async function dagRebuildPass(run, { llmError, llmOpts }) {
|
|
|
84
84
|
try {
|
|
85
85
|
const { rebuildDirtySummaries } = await import('../dag.js');
|
|
86
86
|
const rawCap = envDagRebuildCap();
|
|
87
|
-
//
|
|
88
|
-
// unbounded LLM cost.
|
|
87
|
+
// Hard ceiling so a misconfigured env can't burn unbounded LLM cost.
|
|
89
88
|
const cap = rawCap !== undefined ? Math.min(rawCap, 1000) : 20;
|
|
90
89
|
const rebuildResult = await rebuildDirtySummaries(run.hippoRoot, { ...llmOpts, onError: llmError('dag rebuild'), cap });
|
|
91
90
|
result.summariesRebuilt = rebuildResult.rebuilt;
|
|
@@ -115,7 +114,7 @@ async function dagRebuildPass(run, { llmError, llmOpts }) {
|
|
|
115
114
|
// -------------------------------------------------------------------------
|
|
116
115
|
// 1.9. DAG entity profiles — cluster L2 topic summaries into L3 profiles
|
|
117
116
|
// -------------------------------------------------------------------------
|
|
118
|
-
//
|
|
117
|
+
// Aggregate per-entity L2 summaries (e.g. all the speaker:Alice
|
|
119
118
|
// topic summaries) into a single L3 entity profile. Runs even when phase
|
|
120
119
|
// 1.7 buildDag was skipped (re-clusters existing L2s every sleep).
|
|
121
120
|
//
|
|
@@ -54,13 +54,8 @@ export function mergePass(run) {
|
|
|
54
54
|
&& !e.pinned // a pin merged with a look-alike would read as one of two values
|
|
55
55
|
&& tokenize(e.content).length > 0);
|
|
56
56
|
const used = new Set();
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// clustered across the whole host-wide `survivors` list with no tenant
|
|
60
|
-
// boundary, and mergeContents concatenated cross-tenant content into one
|
|
61
|
-
// row. Map preserves insertion order, so single-tenant stores (every row
|
|
62
|
-
// 'default') get exactly one partition and iterate in the same order as
|
|
63
|
-
// before this fix — byte-identical behavior there.
|
|
57
|
+
// Partition BEFORE the overlap loop so a cluster can never span tenants and merge cross-tenant content.
|
|
58
|
+
// Map keeps insertion order, so a single-tenant store gets one partition in the original order.
|
|
64
59
|
const mergeCandidatesByTenant = new Map();
|
|
65
60
|
for (const entry of mergeCandidates) {
|
|
66
61
|
const key = derivationPartitionKey(entry.tenantId, entry.scope, entry.origin_project);
|
|
@@ -70,12 +65,8 @@ export function mergePass(run) {
|
|
|
70
65
|
else
|
|
71
66
|
mergeCandidatesByTenant.set(key, [entry]);
|
|
72
67
|
}
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// for the whole non-dry-run consolidate — see that
|
|
76
|
-
// declaration's comment. Only needed for real writes — a dry-run preview
|
|
77
|
-
// never reaches batchWriteAndDelete's guard bypass, so there is nothing
|
|
78
|
-
// here for it to protect against.
|
|
68
|
+
// The rejection guard reuses the one consolidateDb handle opened lazily in consolidate(); a dry-run
|
|
69
|
+
// never reaches batchWriteAndDelete's guard bypass, so it has nothing to protect there.
|
|
79
70
|
let mergesSkippedRejected = 0;
|
|
80
71
|
for (const [, tenantCandidates] of mergeCandidatesByTenant) {
|
|
81
72
|
const partition = {
|
|
@@ -115,13 +106,8 @@ function mergeCluster(run, partition, cluster, related, used) {
|
|
|
115
106
|
const mergedContent = mergeContents(cluster);
|
|
116
107
|
const allTags = Array.from(new Set(cluster.flatMap((e) => e.tags))).sort();
|
|
117
108
|
const maxValence = pickStrongestValence(cluster);
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// row will ACTUALLY land in.
|
|
121
|
-
// T1 fix: createMemory now receives tenantId: mergeTenant (the
|
|
122
|
-
// partition's tenant — every member of `cluster` shares it by
|
|
123
|
-
// construction), so the row lands in its source tenant instead of
|
|
124
|
-
// always 'default'.
|
|
109
|
+
// Build the semantic entry FIRST (createMemory is cheap and pure) so the tombstone check below runs
|
|
110
|
+
// under the partition's tenant, the one every cluster member shares and the row will land in.
|
|
125
111
|
let semantic = null;
|
|
126
112
|
if (!dryRun) {
|
|
127
113
|
semantic = {
|
|
@@ -152,9 +138,8 @@ function mergeCluster(run, partition, cluster, related, used) {
|
|
|
152
138
|
result.semanticCreated++;
|
|
153
139
|
// Demote source episodics (they've been compressed into neocortex):
|
|
154
140
|
// scale half_life_days so they decay sooner while staying recoverable.
|
|
155
|
-
// Immediate ranking is deliberately unchanged:
|
|
156
|
-
//
|
|
157
|
-
// summary regresses budget-bounded QA (docs/evals/). The stored
|
|
141
|
+
// Immediate ranking is deliberately unchanged: dropping children below a
|
|
142
|
+
// worse-retrieving summary regresses budget-bounded QA. The stored
|
|
158
143
|
// strength is refreshed to the live value so inspect, replay sampling,
|
|
159
144
|
// and strength-sorted assembly see the truth instead of a fake 0.3.
|
|
160
145
|
// Mutate in place (not a copy): `cluster` holds the same object
|
|
@@ -15,7 +15,7 @@ export interface ConsolidationResult {
|
|
|
15
15
|
semanticCreated: number;
|
|
16
16
|
replayed: number;
|
|
17
17
|
promotedTraces: number;
|
|
18
|
-
/**
|
|
18
|
+
/** Sessions skipped because their events span two derivation scopes. */
|
|
19
19
|
tracesSkippedMixedScope: number;
|
|
20
20
|
extractionCandidates: number;
|
|
21
21
|
extracted: number;
|
|
@@ -35,13 +35,6 @@ export interface ConsolidationResult {
|
|
|
35
35
|
}
|
|
36
36
|
export declare const REPLAY_COUNT_DEFAULT = 5;
|
|
37
37
|
export declare function keptAsWritten(entry: MemoryEntry): boolean;
|
|
38
|
-
/** JSON value shape for a session event's free-form metadata field, cast to
|
|
39
|
-
* once at its `Record<string, unknown>` origin so it can be narrowed via
|
|
40
|
-
* isJsonString below rather than left as unparsed `unknown`. */
|
|
41
|
-
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
42
|
-
[key: string]: JsonValue;
|
|
43
|
-
};
|
|
44
|
-
export declare function isJsonString(value: JsonValue): value is string;
|
|
45
38
|
/** The sleep's one tombstone-check handle: opened on first use, never under dryRun, closed once. */
|
|
46
39
|
interface LazyDb {
|
|
47
40
|
get: () => DatabaseSyncLike | null;
|
package/dist/consolidate/run.js
CHANGED
|
@@ -4,31 +4,9 @@ export const REPLAY_COUNT_DEFAULT = 5;
|
|
|
4
4
|
export function keptAsWritten(entry) {
|
|
5
5
|
return entry.tags.some((tag) => NO_MERGE_TAGS.has(tag));
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// AT1 rejection-guard db handle (docs/plans/2026-08-15-at1-rejected-value-tombstone.md):
|
|
11
|
-
// covers BOTH the auto-promote pass (1.4) and the merge
|
|
12
|
-
// pass (3) — both build deterministic content that
|
|
13
|
-
// batchWriteAndDelete writes through the guard's bypass, so both need a
|
|
14
|
-
// producer-side tombstone check before pushing to pendingWrites.
|
|
15
|
-
//
|
|
16
|
-
// T3 fix (2026-08-15 hardening pass, perf hygiene): memoized lazy getter,
|
|
17
|
-
// not an eager open. The handle only serves these two tombstone checks —
|
|
18
|
-
// a sleep with zero promotable sessions and zero merge clusters never
|
|
19
|
-
// reaches either use site, so opening it unconditionally on every
|
|
20
|
-
// non-dry-run sleep paid a db-open cost for nothing. dryRun still never
|
|
21
|
-
// opens (getConsolidateDb short-circuits before touching the handle).
|
|
22
|
-
// consolidateDbOpened (not just a truthy handle check) is the
|
|
23
|
-
// single source of truth for "was this ever opened", so the finally
|
|
24
|
-
// closes it exactly once and never double-opens.
|
|
25
|
-
//
|
|
26
|
-
// AT1 P2 fix (codex, handle-leak restructure): the getter's lifetime must
|
|
27
|
-
// start IMMEDIATELY before the try whose finally closes it, covering every
|
|
28
|
-
// phase that can touch it — not just the merge pass. An exception thrown by
|
|
29
|
-
// auto-promote (1.4), replay (1.5), batch extraction (1.6), the DAG
|
|
30
|
-
// passes (1.7-1.9), or physics (2) would otherwise propagate past an open handle
|
|
31
|
-
// with nothing to close it.
|
|
7
|
+
// Auto-promote (1.4) and merge (3) write deterministic content through the guard's bypass, so both
|
|
8
|
+
// need this tombstone check. Lazy so a sleep reaching neither never opens it; call it IMMEDIATELY
|
|
9
|
+
// before the try whose finally closes it, so a throw in any phase cannot leak the handle.
|
|
32
10
|
export function lazyConsolidateDb(hippoRoot, dryRun) {
|
|
33
11
|
let consolidateDbHandle = null;
|
|
34
12
|
let consolidateDbOpened = false;
|
|
@@ -33,10 +33,8 @@ export async function consolidate(hippoRoot, options = {}) {
|
|
|
33
33
|
const dryRun = options.dryRun ?? false;
|
|
34
34
|
const result = newConsolidationResult(dryRun);
|
|
35
35
|
const halfLife = migrateHalfLives(hippoRoot, dryRun, result);
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// with no cross-tenant dedup. The api.sleep audit row tags this with the
|
|
39
|
-
// admin synthetic actor; see api.ts:2050 for the rationale.
|
|
36
|
+
// Host-wide by design: per-tenant filtering would mean N runs per host and no cross-tenant dedup.
|
|
37
|
+
// The api.sleep audit row tags this with the admin synthetic actor.
|
|
40
38
|
const all = loadAllEntries(hippoRoot);
|
|
41
39
|
if (dryRun)
|
|
42
40
|
for (const e of all)
|
|
@@ -153,24 +151,14 @@ function logRun(run, decay) {
|
|
|
153
151
|
if (decay.rescuedEntries.length > 0)
|
|
154
152
|
auditRescues(run, decay);
|
|
155
153
|
}
|
|
156
|
-
// One audit row per rescue
|
|
157
|
-
//
|
|
158
|
-
// (and after conflict detection + run logging), not inline in the decay
|
|
159
|
-
// pass — same durability posture as api.ts's top-level 'consolidate'
|
|
160
|
-
// summary audit row (written only once the whole sleep has completed).
|
|
161
|
-
// Writing it earlier would assert rescues for a cycle whose effects
|
|
162
|
-
// never landed if a later phase threw. Real writes only — dry-run
|
|
163
|
-
// previews the decision (details line above) but persists nothing.
|
|
154
|
+
// One audit row per rescue, written only after batchWriteAndDelete commits: writing earlier
|
|
155
|
+
// would assert rescues for a cycle whose effects never landed if a later phase threw.
|
|
164
156
|
function auditRescues(run, { rescuedEntries, rankById }) {
|
|
165
157
|
const { result } = run;
|
|
166
158
|
try {
|
|
167
159
|
const auditDb = openHippoDb(run.hippoRoot);
|
|
168
160
|
try {
|
|
169
|
-
//
|
|
170
|
-
// whole loop — a single failed appendAuditEvent must not silently
|
|
171
|
-
// drop every remaining row. Mirrors the physics pass's
|
|
172
|
-
// skipped-warning precedent: count losses, keep
|
|
173
|
-
// the overall fail-soft posture, tell the operator via details.
|
|
161
|
+
// Per-row try/catch: one failed appendAuditEvent must not silently drop every remaining row.
|
|
174
162
|
let auditFailures = 0;
|
|
175
163
|
for (const entry of rescuedEntries) {
|
|
176
164
|
try {
|