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
@@ -8,7 +8,8 @@ import { resolveTenantId } from '../tenant.js';
8
8
  import { appendAuditEvent, reportAuditWriteFailure } from '../audit.js';
9
9
  import { commonDerivationScope } from '../recall-scope.js';
10
10
  import { log } from '../log.js';
11
- import { REPLAY_COUNT_DEFAULT, isJsonString } from './run.js';
11
+ import { REPLAY_COUNT_DEFAULT } from './run.js';
12
+ import { isJsonString } from '../json.js';
12
13
  // -------------------------------------------------------------------------
13
14
  // 1.4. Auto-promote complete sessions to traces
14
15
  // -------------------------------------------------------------------------
@@ -24,10 +25,8 @@ export function promoteSessionTraces(run) {
24
25
  let tracesSkippedRejected = 0;
25
26
  const windowDays = run.config.autoTraceWindowDays ?? 7;
26
27
  const sinceMs = run.now.getTime() - windowDays * 24 * 60 * 60 * 1000;
27
- // Auto-trace currently runs in a single-tenant context (the env-resolved
28
- // tenant for this process). Multi-tenant deployments that want
29
- // consolidation across all tenants need a per-tenant loop layered on top
30
- // of this — tracked in docs/plans/2026-05-02-continuity-tables-tenant-scope.md.
28
+ // Auto-trace runs single-tenant (the env-resolved tenant); consolidating
29
+ // every tenant needs a per-tenant loop layered on top of this.
31
30
  const consolidationTenant = resolveTenantId({});
32
31
  const promotable = findPromotableSessions(run.hippoRoot, consolidationTenant, sinceMs);
33
32
  for (const session of promotable) {
@@ -60,7 +59,7 @@ function sessionTrace(run, consolidationTenant, sessionId) {
60
59
  session_id: sessionId,
61
60
  limit: 1000,
62
61
  });
63
- // T7: a mixed-scope session would otherwise leak into one trace.
62
+ // A mixed-scope session would otherwise leak into one trace.
64
63
  const sessionScope = commonDerivationScope(events.map((e) => e.scope));
65
64
  if (!sessionScope.ok) {
66
65
  run.result.tracesSkippedMixedScope++;
@@ -91,26 +90,15 @@ function sessionTrace(run, consolidationTenant, sessionId) {
91
90
  tags: ['auto-promoted'],
92
91
  source: 'auto-promote',
93
92
  scope: sessionScope.scope,
94
- // T1 fix (2026-08-15 hardening pass): stamp the trace into the SAME
95
- // tenant the traceExistsForSession idempotency check (above) runs
96
- // under. Before
97
- // this, createMemory omitted tenantId and the trace always landed
98
- // 'default' (memory.ts:535) while the idempotency check ran under
99
- // consolidationTenant — for any non-default tenant that check never
100
- // hit, and the trace regenerated every sleep.
93
+ // Same tenant as the traceExistsForSession check above, or a
94
+ // non-default tenant's trace regenerates every sleep.
101
95
  tenantId: consolidationTenant,
102
96
  baseHalfLifeDays: run.config.defaultHalfLifeDays,
103
97
  });
104
98
  return { trace, outcome };
105
99
  }
106
- // AT1 (same producer-side pattern as the merge pass below): traceExistsForSession
107
- // only sees rows CURRENTLY in the store — once a rejected trace is
108
- // removed, that idempotency check no longer blocks regeneration, and
109
- // this write would otherwise reach batchWriteAndDelete's guard bypass
110
- // unchecked, resurrecting it every sleep. Check under THE ENTRY'S OWN
111
- // stamped tenantId (read off `trace` after createMemory — never guess
112
- // the tenant) + the built content's digest. A hit skips the push
113
- // entirely: not counted as promoted, not added to survivors.
100
+ // traceExistsForSession only sees live rows, so a removed rejected trace would regenerate every
101
+ // sleep; check the tombstone under the entry's own stamped tenantId, never a guessed one.
114
102
  function traceRejected(run, trace, sessionId) {
115
103
  const consolidateDb = run.getConsolidateDb();
116
104
  if (!consolidateDb)
@@ -1,17 +1,16 @@
1
1
  /**
2
- * E2 customer_note first-class object - the LAST E2 object
3
- * (docs/plans/2026-06-01-e2-customer-note-object.md).
2
+ * customer_note first-class object.
4
3
  *
5
4
  * A `customer_note` is a discrete note recorded against an account/customer entity:
6
5
  * a `note` body scoped to a `customer`, evolving via the supersede delta lifecycle.
7
- * Entity-scoping is a free-form `customer` column (the `entities` table is unbuilt -
8
- * E3.1 planned - so an FK is deferred). Unlike project_brief's one-summary-per-repo,
6
+ * Entity-scoping is a free-form `customer` column (the `entities` table is unbuilt,
7
+ * so an FK is deferred). Unlike project_brief's one-summary-per-repo,
9
8
  * a customer accrues MANY discrete notes over time, each with its own supersede chain
10
9
  * (correct a note -> a new version preserving history; close retires it).
11
10
  *
12
11
  * Reuses the project_brief/skill supersede machinery verbatim (superseded_by self-FK
13
12
  * + CAS + INSERT-preflight + server-derived version + change_summary + supersede
14
- * tenant-match trigger). It has NO assembler/renderer (the simplest E2 object): the
13
+ * tenant-match trigger). It has NO assembler/renderer (the simplest first-class object): the
15
14
  * contribution is purely the entity-scoping dimension.
16
15
  *
17
16
  * The `customer_notes` table is the source of truth (survives memory decay); the
@@ -73,8 +72,7 @@ export interface ListCustomerNotesOpts {
73
72
  * the new version = predecessor.version + 1 (server-derived).
74
73
  *
75
74
  * The memory mirror carries a `customer:<lc>` tag (in addition to ['customer_note']
76
- * + caller extraTags) so scope-aware recall treats the note as entity-local - the
77
- * project_brief codex-P2 recall-locality lesson applied to entity scoping. There is
75
+ * + caller extraTags) so scope-aware recall treats the note as entity-local. There is
78
76
  * no self-recursion path (customer_note has no receipt-query/refresh).
79
77
  */
80
78
  export declare function saveCustomerNote(hippoRoot: string, tenantId: string, opts: SaveCustomerNoteOpts, actor?: string): CustomerNote;
@@ -1,17 +1,16 @@
1
1
  /**
2
- * E2 customer_note first-class object - the LAST E2 object
3
- * (docs/plans/2026-06-01-e2-customer-note-object.md).
2
+ * customer_note first-class object.
4
3
  *
5
4
  * A `customer_note` is a discrete note recorded against an account/customer entity:
6
5
  * a `note` body scoped to a `customer`, evolving via the supersede delta lifecycle.
7
- * Entity-scoping is a free-form `customer` column (the `entities` table is unbuilt -
8
- * E3.1 planned - so an FK is deferred). Unlike project_brief's one-summary-per-repo,
6
+ * Entity-scoping is a free-form `customer` column (the `entities` table is unbuilt,
7
+ * so an FK is deferred). Unlike project_brief's one-summary-per-repo,
9
8
  * a customer accrues MANY discrete notes over time, each with its own supersede chain
10
9
  * (correct a note -> a new version preserving history; close retires it).
11
10
  *
12
11
  * Reuses the project_brief/skill supersede machinery verbatim (superseded_by self-FK
13
12
  * + CAS + INSERT-preflight + server-derived version + change_summary + supersede
14
- * tenant-match trigger). It has NO assembler/renderer (the simplest E2 object): the
13
+ * tenant-match trigger). It has NO assembler/renderer (the simplest first-class object): the
15
14
  * contribution is purely the entity-scoping dimension.
16
15
  *
17
16
  * The `customer_notes` table is the source of truth (survives memory decay); the
@@ -94,9 +93,78 @@ const NOTE_COLS = `
94
93
  function buildNoteContent(customer, note) {
95
94
  return `${customer}\n\n${note}`;
96
95
  }
97
- // ---------------------------------------------------------------------------
98
- // Public API
99
- // ---------------------------------------------------------------------------
96
+ // Preflight the supersede target BEFORE inserting the new row (so the new
97
+ // autoincrement id can never be its own supersede target); read the
98
+ // predecessor version in the same SELECT for server-derived versioning.
99
+ function preflightNoteSupersede(db, tenantId, supersedesId) {
100
+ // SAFETY: SELECT projects exactly status, version; .get() returns that
101
+ // shape for the matching row, or undefined when no note/tenant pair matches.
102
+ const pred = db.prepare(`SELECT status, version FROM customer_notes WHERE id = ? AND tenant_id = ?`).get(supersedesId, tenantId);
103
+ if (!pred) {
104
+ throw new NotFoundError(`saveCustomerNote: note ${supersedesId} to supersede not found for tenant ${tenantId}`);
105
+ }
106
+ if (pred.status !== 'active') {
107
+ throw new ConflictError(`saveCustomerNote: note ${supersedesId} is not active (status='${pred.status}'); only active notes can be superseded.`);
108
+ }
109
+ return pred.version + 1;
110
+ }
111
+ function insertNoteRow(db, memoryId, w, version) {
112
+ const result = db.prepare(`
113
+ INSERT INTO customer_notes(
114
+ memory_id, tenant_id, customer, note, version,
115
+ status, superseded_by, superseded_at, change_summary, closed_at, created_at
116
+ ) VALUES (?, ?, ?, ?, ?, 'active', NULL, NULL, ?, NULL, ?)
117
+ `).run(memoryId, w.tenantId, w.customer, w.note, version, w.changeSummary, w.now);
118
+ return Number(result.lastInsertRowid ?? 0);
119
+ }
120
+ function supersedeNoteRow(db, w, supersedesId, noteId, version) {
121
+ const sup = db.prepare(`
122
+ UPDATE customer_notes
123
+ SET status = 'superseded', superseded_by = ?, superseded_at = ?
124
+ WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
125
+ `).run(noteId, w.now, supersedesId, w.tenantId, noteId);
126
+ if (sup.changes === 0) {
127
+ throw new ConflictError(`saveCustomerNote: note ${supersedesId} could not be superseded (no longer active or self-reference).`);
128
+ }
129
+ appendAuditEvent(db, {
130
+ tenantId: w.tenantId,
131
+ actor: w.actor,
132
+ op: 'customer_note_supersede',
133
+ targetId: String(supersedesId),
134
+ metadata: {
135
+ note_id: supersedesId,
136
+ superseded_by: noteId,
137
+ new_version: version,
138
+ },
139
+ });
140
+ }
141
+ /** The afterWrite body: preflight, INSERT, supersede, reload, create audit, all in one SAVEPOINT. */
142
+ function writeNoteRow(db, memoryId, w) {
143
+ const version = w.supersedesId !== undefined ? preflightNoteSupersede(db, w.tenantId, w.supersedesId) : 1;
144
+ const noteId = insertNoteRow(db, memoryId, w, version);
145
+ if (w.supersedesId !== undefined)
146
+ supersedeNoteRow(db, w, w.supersedesId, noteId, version);
147
+ // SAFETY: SELECT ${NOTE_COLS} projects exactly the CustomerNoteRow columns;
148
+ // .get() returns that row, or undefined only if the just-inserted id can't
149
+ // be found.
150
+ const row = db.prepare(`SELECT ${NOTE_COLS} FROM customer_notes WHERE id = ?`)
151
+ .get(noteId);
152
+ if (!row)
153
+ throw new Error('saveCustomerNote: failed to reload saved note row');
154
+ // GDPR-light metadata: ids + flags only, no note text.
155
+ appendAuditEvent(db, {
156
+ tenantId: w.tenantId,
157
+ actor: w.actor,
158
+ op: 'customer_note_create',
159
+ targetId: String(noteId),
160
+ metadata: {
161
+ note_id: noteId,
162
+ customer: w.customer,
163
+ version,
164
+ },
165
+ });
166
+ return row;
167
+ }
100
168
  /**
101
169
  * Create a customer_note (or a new version that supersedes an existing one). Writes
102
170
  * the memory mirror + the customer_notes row atomically inside writeEntry's SAVEPOINT.
@@ -105,8 +173,7 @@ function buildNoteContent(customer, note) {
105
173
  * the new version = predecessor.version + 1 (server-derived).
106
174
  *
107
175
  * The memory mirror carries a `customer:<lc>` tag (in addition to ['customer_note']
108
- * + caller extraTags) so scope-aware recall treats the note as entity-local - the
109
- * project_brief codex-P2 recall-locality lesson applied to entity scoping. There is
176
+ * + caller extraTags) so scope-aware recall treats the note as entity-local. There is
110
177
  * no self-recursion path (customer_note has no receipt-query/refresh).
111
178
  */
112
179
  export function saveCustomerNote(hippoRoot, tenantId, opts, actor = 'cli') {
@@ -125,75 +192,14 @@ export function saveCustomerNote(hippoRoot, tenantId, opts, actor = 'cli') {
125
192
  baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
126
193
  tenantId,
127
194
  });
195
+ const w = {
196
+ tenantId, actor, customer, note: opts.note, changeSummary, supersedesId: opts.supersedesNoteId, now,
197
+ };
128
198
  let savedRow;
129
199
  writeEntry(hippoRoot, mem, {
130
200
  actor,
131
201
  afterWrite: (db, memoryId) => {
132
- // Preflight the supersede target BEFORE inserting the new row (so the new
133
- // autoincrement id can never be its own supersede target); read the
134
- // predecessor version in the same SELECT for server-derived versioning.
135
- // Mirrors saveProjectBrief / saveSkill (codex P1 2026-05-28).
136
- let version = 1;
137
- if (opts.supersedesNoteId !== undefined) {
138
- // SAFETY: SELECT projects exactly status, version; .get() returns that
139
- // shape for the matching row, or undefined when no note/tenant pair matches.
140
- const pred = db.prepare(`SELECT status, version FROM customer_notes WHERE id = ? AND tenant_id = ?`).get(opts.supersedesNoteId, tenantId);
141
- if (!pred) {
142
- throw new NotFoundError(`saveCustomerNote: note ${opts.supersedesNoteId} to supersede not found for tenant ${tenantId}`);
143
- }
144
- if (pred.status !== 'active') {
145
- throw new ConflictError(`saveCustomerNote: note ${opts.supersedesNoteId} is not active (status='${pred.status}'); only active notes can be superseded.`);
146
- }
147
- version = pred.version + 1;
148
- }
149
- const result = db.prepare(`
150
- INSERT INTO customer_notes(
151
- memory_id, tenant_id, customer, note, version,
152
- status, superseded_by, superseded_at, change_summary, closed_at, created_at
153
- ) VALUES (?, ?, ?, ?, ?, 'active', NULL, NULL, ?, NULL, ?)
154
- `).run(memoryId, tenantId, customer, opts.note, version, changeSummary, now);
155
- const noteId = Number(result.lastInsertRowid ?? 0);
156
- if (opts.supersedesNoteId !== undefined) {
157
- const sup = db.prepare(`
158
- UPDATE customer_notes
159
- SET status = 'superseded', superseded_by = ?, superseded_at = ?
160
- WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
161
- `).run(noteId, now, opts.supersedesNoteId, tenantId, noteId);
162
- if (sup.changes === 0) {
163
- throw new ConflictError(`saveCustomerNote: note ${opts.supersedesNoteId} could not be superseded (no longer active or self-reference).`);
164
- }
165
- appendAuditEvent(db, {
166
- tenantId,
167
- actor,
168
- op: 'customer_note_supersede',
169
- targetId: String(opts.supersedesNoteId),
170
- metadata: {
171
- note_id: opts.supersedesNoteId,
172
- superseded_by: noteId,
173
- new_version: version,
174
- },
175
- });
176
- }
177
- // SAFETY: SELECT ${NOTE_COLS} projects exactly the CustomerNoteRow columns;
178
- // .get() returns that row, or undefined only if the just-inserted id can't
179
- // be found.
180
- const row = db.prepare(`SELECT ${NOTE_COLS} FROM customer_notes WHERE id = ?`)
181
- .get(noteId);
182
- if (!row)
183
- throw new Error('saveCustomerNote: failed to reload saved note row');
184
- savedRow = row;
185
- // GDPR-light metadata: ids + flags only, no note text.
186
- appendAuditEvent(db, {
187
- tenantId,
188
- actor,
189
- op: 'customer_note_create',
190
- targetId: String(noteId),
191
- metadata: {
192
- note_id: noteId,
193
- customer,
194
- version,
195
- },
196
- });
202
+ savedRow = writeNoteRow(db, memoryId, w);
197
203
  },
198
204
  afterCommit: () => markGraphDirty(hippoRoot, tenantId, mem.id),
199
205
  });
@@ -246,7 +252,7 @@ export function closeCustomerNote(hippoRoot, tenantId, id, actor = 'cli') {
246
252
  // Closing removes the object from the graph. Remove its rows DIRECTLY (deterministic),
247
253
  // not only via an enqueued rebuild whose queue item is lost if the mirror is later
248
254
  // forgotten (the queue row cascade-deletes with the memory), which would leave the closed
249
- // object stale and could block that forget (codex P1). Still enqueue when a mirror exists
255
+ // object stale and could block that forget. Still enqueue when a mirror exists
250
256
  // so a concurrent rebuild re-derives consistently (harmless if it also runs).
251
257
  removeGraphEntitiesForObject(hippoRoot, tenantId, 'customer', closed.id);
252
258
  if (closed.memoryId) {
package/dist/dag.d.ts CHANGED
@@ -16,11 +16,8 @@ export interface DagBuildResult {
16
16
  candidateClusters: number;
17
17
  summariesCreated: number;
18
18
  factsLinked: number;
19
- /** AT1: clusters skipped because the LLM-synthesized summary landed on a
20
- * rejected value (plan §3 containment — per-cluster catch, not a whole-
21
- * phase abort). Member re-parenting writes are unaffected by construction
22
- * (same id + same content = guard-exempt), so this only ever counts
23
- * summary-creation refusals. */
19
+ /** Clusters skipped because the summary matched a rejected value. Re-parenting writes are
20
+ * guard-exempt (same id + content), so only summary-creation refusals count. */
24
21
  rejected: number;
25
22
  }
26
23
  export declare function buildDag(hippoRoot: string, facts: MemoryEntry[], opts: DagSummaryOptions): Promise<DagBuildResult>;
@@ -33,21 +30,17 @@ export interface DagRebuildResult {
33
30
  capped: boolean;
34
31
  }
35
32
  /**
36
- * v0.30 / E3 — sleep-cycle phase that drains the dirty L2 summary queue.
33
+ * Sleep-cycle phase that drains the dirty L2 summary queue.
37
34
  * Thin orchestrator; the heavy lifting lives in store.ts (load + apply)
38
35
  * and dag.ts:generateDagSummary (LLM call).
39
36
  *
40
- * Per-summary try/catch isolation (plan-eng-r1 MED must-fix) — one
41
- * throwing rebuild does NOT abort the rest of the queue.
37
+ * Per-summary try/catch: one throwing rebuild does NOT abort the rest of the queue.
42
38
  *
43
39
  * Race-loser handling: applyRebuildResult's UPDATE WHERE includes
44
40
  * AND summary_dirty=1, so concurrent sleep's second writer returns
45
41
  * changed=false. Silent skip (neither rebuilt++ nor refused++ nor failed++).
46
42
  *
47
- * T4: applyRebuildResult returns { changed, refused } — a tombstone hit
48
- * (rebuild content matches a previously-rejected value) increments
49
- * `refused`, not `rebuilt`. Dirty still clears either way; only the stat
50
- * split changed (docs/plans/2026-08-15-hardening-at1-followups.md T4).
43
+ * A tombstone hit counts as `refused`, not `rebuilt`; dirty clears either way.
51
44
  */
52
45
  export declare function rebuildDirtySummaries(hippoRoot: string, opts: DagSummaryOptions & {
53
46
  cap?: number;
@@ -57,21 +50,17 @@ export interface EntityProfilesBuildResult {
57
50
  profilesCreated: number;
58
51
  l2sLinked: number;
59
52
  failed: number;
60
- /** AT1: clusters skipped because the profile summary landed on a rejected
61
- * value (plan §3 containment). Kept distinct from `failed` (LLM null /
62
- * rate-limit) — a tombstone hit is a deliberate refusal, not an error. */
53
+ /** Clusters skipped because the profile matched a rejected value. Kept apart from
54
+ * `failed`: a tombstone hit is a deliberate refusal, not an error. */
63
55
  rejected: number;
64
56
  }
65
57
  /**
66
- * v0.30 / E5 — build L3 entity profiles by clustering L2 summaries with
58
+ * Build L3 entity profiles by clustering L2 summaries with
67
59
  * shared entity tags. Threshold 2+ L2s per entity. Mirrors buildDag L1->L2
68
60
  * pattern, one level up.
69
61
  *
70
- * Born-dirty cancellation (E3 lesson): after linking L2 children to the new
71
- * L3 (each link write fires E2 hook on L3 via widened markSummaryDirtyInTx),
72
- * call clearSummaryDirtyAfterBuild with source='buildEntityProfiles-clean'
73
- * so E3 sleep-cycle rebuild doesn't re-rebuild the freshly-built L3 this
74
- * same cycle.
62
+ * Born-dirty cancellation: each L2 link write marks the new L3 dirty, so clear it
63
+ * or the same sleep cycle's rebuild re-rebuilds the freshly built L3.
75
64
  */
76
65
  export declare function buildEntityProfiles(hippoRoot: string, l2Summaries: MemoryEntry[], opts: DagSummaryOptions): Promise<EntityProfilesBuildResult>;
77
66
  //# sourceMappingURL=dag.d.ts.map