hippo-memory 1.62.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 +9 -0
- package/dist/agent-memories/apply.d.ts +2 -0
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +2 -1
- package/dist/agent-memories/claude-code.js +4 -4
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.js +37 -23
- 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 +7 -12
- 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 +4 -3
- package/dist/api/context-select.js +9 -11
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +3 -2
- package/dist/api/context.js +20 -20
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +22 -10
- 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/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 +7 -18
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +3 -2
- 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 +1 -1
- package/dist/capture/compact.js +9 -13
- 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/curate.d.ts +1 -1
- package/dist/cli/curate.js +8 -24
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +10 -21
- package/dist/cli/explain.js +2 -1
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/playbooks.js +4 -9
- package/dist/cli/projects.js +3 -1
- package/dist/cli/recall.js +2 -1
- package/dist/cli/remember.js +6 -18
- package/dist/cli/session-hooks.js +21 -40
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +2 -6
- 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 +7 -12
- package/dist/cli/transfer.js +8 -13
- 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 -0
- 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 +6 -9
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +23 -73
- 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 +6 -6
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +119 -104
- 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 +5 -6
- 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 +1 -1
- 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 +12 -14
- 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 +3 -3
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +9 -13
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +17 -2
- package/dist/project-merge.js +109 -27
- 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 +17 -17
- package/dist/shared.js +93 -97
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +8 -8
- 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 +31 -59
- 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
|
@@ -8,24 +8,11 @@ export interface VerifyOpts {
|
|
|
8
8
|
}
|
|
9
9
|
export declare function verifyGitHubSignature(opts: VerifyOpts): boolean;
|
|
10
10
|
/**
|
|
11
|
-
* Source-aware idempotency key
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Round 2 found that key produced different hashes for the SAME source event
|
|
18
|
-
* delivered via webhook vs via REST backfill, because backfill rawBody is the
|
|
19
|
-
* REST list-item shape while webhook rawBody is the envelope. Result:
|
|
20
|
-
* backfill + later webhook of the same issue created two `kind='raw'` rows
|
|
21
|
-
* with the same artifact_ref. Combined with the deletion bug, deletion could
|
|
22
|
-
* not archive both.
|
|
23
|
-
*
|
|
24
|
-
* v1.3.1 fix: key from the SOURCE-NORMALIZED identifier — artifact_ref plus
|
|
25
|
-
* the source-side updated_at timestamp. Same artifact + same revision = same
|
|
26
|
-
* key, regardless of which path delivered it. Different revisions of the same
|
|
27
|
-
* issue (an edit) get different keys, which is correct: each edit IS a new
|
|
28
|
-
* memory revision.
|
|
11
|
+
* Source-aware idempotency key: artifact_ref plus the source-side updated_at.
|
|
12
|
+
* A raw-body key would hash a webhook envelope and a REST backfill item of the
|
|
13
|
+
* SAME source event differently and ingest it twice. Same artifact + same
|
|
14
|
+
* revision = same key, whichever path delivered it; an edit gets a new key,
|
|
15
|
+
* which is correct because each edit IS a new memory revision.
|
|
29
16
|
*
|
|
30
17
|
* Both inputs are upstream-derived from the parsed event, not from the
|
|
31
18
|
* unsigned delivery header — replay attacks still cannot bypass dedupe.
|
|
@@ -36,30 +23,13 @@ export declare function verifyGitHubSignature(opts: VerifyOpts): boolean;
|
|
|
36
23
|
* - updatedAt: source-side ISO timestamp (issue.updated_at,
|
|
37
24
|
* comment.updated_at, pull_request.updated_at). Empty string when the
|
|
38
25
|
* payload omits it (rare; older REST shapes).
|
|
39
|
-
*
|
|
40
|
-
* Migration note for v1.3.0 → v1.3.1: existing github_event_log rows from
|
|
41
|
-
* v1.3.0 used the round-1 key shape and will not collide with v1.3.1 keys.
|
|
42
|
-
* The first webhook delivery after upgrading creates a new log row with the
|
|
43
|
-
* new key. This is acceptable for a hotfix (no production users on v1.3.0)
|
|
44
|
-
* and correct semantics going forward.
|
|
45
26
|
*/
|
|
46
27
|
export declare function computeIdempotencyKey(artifactRef: string, updatedAt: string | null | undefined): string;
|
|
47
28
|
/**
|
|
48
|
-
*
|
|
49
|
-
* computeIdempotencyKey so an ingest's row
|
|
50
|
-
* a deletion
|
|
51
|
-
*
|
|
52
|
-
* The codex round 3 P0 fix on server.ts was to call computeIdempotencyKey
|
|
53
|
-
* with the right (artifactRef, updatedAt) shape for deletions. That made
|
|
54
|
-
* the deletion key MATCH the ingest key — which collapsed to a "deletion
|
|
55
|
-
* always returns duplicate" bug because both shared github_event_log.
|
|
56
|
-
*
|
|
57
|
-
* v1.3.2 splits the namespace: deletion key = sha256('deleted:' +
|
|
58
|
-
* artifactRef + ':' + updatedAt). Two retries of the SAME deletion event
|
|
59
|
-
* still dedupe (same artifact + same updatedAt + same prefix → same key).
|
|
60
|
-
* Ingest of the same artifact + same updatedAt produces a DIFFERENT key,
|
|
61
|
-
* so a deletion event does not get short-circuited by the ingest's prior
|
|
62
|
-
* log row.
|
|
29
|
+
* Deletion-specific idempotency key: sha256('deleted:' + artifactRef + ':' +
|
|
30
|
+
* updatedAt). Distinct namespace from computeIdempotencyKey so an ingest's row
|
|
31
|
+
* in github_event_log doesn't make a deletion return 'duplicate' before it
|
|
32
|
+
* gets a chance to archive; retries of the SAME deletion still dedupe.
|
|
63
33
|
*
|
|
64
34
|
* Kept as a separate exported function so the namespace prefix is explicit
|
|
65
35
|
* at every call site (server.ts deletion branches, deletion.ts, DLQ replay).
|
|
@@ -17,24 +17,11 @@ export function verifyGitHubSignature(opts) {
|
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Source-aware idempotency key
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Round 2 found that key produced different hashes for the SAME source event
|
|
27
|
-
* delivered via webhook vs via REST backfill, because backfill rawBody is the
|
|
28
|
-
* REST list-item shape while webhook rawBody is the envelope. Result:
|
|
29
|
-
* backfill + later webhook of the same issue created two `kind='raw'` rows
|
|
30
|
-
* with the same artifact_ref. Combined with the deletion bug, deletion could
|
|
31
|
-
* not archive both.
|
|
32
|
-
*
|
|
33
|
-
* v1.3.1 fix: key from the SOURCE-NORMALIZED identifier — artifact_ref plus
|
|
34
|
-
* the source-side updated_at timestamp. Same artifact + same revision = same
|
|
35
|
-
* key, regardless of which path delivered it. Different revisions of the same
|
|
36
|
-
* issue (an edit) get different keys, which is correct: each edit IS a new
|
|
37
|
-
* memory revision.
|
|
20
|
+
* Source-aware idempotency key: artifact_ref plus the source-side updated_at.
|
|
21
|
+
* A raw-body key would hash a webhook envelope and a REST backfill item of the
|
|
22
|
+
* SAME source event differently and ingest it twice. Same artifact + same
|
|
23
|
+
* revision = same key, whichever path delivered it; an edit gets a new key,
|
|
24
|
+
* which is correct because each edit IS a new memory revision.
|
|
38
25
|
*
|
|
39
26
|
* Both inputs are upstream-derived from the parsed event, not from the
|
|
40
27
|
* unsigned delivery header — replay attacks still cannot bypass dedupe.
|
|
@@ -45,32 +32,15 @@ export function verifyGitHubSignature(opts) {
|
|
|
45
32
|
* - updatedAt: source-side ISO timestamp (issue.updated_at,
|
|
46
33
|
* comment.updated_at, pull_request.updated_at). Empty string when the
|
|
47
34
|
* payload omits it (rare; older REST shapes).
|
|
48
|
-
*
|
|
49
|
-
* Migration note for v1.3.0 → v1.3.1: existing github_event_log rows from
|
|
50
|
-
* v1.3.0 used the round-1 key shape and will not collide with v1.3.1 keys.
|
|
51
|
-
* The first webhook delivery after upgrading creates a new log row with the
|
|
52
|
-
* new key. This is acceptable for a hotfix (no production users on v1.3.0)
|
|
53
|
-
* and correct semantics going forward.
|
|
54
35
|
*/
|
|
55
36
|
export function computeIdempotencyKey(artifactRef, updatedAt) {
|
|
56
37
|
return createHash('sha256').update(`${artifactRef}:${updatedAt ?? ''}`).digest('hex');
|
|
57
38
|
}
|
|
58
39
|
/**
|
|
59
|
-
*
|
|
60
|
-
* computeIdempotencyKey so an ingest's row
|
|
61
|
-
* a deletion
|
|
62
|
-
*
|
|
63
|
-
* The codex round 3 P0 fix on server.ts was to call computeIdempotencyKey
|
|
64
|
-
* with the right (artifactRef, updatedAt) shape for deletions. That made
|
|
65
|
-
* the deletion key MATCH the ingest key — which collapsed to a "deletion
|
|
66
|
-
* always returns duplicate" bug because both shared github_event_log.
|
|
67
|
-
*
|
|
68
|
-
* v1.3.2 splits the namespace: deletion key = sha256('deleted:' +
|
|
69
|
-
* artifactRef + ':' + updatedAt). Two retries of the SAME deletion event
|
|
70
|
-
* still dedupe (same artifact + same updatedAt + same prefix → same key).
|
|
71
|
-
* Ingest of the same artifact + same updatedAt produces a DIFFERENT key,
|
|
72
|
-
* so a deletion event does not get short-circuited by the ingest's prior
|
|
73
|
-
* log row.
|
|
40
|
+
* Deletion-specific idempotency key: sha256('deleted:' + artifactRef + ':' +
|
|
41
|
+
* updatedAt). Distinct namespace from computeIdempotencyKey so an ingest's row
|
|
42
|
+
* in github_event_log doesn't make a deletion return 'duplicate' before it
|
|
43
|
+
* gets a chance to archive; retries of the SAME deletion still dedupe.
|
|
74
44
|
*
|
|
75
45
|
* Kept as a separate exported function so the namespace prefix is explicit
|
|
76
46
|
* at every call site (server.ts deletion branches, deletion.ts, DLQ replay).
|
|
@@ -20,7 +20,7 @@ export interface ResolveArgs {
|
|
|
20
20
|
* is non-empty (multi-tenant install with foreign installation)
|
|
21
21
|
* - `installationId` is missing AND either routing table is non-empty
|
|
22
22
|
* AND no `repository.full_name` match (PAT-mode webhook from a foreign
|
|
23
|
-
* account
|
|
23
|
+
* account)
|
|
24
24
|
*
|
|
25
25
|
* Escape hatch: `GITHUB_ALLOW_UNKNOWN_INSTALLATION_FALLBACK=1` restores the
|
|
26
26
|
* env fallback for emergency rollback only. Mirrors the Slack equivalent
|
|
@@ -14,7 +14,7 @@ import { envGithubAllowUnknownInstallationFallback, envTenant } from '../../env.
|
|
|
14
14
|
* is non-empty (multi-tenant install with foreign installation)
|
|
15
15
|
* - `installationId` is missing AND either routing table is non-empty
|
|
16
16
|
* AND no `repository.full_name` match (PAT-mode webhook from a foreign
|
|
17
|
-
* account
|
|
17
|
+
* account)
|
|
18
18
|
*
|
|
19
19
|
* Escape hatch: `GITHUB_ALLOW_UNKNOWN_INSTALLATION_FALLBACK=1` restores the
|
|
20
20
|
* env fallback for emergency rollback only. Mirrors the Slack equivalent
|
|
@@ -5,13 +5,13 @@ import { scopeFromRepository } from './scope.js';
|
|
|
5
5
|
* caller can mark the delivery seen for idempotency and skip the insert.
|
|
6
6
|
*
|
|
7
7
|
* Contract (mirrors src/connectors/slack/transform.ts):
|
|
8
|
-
* - kind is the literal 'raw' (
|
|
8
|
+
* - kind is the literal 'raw' (connector boundary, see src/importers.ts).
|
|
9
9
|
* - artifact_ref formats are stable; deletion paths look up by these strings.
|
|
10
10
|
* - issue: github://<owner/repo>/issue/<number>
|
|
11
11
|
* - issue_comment: github://<owner/repo>/issue/<number>/comment/<id>
|
|
12
12
|
* - pull_request: github://<owner/repo>/pull/<number>
|
|
13
13
|
* - pr_review_comment: github://<owner/repo>/pull/<number>/review_comment/<id>
|
|
14
|
-
* - owner is `user:github:<login>`. Required by the
|
|
14
|
+
* - owner is `user:github:<login>`. Required by the provenance gate.
|
|
15
15
|
* - scope is derived from repository.private via scopeFromRepository (default
|
|
16
16
|
* private when undetermined).
|
|
17
17
|
*/
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* header value so a malicious or misrouted payload cannot satisfy a guard
|
|
8
8
|
* for the wrong event type.
|
|
9
9
|
*/
|
|
10
|
+
import { type JsonValue } from '../../json.js';
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* `private` MUST be optional, not required. The Slack-style
|
|
12
13
|
* fail-safe in scope.ts requires an envelope with `private: undefined` to
|
|
13
14
|
* map to private; a strict boolean type would reject the payload before
|
|
14
15
|
* scope can fail closed.
|
|
@@ -79,15 +80,6 @@ export interface GitHubPullRequestReviewCommentEvent extends GitHubWebhookEnvelo
|
|
|
79
80
|
updated_at?: string;
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
|
-
/**
|
|
83
|
-
* The full value space `JSON.parse` can produce. Boundary-guard functions in
|
|
84
|
-
* this file accept `JsonValue` (never `unknown`) so a value's origin as
|
|
85
|
-
* unparsed external JSON stays visible in its type, then narrow it via the
|
|
86
|
-
* `isJson*` predicates below.
|
|
87
|
-
*/
|
|
88
|
-
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
89
|
-
[key: string]: JsonValue;
|
|
90
|
-
};
|
|
91
83
|
export declare function isGitHubWebhookEnvelope(x: JsonValue): x is JsonValue & GitHubWebhookEnvelope;
|
|
92
84
|
export declare function isGitHubIssueEvent(x: JsonValue, evtHeader: string): x is JsonValue & GitHubIssueEvent;
|
|
93
85
|
export declare function isGitHubIssueCommentEvent(x: JsonValue, evtHeader: string): x is JsonValue & GitHubIssueCommentEvent;
|
|
@@ -7,12 +7,10 @@
|
|
|
7
7
|
* header value so a malicious or misrouted payload cannot satisfy a guard
|
|
8
8
|
* for the wrong event type.
|
|
9
9
|
*/
|
|
10
|
+
import { isJsonString } from '../../json.js';
|
|
10
11
|
function isJsonObject(x) {
|
|
11
12
|
return x !== null && typeof x === 'object';
|
|
12
13
|
}
|
|
13
|
-
function isJsonString(x) {
|
|
14
|
-
return typeof x === 'string';
|
|
15
|
-
}
|
|
16
14
|
function isJsonNumber(x) {
|
|
17
15
|
return typeof x === 'number';
|
|
18
16
|
}
|
|
@@ -11,14 +11,9 @@ export interface DeletionResult {
|
|
|
11
11
|
memoryId: string | null;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* Handle Slack `message_deleted`.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* hit a now-archived row and returned `not_found` instead of `duplicate`.
|
|
18
|
-
*
|
|
19
|
-
* Fix: pass `afterArchive` to `archiveRaw`, which runs inside the same
|
|
20
|
-
* SAVEPOINT as the archive itself. The slack_event_log row commits with the
|
|
21
|
-
* archive or not at all.
|
|
14
|
+
* Handle Slack `message_deleted`. `afterArchive` runs inside the archive's own
|
|
15
|
+
* SAVEPOINT, so the slack_event_log row commits with the archive or not at all
|
|
16
|
+
* and a crash cannot leave a retry hitting `not_found` instead of `duplicate`.
|
|
22
17
|
*/
|
|
23
18
|
export declare function handleMessageDeleted(ctx: Context, input: DeletionInput): DeletionResult;
|
|
24
19
|
//# sourceMappingURL=deletion.d.ts.map
|
|
@@ -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));
|