@manyoars/oars 0.2.0 → 0.4.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 (320) hide show
  1. package/README.md +147 -28
  2. package/dist/api.js +1 -1
  3. package/dist/bin.js +6 -29
  4. package/dist/cli.d.ts +10 -4
  5. package/dist/cli.js +440 -91
  6. package/dist/commands/keys.d.ts +31 -0
  7. package/dist/commands/keys.js +65 -0
  8. package/dist/commands/login.d.ts +1 -1
  9. package/dist/commands/login.js +3 -3
  10. package/dist/errors.d.ts +13 -13
  11. package/dist/errors.js +28 -11
  12. package/dist/gate/checks.d.ts +68 -0
  13. package/dist/gate/checks.js +584 -0
  14. package/dist/gate/command.d.ts +14 -0
  15. package/dist/gate/command.js +61 -0
  16. package/dist/gate/hook-fill-main.d.ts +1 -0
  17. package/dist/gate/hook-fill-main.js +16 -0
  18. package/dist/gate/hook-fill.d.ts +38 -0
  19. package/dist/gate/hook-fill.js +94 -0
  20. package/dist/gate/hook-main.d.ts +1 -0
  21. package/dist/gate/hook-main.js +47 -0
  22. package/dist/gate/hook-process.d.ts +24 -0
  23. package/dist/gate/hook-process.js +112 -0
  24. package/dist/gate/hook-record.d.ts +3 -0
  25. package/dist/gate/hook-record.js +8 -0
  26. package/dist/gate/hook.d.ts +48 -0
  27. package/dist/gate/hook.js +259 -0
  28. package/dist/gate/install.d.ts +25 -0
  29. package/dist/gate/install.js +126 -0
  30. package/dist/gate/local-bin.d.ts +8 -0
  31. package/dist/gate/local-bin.js +36 -0
  32. package/dist/gate/notes.d.ts +19 -0
  33. package/dist/gate/notes.js +27 -0
  34. package/dist/gate/novelty.d.ts +35 -0
  35. package/dist/gate/novelty.js +99 -0
  36. package/dist/gate/policy.d.ts +25 -0
  37. package/dist/gate/policy.js +73 -0
  38. package/dist/gate/record.d.ts +120 -0
  39. package/dist/gate/record.js +125 -0
  40. package/dist/gate/registry-state.d.ts +10 -0
  41. package/dist/gate/registry-state.js +30 -0
  42. package/dist/gate/registry.d.ts +56 -0
  43. package/dist/gate/registry.js +94 -0
  44. package/dist/gate/replay.d.ts +20 -0
  45. package/dist/gate/replay.js +254 -0
  46. package/dist/gate/report.d.ts +50 -0
  47. package/dist/gate/report.js +89 -0
  48. package/dist/gate/scope.d.ts +7 -0
  49. package/dist/gate/scope.js +27 -0
  50. package/dist/gate/transcripts.d.ts +68 -0
  51. package/dist/gate/transcripts.js +232 -0
  52. package/dist/hook-bin.d.ts +1 -0
  53. package/dist/hook-bin.js +50 -0
  54. package/dist/issuer.d.ts +10 -6
  55. package/dist/issuer.js +19 -24
  56. package/dist/main.d.ts +1 -0
  57. package/dist/main.js +67 -0
  58. package/dist/oidc.js +1 -1
  59. package/dist/sessions/actions.d.ts +35 -0
  60. package/dist/sessions/actions.js +429 -0
  61. package/dist/sessions/allow.d.ts +23 -0
  62. package/dist/sessions/allow.js +71 -0
  63. package/dist/sessions/archive.d.ts +11 -0
  64. package/dist/sessions/archive.js +38 -0
  65. package/dist/sessions/audit.d.ts +42 -0
  66. package/dist/sessions/audit.js +104 -0
  67. package/dist/sessions/baseline.d.ts +62 -0
  68. package/dist/sessions/baseline.js +190 -0
  69. package/dist/sessions/call-shape.d.ts +19 -0
  70. package/dist/sessions/call-shape.js +85 -0
  71. package/dist/sessions/capture-runtime.d.ts +10 -0
  72. package/dist/sessions/capture-runtime.js +12 -0
  73. package/dist/sessions/cell-increments.d.ts +47 -0
  74. package/dist/sessions/cell-increments.js +243 -0
  75. package/dist/sessions/cells.d.ts +109 -0
  76. package/dist/sessions/cells.js +261 -0
  77. package/dist/sessions/codex.d.ts +53 -0
  78. package/dist/sessions/codex.js +745 -0
  79. package/dist/sessions/command.d.ts +127 -0
  80. package/dist/sessions/command.js +419 -0
  81. package/dist/sessions/copilot.d.ts +40 -0
  82. package/dist/sessions/copilot.js +432 -0
  83. package/dist/sessions/coverage.d.ts +19 -0
  84. package/dist/sessions/coverage.js +56 -0
  85. package/dist/sessions/cursor.d.ts +24 -0
  86. package/dist/sessions/cursor.js +324 -0
  87. package/dist/sessions/derive.d.ts +57 -0
  88. package/dist/sessions/derive.js +386 -0
  89. package/dist/sessions/exclusion.d.ts +87 -0
  90. package/dist/sessions/exclusion.js +361 -0
  91. package/dist/sessions/extract.d.ts +181 -0
  92. package/dist/sessions/extract.js +1806 -0
  93. package/dist/sessions/fold.d.ts +17 -0
  94. package/dist/sessions/fold.js +71 -0
  95. package/dist/sessions/frame.d.ts +24 -0
  96. package/dist/sessions/frame.js +95 -0
  97. package/dist/sessions/gemini.d.ts +37 -0
  98. package/dist/sessions/gemini.js +613 -0
  99. package/dist/sessions/gold-arms.d.ts +67 -0
  100. package/dist/sessions/gold-arms.js +172 -0
  101. package/dist/sessions/gold.d.ts +197 -0
  102. package/dist/sessions/gold.js +463 -0
  103. package/dist/sessions/graph.d.ts +12 -0
  104. package/dist/sessions/graph.js +34 -0
  105. package/dist/sessions/grok.d.ts +36 -0
  106. package/dist/sessions/grok.js +421 -0
  107. package/dist/sessions/hook-calls.d.ts +3 -0
  108. package/dist/sessions/hook-calls.js +47 -0
  109. package/dist/sessions/hook-index.d.ts +66 -0
  110. package/dist/sessions/hook-index.js +164 -0
  111. package/dist/sessions/hooks.d.ts +17 -0
  112. package/dist/sessions/hooks.js +281 -0
  113. package/dist/sessions/identity.d.ts +27 -0
  114. package/dist/sessions/identity.js +131 -0
  115. package/dist/sessions/install.d.ts +70 -0
  116. package/dist/sessions/install.js +246 -0
  117. package/dist/sessions/lineage.d.ts +19 -0
  118. package/dist/sessions/lineage.js +118 -0
  119. package/dist/sessions/mcp.d.ts +7 -0
  120. package/dist/sessions/mcp.js +157 -0
  121. package/dist/sessions/merkle.d.ts +7 -0
  122. package/dist/sessions/merkle.js +28 -0
  123. package/dist/sessions/motif.d.ts +44 -0
  124. package/dist/sessions/motif.js +59 -0
  125. package/dist/sessions/opencode.d.ts +33 -0
  126. package/dist/sessions/opencode.js +319 -0
  127. package/dist/sessions/otel.d.ts +60 -0
  128. package/dist/sessions/otel.js +494 -0
  129. package/dist/sessions/package-books.d.ts +48 -0
  130. package/dist/sessions/package-books.js +120 -0
  131. package/dist/sessions/package-resolve.d.ts +32 -0
  132. package/dist/sessions/package-resolve.js +185 -0
  133. package/dist/sessions/pending.d.ts +34 -0
  134. package/dist/sessions/pending.js +142 -0
  135. package/dist/sessions/pi.d.ts +36 -0
  136. package/dist/sessions/pi.js +353 -0
  137. package/dist/sessions/pool-books.d.ts +21 -0
  138. package/dist/sessions/pool-books.js +75 -0
  139. package/dist/sessions/pool-choices.d.ts +5 -0
  140. package/dist/sessions/pool-choices.js +11 -0
  141. package/dist/sessions/pool-command.d.ts +115 -0
  142. package/dist/sessions/pool-command.js +669 -0
  143. package/dist/sessions/pool-delta.d.ts +54 -0
  144. package/dist/sessions/pool-delta.js +190 -0
  145. package/dist/sessions/pool-nightly-deps.d.ts +3 -0
  146. package/dist/sessions/pool-nightly-deps.js +11 -0
  147. package/dist/sessions/pool-nightly.d.ts +10 -0
  148. package/dist/sessions/pool-nightly.js +85 -0
  149. package/dist/sessions/pool-outbox.d.ts +76 -0
  150. package/dist/sessions/pool-outbox.js +313 -0
  151. package/dist/sessions/pool-print.d.ts +26 -0
  152. package/dist/sessions/pool-print.js +68 -0
  153. package/dist/sessions/pool-raw.d.ts +27 -0
  154. package/dist/sessions/pool-raw.js +68 -0
  155. package/dist/sessions/pool-replica.d.ts +35 -0
  156. package/dist/sessions/pool-replica.js +91 -0
  157. package/dist/sessions/pool-send-lock.d.ts +20 -0
  158. package/dist/sessions/pool-send-lock.js +87 -0
  159. package/dist/sessions/pool-sent.d.ts +4 -0
  160. package/dist/sessions/pool-sent.js +26 -0
  161. package/dist/sessions/pool-totals.d.ts +10 -0
  162. package/dist/sessions/pool-totals.js +21 -0
  163. package/dist/sessions/pool-wire.d.ts +435 -0
  164. package/dist/sessions/pool-wire.js +1100 -0
  165. package/dist/sessions/pool.d.ts +32 -0
  166. package/dist/sessions/pool.js +164 -0
  167. package/dist/sessions/projection.d.ts +75 -0
  168. package/dist/sessions/projection.js +165 -0
  169. package/dist/sessions/public-vocabulary.d.ts +72 -0
  170. package/dist/sessions/public-vocabulary.js +295 -0
  171. package/dist/sessions/query.d.ts +123 -0
  172. package/dist/sessions/query.js +268 -0
  173. package/dist/sessions/read-adoption.d.ts +2 -0
  174. package/dist/sessions/read-adoption.js +27 -0
  175. package/dist/sessions/read-call-shape.d.ts +2 -0
  176. package/dist/sessions/read-call-shape.js +25 -0
  177. package/dist/sessions/read-change.d.ts +2 -0
  178. package/dist/sessions/read-change.js +30 -0
  179. package/dist/sessions/read-cost.d.ts +4 -0
  180. package/dist/sessions/read-cost.js +48 -0
  181. package/dist/sessions/read-day-move.d.ts +3 -0
  182. package/dist/sessions/read-day-move.js +65 -0
  183. package/dist/sessions/read-fit.d.ts +7 -0
  184. package/dist/sessions/read-fit.js +41 -0
  185. package/dist/sessions/read-interaction.d.ts +5 -0
  186. package/dist/sessions/read-interaction.js +62 -0
  187. package/dist/sessions/read-lead-lag.d.ts +15 -0
  188. package/dist/sessions/read-lead-lag.js +81 -0
  189. package/dist/sessions/read-live.d.ts +6 -0
  190. package/dist/sessions/read-live.js +38 -0
  191. package/dist/sessions/read-method.d.ts +2 -0
  192. package/dist/sessions/read-method.js +29 -0
  193. package/dist/sessions/read-next-step.d.ts +2 -0
  194. package/dist/sessions/read-next-step.js +35 -0
  195. package/dist/sessions/read-order.d.ts +54 -0
  196. package/dist/sessions/read-order.js +62 -0
  197. package/dist/sessions/read-readable.d.ts +8 -0
  198. package/dist/sessions/read-readable.js +24 -0
  199. package/dist/sessions/read-recovery-edge.d.ts +2 -0
  200. package/dist/sessions/read-recovery-edge.js +26 -0
  201. package/dist/sessions/read-recovery.d.ts +20 -0
  202. package/dist/sessions/read-recovery.js +97 -0
  203. package/dist/sessions/read-rows.d.ts +13 -0
  204. package/dist/sessions/read-rows.js +47 -0
  205. package/dist/sessions/read-substitution.d.ts +5 -0
  206. package/dist/sessions/read-substitution.js +51 -0
  207. package/dist/sessions/read-support.d.ts +48 -0
  208. package/dist/sessions/read-support.js +73 -0
  209. package/dist/sessions/read-tool-day.d.ts +2 -0
  210. package/dist/sessions/read-tool-day.js +26 -0
  211. package/dist/sessions/read-version-boundary.d.ts +31 -0
  212. package/dist/sessions/read-version-boundary.js +95 -0
  213. package/dist/sessions/read.d.ts +86 -0
  214. package/dist/sessions/read.js +87 -0
  215. package/dist/sessions/recall-bundle.d.ts +12 -0
  216. package/dist/sessions/recall-bundle.js +33 -0
  217. package/dist/sessions/recall-consequence.d.ts +45 -0
  218. package/dist/sessions/recall-consequence.js +179 -0
  219. package/dist/sessions/recall-frames.d.ts +18 -0
  220. package/dist/sessions/recall-frames.js +44 -0
  221. package/dist/sessions/recall-next.d.ts +145 -0
  222. package/dist/sessions/recall-next.js +241 -0
  223. package/dist/sessions/recall-object.d.ts +139 -0
  224. package/dist/sessions/recall-object.js +78 -0
  225. package/dist/sessions/recall-pool.d.ts +19 -0
  226. package/dist/sessions/recall-pool.js +96 -0
  227. package/dist/sessions/recall-push.d.ts +26 -0
  228. package/dist/sessions/recall-push.js +47 -0
  229. package/dist/sessions/recall-reads.d.ts +124 -0
  230. package/dist/sessions/recall-reads.js +417 -0
  231. package/dist/sessions/recall-render.d.ts +19 -0
  232. package/dist/sessions/recall-render.js +168 -0
  233. package/dist/sessions/recovery-calls.d.ts +21 -0
  234. package/dist/sessions/recovery-calls.js +25 -0
  235. package/dist/sessions/redact.d.ts +17 -0
  236. package/dist/sessions/redact.js +113 -0
  237. package/dist/sessions/repo-root.d.ts +10 -0
  238. package/dist/sessions/repo-root.js +101 -0
  239. package/dist/sessions/schema.d.ts +694 -0
  240. package/dist/sessions/schema.js +167 -0
  241. package/dist/sessions/session-shape.d.ts +76 -0
  242. package/dist/sessions/session-shape.js +355 -0
  243. package/dist/sessions/sources.d.ts +50 -0
  244. package/dist/sessions/sources.js +233 -0
  245. package/dist/sessions/steps.d.ts +105 -0
  246. package/dist/sessions/steps.js +250 -0
  247. package/dist/sessions/store.d.ts +309 -0
  248. package/dist/sessions/store.js +1498 -0
  249. package/dist/sessions/strata.d.ts +26 -0
  250. package/dist/sessions/strata.js +77 -0
  251. package/dist/sessions/survival.d.ts +27 -0
  252. package/dist/sessions/survival.js +230 -0
  253. package/dist/sessions/transcript-tail.d.ts +14 -0
  254. package/dist/sessions/transcript-tail.js +64 -0
  255. package/dist/sessions/verify.d.ts +53 -0
  256. package/dist/sessions/verify.js +286 -0
  257. package/dist/sessions/vocab-load.d.ts +26 -0
  258. package/dist/sessions/vocab-load.js +61 -0
  259. package/dist/sessions/vscode-copilot.d.ts +38 -0
  260. package/dist/sessions/vscode-copilot.js +494 -0
  261. package/dist/telemetry/command.d.ts +8 -0
  262. package/dist/telemetry/command.js +85 -0
  263. package/dist/telemetry/consent.d.ts +17 -0
  264. package/dist/telemetry/consent.js +63 -0
  265. package/dist/telemetry/crash.d.ts +8 -0
  266. package/dist/telemetry/crash.js +52 -0
  267. package/dist/telemetry/emit.d.ts +31 -0
  268. package/dist/telemetry/emit.js +42 -0
  269. package/dist/telemetry/fields.d.ts +16 -0
  270. package/dist/telemetry/fields.js +26 -0
  271. package/dist/telemetry/health.d.ts +21 -0
  272. package/dist/telemetry/health.js +108 -0
  273. package/dist/telemetry/hook-emit.d.ts +4 -0
  274. package/dist/telemetry/hook-emit.js +18 -0
  275. package/dist/telemetry/measures.d.ts +32 -0
  276. package/dist/telemetry/measures.js +128 -0
  277. package/dist/telemetry/observe.d.ts +13 -0
  278. package/dist/telemetry/observe.js +51 -0
  279. package/dist/telemetry/read-events.d.ts +9 -0
  280. package/dist/telemetry/read-events.js +17 -0
  281. package/dist/telemetry/record.d.ts +17 -0
  282. package/dist/telemetry/record.js +26 -0
  283. package/dist/telemetry/report.d.ts +77 -0
  284. package/dist/telemetry/report.js +188 -0
  285. package/dist/telemetry/schema.d.ts +40 -0
  286. package/dist/telemetry/schema.js +187 -0
  287. package/dist/telemetry/send-main.d.ts +1 -0
  288. package/dist/telemetry/send-main.js +11 -0
  289. package/dist/telemetry/send.d.ts +36 -0
  290. package/dist/telemetry/send.js +172 -0
  291. package/dist/telemetry/spool.d.ts +17 -0
  292. package/dist/telemetry/spool.js +70 -0
  293. package/dist/telemetry/wire.d.ts +25 -0
  294. package/dist/telemetry/wire.js +76 -0
  295. package/package.json +4 -5
  296. package/vocab/crates.json +1 -0
  297. package/vocab/go.json +1 -0
  298. package/vocab/harness.json +1 -0
  299. package/vocab/homebrew.json +1 -0
  300. package/vocab/manifest.json +1 -0
  301. package/vocab/maven.json +1 -0
  302. package/vocab/models.json +1 -0
  303. package/vocab/npm.json +1 -0
  304. package/vocab/programs.json +1 -0
  305. package/vocab/pypi.json +1 -0
  306. package/vocab/rubygems.json +1 -0
  307. package/dist/commands/deploy-input.d.ts +0 -38
  308. package/dist/commands/deploy-input.js +0 -150
  309. package/dist/commands/deploy.d.ts +0 -22
  310. package/dist/commands/deploy.js +0 -124
  311. package/dist/commands/status.d.ts +0 -7
  312. package/dist/commands/status.js +0 -18
  313. package/dist/commands/templates.d.ts +0 -11
  314. package/dist/commands/templates.js +0 -32
  315. package/dist/commands/test-charge.d.ts +0 -11
  316. package/dist/commands/test-charge.js +0 -34
  317. package/dist/journal.d.ts +0 -26
  318. package/dist/journal.js +0 -58
  319. package/parity/deploy-plans/paid-api.json +0 -34
  320. package/parity/deploy-plans/per-task-agent.json +0 -34
@@ -0,0 +1,50 @@
1
+ import type { SubagentOrigin } from './extract.js';
2
+ import type { Harness } from './schema.js';
3
+ /** A session found in a source, before its transcript is read. */
4
+ export interface SourceSession {
5
+ /** Names the skeleton (`<source>-<id>`), so it is a safe file name. */
6
+ id: string;
7
+ /** The working folder (a path); null when the source names none. */
8
+ folder: string | null;
9
+ /** With `mtimeMs`, the cursor `state.json` keeps to tell a changed session from one already deposited. */
10
+ size: number;
11
+ mtimeMs: number;
12
+ /** Set when the session is a spawned agent's: it is deposited as a subagent of its parent, so session counts stay main sessions. */
13
+ subagent?: SubagentOrigin;
14
+ }
15
+ export interface SourceStore {
16
+ sessions: SourceSession[];
17
+ /** The session as transcript text for `captureSession`; throws when it cannot be read. */
18
+ transcript: (session: SourceSession) => string | Promise<string>;
19
+ /** Sessions skipped because their id is not a safe file name, counted so the skip is never silent. */
20
+ rejected: number;
21
+ /** Items that could not be inspected, warned about once each and never fatal; `reason` names no path. */
22
+ unreadable: readonly {
23
+ file: string;
24
+ reason: string;
25
+ }[];
26
+ /** The file to blame when `transcript` throws for a session; the store's own file when a source keeps one. */
27
+ where: (session: SourceSession) => string;
28
+ close: () => void;
29
+ }
30
+ export interface Source {
31
+ /** The `--allow` name, the first segment of its `state.json` keys, and the prefix of its skeleton names. */
32
+ name: string;
33
+ harness: Harness;
34
+ /** How the deposit lines name it ("Cursor": `Read 3 Cursor sessions.`). */
35
+ label: string;
36
+ /** Where its store lives; the caller passes the environment so a test never reads the real one. */
37
+ root: (platform: NodeJS.Platform, env: {
38
+ [name: string]: string | undefined;
39
+ }, home: string) => string;
40
+ /** The file reported when the store as a whole cannot be opened. */
41
+ storeFile: (root: string) => string;
42
+ /** A session whose transcript cannot be read ends this source's deposit (Cursor's, as it was before the table) instead of skipping to the next. */
43
+ stopOnTranscriptError?: boolean;
44
+ /** Opens the store read-only; undefined when the machine has none, so a missing store is skipped silently. */
45
+ open: (root: string) => Promise<SourceStore | undefined>;
46
+ }
47
+ /** Every non-Claude source, in the order the deposit reads them. */
48
+ export declare const SOURCES: readonly Source[];
49
+ /** How a source is named in a deposit line; a name the table does not know (a test's) is its own label. */
50
+ export declare const sourceLabel: (name: string, table?: readonly Source[]) => string;
@@ -0,0 +1,233 @@
1
+ // The source table: one entry per non-Claude harness whose sessions `deposit` can read. `--allow <name>` opens an entry, the nightly hook
2
+ // runtime, `allow.unmatched`, the deposit lines, `measure`, and `verify` read this table, so a new harness is one entry here plus its reader file.
3
+ // Claude Code is not an entry: its transcripts are files the deposit walks itself (`depositLocked`), and its behavior does not change.
4
+ import { join } from 'node:path';
5
+ import { codexRoot, openCodexStore } from './codex.js';
6
+ import { copilotRoot, openCopilotStore } from './copilot.js';
7
+ import { cursorRoot, openCursorStore } from './cursor.js';
8
+ import { grokRoot, openGrokStore } from './grok.js';
9
+ import { geminiRoot, openGeminiStore } from './gemini.js';
10
+ import { openPiStore, piRoot } from './pi.js';
11
+ import { openOtelStore, otelRoot } from './otel.js';
12
+ import { opencodeRoot, openOpencodeStore } from './opencode.js';
13
+ import { openVscodeCopilotStore, vscodeAppData } from './vscode-copilot.js';
14
+ /** A session handed back to its own store was listed by it, so a miss is a bug in the caller. */
15
+ const need = (value) => {
16
+ if (value === undefined)
17
+ throw new Error('session not listed by its source');
18
+ return value;
19
+ };
20
+ const cursor = {
21
+ name: 'cursor',
22
+ harness: 'cursor',
23
+ label: 'Cursor',
24
+ stopOnTranscriptError: true,
25
+ root: (platform, env, home) => cursorRoot(platform, env, home),
26
+ storeFile: (root) => join(root, 'globalStorage', 'state.vscdb'),
27
+ open: async (root) => {
28
+ const store = await openCursorStore(root);
29
+ if (store === undefined)
30
+ return undefined;
31
+ const byId = new Map(store.sessions.map((session) => [session.composerId, session]));
32
+ return {
33
+ sessions: store.sessions.map((session) => ({ id: session.composerId, folder: session.folder, size: session.messages, mtimeMs: session.lastUpdatedAt })),
34
+ transcript: (session) => store.transcript(need(byId.get(session.id))),
35
+ rejected: store.rejected,
36
+ unreadable: [],
37
+ where: () => join(root, 'globalStorage', 'state.vscdb'),
38
+ close: store.close,
39
+ };
40
+ },
41
+ };
42
+ const codex = {
43
+ name: 'codex',
44
+ harness: 'codex',
45
+ label: 'Codex',
46
+ root: (_platform, env, home) => codexRoot(env, home),
47
+ storeFile: (root) => join(root, 'sessions'),
48
+ open: async (root) => {
49
+ const store = await openCodexStore(root);
50
+ if (store === undefined)
51
+ return undefined;
52
+ const byId = new Map(store.sessions.map((session) => [session.id, session]));
53
+ return {
54
+ sessions: store.sessions.map((session) => ({
55
+ id: session.id,
56
+ folder: session.folder,
57
+ size: session.size,
58
+ mtimeMs: session.mtimeMs,
59
+ ...(session.parent === null ? {} : { subagent: { parent: `codex-${session.parent}`, agentType: session.agentRole, spawnedBy: null } }),
60
+ })),
61
+ transcript: async (session) => await store.transcript(need(byId.get(session.id))),
62
+ rejected: store.rejected,
63
+ unreadable: store.unreadable,
64
+ where: (session) => byId.get(session.id)?.file ?? root,
65
+ close: () => undefined,
66
+ };
67
+ },
68
+ };
69
+ const copilot = {
70
+ name: 'copilot',
71
+ harness: 'copilot',
72
+ label: 'Copilot',
73
+ root: (_platform, env, home) => copilotRoot(env, home),
74
+ storeFile: (root) => join(root, 'session-state'),
75
+ open: async (root) => {
76
+ const store = await openCopilotStore(root);
77
+ if (store === undefined)
78
+ return undefined;
79
+ const byId = new Map(store.sessions.map((session) => [session.id, session]));
80
+ return {
81
+ sessions: store.sessions.map((session) => ({ id: session.id, folder: session.folder, size: session.size, mtimeMs: session.mtimeMs })),
82
+ transcript: async (session) => await store.transcript(need(byId.get(session.id))),
83
+ rejected: store.rejected,
84
+ unreadable: store.unreadable,
85
+ where: (session) => byId.get(session.id)?.file ?? root,
86
+ close: () => undefined,
87
+ };
88
+ },
89
+ };
90
+ const gemini = {
91
+ name: 'gemini',
92
+ harness: 'gemini',
93
+ label: 'Gemini',
94
+ root: (_platform, env, home) => geminiRoot(env, home),
95
+ storeFile: (root) => join(root, 'tmp'),
96
+ open: async (root) => {
97
+ const store = await openGeminiStore(root);
98
+ if (store === undefined)
99
+ return undefined;
100
+ const byId = new Map(store.sessions.map((session) => [session.id, session]));
101
+ return {
102
+ sessions: store.sessions.map((session) => ({
103
+ id: session.id,
104
+ folder: session.folder,
105
+ size: session.size,
106
+ mtimeMs: session.mtimeMs,
107
+ ...(session.parent === null ? {} : { subagent: { parent: `gemini-${session.parent}`, agentType: null, spawnedBy: null } }),
108
+ })),
109
+ transcript: async (session) => await store.transcript(need(byId.get(session.id))),
110
+ rejected: store.rejected,
111
+ unreadable: store.unreadable,
112
+ where: (session) => byId.get(session.id)?.file ?? root,
113
+ close: () => undefined,
114
+ };
115
+ },
116
+ };
117
+ const grok = {
118
+ name: 'grok',
119
+ harness: 'grok',
120
+ label: 'Grok Build',
121
+ root: (_platform, env, home) => grokRoot(env, home),
122
+ storeFile: (root) => join(root, 'sessions'),
123
+ open: async (root) => {
124
+ const store = await openGrokStore(root);
125
+ if (store === undefined)
126
+ return undefined;
127
+ const byId = new Map(store.sessions.map((session) => [session.id, session]));
128
+ return {
129
+ sessions: store.sessions.map((session) => ({ id: session.id, folder: session.folder, size: session.size, mtimeMs: session.mtimeMs })),
130
+ transcript: async (session) => await store.transcript(need(byId.get(session.id))),
131
+ rejected: store.rejected,
132
+ unreadable: store.unreadable,
133
+ where: (session) => join(byId.get(session.id)?.dir ?? root, 'updates.jsonl'),
134
+ close: () => undefined,
135
+ };
136
+ },
137
+ };
138
+ const opencode = {
139
+ name: 'opencode',
140
+ harness: 'opencode',
141
+ label: 'OpenCode',
142
+ root: (_platform, env, home) => opencodeRoot(env, home),
143
+ storeFile: (root) => join(root, 'opencode.db'),
144
+ open: async (root) => {
145
+ const store = await openOpencodeStore(root);
146
+ if (store === undefined)
147
+ return undefined;
148
+ const byId = new Map(store.sessions.map((session) => [session.id, session]));
149
+ return {
150
+ sessions: store.sessions.map((session) => ({
151
+ id: session.id,
152
+ folder: session.folder,
153
+ size: session.size,
154
+ mtimeMs: session.mtimeMs,
155
+ ...(session.parent === null ? {} : { subagent: { parent: `opencode-${session.parent}`, agentType: session.agent, spawnedBy: null } }),
156
+ })),
157
+ transcript: (session) => store.transcript(need(byId.get(session.id))),
158
+ rejected: store.rejected,
159
+ unreadable: [],
160
+ where: () => join(root, 'opencode.db'),
161
+ close: store.close,
162
+ };
163
+ },
164
+ };
165
+ const pi = {
166
+ name: 'pi',
167
+ harness: 'pi',
168
+ label: 'Pi',
169
+ root: (_platform, env, home) => piRoot(env, home),
170
+ storeFile: (root) => join(root, 'sessions'),
171
+ open: async (root) => {
172
+ const store = await openPiStore(root);
173
+ if (store === undefined)
174
+ return undefined;
175
+ const byId = new Map(store.sessions.map((session) => [session.id, session]));
176
+ return {
177
+ sessions: store.sessions.map((session) => ({ id: session.id, folder: session.folder, size: session.size, mtimeMs: session.mtimeMs })),
178
+ transcript: async (session) => await store.transcript(need(byId.get(session.id))),
179
+ rejected: store.rejected,
180
+ unreadable: store.unreadable,
181
+ where: (session) => byId.get(session.id)?.file ?? root,
182
+ close: () => undefined,
183
+ };
184
+ },
185
+ };
186
+ const vscodeCopilot = {
187
+ name: 'vscode-copilot',
188
+ harness: 'vscode-copilot',
189
+ label: 'VS Code Copilot Chat',
190
+ root: (platform, env, home) => vscodeAppData(platform, env, home),
191
+ storeFile: (root) => root,
192
+ open: async (root) => {
193
+ const store = await openVscodeCopilotStore(root);
194
+ if (store === undefined)
195
+ return undefined;
196
+ const byId = new Map(store.sessions.map((session) => [session.id, session]));
197
+ return {
198
+ sessions: store.sessions.map((session) => ({ id: session.id, folder: session.folder, size: session.size, mtimeMs: session.mtimeMs })),
199
+ transcript: async (session) => await store.transcript(need(byId.get(session.id))),
200
+ rejected: store.rejected,
201
+ unreadable: store.unreadable,
202
+ where: (session) => byId.get(session.id)?.files[0] ?? root,
203
+ close: () => undefined,
204
+ };
205
+ },
206
+ };
207
+ // A folder of OTLP JSON trace files (`$OARS_OTEL_TRACES`, else the config folder's `otel-traces`): a configured folder rather than an `import` command, so the cursor, the
208
+ // allowlist, the nightly run, and the fail-open reads are the ones every other source has.
209
+ const otel = {
210
+ name: 'otel',
211
+ harness: 'otel',
212
+ label: 'OpenTelemetry GenAI',
213
+ root: (platform, env, home) => otelRoot(env, home, platform),
214
+ storeFile: (root) => root,
215
+ open: async (root) => {
216
+ const store = await openOtelStore(root);
217
+ if (store === undefined)
218
+ return undefined;
219
+ const byId = new Map(store.sessions.map((session) => [session.id, session]));
220
+ return {
221
+ sessions: store.sessions.map((session) => ({ id: session.id, folder: session.folder, size: session.size, mtimeMs: session.mtimeMs, ...(session.subagent === undefined ? {} : { subagent: session.subagent }) })),
222
+ transcript: (session) => store.transcript(need(byId.get(session.id))),
223
+ rejected: store.rejected,
224
+ unreadable: store.unreadable,
225
+ where: (session) => byId.get(session.id)?.file ?? root,
226
+ close: () => undefined,
227
+ };
228
+ },
229
+ };
230
+ /** Every non-Claude source, in the order the deposit reads them. */
231
+ export const SOURCES = [cursor, codex, copilot, gemini, grok, opencode, pi, vscodeCopilot, otel];
232
+ /** How a source is named in a deposit line; a name the table does not know (a test's) is its own label. */
233
+ export const sourceLabel = (name, table = SOURCES) => table.find((source) => source.name === name)?.label ?? name;
@@ -0,0 +1,105 @@
1
+ import { type Step, type StepCounts, type StepRow, type StepStatus, type Trigger } from './schema.js';
2
+ import { type DepositTrace } from './store.js';
3
+ /** Where one run's rows get their identity: the steps of one nightly or manual run share `run`. */
4
+ export interface StepContext {
5
+ storeDir: string;
6
+ now: () => number;
7
+ run: string;
8
+ trigger: Trigger;
9
+ }
10
+ export declare const newContext: (storeDir: string, now: () => number, trigger: Trigger) => StepContext;
11
+ /** What a step reports when it ends: the counts it read and wrote and its idempotency key (spec 4.2). */
12
+ export interface StepReport {
13
+ in: StepCounts;
14
+ out: StepCounts;
15
+ key: string | null;
16
+ }
17
+ export declare const appendStep: (storeDir: string, row: StepRow) => Promise<void>;
18
+ export declare const readSteps: (storeDir: string) => Promise<StepRow[]>;
19
+ /** The first informative line of a failure, masked like every other text the store keeps (D1's text rule) and cut to 300 characters. */
20
+ export declare const errorLine: (storeDir: string, error: unknown) => Promise<string>;
21
+ /** Appends the row for a step that ended (or was skipped) now, having taken `ms`. */
22
+ export declare const logStep: (context: StepContext, step: Step, status: StepStatus, ms: number, report: StepReport, error: string | null) => Promise<void>;
23
+ /** A step an earlier step of the run left undone (spec 4.4): logged `skipped`, with no counts and no key. */
24
+ export declare const logSkipped: (context: StepContext, step: Step) => Promise<void>;
25
+ /** Writes a row without ever failing: a log that cannot be appended (or a failure line that cannot be redacted) must not change a step's outcome or hide its real error. */
26
+ export declare const tryLog: (write: () => Promise<void>) => Promise<void>;
27
+ /**
28
+ * Runs `work` and logs its row: `ok` with what it reports, or `error` with the failure's first line and `failedKey` (a key built from
29
+ * the step's inputs before the work, so a retry of the same work has the same key), after which the failure goes on to the caller.
30
+ */
31
+ export declare const runStep: <T>(context: StepContext, step: Step, work: () => Promise<{
32
+ value: T;
33
+ report: StepReport;
34
+ }>, failedKey: string) => Promise<T>;
35
+ /** The runs of one step and key that failed in a row. Three is the line spec 4.4 draws. */
36
+ export declare const STUCK_RUNS = 3;
37
+ export interface StuckStep {
38
+ step: Step;
39
+ key: string;
40
+ /** Failed runs in a row, ending at the newest. */
41
+ runs: number;
42
+ /** The first error line of the newest failure. */
43
+ error: string;
44
+ }
45
+ /**
46
+ * Keys that failed on three runs in a row, per step and trigger (a nightly that fails every night is not cleared by the `stop` deposits
47
+ * between nights). A `skipped` or `deferred` row is no run of its step (a deferred deposit neither failed nor succeeded: it is waiting in the pending list); an `ok` row ends the open streaks of its own step and trigger, and of its step on the same key under another trigger, because a step's
48
+ * good run can carry a key (verify's high-water mark) that its failed run could not know before the work.
49
+ */
50
+ export declare const stuckSteps: (rows: readonly StepRow[]) => StuckStep[];
51
+ export interface StepLine {
52
+ step: Step;
53
+ /** The extractor version the runs ran at: spec 3.2 counts step time per `ev`. */
54
+ ev: string;
55
+ runs: number;
56
+ ok: number;
57
+ error: number;
58
+ skipped: number;
59
+ /** Stop deposits that found the store locked and went to the pending list: not failed runs, and not in `error`. */
60
+ deferred: number;
61
+ /** Mean wall time of the runs that ran (not the skipped or deferred ones), in ms. */
62
+ meanMs: number;
63
+ }
64
+ /** The spec 3.2 counters this pipeline can derive locally from the step log. */
65
+ export interface StepCounters {
66
+ runs: number;
67
+ steps: StepLine[];
68
+ /** Capture records deposited, by kind, summed over every ok deposit. */
69
+ deposited: StepCounts;
70
+ /** The newest ok derive's output, by type. */
71
+ derived: StepCounts;
72
+ /** The newest ok index's output: the recurring signatures and projects it holds. */
73
+ indexed: StepCounts;
74
+ /** The newest ok verify's `out`: invariants checked and passed, `cv-behind` sessions, sessions failing the raw audit (`merkle`), and stuck steps. */
75
+ verify: StepCounts | null;
76
+ recall: {
77
+ queries: number;
78
+ matches: number;
79
+ };
80
+ /** Failed runs by `<step>@<ev>`. */
81
+ failures: StepCounts;
82
+ stuck: StuckStep[];
83
+ }
84
+ export declare const stepCounters: (rows: readonly StepRow[]) => StepCounters;
85
+ export declare const formatStepCounters: (counters: StepCounters) => string;
86
+ /** A key built from its inputs and `ev` (spec 4.2), hashed; a recall has none in the spec (a lookup writes nothing), so its query is the input. */
87
+ export declare const stampKey: (...parts: readonly string[]) => string;
88
+ /**
89
+ * A deposit's key from its inputs (spec 4.2 names the files' state, which is not known before the work): `cv`, `ev`, the trigger, the allow
90
+ * entries or transcript asked for, and the redact domains. The `stop` hook passes no domains (it reads none), so its key has none.
91
+ */
92
+ export declare const inputKey: (trigger: Trigger, requested: readonly string[], domains: readonly string[]) => string;
93
+ /**
94
+ * Runs a deposit and logs its rows: `deposit` with the records it wrote by kind, then, when it ended with the whole-store pass,
95
+ * `derive` and `index` (spec 4.1: one row per step at a nightly or manual run). `key` is built from the deposit's inputs (the trigger and the transcripts
96
+ * it was asked to deposit), so the failed row and the ok row of one deposit carry the same key and a failure that recovers is not stuck.
97
+ * A deposit that fails logs `error` and, when a derive was to follow, `skipped` for it and for the index (spec 4.4). A derive that fails
98
+ * after the deposit wrote its records logs `deposit` ok, `derive` error, and `index` skipped; an index that fails logs `derive` ok and then
99
+ * `index` error; the failure goes on to the caller. The SessionEnd hook runs
100
+ * `withDerive` false: derive follows at the nightly run.
101
+ */
102
+ export declare const runDeposit: <T>(context: StepContext, withDerive: boolean, key: string, work: (trace: DepositTrace) => Promise<{
103
+ value: T;
104
+ in: StepCounts;
105
+ }>) => Promise<T>;
@@ -0,0 +1,250 @@
1
+ // The step log: `pipeline-runs.jsonl`, one row per run of a pipeline step, appended when the step ends (runtime-commons pipeline spec 3.1).
2
+ // Local only: it sits in the store directory beside `hook-calls.jsonl`, and `measure` prints the spec 3.2 counters from it.
3
+ import { randomUUID } from 'node:crypto';
4
+ import { appendFile, mkdir, readFile } from 'node:fs/promises';
5
+ import { homedir } from 'node:os';
6
+ import { performance } from 'node:perf_hooks';
7
+ import { CliError, ExitCode } from '../errors.js';
8
+ import { makeRedactor } from './redact.js';
9
+ import { CAPTURE_VERSION, EXTRACTOR_VERSION, parseStepRow } from './schema.js';
10
+ import { digest, newTrace, paths, readRedactDomains } from './store.js';
11
+ export const newContext = (storeDir, now, trigger) => ({ storeDir, now, run: randomUUID(), trigger });
12
+ export const appendStep = async (storeDir, row) => {
13
+ await mkdir(storeDir, { recursive: true });
14
+ await appendFile(paths(storeDir).pipelineRuns, `${JSON.stringify(row)}\n`, { mode: 0o600 });
15
+ };
16
+ export const readSteps = async (storeDir) => {
17
+ let text = '';
18
+ try {
19
+ text = await readFile(paths(storeDir).pipelineRuns, 'utf8');
20
+ }
21
+ catch (error) {
22
+ if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
23
+ throw error;
24
+ }
25
+ const rows = [];
26
+ for (const line of text.split('\n')) {
27
+ if (line === '')
28
+ continue;
29
+ try {
30
+ const row = parseStepRow(JSON.parse(line));
31
+ if (row !== undefined)
32
+ rows.push(row);
33
+ }
34
+ catch {
35
+ // A torn last line is skipped.
36
+ }
37
+ }
38
+ return rows;
39
+ };
40
+ /**
41
+ * Reduces the home folder to `<home>` in whatever form a path takes: either separator, any letter case (a Windows drive letter, a
42
+ * `transcript_path` from hook stdin). An empty home matches nothing, since splitting on '' would garble the whole message.
43
+ */
44
+ const scrubHome = (text) => {
45
+ const home = homedir();
46
+ if (home === '')
47
+ return text;
48
+ const pattern = home.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/(?:\\\\|\/)/g, '[\\\\/]');
49
+ return text.replace(new RegExp(pattern, 'gi'), '<home>');
50
+ };
51
+ /** The first informative line of a failure, masked like every other text the store keeps (D1's text rule) and cut to 300 characters. */
52
+ export const errorLine = async (storeDir, error) => {
53
+ const first = (error instanceof Error ? error.message : String(error)).split('\n').find((line) => line.trim() !== '') ?? 'unknown error';
54
+ // A path under the home folder is reduced to `<home>`, so the shared record never holds the user name (the class A rule in docs/sessions-store.md).
55
+ const scrubbed = scrubHome(first);
56
+ // A store whose state.json cannot be read still gets its failure row (spec 4.4): the line is masked with no domains then.
57
+ const domains = await readRedactDomains(storeDir).catch(() => []);
58
+ return makeRedactor(domains)(scrubbed).slice(0, 300);
59
+ };
60
+ /** Appends the row for a step that ended (or was skipped) now, having taken `ms`. */
61
+ export const logStep = (context, step, status, ms, report, error) => appendStep(context.storeDir, {
62
+ at: new Date(context.now()).toISOString(),
63
+ run: context.run,
64
+ trigger: context.trigger,
65
+ step,
66
+ status,
67
+ ms: Math.round(ms),
68
+ in: report.in,
69
+ out: report.out,
70
+ cv: CAPTURE_VERSION,
71
+ ev: EXTRACTOR_VERSION,
72
+ key: report.key,
73
+ error,
74
+ });
75
+ /** A step an earlier step of the run left undone (spec 4.4): logged `skipped`, with no counts and no key. */
76
+ export const logSkipped = (context, step) => logStep(context, step, 'skipped', 0, { in: {}, out: {}, key: null }, null);
77
+ /** Writes a row without ever failing: a log that cannot be appended (or a failure line that cannot be redacted) must not change a step's outcome or hide its real error. */
78
+ export const tryLog = async (write) => {
79
+ try {
80
+ await write();
81
+ }
82
+ catch {
83
+ // The step's value, or its own error, is what the caller gets.
84
+ }
85
+ };
86
+ /** Logs a failed step: `error`, the failure's first line, and the key the step had from its inputs. */
87
+ const logFailure = (context, step, ms, key, failure) => tryLog(async () => logStep(context, step, 'error', ms, { in: {}, out: {}, key }, await errorLine(context.storeDir, failure)));
88
+ /**
89
+ * Runs `work` and logs its row: `ok` with what it reports, or `error` with the failure's first line and `failedKey` (a key built from
90
+ * the step's inputs before the work, so a retry of the same work has the same key), after which the failure goes on to the caller.
91
+ */
92
+ export const runStep = async (context, step, work, failedKey) => {
93
+ const started = performance.now();
94
+ let done;
95
+ try {
96
+ done = await work();
97
+ }
98
+ catch (error) {
99
+ await logFailure(context, step, performance.now() - started, failedKey, error);
100
+ throw error;
101
+ }
102
+ await tryLog(() => logStep(context, step, 'ok', performance.now() - started, done.report, null));
103
+ return done.value;
104
+ };
105
+ /** The runs of one step and key that failed in a row. Three is the line spec 4.4 draws. */
106
+ export const STUCK_RUNS = 3;
107
+ /**
108
+ * Keys that failed on three runs in a row, per step and trigger (a nightly that fails every night is not cleared by the `stop` deposits
109
+ * between nights). A `skipped` or `deferred` row is no run of its step (a deferred deposit neither failed nor succeeded: it is waiting in the pending list); an `ok` row ends the open streaks of its own step and trigger, and of its step on the same key under another trigger, because a step's
110
+ * good run can carry a key (verify's high-water mark) that its failed run could not know before the work.
111
+ */
112
+ export const stuckSteps = (rows) => {
113
+ const streaks = new Map();
114
+ for (const row of rows) {
115
+ if (row.key === null || row.status === 'skipped' || row.status === 'deferred')
116
+ continue;
117
+ const id = `${row.step}:${row.trigger}:${row.key}`;
118
+ if (row.status === 'ok') {
119
+ // Its own trigger's streaks end whatever key the good run carries; another trigger's streak ends only on the same key, which now has output (spec 4.2).
120
+ for (const open of [...streaks.keys()])
121
+ if (open.startsWith(`${row.step}:${row.trigger}:`) || (open.startsWith(`${row.step}:`) && open.endsWith(`:${row.key}`)))
122
+ streaks.delete(open);
123
+ continue;
124
+ }
125
+ streaks.set(id, { step: row.step, key: row.key, runs: (streaks.get(id)?.runs ?? 0) + 1, error: row.error ?? '' });
126
+ }
127
+ return [...streaks.values()].filter((streak) => streak.runs >= STUCK_RUNS);
128
+ };
129
+ const add = (counts, kind, count) => {
130
+ counts[kind] = (counts[kind] ?? 0) + count;
131
+ };
132
+ export const stepCounters = (rows) => {
133
+ const lines = new Map();
134
+ const deposited = {};
135
+ const failures = {};
136
+ let derived = {};
137
+ let indexed = {};
138
+ let verify = null;
139
+ const recall = { queries: 0, matches: 0 };
140
+ for (const row of rows) {
141
+ const line = lines.get(`${row.step}@${row.ev}`) ?? { step: row.step, ev: row.ev, runs: 0, ok: 0, error: 0, skipped: 0, deferred: 0, ms: 0 };
142
+ line.runs += 1;
143
+ line[row.status] += 1;
144
+ if (row.status !== 'skipped' && row.status !== 'deferred')
145
+ line.ms += row.ms;
146
+ lines.set(`${row.step}@${row.ev}`, line);
147
+ if (row.status === 'error')
148
+ add(failures, `${row.step}@${row.ev}`, 1);
149
+ if (row.status !== 'ok')
150
+ continue;
151
+ if (row.step === 'deposit')
152
+ for (const [kind, count] of Object.entries(row.out))
153
+ add(deposited, kind, count);
154
+ if (row.step === 'derive')
155
+ derived = row.out;
156
+ if (row.step === 'index')
157
+ indexed = row.out;
158
+ if (row.step === 'verify')
159
+ verify = row.out;
160
+ if (row.step === 'recall') {
161
+ recall.queries += 1;
162
+ recall.matches += row.out.matches ?? 0;
163
+ }
164
+ }
165
+ const steps = [...lines.values()].map((line) => ({ step: line.step, ev: line.ev, runs: line.runs, ok: line.ok, error: line.error, skipped: line.skipped, deferred: line.deferred, meanMs: line.runs === line.skipped + line.deferred ? 0 : Math.round(line.ms / (line.runs - line.skipped - line.deferred)) }));
166
+ return { runs: rows.length, steps, deposited, derived, indexed, verify, recall, failures, stuck: stuckSteps(rows) };
167
+ };
168
+ export const formatStepCounters = (counters) => {
169
+ if (counters.runs === 0)
170
+ return 'pipeline step log: no step has run yet';
171
+ const row = (label, value) => `${label.padEnd(40)} ${value.padStart(12)}`;
172
+ const by = (label, counts) => Object.entries(counts).sort(([a], [b]) => a.localeCompare(b)).map(([kind, count]) => row(`${label} ${kind}`, String(count)));
173
+ const verify = counters.verify;
174
+ return [
175
+ row('pipeline step log (all time)', 'value'),
176
+ ...counters.steps.flatMap((line) => [row(`${line.step}@${line.ev} runs (ok/error/skipped)`, `${String(line.ok)}/${String(line.error)}/${String(line.skipped)}`), row(`${line.step}@${line.ev} mean ms`, String(line.meanMs)), ...(line.deferred === 0 ? [] : [row(`${line.step}@${line.ev} deferred (store locked)`, String(line.deferred))])]),
177
+ ...by('records deposited,', counters.deposited),
178
+ ...by('records derived,', counters.derived),
179
+ ...by('index holds,', counters.indexed),
180
+ ...(verify === null ? [] : [row('invariants passed of checked', `${String(verify.passed ?? 0)}/${String(verify.checked ?? 0)}`), row('cv-behind sessions', String(verify.cvBehind ?? 0)), row('sessions failing the raw audit', String(verify.merkle ?? 0))]),
181
+ ...(counters.recall.queries === 0 ? [] : [row('recall queries', String(counters.recall.queries)), row('recall matches', String(counters.recall.matches))]),
182
+ ...by('failed runs,', counters.failures),
183
+ row('stuck steps', String(counters.stuck.length)),
184
+ ...counters.stuck.map((stuck) => ` stuck: ${stuck.step} key ${stuck.key}, ${String(stuck.runs)} runs in a row: ${stuck.error}`),
185
+ ].join('\n');
186
+ };
187
+ /** A key built from its inputs and `ev` (spec 4.2), hashed; a recall has none in the spec (a lookup writes nothing), so its query is the input. */
188
+ export const stampKey = (...parts) => digest([EXTRACTOR_VERSION, ...parts]);
189
+ /**
190
+ * A deposit's key from its inputs (spec 4.2 names the files' state, which is not known before the work): `cv`, `ev`, the trigger, the allow
191
+ * entries or transcript asked for, and the redact domains. The `stop` hook passes no domains (it reads none), so its key has none.
192
+ */
193
+ export const inputKey = (trigger, requested, domains) => digest([CAPTURE_VERSION, EXTRACTOR_VERSION, trigger, ...[...requested].sort(), '--', ...[...domains].sort()]);
194
+ /**
195
+ * Runs a deposit and logs its rows: `deposit` with the records it wrote by kind, then, when it ended with the whole-store pass,
196
+ * `derive` and `index` (spec 4.1: one row per step at a nightly or manual run). `key` is built from the deposit's inputs (the trigger and the transcripts
197
+ * it was asked to deposit), so the failed row and the ok row of one deposit carry the same key and a failure that recovers is not stuck.
198
+ * A deposit that fails logs `error` and, when a derive was to follow, `skipped` for it and for the index (spec 4.4). A derive that fails
199
+ * after the deposit wrote its records logs `deposit` ok, `derive` error, and `index` skipped; an index that fails logs `derive` ok and then
200
+ * `index` error; the failure goes on to the caller. The SessionEnd hook runs
201
+ * `withDerive` false: derive follows at the nightly run.
202
+ */
203
+ export const runDeposit = async (context, withDerive, key, work) => {
204
+ const trace = newTrace();
205
+ const started = performance.now();
206
+ let done;
207
+ try {
208
+ done = await work(trace);
209
+ }
210
+ catch (error) {
211
+ // A usage error is the operator's mistake, not a failed run of the step: no row, so repeating it is never "stuck".
212
+ if (error instanceof CliError && error.exitCode === ExitCode.USAGE)
213
+ throw error;
214
+ await logFailure(context, 'deposit', performance.now() - started, key, error);
215
+ if (withDerive) {
216
+ await tryLog(() => logSkipped(context, 'derive'));
217
+ await tryLog(() => logSkipped(context, 'index'));
218
+ }
219
+ throw error;
220
+ }
221
+ // The pending sessions a deposit took first are inputs it was asked for beside its own transcripts.
222
+ const counted = { ...done.in, ...trace.pending };
223
+ // A Stop deposit that found the store locked wrote nothing and went to the pending list: its own outcome, with the refusal's line.
224
+ const refusal = trace.deferred;
225
+ if (refusal !== undefined) {
226
+ await tryLog(() => logStep(context, 'deposit', 'deferred', performance.now() - started, { in: counted, out: trace.kinds, key }, refusal));
227
+ return done.value;
228
+ }
229
+ const derive = trace.derive;
230
+ const failure = trace.deriveFailure;
231
+ const index = trace.index;
232
+ const indexFailure = trace.indexFailure;
233
+ // The deposit row keeps its own time: the derive and index passes ran inside its work and have rows of their own.
234
+ const passes = (derive?.ms ?? failure?.ms ?? 0) + (index?.ms ?? indexFailure?.ms ?? 0);
235
+ await tryLog(() => logStep(context, 'deposit', 'ok', performance.now() - started - passes, { in: counted, out: trace.kinds, key }, null));
236
+ if (derive !== undefined)
237
+ await tryLog(() => logStep(context, 'derive', 'ok', derive.ms, { in: derive.in, out: derive.out, key: derive.key }, null));
238
+ if (failure !== undefined) {
239
+ await logFailure(context, 'derive', failure.ms, failure.key, failure.error);
240
+ await tryLog(() => logSkipped(context, 'index'));
241
+ throw failure.error;
242
+ }
243
+ if (index !== undefined)
244
+ await tryLog(() => logStep(context, 'index', 'ok', index.ms, { in: index.in, out: index.out, key: index.key }, null));
245
+ if (indexFailure !== undefined) {
246
+ await logFailure(context, 'index', indexFailure.ms, indexFailure.key, indexFailure.error);
247
+ throw indexFailure.error;
248
+ }
249
+ return done.value;
250
+ };