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.
Files changed (377) hide show
  1. package/README.md +37 -53
  2. package/dist/agent-memories/apply.d.ts +3 -1
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +4 -1
  5. package/dist/agent-memories/claude-code.js +53 -9
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.d.ts +3 -3
  9. package/dist/agent-memories/sync.js +46 -24
  10. package/dist/agent-memories/types.d.ts +0 -2
  11. package/dist/ambient-store.d.ts +4 -4
  12. package/dist/ambient-store.js +4 -3
  13. package/dist/api/assemble.d.ts +7 -10
  14. package/dist/api/assemble.js +62 -66
  15. package/dist/api/audit.d.ts +2 -2
  16. package/dist/api/audit.js +2 -2
  17. package/dist/api/auth.d.ts +6 -6
  18. package/dist/api/auth.js +5 -7
  19. package/dist/api/context-select.d.ts +50 -0
  20. package/dist/api/context-select.js +342 -0
  21. package/dist/api/context-types.d.ts +12 -17
  22. package/dist/api/context.d.ts +5 -4
  23. package/dist/api/context.js +208 -535
  24. package/dist/api/dormant.js +1 -1
  25. package/dist/api/drill-down.d.ts +4 -4
  26. package/dist/api/drill-down.js +56 -41
  27. package/dist/api/outcome.d.ts +8 -13
  28. package/dist/api/outcome.js +13 -17
  29. package/dist/api/promote.d.ts +5 -8
  30. package/dist/api/promote.js +55 -66
  31. package/dist/api/quarantine.js +3 -2
  32. package/dist/api/recall-types.d.ts +43 -55
  33. package/dist/api/recall.d.ts +3 -3
  34. package/dist/api/recall.js +310 -456
  35. package/dist/api/remember.d.ts +2 -2
  36. package/dist/api/sleep.d.ts +7 -35
  37. package/dist/api/sleep.js +206 -220
  38. package/dist/api/tokens.d.ts +2 -2
  39. package/dist/api/tokens.js +2 -2
  40. package/dist/api/types.d.ts +6 -10
  41. package/dist/api/types.js +2 -4
  42. package/dist/audit-prune.d.ts +4 -6
  43. package/dist/audit-prune.js +3 -5
  44. package/dist/audit.js +11 -33
  45. package/dist/auth.d.ts +8 -9
  46. package/dist/auth.js +5 -7
  47. package/dist/autolearn.d.ts +1 -1
  48. package/dist/autolearn.js +1 -1
  49. package/dist/availability.js +3 -5
  50. package/dist/capture/command.d.ts +5 -4
  51. package/dist/capture/command.js +8 -22
  52. package/dist/capture/compact.d.ts +2 -2
  53. package/dist/capture/compact.js +11 -15
  54. package/dist/capture/extract.js +37 -115
  55. package/dist/capture-error.d.ts +1 -1
  56. package/dist/capture-error.js +1 -1
  57. package/dist/churn-git.d.ts +1 -1
  58. package/dist/churn-git.js +1 -1
  59. package/dist/cli/audit.js +3 -4
  60. package/dist/cli/auth.js +3 -6
  61. package/dist/cli/briefs.js +324 -306
  62. package/dist/cli/context.js +44 -34
  63. package/dist/cli/continuity.js +283 -271
  64. package/dist/cli/curate.d.ts +1 -1
  65. package/dist/cli/curate.js +43 -58
  66. package/dist/cli/dag.js +5 -9
  67. package/dist/cli/decisions.js +334 -345
  68. package/dist/cli/explain.js +68 -61
  69. package/dist/cli/goals.js +1 -1
  70. package/dist/cli/init.js +1 -1
  71. package/dist/cli/maintenance.js +62 -51
  72. package/dist/cli/playbooks.js +391 -379
  73. package/dist/cli/projects.js +11 -6
  74. package/dist/cli/recall.js +30 -44
  75. package/dist/cli/remember.js +118 -87
  76. package/dist/cli/session-hooks.js +106 -115
  77. package/dist/cli/setup.d.ts +1 -1
  78. package/dist/cli/setup.js +267 -250
  79. package/dist/cli/shared.js +3 -3
  80. package/dist/cli/slack.js +1 -1
  81. package/dist/cli/sleep.js +27 -1
  82. package/dist/cli/status.d.ts +4 -4
  83. package/dist/cli/status.js +80 -76
  84. package/dist/cli/transfer.js +88 -107
  85. package/dist/cli/usage.js +9 -6
  86. package/dist/cli.d.ts +1 -1
  87. package/dist/cli.js +4 -9
  88. package/dist/compaction-record.d.ts +2 -2
  89. package/dist/compaction-record.js +89 -68
  90. package/dist/compare.d.ts +11 -16
  91. package/dist/compare.js +11 -16
  92. package/dist/config.d.ts +19 -18
  93. package/dist/config.js +90 -67
  94. package/dist/connectors/github/backfill.d.ts +2 -2
  95. package/dist/connectors/github/backfill.js +8 -15
  96. package/dist/connectors/github/cli-impl.js +3 -8
  97. package/dist/connectors/github/deletion.d.ts +5 -12
  98. package/dist/connectors/github/deletion.js +5 -12
  99. package/dist/connectors/github/dlq.d.ts +6 -9
  100. package/dist/connectors/github/dlq.js +2 -3
  101. package/dist/connectors/github/ingest.d.ts +5 -7
  102. package/dist/connectors/github/ingest.js +8 -12
  103. package/dist/connectors/github/octokit-client.d.ts +3 -5
  104. package/dist/connectors/github/octokit-client.js +5 -6
  105. package/dist/connectors/github/signature.d.ts +9 -39
  106. package/dist/connectors/github/signature.js +9 -39
  107. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  108. package/dist/connectors/github/tenant-routing.js +1 -1
  109. package/dist/connectors/github/transform.js +2 -2
  110. package/dist/connectors/github/types.d.ts +2 -10
  111. package/dist/connectors/github/types.js +1 -3
  112. package/dist/connectors/slack/deletion.d.ts +3 -8
  113. package/dist/connectors/slack/deletion.js +3 -8
  114. package/dist/connectors/slack/dlq.d.ts +1 -1
  115. package/dist/connectors/slack/ingest.d.ts +1 -1
  116. package/dist/connectors/slack/ingest.js +7 -16
  117. package/dist/connectors/slack/signature.d.ts +1 -1
  118. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  119. package/dist/connectors/slack/tenant-routing.js +3 -5
  120. package/dist/connectors/slack/transform.d.ts +5 -6
  121. package/dist/connectors/slack/transform.js +5 -6
  122. package/dist/connectors/slack/types.d.ts +2 -6
  123. package/dist/connectors/slack/types.js +1 -3
  124. package/dist/connectors/slack/web-client.js +10 -3
  125. package/dist/connectors/slack/workspaces.d.ts +3 -5
  126. package/dist/connectors/slack/workspaces.js +3 -5
  127. package/dist/consolidate/conflicts.js +3 -14
  128. package/dist/consolidate/decay.js +9 -29
  129. package/dist/consolidate/llm-passes.js +4 -5
  130. package/dist/consolidate/merge.js +8 -23
  131. package/dist/consolidate/run.d.ts +1 -8
  132. package/dist/consolidate/run.js +3 -25
  133. package/dist/consolidate/sleep.js +5 -17
  134. package/dist/consolidate/traces.js +9 -21
  135. package/dist/customer-notes.d.ts +5 -7
  136. package/dist/customer-notes.js +82 -76
  137. package/dist/dag.d.ts +10 -21
  138. package/dist/dag.js +189 -203
  139. package/dist/db/continuity.js +2 -2
  140. package/dist/db/migrations/v14.js +1 -1
  141. package/dist/db/migrations/v15.js +1 -2
  142. package/dist/db/migrations/v16.js +3 -4
  143. package/dist/db/migrations/v17.js +2 -3
  144. package/dist/db/migrations/v19.js +1 -1
  145. package/dist/db/migrations/v20.js +1 -1
  146. package/dist/db/migrations/v21.js +2 -6
  147. package/dist/db/migrations/v22.js +2 -4
  148. package/dist/db/migrations/v23.js +1 -1
  149. package/dist/db/migrations/v24.js +4 -6
  150. package/dist/db/migrations/v25.js +2 -3
  151. package/dist/db/migrations/v26.js +3 -3
  152. package/dist/db/migrations/v27.js +2 -10
  153. package/dist/db/migrations/v28.js +5 -8
  154. package/dist/db/migrations/v29.js +3 -4
  155. package/dist/db/migrations/v30.js +2 -2
  156. package/dist/db/migrations/v31.js +1 -1
  157. package/dist/db/migrations/v32.js +1 -1
  158. package/dist/db/migrations/v33.js +3 -3
  159. package/dist/db/migrations/v34.js +1 -1
  160. package/dist/db/migrations/v35.js +3 -4
  161. package/dist/db/migrations/v36.js +3 -4
  162. package/dist/db/migrations/v37.js +5 -5
  163. package/dist/db/migrations/v38.js +7 -8
  164. package/dist/db/migrations/v39.js +1 -1
  165. package/dist/db/migrations/v40.js +4 -16
  166. package/dist/db/migrations/v41.js +3 -4
  167. package/dist/db/migrations/v42.js +3 -4
  168. package/dist/db/migrations/v45.js +1 -1
  169. package/dist/db/migrations/v46.js +1 -1
  170. package/dist/db/migrations/v47.js +1 -1
  171. package/dist/db/migrations/v48.js +1 -1
  172. package/dist/decisions.d.ts +2 -2
  173. package/dist/decisions.js +97 -80
  174. package/dist/dedupe.js +86 -61
  175. package/dist/delivery-recorder.js +154 -135
  176. package/dist/doctor.js +129 -110
  177. package/dist/dormant.js +1 -4
  178. package/dist/embedding-provider.d.ts +4 -8
  179. package/dist/embedding-provider.js +4 -8
  180. package/dist/embeddings.js +55 -47
  181. package/dist/env.d.ts +1 -1
  182. package/dist/env.js +12 -12
  183. package/dist/escape.d.ts +5 -0
  184. package/dist/escape.js +10 -0
  185. package/dist/eval-stats.d.ts +1 -2
  186. package/dist/eval-stats.js +1 -2
  187. package/dist/eval-suite.js +27 -21
  188. package/dist/extract.js +4 -9
  189. package/dist/failure-log.d.ts +3 -3
  190. package/dist/failure-log.js +1 -1
  191. package/dist/forward-claim-detector.d.ts +2 -4
  192. package/dist/forward-claim-detector.js +6 -11
  193. package/dist/goals.d.ts +3 -3
  194. package/dist/goals.js +103 -91
  195. package/dist/graph/read.d.ts +2 -2
  196. package/dist/graph/read.js +5 -6
  197. package/dist/graph/types.d.ts +8 -8
  198. package/dist/graph/write.d.ts +7 -14
  199. package/dist/graph/write.js +16 -23
  200. package/dist/graph-extract.d.ts +7 -8
  201. package/dist/graph-extract.js +62 -72
  202. package/dist/graph-recall.d.ts +2 -2
  203. package/dist/graph-recall.js +55 -49
  204. package/dist/graph-stream.d.ts +5 -6
  205. package/dist/graph-stream.js +66 -57
  206. package/dist/graph-view.d.ts +2 -2
  207. package/dist/graph-view.js +7 -7
  208. package/dist/half-life-migration.d.ts +1 -2
  209. package/dist/half-life-migration.js +2 -3
  210. package/dist/hooks/codex-session.js +1 -1
  211. package/dist/hooks/codex-wrapper.d.ts +1 -1
  212. package/dist/hooks/codex-wrapper.js +3 -2
  213. package/dist/hooks/json-hooks.d.ts +2 -2
  214. package/dist/hooks/json-hooks.js +5 -4
  215. package/dist/hooks/opencode.d.ts +1 -1
  216. package/dist/hooks/opencode.js +5 -4
  217. package/dist/hooks/shared.d.ts +3 -7
  218. package/dist/hooks/shared.js +1 -8
  219. package/dist/http-util.d.ts +2 -3
  220. package/dist/http-util.js +3 -0
  221. package/dist/importers/core.d.ts +2 -9
  222. package/dist/importers/core.js +15 -30
  223. package/dist/importers/sources.js +2 -1
  224. package/dist/importers/vault.js +2 -20
  225. package/dist/incidents.d.ts +1 -1
  226. package/dist/incidents.js +46 -39
  227. package/dist/instruction-detect.d.ts +1 -1
  228. package/dist/instruction-detect.js +1 -1
  229. package/dist/invalidation.d.ts +3 -0
  230. package/dist/invalidation.js +160 -114
  231. package/dist/json.d.ts +5 -0
  232. package/dist/json.js +4 -0
  233. package/dist/judgment.js +1 -2
  234. package/dist/local-embedding.js +1 -1
  235. package/dist/mcp/admin-tools.js +7 -17
  236. package/dist/mcp/format.js +1 -1
  237. package/dist/mcp/framing.js +3 -6
  238. package/dist/mcp/protocol.d.ts +2 -5
  239. package/dist/mcp/protocol.js +1 -3
  240. package/dist/mcp/recall-tools.js +12 -15
  241. package/dist/mcp/request.js +4 -3
  242. package/dist/mcp/session-state.js +2 -3
  243. package/dist/mcp/stdio.js +2 -1
  244. package/dist/mcp/tools.js +9 -6
  245. package/dist/memory-value-weights.d.ts +5 -8
  246. package/dist/memory-value-weights.js +5 -8
  247. package/dist/memory-value.d.ts +13 -13
  248. package/dist/memory-value.js +26 -37
  249. package/dist/memory.d.ts +20 -22
  250. package/dist/memory.js +24 -48
  251. package/dist/multihop.d.ts +1 -1
  252. package/dist/multihop.js +3 -2
  253. package/dist/owner-validation.d.ts +4 -5
  254. package/dist/owner-validation.js +4 -5
  255. package/dist/physics.d.ts +4 -4
  256. package/dist/physics.js +7 -9
  257. package/dist/policies.d.ts +9 -10
  258. package/dist/policies.js +96 -81
  259. package/dist/postinstall.js +3 -6
  260. package/dist/predictions/planning-fallacy.d.ts +9 -14
  261. package/dist/predictions/planning-fallacy.js +10 -16
  262. package/dist/predictions/store.d.ts +15 -23
  263. package/dist/predictions/store.js +36 -33
  264. package/dist/processes.d.ts +2 -7
  265. package/dist/processes.js +88 -72
  266. package/dist/project-briefs.d.ts +2 -3
  267. package/dist/project-briefs.js +141 -118
  268. package/dist/project-identity.d.ts +22 -9
  269. package/dist/project-identity.js +47 -12
  270. package/dist/project-merge.d.ts +28 -5
  271. package/dist/project-merge.js +213 -46
  272. package/dist/project-remote.d.ts +12 -0
  273. package/dist/project-remote.js +138 -0
  274. package/dist/prompt-recall.js +1 -2
  275. package/dist/rate-limit.d.ts +1 -1
  276. package/dist/rate-limit.js +1 -1
  277. package/dist/raw-archive.d.ts +9 -0
  278. package/dist/raw-archive.js +70 -53
  279. package/dist/recall-history.d.ts +19 -20
  280. package/dist/recall-history.js +24 -42
  281. package/dist/recall-pipeline.js +4 -28
  282. package/dist/recall-scope.d.ts +7 -8
  283. package/dist/recall-scope.js +7 -8
  284. package/dist/recall-trace.d.ts +5 -9
  285. package/dist/recall-trace.js +6 -10
  286. package/dist/refine-llm.d.ts +1 -1
  287. package/dist/refine-llm.js +2 -2
  288. package/dist/reject-flow.d.ts +3 -4
  289. package/dist/reject-flow.js +122 -117
  290. package/dist/rejection.d.ts +5 -6
  291. package/dist/rejection.js +7 -15
  292. package/dist/rerankers/clef.d.ts +1 -1
  293. package/dist/rerankers/jev.d.ts +1 -2
  294. package/dist/rerankers/jev.js +4 -5
  295. package/dist/rerankers/llm.d.ts +1 -2
  296. package/dist/rerankers/llm.js +1 -2
  297. package/dist/rerankers/types.d.ts +1 -2
  298. package/dist/rrf.d.ts +2 -2
  299. package/dist/rrf.js +2 -2
  300. package/dist/search/bm25-search.d.ts +1 -1
  301. package/dist/search/bm25-search.js +2 -1
  302. package/dist/search/boosts.js +2 -1
  303. package/dist/search/hybrid.d.ts +1 -1
  304. package/dist/search/hybrid.js +2 -1
  305. package/dist/search/physics-search.d.ts +1 -1
  306. package/dist/search/physics-search.js +2 -1
  307. package/dist/search/types.d.ts +2 -0
  308. package/dist/search/types.js +3 -1
  309. package/dist/secret-detect.d.ts +4 -5
  310. package/dist/secret-detect.js +6 -10
  311. package/dist/server/auth.js +5 -5
  312. package/dist/server/client-ip.js +1 -1
  313. package/dist/server/cursor.js +2 -1
  314. package/dist/server/mcp-http.js +4 -4
  315. package/dist/server/request.d.ts +3 -6
  316. package/dist/server/request.js +6 -7
  317. package/dist/server/routes/admin.js +5 -4
  318. package/dist/server/routes/customer-notes.js +6 -5
  319. package/dist/server/routes/decisions.js +4 -3
  320. package/dist/server/routes/incidents.js +7 -5
  321. package/dist/server/routes/memories.js +7 -7
  322. package/dist/server/routes/policies.js +3 -2
  323. package/dist/server/routes/predictions.js +12 -15
  324. package/dist/server/routes/processes.js +3 -2
  325. package/dist/server/routes/project-briefs.js +8 -7
  326. package/dist/server/routes/recall.js +95 -93
  327. package/dist/server/routes/skills.js +6 -5
  328. package/dist/server/types.d.ts +1 -1
  329. package/dist/server/validation.d.ts +1 -2
  330. package/dist/server/validation.js +7 -14
  331. package/dist/server-detect.js +72 -58
  332. package/dist/server.d.ts +2 -2
  333. package/dist/server.js +131 -117
  334. package/dist/shared.d.ts +26 -17
  335. package/dist/shared.js +102 -104
  336. package/dist/skills.d.ts +3 -3
  337. package/dist/skills.js +88 -72
  338. package/dist/store/audit-event.d.ts +2 -2
  339. package/dist/store/audit-event.js +1 -1
  340. package/dist/store/candidates.d.ts +2 -2
  341. package/dist/store/candidates.js +4 -3
  342. package/dist/store/conflicts.js +30 -22
  343. package/dist/store/delete-and-batch.d.ts +11 -14
  344. package/dist/store/delete-and-batch.js +40 -91
  345. package/dist/store/entry-reads.d.ts +17 -25
  346. package/dist/store/entry-reads.js +59 -40
  347. package/dist/store/entry-row.d.ts +6 -24
  348. package/dist/store/entry-row.js +6 -24
  349. package/dist/store/entry-writes.d.ts +6 -7
  350. package/dist/store/entry-writes.js +13 -11
  351. package/dist/store/handoffs.d.ts +1 -1
  352. package/dist/store/handoffs.js +7 -10
  353. package/dist/store/index-and-stats.d.ts +2 -6
  354. package/dist/store/index-and-stats.js +4 -10
  355. package/dist/store/mirrors.d.ts +6 -19
  356. package/dist/store/mirrors.js +14 -39
  357. package/dist/store/open.js +9 -31
  358. package/dist/store/rows.d.ts +5 -11
  359. package/dist/store/rows.js +6 -11
  360. package/dist/store/search-rows.d.ts +17 -34
  361. package/dist/store/search-rows.js +34 -56
  362. package/dist/store/sessions.d.ts +4 -5
  363. package/dist/store/sessions.js +5 -6
  364. package/dist/store/summaries.d.ts +13 -17
  365. package/dist/store/summaries.js +26 -70
  366. package/dist/support-bundle.js +4 -8
  367. package/dist/tenant.d.ts +1 -5
  368. package/dist/token-ledger.d.ts +1 -1
  369. package/dist/token-ledger.js +3 -5
  370. package/dist/trace.js +1 -3
  371. package/dist/version.d.ts +1 -1
  372. package/dist/version.js +1 -1
  373. package/dist/working-memory.d.ts +1 -1
  374. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  375. package/extensions/openclaw-plugin/package.json +1 -1
  376. package/openclaw.plugin.json +1 -1
  377. package/package.json +1 -1
@@ -5,13 +5,13 @@
5
5
  * - /repos/{repo}/issues/comments -> issue_comments_hwm
6
6
  * - /repos/{repo}/pulls/comments -> pr_review_comments_hwm
7
7
  *
8
- * Crash safety (codex P1 #3): each stream's HWM is persisted ONLY after
8
+ * Crash safety: each stream's HWM is persisted ONLY after
9
9
  * the stream fully drains. If stream 2 throws mid-flight, stream 1's HWM
10
10
  * is committed and stream 2's stays NULL (or its prior value). Rerun
11
11
  * picks up from stream 1's saved HWM and re-fetches stream 2 from its
12
12
  * last committed point.
13
13
  *
14
- * Codex P1 #2: the /issues endpoint returns BOTH issues and PRs (a PR is
14
+ * The /issues endpoint returns BOTH issues and PRs (a PR is
15
15
  * an issue with a `pull_request` field). We skip PRs here so they don't
16
16
  * get ingested under the issues schema. PRs are handled via webhook in
17
17
  * V1 (no /pulls backfill stream — review comments cover the discussion
@@ -5,13 +5,13 @@
5
5
  * - /repos/{repo}/issues/comments -> issue_comments_hwm
6
6
  * - /repos/{repo}/pulls/comments -> pr_review_comments_hwm
7
7
  *
8
- * Crash safety (codex P1 #3): each stream's HWM is persisted ONLY after
8
+ * Crash safety: each stream's HWM is persisted ONLY after
9
9
  * the stream fully drains. If stream 2 throws mid-flight, stream 1's HWM
10
10
  * is committed and stream 2's stays NULL (or its prior value). Rerun
11
11
  * picks up from stream 1's saved HWM and re-fetches stream 2 from its
12
12
  * last committed point.
13
13
  *
14
- * Codex P1 #2: the /issues endpoint returns BOTH issues and PRs (a PR is
14
+ * The /issues endpoint returns BOTH issues and PRs (a PR is
15
15
  * an issue with a `pull_request` field). We skip PRs here so they don't
16
16
  * get ingested under the issues schema. PRs are handled via webhook in
17
17
  * V1 (no /pulls backfill stream — review comments cover the discussion
@@ -110,18 +110,15 @@ function isCommentItem(x) {
110
110
  }
111
111
  /**
112
112
  * Drain one stream end-to-end. Pauses and retries on rate-limit. Throws on
113
- * any other fetch error so the caller leaves the HWM unchanged (round 1
114
- * codex P1 #3 crash safety).
113
+ * any other fetch error so the caller leaves the HWM unchanged.
115
114
  *
116
- * v1.3.1 (round 2 codex P1s + claude P1):
117
115
  * - Tracks max(updated_at) across ALL fetched items, including ones
118
116
  * `toIngestEvent` rejects (e.g., PRs returned via `/issues`). Otherwise
119
117
  * a page of pure PRs would never advance the HWM and the next run would
120
118
  * re-fetch the same window forever.
121
119
  * - Returns `drained: boolean` — true only when the stream actually ran
122
- * to next=null. Callers MUST NOT advance the HWM when drained=false.
123
- * Was a bug in v1.3.0: hitting maxPerStream cap returned the partial
124
- * maxUpdatedAt and the caller persisted it, skipping the unfetched tail.
120
+ * to next=null. Callers MUST NOT advance the HWM when drained=false,
121
+ * or a capped run would persist a partial HWM and skip the unfetched tail.
125
122
  */
126
123
  async function drainStream(ctx, url0, toIngestEvent, fetcher, token, sleep, maxItems) {
127
124
  let url = url0;
@@ -141,9 +138,7 @@ async function drainStream(ctx, url0, toIngestEvent, fetcher, token, sleep, maxI
141
138
  }
142
139
  pages++;
143
140
  for (const item of page.items) {
144
- // v1.3.1: track updated_at on EVERY item, before the toIngestEvent
145
- // filter. Skipped PRs from /issues still contribute to the HWM so
146
- // PR-only pages don't loop forever.
141
+ // Track updated_at on EVERY item, before the toIngestEvent filter, so PR-only pages don't loop forever.
147
142
  // SAFETY: updated_at is GitHub's ISO-timestamp field on every item
148
143
  // shape this stream returns; a non-string value would only fail the
149
144
  // string comparisons below, matching pre-migration passthrough.
@@ -175,7 +170,7 @@ async function drainStream(ctx, url0, toIngestEvent, fetcher, token, sleep, maxI
175
170
  function issueItemToEvent(item, repository) {
176
171
  if (!isIssuesItem(item))
177
172
  return null;
178
- // Codex P1 #2: /issues returns PRs too — skip them.
173
+ // /issues returns PRs too; skip them.
179
174
  if (item.pull_request)
180
175
  return null;
181
176
  const payload = {
@@ -240,9 +235,7 @@ function prReviewCommentItemToEvent(item, repository) {
240
235
  /** Drains one stream, then persists its HWM only when the stream ran to the end. */
241
236
  async function backfillStream(ctx, opts, sleep, stream) {
242
237
  const res = await drainStream(ctx, stream.url, stream.toIngestEvent, opts.fetcher, opts.token, sleep, opts.maxPerStream);
243
- // v1.3.1: only advance HWM when the stream actually drained. A capped run
244
- // (--max) must leave the HWM at its previous value so the next invocation
245
- // re-fetches the unprocessed tail.
238
+ // A capped run (--max) must leave the HWM at its previous value so the next run re-fetches the unprocessed tail.
246
239
  if (res.drained && res.maxUpdatedAt) {
247
240
  writeOneHwm(ctx.hippoRoot, ctx.tenantId, opts.repoFullName, stream.column, res.maxUpdatedAt);
248
241
  }
@@ -162,9 +162,7 @@ export async function cmdGithubDlqReplay(hippoRoot, args, flags) {
162
162
  tenantId: resolveTenantId({}),
163
163
  actor: adminActor('cli:github-dlq-replay'),
164
164
  };
165
- // v1.3.1 hotfix (codex P1): without an ingestHook the v1.3.0 CLI was a
166
- // dry-run that printed "replay ok" while only bumping retry_count. Wire the
167
- // real hook so `replay` actually re-runs the ingest path.
165
+ // Without an ingestHook replay only bumps retry_count while printing "replay ok"; the real hook re-runs ingest.
168
166
  const result = await replayDlqEntry(ctx, id, {
169
167
  force,
170
168
  webhookSecret: envGithubWebhookSecret(),
@@ -175,11 +173,8 @@ export async function cmdGithubDlqReplay(hippoRoot, args, flags) {
175
173
  if (!event) {
176
174
  return { memoryId: null };
177
175
  }
178
- // v1.3.2 (codex round 3 P1): a replayed `issue_comment.deleted` or
179
- // `pull_request_review_comment.deleted` row must route to the deletion
180
- // handler, NOT to ingestEvent. The v1.3.1 hook unconditionally called
181
- // ingestEvent and would have written the deleted comment as a NEW raw
182
- // memory instead of archiving the matching ones.
176
+ // A replayed comment `.deleted` row must route to the deletion handler, NOT to ingestEvent,
177
+ // which would write the deleted comment as a NEW raw memory instead of archiving the matching ones.
183
178
  if ((event.eventName === 'issue_comment' || event.eventName === 'pull_request_review_comment') &&
184
179
  event.payload.action === 'deleted') {
185
180
  const repo = event.payload.repository?.full_name ?? '';
@@ -19,21 +19,14 @@ export interface DeletionResult {
19
19
  /**
20
20
  * Handle GitHub `issue_comment.deleted` and `pull_request_review_comment.deleted`.
21
21
  *
22
- * Codex round 1 P0 #5: filter by tenant_id + kind='raw'. Multi-row archive:
22
+ * Filter by tenant_id + kind='raw'. Multi-row archive:
23
23
  * GitHub edits keep the same artifact_ref, so multiple active raw rows can
24
24
  * match a single deletion event. Archive ALL of them.
25
25
  *
26
- * Claude round 2 P0 #2 (v1.3.1 hotfix): the v1.3.0 implementation called
27
- * archiveRaw N times in a loop, each opening its own DB handle and SAVEPOINT.
28
- * The first archive's afterArchive committed the idempotency mark. If archive
29
- * 2..N threw, idempotency was already committed and retry returned 'duplicate'
30
- * with archivedCount=0 — survivors stayed searchable, leaking private bodies.
31
- *
32
- * v1.3.1 fix: ONE shared DB handle wrapping ALL archives + the idempotency
33
- * mark in a single outer SAVEPOINT. Any per-row failure rolls back the entire
34
- * batch (including idempotency), so retry re-attempts cleanly. archiveRawMemory
35
- * (the lower-level function from raw-archive.js) runs its own inner SAVEPOINT
36
- * which nests safely inside the outer one.
26
+ * ONE shared DB handle wraps ALL archives + the idempotency mark in a single
27
+ * outer SAVEPOINT: a per-row failure rolls back the whole batch, idempotency
28
+ * included, so a retry re-attempts cleanly instead of leaving searchable
29
+ * survivors. archiveRawMemory's own inner SAVEPOINT nests safely inside it.
37
30
  *
38
31
  * Tenant scope and kind='raw' filtering are load-bearing: without them a
39
32
  * deletion event from tenant A could archive tenant B's row sharing the same
@@ -4,21 +4,14 @@ import { hasSeenKey, markKeySeen } from './idempotency.js';
4
4
  /**
5
5
  * Handle GitHub `issue_comment.deleted` and `pull_request_review_comment.deleted`.
6
6
  *
7
- * Codex round 1 P0 #5: filter by tenant_id + kind='raw'. Multi-row archive:
7
+ * Filter by tenant_id + kind='raw'. Multi-row archive:
8
8
  * GitHub edits keep the same artifact_ref, so multiple active raw rows can
9
9
  * match a single deletion event. Archive ALL of them.
10
10
  *
11
- * Claude round 2 P0 #2 (v1.3.1 hotfix): the v1.3.0 implementation called
12
- * archiveRaw N times in a loop, each opening its own DB handle and SAVEPOINT.
13
- * The first archive's afterArchive committed the idempotency mark. If archive
14
- * 2..N threw, idempotency was already committed and retry returned 'duplicate'
15
- * with archivedCount=0 — survivors stayed searchable, leaking private bodies.
16
- *
17
- * v1.3.1 fix: ONE shared DB handle wrapping ALL archives + the idempotency
18
- * mark in a single outer SAVEPOINT. Any per-row failure rolls back the entire
19
- * batch (including idempotency), so retry re-attempts cleanly. archiveRawMemory
20
- * (the lower-level function from raw-archive.js) runs its own inner SAVEPOINT
21
- * which nests safely inside the outer one.
11
+ * ONE shared DB handle wraps ALL archives + the idempotency mark in a single
12
+ * outer SAVEPOINT: a per-row failure rolls back the whole batch, idempotency
13
+ * included, so a retry re-attempts cleanly instead of leaving searchable
14
+ * survivors. archiveRawMemory's own inner SAVEPOINT nests safely inside it.
22
15
  *
23
16
  * Tenant scope and kind='raw' filtering are load-bearing: without them a
24
17
  * deletion event from tenant A could archive tenant B's row sharing the same
@@ -3,9 +3,8 @@ import type { Context } from '../../api.js';
3
3
  /**
4
4
  * GitHub webhook DLQ. Mirrors the Slack DLQ shape (src/connectors/slack/dlq.ts)
5
5
  * but carries GitHub-specific metadata: event_name, delivery_id, signature,
6
- * installation_id, repo_full_name. Codex P1 #5 mandates this rich context
7
- * so a `hippo gh dlq replay` operator can triage without re-deriving anything
8
- * from the raw payload.
6
+ * installation_id, repo_full_name, so a `hippo gh dlq replay` operator can
7
+ * triage without re-deriving anything from the raw payload.
9
8
  *
10
9
  * Buckets:
11
10
  * - parse_error — raw_payload was not valid JSON
@@ -56,7 +55,7 @@ export interface ReplayDlqOpts {
56
55
  /** Current webhook secret. If omitted, signature check is skipped (force-only path). */
57
56
  webhookSecret?: string;
58
57
  /**
59
- * Previous webhook secret during rotation (v1.3.1 hotfix — claude P1).
58
+ * Previous webhook secret during rotation.
60
59
  * Operators rotating GITHUB_WEBHOOK_SECRET would otherwise be forced into
61
60
  * --force on DLQ rows written under the old secret. Plumbed through to
62
61
  * verifyGitHubSignature.previousSecret.
@@ -79,10 +78,8 @@ export interface ReplayResult {
79
78
  * route already knows how to route an envelope, so it passes that capability
80
79
  * back in.
81
80
  *
82
- * v1.3.2 (claude review): the v1.3.1 contract advertised an `idempotencyKey`
83
- * field, but the v1.3.1 ingest re-derives the key from the parsed event
84
- * itself, so the field was a phantom — any future hook that trusted the
85
- * passed-in value would dedupe against a stale key. Field removed.
81
+ * No `idempotencyKey` field: ingest re-derives the key from the parsed event,
82
+ * so a hook trusting a passed-in key would dedupe against a stale one.
86
83
  */
87
84
  export type IngestHook = (ctx: Context, args: {
88
85
  rawPayload: string;
@@ -101,7 +98,7 @@ export type IngestHook = (ctx: Context, args: {
101
98
  * 4. Type-guard the envelope. Fail → bump, `unhandled`.
102
99
  * 5. If an `ingestHook` is supplied, call it and return its memoryId.
103
100
  * If not (dry-run path), bump retry_count and return status `replayed`
104
- * with memoryId=null. The webhook route wires the real hook in Task 14.
101
+ * with memoryId=null. The webhook route wires the real hook.
105
102
  *
106
103
  * Mirrors Slack's "always use current routing" policy: replays use the
107
104
  * deployment state NOW, not at the time of original DLQing.
@@ -79,7 +79,7 @@ function bumpRetryCount(hippoRoot, id) {
79
79
  * 4. Type-guard the envelope. Fail → bump, `unhandled`.
80
80
  * 5. If an `ingestHook` is supplied, call it and return its memoryId.
81
81
  * If not (dry-run path), bump retry_count and return status `replayed`
82
- * with memoryId=null. The webhook route wires the real hook in Task 14.
82
+ * with memoryId=null. The webhook route wires the real hook.
83
83
  *
84
84
  * Mirrors Slack's "always use current routing" policy: replays use the
85
85
  * deployment state NOW, not at the time of original DLQing.
@@ -125,8 +125,7 @@ export async function replayDlqEntry(ctx, id, opts = {}) {
125
125
  // Real replay path. The route's IngestHook is responsible for routing,
126
126
  // idempotency, and writing the memory. The DLQ module only validates the
127
127
  // surface and bumps the retry counter.
128
- // v1.3.2: dropped the stale idempotencyKey arg — the hook re-derives it
129
- // from the parsed event (artifact_ref + updated_at) since v1.3.1.
128
+ // No idempotencyKey arg: the hook re-derives it from the parsed event (artifact_ref + updated_at).
130
129
  const eventName = row.eventName ?? '';
131
130
  const deliveryId = row.deliveryId ?? '';
132
131
  const { memoryId } = await opts.ingestHook(ctx, {
@@ -2,11 +2,11 @@
2
2
  * GitHub event ingest with afterWrite race-safe idempotency.
3
3
  *
4
4
  * Mirrors src/connectors/slack/ingest.ts. The dedupe key is sha256(eventName +
5
- * ':' + rawBody) (codex P0 #3) — derived from the signed body, not from the
5
+ * ':' + rawBody), derived from the signed body, not from the
6
6
  * unsigned X-GitHub-Delivery header, so a replay attacker cannot bypass
7
7
  * idempotency by rotating the delivery UUID.
8
8
  *
9
- * Race semantics (codex P1 #6):
9
+ * Race semantics:
10
10
  * - Fast path: hasSeenKey pre-check returns 'duplicate' for the common case.
11
11
  * - Slow path: hasSeenKey passes (no row yet). Two workers may race into
12
12
  * remember() concurrently. Inside the writeEntry SAVEPOINT, INSERT OR
@@ -15,10 +15,8 @@
15
15
  * rolls back this worker's memory row). Exactly one memory exists per
16
16
  * idempotency_key.
17
17
  *
18
- * The Slack precedent's race test was insufficient — it tested the fast path,
19
- * not the SAVEPOINT collision. The `__testInjectBeforeLog` hook below lets
20
- * tests pre-populate github_event_log inside the SAVEPOINT to actually
21
- * exercise the changes=0 -> rollback path.
18
+ * The `__testInjectBeforeLog` hook below lets tests pre-populate github_event_log
19
+ * inside the SAVEPOINT to exercise the changes=0 -> rollback path, not just the fast path.
22
20
  */
23
21
  import { type Context } from '../../api.js';
24
22
  import { type DatabaseSyncLike } from '../../db.js';
@@ -50,7 +48,7 @@ export type IngestEvent = {
50
48
  export interface IngestInput {
51
49
  /** The X-GitHub-Event header value + parsed body, discriminated. */
52
50
  event: IngestEvent;
53
- /** The raw HTTP body — used for the idempotency key (replay-safe per codex P0 #3). */
51
+ /** The raw HTTP body, used for the idempotency key (replay-safe). */
54
52
  rawBody: string;
55
53
  /** X-GitHub-Delivery header value, audit metadata only. */
56
54
  deliveryId: string;
@@ -2,11 +2,11 @@
2
2
  * GitHub event ingest with afterWrite race-safe idempotency.
3
3
  *
4
4
  * Mirrors src/connectors/slack/ingest.ts. The dedupe key is sha256(eventName +
5
- * ':' + rawBody) (codex P0 #3) — derived from the signed body, not from the
5
+ * ':' + rawBody), derived from the signed body, not from the
6
6
  * unsigned X-GitHub-Delivery header, so a replay attacker cannot bypass
7
7
  * idempotency by rotating the delivery UUID.
8
8
  *
9
- * Race semantics (codex P1 #6):
9
+ * Race semantics:
10
10
  * - Fast path: hasSeenKey pre-check returns 'duplicate' for the common case.
11
11
  * - Slow path: hasSeenKey passes (no row yet). Two workers may race into
12
12
  * remember() concurrently. Inside the writeEntry SAVEPOINT, INSERT OR
@@ -15,10 +15,8 @@
15
15
  * rolls back this worker's memory row). Exactly one memory exists per
16
16
  * idempotency_key.
17
17
  *
18
- * The Slack precedent's race test was insufficient — it tested the fast path,
19
- * not the SAVEPOINT collision. The `__testInjectBeforeLog` hook below lets
20
- * tests pre-populate github_event_log inside the SAVEPOINT to actually
21
- * exercise the changes=0 -> rollback path.
18
+ * The `__testInjectBeforeLog` hook below lets tests pre-populate github_event_log
19
+ * inside the SAVEPOINT to exercise the changes=0 -> rollback path, not just the fast path.
22
20
  */
23
21
  import { remember } from '../../api.js';
24
22
  import { openHippoDb, closeHippoDb } from '../../db.js';
@@ -39,7 +37,7 @@ function transformEvent(event) {
39
37
  }
40
38
  }
41
39
  /**
42
- * v1.3.1: extract the source-normalized identifier the idempotency key needs.
40
+ * Extract the source-normalized identifier the idempotency key needs.
43
41
  * Backfill and webhook both produce IngestEvent objects describing the same
44
42
  * source revision, so deriving the key from these fields collapses both paths
45
43
  * onto the same dedupe row. Mirrors the artifactRef strings in transform.ts.
@@ -113,10 +111,8 @@ export function ingestEvent(ctx, input) {
113
111
  }
114
112
  }
115
113
  if (e instanceof RejectedValueError) {
116
- // AT1 (plan §3 containment): a tombstone hit is a PERMANENT skip, not
117
- // a transient failure — never DLQ-retry it. Mark the idempotency key
118
- // seen exactly like the empty-body branch above so a GitHub retry of
119
- // the same delivery acks as done, not error.
114
+ // A tombstone hit is a PERMANENT skip, never DLQ-retried: mark the key seen like the empty-body
115
+ // branch above so a GitHub retry of the same delivery acks as done, not error.
120
116
  markKeySeenWithoutMemory(ctx.hippoRoot, idempotencyKey, input);
121
117
  return { status: 'skipped', memoryId: null };
122
118
  }
@@ -135,7 +131,7 @@ function markKeySeenWithoutMemory(hippoRoot, idempotencyKey, input) {
135
131
  }
136
132
  }
137
133
  function rememberWithEventLog(ctx, input, idempotencyKey, opts) {
138
- // v1.12.0: drop the legacy `|| 'connector:github'` fallback (see slack/ingest.ts:73 for rationale).
134
+ // No `|| 'connector:github'` fallback (see rememberWithEventLog in slack/ingest.ts for rationale).
139
135
  const result = remember(ctx, {
140
136
  ...opts,
141
137
  untrusted: true,
@@ -4,13 +4,11 @@
4
4
  * Production uses `realGitHubFetcher` against `https://api.github.com`.
5
5
  * Tests inject a fake `GitHubFetcher` so they never hit the network.
6
6
  *
7
- * Codex P1 #4 mandate: any non-200 response that is NOT a recognized
8
- * rate-limit pause MUST throw `GitHubFetchError`. Silently turning
9
- * 401/403/404/500 into empty pages produced empty backfills with no
10
- * operator signal, so this code path is now load-bearing.
7
+ * Any non-200 response that is NOT a recognized rate-limit pause MUST throw
8
+ * `GitHubFetchError`: an empty page would be an empty backfill with no operator signal.
11
9
  */
12
- import type { JsonValue } from './types.js';
13
10
  import { type RateLimitInfo } from './ratelimit.js';
11
+ import type { JsonValue } from '../../json.js';
14
12
  export declare class GitHubFetchError extends Error {
15
13
  readonly status: number;
16
14
  readonly bodyExcerpt: string;
@@ -4,10 +4,8 @@
4
4
  * Production uses `realGitHubFetcher` against `https://api.github.com`.
5
5
  * Tests inject a fake `GitHubFetcher` so they never hit the network.
6
6
  *
7
- * Codex P1 #4 mandate: any non-200 response that is NOT a recognized
8
- * rate-limit pause MUST throw `GitHubFetchError`. Silently turning
9
- * 401/403/404/500 into empty pages produced empty backfills with no
10
- * operator signal, so this code path is now load-bearing.
7
+ * Any non-200 response that is NOT a recognized rate-limit pause MUST throw
8
+ * `GitHubFetchError`: an empty page would be an empty backfill with no operator signal.
11
9
  */
12
10
  import { parseRateLimit } from './ratelimit.js';
13
11
  import { fetchWithRetry } from '../../http-retry.js';
@@ -44,6 +42,7 @@ function headersToRecord(h) {
44
42
  return Object.fromEntries(entries);
45
43
  }
46
44
  const GITHUB_TIMEOUT_MS = 30_000;
45
+ const ERROR_BODY_SNIPPET_CHARS = 256;
47
46
  export const realGitHubFetcher = async ({ url, token }) => {
48
47
  const res = await fetchWithRetry(url, {
49
48
  headers: {
@@ -54,10 +53,10 @@ export const realGitHubFetcher = async ({ url, token }) => {
54
53
  }, { timeoutMs: GITHUB_TIMEOUT_MS });
55
54
  const headers = headersToRecord(res.headers);
56
55
  const rateLimit = parseRateLimit(headers, res.status);
57
- // Codex P1 #4: don't silently turn 401/403/404/500 into empty pages.
56
+ // Don't silently turn 401/403/404/500 into empty pages.
58
57
  if (res.status !== 200 && rateLimit.reason === 'none') {
59
58
  const body = await res.text().catch(() => '');
60
- throw new GitHubFetchError(res.status, body.slice(0, 256), url);
59
+ throw new GitHubFetchError(res.status, body.slice(0, ERROR_BODY_SNIPPET_CHARS), url);
61
60
  }
62
61
  // SAFETY: GitHub's list/paginated endpoints (issues, comments, etc. — the
63
62
  // only endpoints this backfill fetcher targets) return a JSON array body
@@ -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. v1.3.1 hotfix (codex round 1 P0 #3 + claude
12
- * round 2 P0 #3).
13
- *
14
- * Round 1 design: sha256(eventName + ':' + rawBody) so an attacker rotating
15
- * X-GitHub-Delivery cannot bypass dedupe.
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
- * v1.3.2: deletion-specific idempotency key. Distinct namespace from
49
- * computeIdempotencyKey so an ingest's row in github_event_log doesn't make
50
- * a deletion event return 'duplicate' before it gets a chance to archive.
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. v1.3.1 hotfix (codex round 1 P0 #3 + claude
21
- * round 2 P0 #3).
22
- *
23
- * Round 1 design: sha256(eventName + ':' + rawBody) so an attacker rotating
24
- * X-GitHub-Delivery cannot bypass dedupe.
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
- * v1.3.2: deletion-specific idempotency key. Distinct namespace from
60
- * computeIdempotencyKey so an ingest's row in github_event_log doesn't make
61
- * a deletion event return 'duplicate' before it gets a chance to archive.
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 — codex P0 #4 regression target)
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 — codex P0 #4 regression target)
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' (E1.x connector boundary, see src/importers.ts).
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 v0.40.0 provenance gate.
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
- * Codex P1 #7: `private` MUST be optional, not required. The Slack-style
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`. v0.39 commit 3 closes the prior race where
15
- * the archive committed but `markEventSeen` ran on a second db handle — a
16
- * crash between them left the deletion event un-acked, and the next retry
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