@abelxiaoxing/cadence 1.8.4 → 2.0.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 (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +94 -21
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -0,0 +1,2245 @@
1
+ import { decodeNavigation } from "../../worker-public.mjs";
2
+ import { type ActiveRuns, decodeActiveRuns } from "./active.ts";
3
+ import { decodeServiceBuild, type ServiceBuild } from "./build.ts";
4
+ import {
5
+ type CallDeclarationLink,
6
+ type CaptureEvent,
7
+ type CheckNativeSettlement,
8
+ type ContentOwner,
9
+ callLink,
10
+ type DetailDescriptor,
11
+ decodeCaptureEvent,
12
+ decodeCheckNativeSettlement,
13
+ decodeDetailDescriptor,
14
+ decodeOmittedBlock,
15
+ decodeRecordingEvent,
16
+ decodeRecordingLink,
17
+ decodeRunSettlement,
18
+ decodeSourceIdentity,
19
+ type OmittedBlock,
20
+ type RecordingEvent,
21
+ type RecordingLink,
22
+ type RunSettlement,
23
+ type SourceIdentity,
24
+ settlementOutcome,
25
+ } from "./capture.ts";
26
+ import {
27
+ assignmentId,
28
+ bool,
29
+ canonicalJson,
30
+ closed,
31
+ compareSequence,
32
+ decodeHash,
33
+ decodeId,
34
+ decodePublicError,
35
+ decodeSequence,
36
+ EXPORT_SCHEMA,
37
+ integer,
38
+ invalid,
39
+ LIMITS,
40
+ list,
41
+ literal,
42
+ nullable,
43
+ oneOf,
44
+ type PublicError,
45
+ SERVICE_PROTOCOL,
46
+ text,
47
+ utf8Bytes,
48
+ WORKER_PROTOCOL,
49
+ } from "./core.ts";
50
+ import {
51
+ type ByteRange,
52
+ type CaptureCoverage,
53
+ type CheckSelection,
54
+ type CommandIdentity,
55
+ type ContentRef,
56
+ type ContextVersion,
57
+ checkSelection,
58
+ type DependencyHandoff,
59
+ decodeCommandIdentity,
60
+ decodeContentRef,
61
+ decodeContext,
62
+ decodeCoverage,
63
+ decodeCursor,
64
+ decodeGraph,
65
+ decodeHandoff,
66
+ decodeModelSelection,
67
+ decodeScope,
68
+ type ExecutionOutcome,
69
+ type Graph,
70
+ type ModelSelection,
71
+ outcomes,
72
+ type Publication,
73
+ type ReadCursor,
74
+ range,
75
+ type Scope,
76
+ tag,
77
+ } from "./domain.ts";
78
+ export interface Receipt {
79
+ commandId: string;
80
+ operationId: string;
81
+ runId: string | null;
82
+ admission: "accepted" | "refused" | "uncertain";
83
+ committedSeq: string;
84
+ execution: ExecutionOutcome | null;
85
+ settlement: RunSettlement | null;
86
+ created: {
87
+ kind: "task";
88
+ taskId: string;
89
+ state: "reserved" | "published";
90
+ } | null;
91
+ publication: Publication;
92
+ }
93
+ export function decodeReceipt(v: unknown): Receipt {
94
+ canonicalJson(v);
95
+ const o = closed(v, [
96
+ "commandId",
97
+ "operationId",
98
+ "runId",
99
+ "admission",
100
+ "committedSeq",
101
+ "execution",
102
+ "settlement",
103
+ "created",
104
+ "publication",
105
+ ]);
106
+ const r: Receipt = {
107
+ commandId: decodeId(o.commandId),
108
+ operationId: decodeId(o.operationId),
109
+ runId: nullable(o.runId, decodeId),
110
+ admission: oneOf(o.admission, ["accepted", "refused", "uncertain"]),
111
+ committedSeq: decodeSequence(o.committedSeq),
112
+ execution: nullable(o.execution, (v) => oneOf(v, outcomes)),
113
+ settlement: nullable(o.settlement, decodeRunSettlement),
114
+ created: nullable(o.created, (v) => {
115
+ const c = closed(v, ["kind", "taskId", "state"]);
116
+ return {
117
+ kind: literal(c.kind, "task"),
118
+ taskId: decodeId(c.taskId),
119
+ state: oneOf(c.state, ["reserved", "published"]),
120
+ };
121
+ }),
122
+ publication: oneOf(o.publication, [
123
+ "pending",
124
+ "published",
125
+ "conflict",
126
+ "uncertain",
127
+ ]),
128
+ };
129
+ if (
130
+ r.committedSeq === "0" ||
131
+ r.settlement?.kind === "sdk" ||
132
+ (r.execution === "completed" && !r.settlement) ||
133
+ (r.execution !== null &&
134
+ r.settlement !== null &&
135
+ settlementOutcome(r.settlement) !== r.execution) ||
136
+ (r.created?.state === "published" && r.publication !== "published")
137
+ )
138
+ invalid();
139
+ return r;
140
+ }
141
+ export interface StoredCommand {
142
+ identity: CommandIdentity;
143
+ receipt: Receipt;
144
+ }
145
+ export function decodeStoredCommand(v: unknown): StoredCommand {
146
+ canonicalJson(v);
147
+ const o = closed(v, ["identity", "receipt"]);
148
+ const identity = decodeCommandIdentity(o.identity),
149
+ receipt = decodeReceipt(o.receipt);
150
+ if (identity.kind === "cancel" && receipt.runId !== identity.runId) invalid();
151
+ const create =
152
+ identity.kind === "action" && identity.action.kind === "create";
153
+ if (
154
+ (!create && receipt.created !== null) ||
155
+ (create && receipt.admission === "accepted" && receipt.created === null)
156
+ )
157
+ invalid();
158
+ if (identity.kind === "action") {
159
+ const episode = ["start", "generate-plan", "check"].includes(
160
+ identity.action.kind,
161
+ );
162
+ if (
163
+ (receipt.admission === "accepted" &&
164
+ episode !== (receipt.runId !== null)) ||
165
+ (!episode &&
166
+ (receipt.execution !== null || receipt.settlement !== null)) ||
167
+ (receipt.settlement !== null &&
168
+ receipt.settlement.kind !==
169
+ (identity.action.kind === "check" ? "check" : "parent"))
170
+ )
171
+ invalid();
172
+ }
173
+ return { identity, receipt };
174
+ }
175
+ export type Reply<T> =
176
+ | { kind: "ok"; value: T }
177
+ | { kind: "error"; error: PublicError };
178
+ export function decodeReply<T>(v: unknown, read: (v: unknown) => T): Reply<T> {
179
+ if (tag(v) === "ok") {
180
+ const o = closed(v, ["kind", "value"]);
181
+ return { kind: "ok", value: read(o.value) };
182
+ }
183
+ const o = closed(v, ["kind", "error"]);
184
+ literal(o.kind, "error");
185
+ return { kind: "error", error: decodePublicError(o.error) };
186
+ }
187
+ export type ReceiptLookup =
188
+ | { status: "found"; receipt: Receipt }
189
+ | { status: "absent"; committedSeq: string }
190
+ | { status: "inconclusive"; error: PublicError };
191
+ export function decodeReceiptLookup(v: unknown): ReceiptLookup {
192
+ canonicalJson(v);
193
+ switch (tag(v, "status")) {
194
+ case "found": {
195
+ const o = closed(v, ["status", "receipt"]);
196
+ return { status: "found", receipt: decodeReceipt(o.receipt) };
197
+ }
198
+ case "absent": {
199
+ const o = closed(v, ["status", "committedSeq"]);
200
+ return { status: "absent", committedSeq: decodeSequence(o.committedSeq) };
201
+ }
202
+ case "inconclusive": {
203
+ const o = closed(v, ["status", "error"]);
204
+ return { status: "inconclusive", error: decodePublicError(o.error) };
205
+ }
206
+ default:
207
+ return invalid();
208
+ }
209
+ }
210
+
211
+ export interface RunProjection {
212
+ runId: string;
213
+ taskId: string;
214
+ childRunId: string | null;
215
+ assignmentId: string | null;
216
+ admission: RunIdentity;
217
+ lifecycle: "queued" | "preparing" | "running" | "closing" | "settled";
218
+ outcome: ExecutionOutcome | null;
219
+ settlement: RunSettlement | null;
220
+ reason:
221
+ | "capacity"
222
+ | "dependency"
223
+ | "writer"
224
+ | "parent-stopped"
225
+ | "wait-expired"
226
+ | "handoff-overflow"
227
+ | "stale-dependency"
228
+ | "quarantine"
229
+ | null;
230
+ publication: Publication;
231
+ coverage: CaptureCoverage;
232
+ result: ContentRef | null;
233
+ }
234
+ export function decodeRunProjection(v: unknown): RunProjection {
235
+ const o = closed(v, [
236
+ "runId",
237
+ "taskId",
238
+ "childRunId",
239
+ "assignmentId",
240
+ "admission",
241
+ "lifecycle",
242
+ "outcome",
243
+ "settlement",
244
+ "reason",
245
+ "publication",
246
+ "coverage",
247
+ "result",
248
+ ]);
249
+ const r: RunProjection = {
250
+ runId: decodeId(o.runId),
251
+ taskId: decodeId(o.taskId),
252
+ childRunId: nullable(o.childRunId, decodeId),
253
+ assignmentId: nullable(o.assignmentId, assignmentId),
254
+ admission: decodeRunIdentity(o.admission),
255
+ lifecycle: oneOf(o.lifecycle, [
256
+ "queued",
257
+ "preparing",
258
+ "running",
259
+ "closing",
260
+ "settled",
261
+ ]),
262
+ outcome: nullable(o.outcome, (v) => oneOf(v, outcomes)),
263
+ settlement: nullable(o.settlement, decodeRunSettlement),
264
+ reason: nullable(o.reason, (v) =>
265
+ oneOf(v, [
266
+ "capacity",
267
+ "dependency",
268
+ "writer",
269
+ "parent-stopped",
270
+ "wait-expired",
271
+ "handoff-overflow",
272
+ "stale-dependency",
273
+ "quarantine",
274
+ ]),
275
+ ),
276
+ publication: oneOf(o.publication, [
277
+ "pending",
278
+ "published",
279
+ "conflict",
280
+ "uncertain",
281
+ ]),
282
+ coverage: decodeCoverage(o.coverage),
283
+ result: nullable(o.result, decodeContentRef),
284
+ };
285
+ if (
286
+ r.admission.context.task?.id !== r.taskId ||
287
+ (r.childRunId !== null &&
288
+ !r.admission.assignments.some(
289
+ (a) =>
290
+ a.childRunId === r.childRunId && a.assignmentId === r.assignmentId,
291
+ )) ||
292
+ (r.settlement?.kind === "parent" &&
293
+ canonicalJson(
294
+ r.settlement.required.map(({ assignmentId, childRunId }) => ({
295
+ assignmentId,
296
+ childRunId,
297
+ })),
298
+ ) !== canonicalJson(r.admission.assignments)) ||
299
+ (r.settlement?.kind === "sdk" &&
300
+ r.settlement.recording.status === "committed" &&
301
+ (r.settlement.recording.detail.childRunId !== r.childRunId ||
302
+ (r.result !== null &&
303
+ canonicalJson(r.result) !==
304
+ canonicalJson(r.settlement.recording.detail.content)))) ||
305
+ (r.settlement !== null &&
306
+ ((r.childRunId !== null && r.settlement.kind !== "sdk") ||
307
+ (r.childRunId === null &&
308
+ r.settlement.kind !==
309
+ (r.admission.kind === "check" ? "check" : "parent")) ||
310
+ (r.outcome !== null &&
311
+ settlementOutcome(r.settlement) !== r.outcome))) ||
312
+ (r.childRunId === null) !== (r.assignmentId === null) ||
313
+ (r.lifecycle === "settled") !== (r.outcome !== null) ||
314
+ (r.outcome === "completed" &&
315
+ (!r.settlement ||
316
+ settlementOutcome(r.settlement) !== "completed" ||
317
+ r.coverage.observed !== "complete"))
318
+ )
319
+ invalid();
320
+ return r;
321
+ }
322
+ function runInScope(run: RunProjection, scope: Scope): boolean {
323
+ return scope.kind === "task"
324
+ ? run.taskId === scope.id
325
+ : scope.kind === "run"
326
+ ? run.runId === scope.id
327
+ : run.childRunId === scope.id;
328
+ }
329
+ function runInView(
330
+ run: RunProjection,
331
+ scope: Scope,
332
+ context: ControlContext,
333
+ ): boolean {
334
+ return (
335
+ runInScope(run, scope) &&
336
+ run.taskId === controlTask(context) &&
337
+ run.admission.context.workspaceId === controlWorkspace(context)
338
+ );
339
+ }
340
+ /** Exact bounded live horizon: all active parents/children (<=8 episodes x17),
341
+ * then newest fully settled episode groups that fit BOTH 200 rows and 512KiB.
342
+ * Never evict an active row to show an older one. The historicalRuns first-page
343
+ * request exposes all retained runs at this watermark, independent of horizon.
344
+ * Metadata-only updates use runs=[]; all affected sibling rows share ONE update.
345
+ * If a whole update cannot fit, producer requires resync, never split same-seq rows.
346
+ */
347
+ export interface Snapshot {
348
+ protocol: 1;
349
+ storeId: string;
350
+ epoch: string;
351
+ scope: Scope;
352
+ committedSeq: string;
353
+ context: ControlContext;
354
+ runs: RunProjection[];
355
+ horizon: { policy: "active-and-recent"; historicalRuns: ListRequest };
356
+ }
357
+ export function decodeSnapshot(v: unknown): Snapshot {
358
+ canonicalJson(v, LIMITS.pageBytes);
359
+ const o = closed(v, [
360
+ "protocol",
361
+ "storeId",
362
+ "epoch",
363
+ "scope",
364
+ "committedSeq",
365
+ "context",
366
+ "runs",
367
+ "horizon",
368
+ ]);
369
+ const r: Snapshot = {
370
+ protocol: literal(o.protocol, SERVICE_PROTOCOL),
371
+ storeId: decodeId(o.storeId),
372
+ epoch: decodeId(o.epoch),
373
+ scope: decodeScope(o.scope),
374
+ committedSeq: decodeSequence(o.committedSeq),
375
+ context: decodeControlContext(o.context),
376
+ runs: list(o.runs, LIMITS.maxPageEntries, decodeRunProjection),
377
+ horizon: decodeHorizon(o.horizon),
378
+ };
379
+ if (
380
+ !contextFits(r.context, r.epoch, r.scope) ||
381
+ canonicalJson(r.horizon.historicalRuns.scope) !== canonicalJson(r.scope) ||
382
+ r.horizon.historicalRuns.snapshotSeq !== r.committedSeq ||
383
+ r.horizon.historicalRuns.storeId !== r.storeId ||
384
+ r.runs.some((run) => !runInView(run, r.scope, r.context)) ||
385
+ new Set(r.runs.map((run) => run.childRunId ?? run.runId)).size !==
386
+ r.runs.length
387
+ )
388
+ invalid();
389
+ return r;
390
+ }
391
+ export type LiveEvent =
392
+ | {
393
+ kind: "update";
394
+ storeId: string;
395
+ epoch: string;
396
+ scope: Scope;
397
+ previousSeq: string;
398
+ committedSeq: string;
399
+ runs: RunProjection[];
400
+ removed: string[];
401
+ context: ControlContext;
402
+ }
403
+ | {
404
+ kind: "heartbeat";
405
+ storeId: string;
406
+ epoch: string;
407
+ scope: Scope;
408
+ committedSeq: string;
409
+ };
410
+ export function decodeLiveEvent(v: unknown): LiveEvent {
411
+ canonicalJson(v, LIMITS.pageBytes);
412
+ const kind = tag(v);
413
+ const o = closed(
414
+ v,
415
+ kind === "update"
416
+ ? [
417
+ "kind",
418
+ "storeId",
419
+ "epoch",
420
+ "scope",
421
+ "previousSeq",
422
+ "committedSeq",
423
+ "runs",
424
+ "removed",
425
+ "context",
426
+ ]
427
+ : ["kind", "storeId", "epoch", "scope", "committedSeq"],
428
+ );
429
+ const base = {
430
+ storeId: decodeId(o.storeId),
431
+ epoch: decodeId(o.epoch),
432
+ scope: decodeScope(o.scope),
433
+ committedSeq: decodeSequence(o.committedSeq),
434
+ };
435
+ if (kind === "update") {
436
+ const previousSeq = decodeSequence(o.previousSeq);
437
+ if (compareSequence(base.committedSeq, previousSeq) <= 0) invalid();
438
+ const context = decodeControlContext(o.context);
439
+ if (!contextFits(context, base.epoch, base.scope)) invalid();
440
+ const runs = list(o.runs, LIMITS.maxPageEntries, decodeRunProjection);
441
+ const removed = list(o.removed, LIMITS.maxPageEntries, decodeId);
442
+ const keys = runs.map((r) => r.childRunId ?? r.runId);
443
+ if (
444
+ runs.some((r) => !runInView(r, base.scope, context)) ||
445
+ new Set(keys).size !== keys.length ||
446
+ new Set(removed).size !== removed.length ||
447
+ removed.some((id) => keys.includes(id))
448
+ )
449
+ invalid();
450
+ return { kind, ...base, previousSeq, runs, removed, context };
451
+ }
452
+ literal(kind, "heartbeat");
453
+ return { kind: "heartbeat", ...base };
454
+ }
455
+ /** Strip only the opaque navigation field, preserving every exact read selector. */
456
+ export function readIdentity<T extends { navigation?: string }>(
457
+ value: T,
458
+ ): Omit<T, "navigation"> {
459
+ const { navigation: _navigation, ...identity } = value;
460
+ return identity;
461
+ }
462
+ export interface DetailProgress {
463
+ kind: "progress";
464
+ cursor: ReadCursor;
465
+ next: ReadCursor;
466
+ coverage: CaptureCoverage;
467
+ }
468
+ export interface RequestProgress<T> {
469
+ kind: "progress";
470
+ request: T;
471
+ next: T;
472
+ coverage: CaptureCoverage;
473
+ }
474
+ function progressPair<T extends { navigation?: string }>(
475
+ request: T,
476
+ next: T,
477
+ ): void {
478
+ // Structural correlation only. The private history owner must authenticate
479
+ // semantic phase/address advancement; nonce inequality does NOT prove work.
480
+ if (
481
+ !next.navigation ||
482
+ next.navigation === request.navigation ||
483
+ canonicalJson(readIdentity(request)) !== canonicalJson(readIdentity(next))
484
+ )
485
+ invalid();
486
+ }
487
+ export type DetailReadResult = DetailPage | DetailProgress;
488
+ export function decodeDetailReadResult(v: unknown): DetailReadResult {
489
+ if (Object.getOwnPropertyDescriptor(v ?? {}, "kind")?.value !== "progress")
490
+ return decodeDetailPage(v);
491
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
492
+ const o = closed(v, ["kind", "cursor", "next", "coverage"]),
493
+ cursor = decodeCursor(o.cursor),
494
+ next = decodeCursor(o.next);
495
+ progressPair(cursor, next);
496
+ return {
497
+ kind: "progress",
498
+ cursor,
499
+ next,
500
+ coverage: decodeCoverage(o.coverage),
501
+ };
502
+ }
503
+ export interface DetailPage {
504
+ cursor: ReadCursor;
505
+ content: ContentRef;
506
+ range: ByteRange | null;
507
+ text: string;
508
+ next: ReadCursor | null;
509
+ coverage: CaptureCoverage;
510
+ }
511
+ export function decodeDetailPage(v: unknown): DetailPage {
512
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
513
+ const o = closed(v, [
514
+ "cursor",
515
+ "content",
516
+ "range",
517
+ "text",
518
+ "next",
519
+ "coverage",
520
+ ]);
521
+ const r: DetailPage = {
522
+ cursor: decodeCursor(o.cursor),
523
+ content: decodeContentRef(o.content),
524
+ range: nullable(o.range, range),
525
+ text: text(o.text, LIMITS.detailPageBytes),
526
+ next: nullable(o.next, decodeCursor),
527
+ coverage: decodeCoverage(o.coverage),
528
+ };
529
+ if (
530
+ r.cursor.recordId !== r.content.recordId ||
531
+ r.cursor.recordVersion !== r.content.version ||
532
+ r.cursor.field !== r.content.field ||
533
+ (r.range
534
+ ? r.range.start !== r.cursor.offset ||
535
+ r.range.end > r.content.bytes ||
536
+ r.range.end - r.range.start !== utf8Bytes(r.text)
537
+ : r.content.bytes !== 0 || r.cursor.offset !== 0 || r.text !== "")
538
+ )
539
+ invalid();
540
+ if (
541
+ r.coverage.observed === "complete" &&
542
+ (r.next !== null) !== (r.range?.end ?? 0) < r.content.bytes
543
+ )
544
+ invalid();
545
+ if (
546
+ r.next &&
547
+ (r.next.offset !== r.range?.end ||
548
+ canonicalJson(readIdentity({ ...r.next, offset: r.cursor.offset })) !==
549
+ canonicalJson(readIdentity(r.cursor)))
550
+ )
551
+ invalid();
552
+ return r;
553
+ }
554
+ export interface SearchHit {
555
+ owner: RecordOwner;
556
+ content: ContentRef;
557
+ range: ByteRange;
558
+ preview: string;
559
+ cursor: ReadCursor;
560
+ }
561
+ export interface SearchPage {
562
+ cursor: ReadCursor;
563
+ hits: SearchHit[];
564
+ next: ReadCursor | null;
565
+ coverage: CaptureCoverage;
566
+ }
567
+ export function decodeSearchPage(v: unknown): SearchPage {
568
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
569
+ const o = closed(v, ["cursor", "hits", "next", "coverage"]);
570
+ const cursor = decodeCursor(o.cursor);
571
+ if (!cursor.query) invalid();
572
+ const queryBytes = utf8Bytes(cursor.query);
573
+ const hits = list(o.hits, LIMITS.maxPageEntries, (v) => {
574
+ const h = closed(v, ["content", "range", "preview", "cursor", "owner"]);
575
+ const content = decodeContentRef(h.content);
576
+ const owner = decodeRecordOwner(h.owner);
577
+ if (!ownerInScope(owner, cursor.scope)) invalid();
578
+ const selected = range(h.range);
579
+ const at = decodeCursor(h.cursor);
580
+ if (
581
+ selected.end > content.bytes ||
582
+ selected.end - selected.start !== queryBytes ||
583
+ at.recordId !== content.recordId ||
584
+ at.recordVersion !== content.version ||
585
+ at.field !== content.field ||
586
+ at.offset !== selected.start ||
587
+ at.storeId !== cursor.storeId ||
588
+ canonicalJson(at.scope) !== canonicalJson(cursor.scope) ||
589
+ at.snapshotSeq !== cursor.snapshotSeq ||
590
+ at.query !== cursor.query ||
591
+ at.navigation !== undefined
592
+ )
593
+ invalid();
594
+ return {
595
+ content,
596
+ owner,
597
+ range: selected,
598
+ preview: text(h.preview, LIMITS.previewBytes),
599
+ cursor: at,
600
+ };
601
+ });
602
+ const next = nullable(o.next, decodeCursor);
603
+ if (
604
+ next &&
605
+ (next.storeId !== cursor.storeId ||
606
+ canonicalJson(next.scope) !== canonicalJson(cursor.scope) ||
607
+ next.query !== cursor.query ||
608
+ next.snapshotSeq !== cursor.snapshotSeq ||
609
+ (next.recordId === cursor.recordId &&
610
+ next.recordVersion === cursor.recordVersion &&
611
+ next.field === cursor.field &&
612
+ (next.offset < cursor.offset ||
613
+ (next.offset === cursor.offset &&
614
+ (!next.navigation || next.navigation === cursor.navigation)))))
615
+ )
616
+ invalid();
617
+ return { cursor, hits, next, coverage: decodeCoverage(o.coverage) };
618
+ }
619
+
620
+ /** Historical identity is immutable; it does not confer current control authority. */
621
+ export interface RunIdentity {
622
+ operationId: string;
623
+ commandId: string;
624
+ kind: "start" | "generate-plan" | "check";
625
+ context: ContextVersion;
626
+ assignments: { assignmentId: string; childRunId: string }[];
627
+ graph: { recordId: string; revision: number; sha256: string } | null;
628
+ }
629
+ export function decodeRunIdentity(v: unknown): RunIdentity {
630
+ const o = closed(v, [
631
+ "operationId",
632
+ "commandId",
633
+ "kind",
634
+ "context",
635
+ "assignments",
636
+ "graph",
637
+ ]);
638
+ const context = decodeContext(o.context);
639
+ const graph = nullable(o.graph, (v) => {
640
+ const g = closed(v, ["recordId", "revision", "sha256"]);
641
+ return {
642
+ recordId: decodeId(g.recordId),
643
+ revision: integer(g.revision, 1),
644
+ sha256: decodeHash(g.sha256),
645
+ };
646
+ });
647
+ const kind = oneOf(o.kind, ["start", "generate-plan", "check"]);
648
+ const assignments = list(o.assignments, LIMITS.assignments, (v) => {
649
+ const a = closed(v, ["assignmentId", "childRunId"]);
650
+ return {
651
+ assignmentId: assignmentId(a.assignmentId),
652
+ childRunId: decodeId(a.childRunId),
653
+ };
654
+ });
655
+ if (
656
+ (kind === "check") !== (assignments.length === 0) ||
657
+ new Set(assignments.map((a) => a.assignmentId)).size !==
658
+ assignments.length ||
659
+ new Set(assignments.map((a) => a.childRunId)).size !== assignments.length
660
+ )
661
+ invalid();
662
+ if (
663
+ !context.task ||
664
+ (kind === "start" &&
665
+ (!graph ||
666
+ graph.revision !== context.task.graphRevision ||
667
+ graph.sha256 !== context.task.graphIdentity)) ||
668
+ (kind !== "start" && graph !== null)
669
+ )
670
+ invalid();
671
+ return {
672
+ operationId: decodeId(o.operationId),
673
+ commandId: decodeId(o.commandId),
674
+ kind,
675
+ context,
676
+ assignments,
677
+ graph,
678
+ };
679
+ }
680
+ export type ControlContext =
681
+ | { status: "available"; version: ContextVersion }
682
+ | {
683
+ status: "unavailable";
684
+ workspaceId: string;
685
+ taskId: string | null;
686
+ error: PublicError;
687
+ };
688
+ export function decodeControlContext(v: unknown): ControlContext {
689
+ if (tag(v, "status") === "available") {
690
+ const o = closed(v, ["status", "version"]);
691
+ return { status: "available", version: decodeContext(o.version) };
692
+ }
693
+ const o = closed(v, ["status", "workspaceId", "taskId", "error"]);
694
+ literal(o.status, "unavailable");
695
+ const error = decodePublicError(o.error);
696
+ literal(error.effect, "none");
697
+ return {
698
+ status: "unavailable",
699
+ workspaceId: decodeId(o.workspaceId),
700
+ taskId: nullable(o.taskId, decodeId),
701
+ error,
702
+ };
703
+ }
704
+ export function controlWorkspace(c: ControlContext): string {
705
+ return c.status === "available" ? c.version.workspaceId : c.workspaceId;
706
+ }
707
+ export function controlTask(c: ControlContext): string | null {
708
+ return c.status === "available" ? (c.version.task?.id ?? null) : c.taskId;
709
+ }
710
+ function contextFits(c: ControlContext, epoch: string, scope: Scope): boolean {
711
+ return (
712
+ (c.status !== "available" || c.version.epoch === epoch) &&
713
+ (scope.kind !== "task" || controlTask(c) === scope.id)
714
+ );
715
+ }
716
+ export interface SessionBootstrap {
717
+ build?: ServiceBuild;
718
+ protocol: 1;
719
+ storeId: string;
720
+ epoch: string;
721
+ accessGeneration: number;
722
+ authority: "browser" | "local";
723
+ csrf: string | null;
724
+ capabilities: { actions: boolean; manualChecks: boolean; history: boolean };
725
+ }
726
+ export function decodeSessionBootstrap(v: unknown): SessionBootstrap {
727
+ const o = closed(v, [
728
+ "protocol",
729
+ "storeId",
730
+ "epoch",
731
+ "accessGeneration",
732
+ "authority",
733
+ "csrf",
734
+ "capabilities",
735
+ ...(Object.getOwnPropertyDescriptor(v ?? {}, "build") ? ["build"] : []),
736
+ ]);
737
+ const c = closed(o.capabilities, ["actions", "manualChecks", "history"]);
738
+ const authority = oneOf(o.authority, ["browser", "local"]),
739
+ csrf = nullable(o.csrf, decodeId);
740
+ const capabilities = {
741
+ actions: bool(c.actions),
742
+ manualChecks: bool(c.manualChecks),
743
+ history: bool(c.history),
744
+ };
745
+ if (
746
+ (authority === "browser" && (csrf === null || capabilities.manualChecks)) ||
747
+ (authority === "local" && csrf !== null)
748
+ )
749
+ invalid();
750
+ return {
751
+ ...(o.build === undefined ? {} : { build: decodeServiceBuild(o.build) }),
752
+ protocol: literal(o.protocol, SERVICE_PROTOCOL),
753
+ storeId: decodeId(o.storeId),
754
+ epoch: decodeId(o.epoch),
755
+ accessGeneration: integer(o.accessGeneration, 1),
756
+ authority,
757
+ csrf,
758
+ capabilities,
759
+ };
760
+ }
761
+ export interface WorkspaceBootstrap {
762
+ /** Absent on old/uncomposed readers: never evidence of an empty active roster. */
763
+ activeRuns?: ActiveRuns;
764
+ workspaceId: string;
765
+ storeId: string;
766
+ epoch: string;
767
+ committedSeq: string;
768
+ rootLabel: string;
769
+ context: ControlContext;
770
+ models: ModelSelection[];
771
+ }
772
+ export function decodeWorkspaceBootstrap(v: unknown): WorkspaceBootstrap {
773
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
774
+ const o = closed(v, [
775
+ "workspaceId",
776
+ "storeId",
777
+ "epoch",
778
+ "committedSeq",
779
+ "rootLabel",
780
+ "context",
781
+ "models",
782
+ ...(Object.hasOwn(v ?? {}, "activeRuns") ? ["activeRuns"] : []),
783
+ ]);
784
+ const r: WorkspaceBootstrap = {
785
+ ...(o.activeRuns === undefined
786
+ ? {}
787
+ : { activeRuns: decodeActiveRuns(o.activeRuns) }),
788
+ workspaceId: decodeId(o.workspaceId),
789
+ storeId: decodeId(o.storeId),
790
+ epoch: decodeId(o.epoch),
791
+ committedSeq: decodeSequence(o.committedSeq),
792
+ rootLabel: text(o.rootLabel, 4096, true),
793
+ context: decodeControlContext(o.context),
794
+ models: list(o.models, LIMITS.maxPageEntries, decodeModelSelection),
795
+ };
796
+ if (
797
+ r.activeRuns &&
798
+ (r.activeRuns.storeId !== r.storeId ||
799
+ r.activeRuns.workspaceId !== r.workspaceId ||
800
+ r.activeRuns.epoch !== r.epoch)
801
+ )
802
+ invalid();
803
+ if (
804
+ controlWorkspace(r.context) !== r.workspaceId ||
805
+ controlTask(r.context) !== null ||
806
+ (r.context.status === "available" && r.context.version.epoch !== r.epoch)
807
+ )
808
+ invalid();
809
+ return r;
810
+ }
811
+
812
+ export interface RecordOwner {
813
+ taskId: string;
814
+ runId: string | null;
815
+ childRunId: string | null;
816
+ entity: {
817
+ kind:
818
+ | "task"
819
+ | "run"
820
+ | "child"
821
+ | "message"
822
+ | "tool"
823
+ | "check"
824
+ | "changes"
825
+ | "review";
826
+ id: string;
827
+ version: number;
828
+ };
829
+ }
830
+ export function decodeRecordOwner(v: unknown): RecordOwner {
831
+ const o = closed(v, ["taskId", "runId", "childRunId", "entity"]),
832
+ e = closed(o.entity, ["kind", "id", "version"]);
833
+ const r: RecordOwner = {
834
+ taskId: decodeId(o.taskId),
835
+ runId: nullable(o.runId, decodeId),
836
+ childRunId: nullable(o.childRunId, decodeId),
837
+ entity: {
838
+ kind: oneOf(e.kind, [
839
+ "task",
840
+ "run",
841
+ "child",
842
+ "message",
843
+ "tool",
844
+ "check",
845
+ "changes",
846
+ "review",
847
+ ]),
848
+ id: decodeId(e.id),
849
+ version: integer(e.version, 1),
850
+ },
851
+ };
852
+ if (
853
+ (r.childRunId !== null && r.runId === null) ||
854
+ (r.entity.kind === "task" &&
855
+ (r.entity.id !== r.taskId || r.runId !== null)) ||
856
+ (r.entity.kind === "run" &&
857
+ (r.entity.id !== r.runId || r.childRunId !== null)) ||
858
+ (r.entity.kind === "child" && r.entity.id !== r.childRunId) ||
859
+ (["message", "tool"].includes(r.entity.kind) && r.childRunId === null) ||
860
+ (["check", "changes", "review"].includes(r.entity.kind) &&
861
+ r.childRunId !== null)
862
+ )
863
+ invalid();
864
+ return r;
865
+ }
866
+ export function ownerInScope(owner: RecordOwner, scope: Scope): boolean {
867
+ return scope.kind === "task"
868
+ ? owner.taskId === scope.id
869
+ : scope.kind === "run"
870
+ ? owner.runId === scope.id
871
+ : owner.childRunId === scope.id;
872
+ }
873
+ export interface ObservationFact {
874
+ identity: string;
875
+ complete: boolean;
876
+ checkoutGeneration: number;
877
+ }
878
+ function observationFact(v: unknown): ObservationFact {
879
+ const o = closed(v, ["identity", "complete", "checkoutGeneration"]);
880
+ return {
881
+ identity: decodeHash(o.identity),
882
+ complete: bool(o.complete),
883
+ checkoutGeneration: integer(o.checkoutGeneration),
884
+ };
885
+ }
886
+ export type AssignmentStateFact = {
887
+ kind: "assignment-state";
888
+ childRunId: string;
889
+ state: "waiting" | "ready" | "preparing" | "closing" | "unstarted";
890
+ reason: RunProjection["reason"];
891
+ };
892
+ export type AdmissionClosedFact = {
893
+ kind: "admission-closed";
894
+ reason:
895
+ | "failed"
896
+ | "canceled"
897
+ | "timeout"
898
+ | "wait-expired"
899
+ | "handoff-overflow"
900
+ | "stale-dependency"
901
+ | "quarantine";
902
+ };
903
+ export type OwnerFact =
904
+ | AssignmentStateFact
905
+ | AdmissionClosedFact
906
+ | { kind: "parent-admitted"; runId: string; identity: RunIdentity }
907
+ | {
908
+ kind: "parent-settled";
909
+ runId: string;
910
+ settlement: Extract<RunSettlement, { kind: "parent" }>;
911
+ }
912
+ | {
913
+ kind: "gate";
914
+ /** Absent only on historical run-wide gate observations. */
915
+ reservationId?: string;
916
+ lane: "reader" | "writer" | "check";
917
+ state: "waiting" | "held" | "released" | "unknown";
918
+ checkoutGeneration: number;
919
+ }
920
+ | {
921
+ kind: "handoff";
922
+ childRunId: string;
923
+ handoff: DependencyHandoff;
924
+ observation: ObservationFact;
925
+ }
926
+ | {
927
+ kind: "changes";
928
+ observationId: string;
929
+ stage: "before" | "after" | "review";
930
+ observation: ObservationFact;
931
+ content: ContentRef;
932
+ }
933
+ | {
934
+ kind: "check-start";
935
+ checkId: string;
936
+ selection: CheckSelection;
937
+ cwdId: string;
938
+ cwdLabel: string;
939
+ checkEnvGeneration: number;
940
+ before: ObservationFact;
941
+ }
942
+ | {
943
+ kind: "check-settled";
944
+ /** Exact one-step advance under this operation's held check reservation. */
945
+ ownAdvance?: {
946
+ operationId: string;
947
+ reservationId: string;
948
+ from: number;
949
+ to: number;
950
+ };
951
+ checkId: string;
952
+ native: CheckNativeSettlement;
953
+ status: "passed" | "failed" | "canceled" | "stale";
954
+ before: ObservationFact;
955
+ after: ObservationFact;
956
+ output: ContentRef | null;
957
+ coverage: CaptureCoverage;
958
+ }
959
+ | {
960
+ kind: "review";
961
+ reviewId: string;
962
+ runId: string;
963
+ checksRevision: number;
964
+ changesIdentity: string;
965
+ acceptIssues: boolean;
966
+ outcome: "reviewed" | "reviewed-with-issues";
967
+ content: ContentRef;
968
+ }
969
+ | {
970
+ kind: "publication-intent";
971
+ publicationId: string;
972
+ expectedRevision: number;
973
+ expectedIdentity: string | null;
974
+ nextRevision: number;
975
+ nextIdentity: string;
976
+ }
977
+ | {
978
+ kind: "publication-settled";
979
+ publicationId: string;
980
+ status: "published" | "conflict" | "uncertain";
981
+ observed: { revision: number; identity: string } | null;
982
+ };
983
+ export function decodeOwnerFact(v: unknown): OwnerFact {
984
+ canonicalJson(v, LIMITS.controlBytes);
985
+ const kind = tag(v);
986
+ switch (kind) {
987
+ case "assignment-state": {
988
+ const o = closed(v, ["kind", "childRunId", "state", "reason"]);
989
+ const state = oneOf(o.state, [
990
+ "waiting",
991
+ "ready",
992
+ "preparing",
993
+ "closing",
994
+ "unstarted",
995
+ ]);
996
+ const reason = nullable(o.reason, (v) =>
997
+ oneOf(v, [
998
+ "capacity",
999
+ "dependency",
1000
+ "writer",
1001
+ "parent-stopped",
1002
+ "wait-expired",
1003
+ "handoff-overflow",
1004
+ "stale-dependency",
1005
+ "quarantine",
1006
+ ]),
1007
+ );
1008
+ if ((state === "waiting" || state === "unstarted") && reason === null)
1009
+ invalid();
1010
+ if ((state === "ready" || state === "preparing") && reason !== null)
1011
+ invalid();
1012
+ return { kind, childRunId: decodeId(o.childRunId), state, reason };
1013
+ }
1014
+ case "admission-closed": {
1015
+ const o = closed(v, ["kind", "reason"]);
1016
+ return {
1017
+ kind,
1018
+ reason: oneOf(o.reason, [
1019
+ "failed",
1020
+ "canceled",
1021
+ "timeout",
1022
+ "wait-expired",
1023
+ "handoff-overflow",
1024
+ "stale-dependency",
1025
+ "quarantine",
1026
+ ]),
1027
+ };
1028
+ }
1029
+ case "parent-admitted": {
1030
+ const o = closed(v, ["kind", "runId", "identity"]);
1031
+ return {
1032
+ kind,
1033
+ runId: decodeId(o.runId),
1034
+ identity: decodeRunIdentity(o.identity),
1035
+ };
1036
+ }
1037
+ case "parent-settled": {
1038
+ const o = closed(v, ["kind", "runId", "settlement"]),
1039
+ settlement = decodeRunSettlement(o.settlement);
1040
+ if (settlement.kind !== "parent") invalid();
1041
+ return { kind, runId: decodeId(o.runId), settlement };
1042
+ }
1043
+ case "gate": {
1044
+ const hasReservation =
1045
+ typeof v === "object" &&
1046
+ v !== null &&
1047
+ Object.hasOwn(v, "reservationId");
1048
+ const o = closed(v, [
1049
+ "kind",
1050
+ "lane",
1051
+ "state",
1052
+ "checkoutGeneration",
1053
+ ...(hasReservation ? ["reservationId"] : []),
1054
+ ]);
1055
+ return {
1056
+ kind,
1057
+ ...(hasReservation ? { reservationId: decodeId(o.reservationId) } : {}),
1058
+ lane: oneOf(o.lane, ["reader", "writer", "check"]),
1059
+ state: oneOf(o.state, ["waiting", "held", "released", "unknown"]),
1060
+ checkoutGeneration: integer(o.checkoutGeneration),
1061
+ };
1062
+ }
1063
+ case "handoff": {
1064
+ const o = closed(v, ["kind", "childRunId", "handoff", "observation"]);
1065
+ return {
1066
+ kind,
1067
+ childRunId: decodeId(o.childRunId),
1068
+ handoff: decodeHandoff(o.handoff),
1069
+ observation: observationFact(o.observation),
1070
+ };
1071
+ }
1072
+ case "changes": {
1073
+ const o = closed(v, [
1074
+ "kind",
1075
+ "observationId",
1076
+ "stage",
1077
+ "observation",
1078
+ "content",
1079
+ ]),
1080
+ content = decodeContentRef(o.content);
1081
+ literal(content.field, "changes");
1082
+ return {
1083
+ kind,
1084
+ observationId: decodeId(o.observationId),
1085
+ stage: oneOf(o.stage, ["before", "after", "review"]),
1086
+ observation: observationFact(o.observation),
1087
+ content,
1088
+ };
1089
+ }
1090
+ case "check-start": {
1091
+ const o = closed(v, [
1092
+ "kind",
1093
+ "checkId",
1094
+ "selection",
1095
+ "cwdId",
1096
+ "cwdLabel",
1097
+ "checkEnvGeneration",
1098
+ "before",
1099
+ ]);
1100
+ return {
1101
+ kind,
1102
+ checkId: decodeId(o.checkId),
1103
+ selection: checkSelection(o.selection),
1104
+ cwdId: decodeId(o.cwdId),
1105
+ cwdLabel: text(o.cwdLabel, 4096, true),
1106
+ checkEnvGeneration: integer(o.checkEnvGeneration, 1),
1107
+ before: observationFact(o.before),
1108
+ };
1109
+ }
1110
+ case "check-settled": {
1111
+ const hasAdvance =
1112
+ typeof v === "object" && v !== null && Object.hasOwn(v, "ownAdvance");
1113
+ const o = closed(v, [
1114
+ "kind",
1115
+ "checkId",
1116
+ "native",
1117
+ "status",
1118
+ "before",
1119
+ "after",
1120
+ "output",
1121
+ "coverage",
1122
+ ...(hasAdvance ? ["ownAdvance"] : []),
1123
+ ]);
1124
+ let ownAdvance: Extract<
1125
+ OwnerFact,
1126
+ { kind: "check-settled" }
1127
+ >["ownAdvance"];
1128
+ if (hasAdvance) {
1129
+ const a = closed(o.ownAdvance, [
1130
+ "operationId",
1131
+ "reservationId",
1132
+ "from",
1133
+ "to",
1134
+ ]);
1135
+ ownAdvance = {
1136
+ operationId: decodeId(a.operationId),
1137
+ reservationId: decodeId(a.reservationId),
1138
+ from: integer(a.from),
1139
+ to: integer(a.to),
1140
+ };
1141
+ if (
1142
+ ownAdvance.to !== ownAdvance.from + 1 ||
1143
+ ownAdvance.reservationId !== o.checkId
1144
+ )
1145
+ invalid();
1146
+ }
1147
+ const native = decodeCheckNativeSettlement(o.native),
1148
+ status = oneOf(o.status, ["passed", "failed", "canceled", "stale"]),
1149
+ before = observationFact(o.before),
1150
+ after = observationFact(o.after),
1151
+ output = nullable(o.output, decodeContentRef),
1152
+ coverage = decodeCoverage(o.coverage);
1153
+ if (output && output.field !== "check") invalid();
1154
+ if (
1155
+ status === "passed" &&
1156
+ (native.outcome !== "completed" ||
1157
+ canonicalJson({
1158
+ ...before,
1159
+ checkoutGeneration: after.checkoutGeneration,
1160
+ }) !== canonicalJson(after) ||
1161
+ (ownAdvance
1162
+ ? before.checkoutGeneration !== ownAdvance.from ||
1163
+ after.checkoutGeneration !== ownAdvance.to ||
1164
+ output === null
1165
+ : before.checkoutGeneration !== after.checkoutGeneration) ||
1166
+ coverage.observed !== "complete")
1167
+ )
1168
+ invalid();
1169
+ if (
1170
+ status === "canceled" &&
1171
+ !["canceled", "timeout"].includes(native.outcome)
1172
+ )
1173
+ invalid();
1174
+ return {
1175
+ kind,
1176
+ ...(ownAdvance ? { ownAdvance } : {}),
1177
+ checkId: decodeId(o.checkId),
1178
+ native,
1179
+ status,
1180
+ before,
1181
+ after,
1182
+ output,
1183
+ coverage,
1184
+ };
1185
+ }
1186
+ case "review": {
1187
+ const o = closed(v, [
1188
+ "kind",
1189
+ "reviewId",
1190
+ "runId",
1191
+ "checksRevision",
1192
+ "changesIdentity",
1193
+ "acceptIssues",
1194
+ "outcome",
1195
+ "content",
1196
+ ]);
1197
+ const acceptIssues = bool(o.acceptIssues),
1198
+ outcome = oneOf(o.outcome, ["reviewed", "reviewed-with-issues"]),
1199
+ content = decodeContentRef(o.content);
1200
+ literal(content.field, "review");
1201
+ if (outcome === "reviewed-with-issues" && !acceptIssues) invalid();
1202
+ return {
1203
+ kind,
1204
+ reviewId: decodeId(o.reviewId),
1205
+ runId: decodeId(o.runId),
1206
+ checksRevision: integer(o.checksRevision),
1207
+ changesIdentity: decodeHash(o.changesIdentity),
1208
+ acceptIssues,
1209
+ outcome,
1210
+ content,
1211
+ };
1212
+ }
1213
+ case "publication-intent": {
1214
+ const o = closed(v, [
1215
+ "kind",
1216
+ "publicationId",
1217
+ "expectedRevision",
1218
+ "expectedIdentity",
1219
+ "nextRevision",
1220
+ "nextIdentity",
1221
+ ]);
1222
+ const expectedRevision = integer(o.expectedRevision),
1223
+ expectedIdentity = nullable(o.expectedIdentity, decodeHash),
1224
+ nextRevision = integer(o.nextRevision, 1);
1225
+ if (
1226
+ (expectedRevision === 0) !== (expectedIdentity === null) ||
1227
+ nextRevision !== expectedRevision + 1
1228
+ )
1229
+ invalid();
1230
+ return {
1231
+ kind,
1232
+ publicationId: decodeId(o.publicationId),
1233
+ expectedRevision,
1234
+ expectedIdentity,
1235
+ nextRevision,
1236
+ nextIdentity: decodeHash(o.nextIdentity),
1237
+ };
1238
+ }
1239
+ case "publication-settled": {
1240
+ const o = closed(v, ["kind", "publicationId", "status", "observed"]);
1241
+ const status = oneOf(o.status, ["published", "conflict", "uncertain"]),
1242
+ observed = nullable(o.observed, (v) => {
1243
+ const x = closed(v, ["revision", "identity"]);
1244
+ return {
1245
+ revision: integer(x.revision, 1),
1246
+ identity: decodeHash(x.identity),
1247
+ };
1248
+ });
1249
+ if (status === "published" && observed === null) invalid();
1250
+ return {
1251
+ kind,
1252
+ publicationId: decodeId(o.publicationId),
1253
+ status,
1254
+ observed,
1255
+ };
1256
+ }
1257
+ default:
1258
+ return invalid();
1259
+ }
1260
+ }
1261
+ export interface OwnerLink {
1262
+ epoch: string;
1263
+ operationId: string;
1264
+ taskId: string;
1265
+ runId: string | null;
1266
+ }
1267
+ export function decodeOwnerLink(v: unknown): OwnerLink {
1268
+ const o = closed(v, ["epoch", "operationId", "taskId", "runId"]);
1269
+ return {
1270
+ epoch: decodeId(o.epoch),
1271
+ operationId: decodeId(o.operationId),
1272
+ taskId: decodeId(o.taskId),
1273
+ runId: nullable(o.runId, decodeId),
1274
+ };
1275
+ }
1276
+ export type OwnerRecordingEvent =
1277
+ | { kind: "fact"; fact: OwnerFact }
1278
+ | {
1279
+ kind: "content-begin";
1280
+ detailId: string;
1281
+ owner: RecordOwner;
1282
+ field: "plan" | "check" | "changes" | "review";
1283
+ encoding: "utf8";
1284
+ }
1285
+ | { kind: "content-chunk"; detailId: string; text: string }
1286
+ | { kind: "content-end"; detailId: string; coverage: CaptureCoverage };
1287
+ export interface OwnerRecordingInput {
1288
+ observedMs: number;
1289
+ link: OwnerLink;
1290
+ events: OwnerRecordingEvent[];
1291
+ }
1292
+ export function decodeOwnerRecordingInput(v: unknown): OwnerRecordingInput {
1293
+ canonicalJson(v, LIMITS.ownerQueueBytes);
1294
+ const o = closed(v, ["observedMs", "link", "events"]),
1295
+ link = decodeOwnerLink(o.link);
1296
+ const events = list<OwnerRecordingEvent>(
1297
+ o.events,
1298
+ LIMITS.ownerQueueFrames,
1299
+ (v) => {
1300
+ const kind = tag(v);
1301
+ if (kind === "fact") {
1302
+ const e = closed(v, ["kind", "fact"]),
1303
+ fact = decodeOwnerFact(e.fact);
1304
+ if (
1305
+ ("runId" in fact && fact.runId !== link.runId) ||
1306
+ (fact.kind === "parent-admitted" &&
1307
+ (fact.identity.context.task?.id !== link.taskId ||
1308
+ fact.identity.operationId !== link.operationId)) ||
1309
+ ([
1310
+ "gate",
1311
+ "assignment-state",
1312
+ "admission-closed",
1313
+ "handoff",
1314
+ "check-start",
1315
+ "check-settled",
1316
+ ].includes(fact.kind) &&
1317
+ link.runId === null)
1318
+ )
1319
+ invalid();
1320
+ return { kind, fact };
1321
+ }
1322
+ if (kind === "content-begin") {
1323
+ const e = closed(v, ["kind", "detailId", "owner", "field", "encoding"]),
1324
+ owner = decodeRecordOwner(e.owner);
1325
+ const field = oneOf(e.field, ["plan", "check", "changes", "review"]);
1326
+ if (
1327
+ owner.taskId !== link.taskId ||
1328
+ owner.runId !== link.runId ||
1329
+ owner.childRunId !== null ||
1330
+ owner.entity.kind !== (field === "plan" ? "task" : field)
1331
+ )
1332
+ invalid();
1333
+ return {
1334
+ kind,
1335
+ detailId: decodeId(e.detailId),
1336
+ owner,
1337
+ field,
1338
+ encoding: literal(e.encoding, "utf8"),
1339
+ };
1340
+ }
1341
+ if (kind === "content-chunk") {
1342
+ const e = closed(v, ["kind", "detailId", "text"]);
1343
+ return {
1344
+ kind,
1345
+ detailId: decodeId(e.detailId),
1346
+ text: text(e.text, LIMITS.chunkBytes),
1347
+ };
1348
+ }
1349
+ const e = closed(v, ["kind", "detailId", "coverage"]);
1350
+ literal(kind, "content-end");
1351
+ return {
1352
+ kind: "content-end",
1353
+ detailId: decodeId(e.detailId),
1354
+ coverage: decodeCoverage(e.coverage),
1355
+ };
1356
+ },
1357
+ );
1358
+ return { observedMs: integer(o.observedMs), link, events };
1359
+ }
1360
+
1361
+ export const READERS = [
1362
+ "tasks",
1363
+ "runs",
1364
+ "messages",
1365
+ "tools",
1366
+ "contents",
1367
+ "events",
1368
+ "checks",
1369
+ "changes",
1370
+ "reviews",
1371
+ ] as const;
1372
+ export type ReaderKind = (typeof READERS)[number];
1373
+ export type ListScope = Scope | { kind: "workspace"; id: string };
1374
+ export interface ListRequest {
1375
+ navigation?: string;
1376
+ reader: ReaderKind;
1377
+ storeId: string;
1378
+ scope: ListScope;
1379
+ snapshotSeq: string | null;
1380
+ after: { recordId: string; version: number; offset: number } | null;
1381
+ limit: number;
1382
+ }
1383
+ export function decodeListRequest(v: unknown): ListRequest {
1384
+ const o = closed(v, [
1385
+ "reader",
1386
+ "storeId",
1387
+ "scope",
1388
+ "snapshotSeq",
1389
+ "after",
1390
+ "limit",
1391
+ ...(Object.hasOwn(v ?? {}, "navigation") ? ["navigation"] : []),
1392
+ ]);
1393
+ const reader = oneOf(o.reader, READERS),
1394
+ s = closed(o.scope, ["kind", "id"]);
1395
+ const scope: ListScope = {
1396
+ kind: oneOf(s.kind, ["workspace", "task", "run", "child"]),
1397
+ id: decodeId(s.id),
1398
+ };
1399
+ const snapshotSeq = nullable(o.snapshotSeq, decodeSequence);
1400
+ const after = nullable(o.after, (v) => {
1401
+ const a = closed(v, ["recordId", "version", "offset"]);
1402
+ return {
1403
+ recordId: decodeId(a.recordId),
1404
+ version: integer(a.version, 1),
1405
+ offset: integer(a.offset),
1406
+ };
1407
+ });
1408
+ if (
1409
+ (after && snapshotSeq === null) ||
1410
+ (reader === "tasks") !== (scope.kind === "workspace")
1411
+ )
1412
+ invalid();
1413
+ return {
1414
+ reader,
1415
+ ...(Object.hasOwn(o, "navigation")
1416
+ ? { navigation: decodeNavigation(o.navigation) }
1417
+ : {}),
1418
+ storeId: decodeId(o.storeId),
1419
+ scope,
1420
+ snapshotSeq,
1421
+ after,
1422
+ limit: integer(o.limit, 1, LIMITS.maxPageEntries),
1423
+ };
1424
+ }
1425
+ function decodeHorizon(v: unknown): Snapshot["horizon"] {
1426
+ const o = closed(v, ["policy", "historicalRuns"]),
1427
+ historicalRuns = decodeListRequest(o.historicalRuns);
1428
+ if (
1429
+ historicalRuns.reader !== "runs" ||
1430
+ historicalRuns.after !== null ||
1431
+ historicalRuns.snapshotSeq === null
1432
+ )
1433
+ invalid();
1434
+ return { policy: literal(o.policy, "active-and-recent"), historicalRuns };
1435
+ }
1436
+ export type HistoryRecord =
1437
+ | {
1438
+ kind: "task";
1439
+ owner: RecordOwner;
1440
+ version: number;
1441
+ goal: string;
1442
+ plan: ContentRef | null;
1443
+ }
1444
+ | { kind: "run"; projection: RunProjection }
1445
+ | {
1446
+ kind: "message";
1447
+ owner: RecordOwner;
1448
+ version: number;
1449
+ role: "user" | "assistant" | "tool-result";
1450
+ source: SourceIdentity;
1451
+ resultCallId: string | null;
1452
+ state: "partial" | "final";
1453
+ finalBlockCount: number | null;
1454
+ finalText: ContentRef | null;
1455
+ }
1456
+ | {
1457
+ kind: "tool";
1458
+ owner: RecordOwner;
1459
+ version: number;
1460
+ source: SourceIdentity;
1461
+ name: string;
1462
+ declaration: CallDeclarationLink | null;
1463
+ declarationStage: "partial" | "final" | null;
1464
+ startObserved: boolean;
1465
+ result: Extract<CaptureEvent, { kind: "tool-end" }> | null;
1466
+ }
1467
+ | {
1468
+ kind: "content";
1469
+ owner: RecordOwner;
1470
+ version: number;
1471
+ detailId: string;
1472
+ descriptor: DetailDescriptor;
1473
+ content: ContentRef;
1474
+ coverage: CaptureCoverage;
1475
+ }
1476
+ | {
1477
+ kind: "omitted";
1478
+ owner: RecordOwner;
1479
+ version: number;
1480
+ block: OmittedBlock;
1481
+ }
1482
+ | {
1483
+ kind: "capture-fact";
1484
+ recordId: string;
1485
+ committedSeq: string;
1486
+ link: RecordingLink;
1487
+ capture: RecordingEvent;
1488
+ }
1489
+ | {
1490
+ kind: "owner-fact";
1491
+ observedMs: number;
1492
+ recordId: string;
1493
+ committedSeq: string;
1494
+ link: OwnerLink;
1495
+ fact: OwnerFact;
1496
+ };
1497
+ export function decodeHistoryRecord(v: unknown): HistoryRecord {
1498
+ const kind = tag(v);
1499
+ if (kind === "run") {
1500
+ const o = closed(v, ["kind", "projection"]);
1501
+ return { kind, projection: decodeRunProjection(o.projection) };
1502
+ }
1503
+ if (kind === "capture-fact") {
1504
+ const o = closed(v, [
1505
+ "kind",
1506
+ "recordId",
1507
+ "committedSeq",
1508
+ "link",
1509
+ "capture",
1510
+ ]),
1511
+ committedSeq = decodeSequence(o.committedSeq);
1512
+ if (committedSeq === "0") invalid();
1513
+ return {
1514
+ kind,
1515
+ recordId: decodeId(o.recordId),
1516
+ committedSeq,
1517
+ link: decodeRecordingLink(o.link),
1518
+ capture: decodeRecordingEvent(o.capture),
1519
+ };
1520
+ }
1521
+ if (kind === "owner-fact") {
1522
+ const o = closed(v, [
1523
+ "kind",
1524
+ "recordId",
1525
+ "committedSeq",
1526
+ "observedMs",
1527
+ "link",
1528
+ "fact",
1529
+ ]);
1530
+ const input = decodeOwnerRecordingInput({
1531
+ observedMs: o.observedMs,
1532
+ link: o.link,
1533
+ events: [{ kind: "fact", fact: o.fact }],
1534
+ });
1535
+ const event = input.events[0];
1536
+ if (event.kind !== "fact") invalid();
1537
+ const committedSeq = decodeSequence(o.committedSeq);
1538
+ if (committedSeq === "0") invalid();
1539
+ return {
1540
+ kind,
1541
+ recordId: decodeId(o.recordId),
1542
+ committedSeq,
1543
+ link: input.link,
1544
+ observedMs: input.observedMs,
1545
+ fact: event.fact,
1546
+ };
1547
+ }
1548
+ const common = ["kind", "owner", "version"];
1549
+ const extras =
1550
+ kind === "task"
1551
+ ? ["goal", "plan"]
1552
+ : kind === "message"
1553
+ ? [
1554
+ "role",
1555
+ "source",
1556
+ "resultCallId",
1557
+ "state",
1558
+ "finalBlockCount",
1559
+ "finalText",
1560
+ ]
1561
+ : kind === "tool"
1562
+ ? [
1563
+ "source",
1564
+ "name",
1565
+ "declaration",
1566
+ "declarationStage",
1567
+ "startObserved",
1568
+ "result",
1569
+ ]
1570
+ : kind === "content"
1571
+ ? ["detailId", "descriptor", "content", "coverage"]
1572
+ : ["block"];
1573
+ const o = closed(v, [...common, ...extras]),
1574
+ owner = decodeRecordOwner(o.owner),
1575
+ version = integer(o.version, 1);
1576
+ if (
1577
+ ["task", "message", "tool"].includes(kind) &&
1578
+ (owner.entity.kind !== kind || owner.entity.version !== version)
1579
+ )
1580
+ invalid();
1581
+ if (kind === "task") {
1582
+ const plan = nullable(o.plan, decodeContentRef);
1583
+ if (plan && plan.field !== "plan") invalid();
1584
+ return { kind, owner, version, goal: text(o.goal, 8192, true), plan };
1585
+ }
1586
+ if (kind === "message") {
1587
+ const role = oneOf(o.role, ["user", "assistant", "tool-result"]),
1588
+ state = oneOf(o.state, ["partial", "final"]),
1589
+ finalBlockCount = nullable(o.finalBlockCount, (v) =>
1590
+ integer(v, 0, LIMITS.dataFrames),
1591
+ ),
1592
+ finalText = nullable(o.finalText, decodeContentRef),
1593
+ resultCallId = nullable(o.resultCallId, decodeId);
1594
+ if (
1595
+ (state === "final") !== (finalBlockCount !== null) ||
1596
+ (state === "partial" && finalText !== null) ||
1597
+ (finalText && finalText.field !== "final-text") ||
1598
+ (role !== "tool-result" && resultCallId !== null)
1599
+ )
1600
+ invalid();
1601
+ return {
1602
+ kind,
1603
+ owner,
1604
+ version,
1605
+ role,
1606
+ state,
1607
+ finalBlockCount,
1608
+ finalText,
1609
+ resultCallId,
1610
+ source: decodeSourceIdentity(o.source),
1611
+ };
1612
+ }
1613
+ if (kind === "tool") {
1614
+ const declaration = nullable(o.declaration, callLink),
1615
+ declarationStage = nullable(o.declarationStage, (v) =>
1616
+ oneOf(v, ["partial", "final"]),
1617
+ ),
1618
+ startObserved = bool(o.startObserved);
1619
+ const result = nullable(o.result, (v) => {
1620
+ const e = decodeCaptureEvent(v);
1621
+ if (e.kind !== "tool-end") invalid();
1622
+ return e;
1623
+ });
1624
+ if (
1625
+ (declaration === null) !== (declarationStage === null) ||
1626
+ (result &&
1627
+ (result.callId !== owner.entity.id ||
1628
+ result.startObserved !== startObserved ||
1629
+ canonicalJson(result.declaration) !== canonicalJson(declaration)))
1630
+ )
1631
+ invalid();
1632
+ return {
1633
+ kind,
1634
+ owner,
1635
+ version,
1636
+ declaration,
1637
+ declarationStage,
1638
+ startObserved,
1639
+ result,
1640
+ source: decodeSourceIdentity(o.source),
1641
+ name: text(o.name, 128, true),
1642
+ };
1643
+ }
1644
+ if (kind === "content") {
1645
+ const descriptor = decodeDetailDescriptor(o.descriptor),
1646
+ content = decodeContentRef(o.content);
1647
+ if (
1648
+ descriptor.field !== content.field ||
1649
+ !descriptorFitsOwner(descriptor.owner, owner)
1650
+ )
1651
+ invalid();
1652
+ return {
1653
+ kind,
1654
+ owner,
1655
+ version,
1656
+ detailId: decodeId(o.detailId),
1657
+ descriptor,
1658
+ content,
1659
+ coverage: decodeCoverage(o.coverage),
1660
+ };
1661
+ }
1662
+ literal(kind, "omitted");
1663
+ const block = decodeOmittedBlock(o.block);
1664
+ if (!descriptorFitsOwner(block.owner, owner)) invalid();
1665
+ return { kind: "omitted", owner, version, block };
1666
+ }
1667
+ function descriptorFitsOwner(
1668
+ content: ContentOwner,
1669
+ owner: RecordOwner,
1670
+ ): boolean {
1671
+ return content.kind === "message" || content.kind === "message-final"
1672
+ ? owner.entity.kind === "message" && content.messageId === owner.entity.id
1673
+ : content.kind === "tool"
1674
+ ? owner.entity.kind === "tool" && content.callId === owner.entity.id
1675
+ : owner.entity.kind === "child";
1676
+ }
1677
+ function recordFits(record: HistoryRecord, request: ListRequest): boolean {
1678
+ const { scope, reader } = request;
1679
+ const owner =
1680
+ record.kind === "run"
1681
+ ? {
1682
+ taskId: record.projection.taskId,
1683
+ runId: record.projection.runId,
1684
+ childRunId: record.projection.childRunId,
1685
+ }
1686
+ : record.kind === "owner-fact"
1687
+ ? { ...record.link, childRunId: null }
1688
+ : record.kind === "capture-fact"
1689
+ ? record.link
1690
+ : record.owner;
1691
+ if (
1692
+ scope.kind !== "workspace" &&
1693
+ !(scope.kind === "task"
1694
+ ? owner.taskId === scope.id
1695
+ : scope.kind === "run"
1696
+ ? owner.runId === scope.id
1697
+ : owner.childRunId === scope.id)
1698
+ )
1699
+ return false;
1700
+ if (reader === "tasks") return record.kind === "task";
1701
+ if (reader === "runs") return record.kind === "run";
1702
+ if (reader === "messages") return record.kind === "message";
1703
+ if (reader === "tools") return record.kind === "tool";
1704
+ if (reader === "contents")
1705
+ return record.kind === "content" || record.kind === "omitted";
1706
+ if (record.kind === "capture-fact") return reader === "events";
1707
+ if (record.kind !== "owner-fact") return false;
1708
+ return (
1709
+ reader === "events" ||
1710
+ (reader === "checks" &&
1711
+ ["check-start", "check-settled"].includes(record.fact.kind)) ||
1712
+ (reader === "changes" && record.fact.kind === "changes") ||
1713
+ (reader === "reviews" && record.fact.kind === "review")
1714
+ );
1715
+ }
1716
+ export interface HistoryPage {
1717
+ request: ListRequest;
1718
+ records: HistoryRecord[];
1719
+ next: ListRequest | null;
1720
+ coverage: CaptureCoverage;
1721
+ }
1722
+ export function decodeHistoryPage(v: unknown): HistoryPage {
1723
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
1724
+ const o = closed(v, ["request", "records", "next", "coverage"]),
1725
+ request = decodeListRequest(o.request);
1726
+ if (request.snapshotSeq === null) invalid();
1727
+ const records = list(o.records, request.limit, decodeHistoryRecord),
1728
+ next = nullable(o.next, decodeListRequest);
1729
+ if (
1730
+ records.some((r) => !recordFits(r, request)) ||
1731
+ (next &&
1732
+ (next.after === null ||
1733
+ canonicalJson(readIdentity({ ...next, after: request.after })) !==
1734
+ canonicalJson(readIdentity(request)) ||
1735
+ canonicalJson(next.after) === canonicalJson(request.after)))
1736
+ )
1737
+ invalid();
1738
+ return { request, records, next, coverage: decodeCoverage(o.coverage) };
1739
+ }
1740
+ export interface HistorySnapshotTransition {
1741
+ kind: "snapshot";
1742
+ request: ListRequest;
1743
+ next: ListRequest;
1744
+ coverage: CaptureCoverage;
1745
+ }
1746
+ export type HistoryReadResult =
1747
+ | HistoryPage
1748
+ | RequestProgress<ListRequest>
1749
+ | HistorySnapshotTransition;
1750
+ export function decodeHistoryReadResult(v: unknown): HistoryReadResult {
1751
+ if (Object.getOwnPropertyDescriptor(v ?? {}, "kind")?.value === "snapshot") {
1752
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
1753
+ const o = closed(v, ["kind", "request", "next", "coverage"]),
1754
+ request = decodeListRequest(o.request),
1755
+ next = decodeListRequest(o.next);
1756
+ if (
1757
+ request.snapshotSeq !== null ||
1758
+ request.after !== null ||
1759
+ next.snapshotSeq === null ||
1760
+ !next.navigation ||
1761
+ canonicalJson(readIdentity({ ...next, snapshotSeq: null })) !==
1762
+ canonicalJson(readIdentity(request))
1763
+ )
1764
+ invalid();
1765
+ return {
1766
+ kind: "snapshot",
1767
+ request,
1768
+ next,
1769
+ coverage: decodeCoverage(o.coverage),
1770
+ };
1771
+ }
1772
+ if (Object.getOwnPropertyDescriptor(v ?? {}, "kind")?.value !== "progress")
1773
+ return decodeHistoryPage(v);
1774
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
1775
+ const o = closed(v, ["kind", "request", "next", "coverage"]),
1776
+ request = decodeListRequest(o.request),
1777
+ next = decodeListRequest(o.next);
1778
+ progressPair(request, next);
1779
+ return {
1780
+ kind: "progress",
1781
+ request,
1782
+ next,
1783
+ coverage: decodeCoverage(o.coverage),
1784
+ };
1785
+ }
1786
+ /** First pages alone may select a newer committed snapshot. Continuations never rebase. */
1787
+ export function historyReplyMatches(
1788
+ request: ListRequest,
1789
+ page: HistoryReadResult,
1790
+ ): boolean {
1791
+ return (
1792
+ canonicalJson({ ...page.request, snapshotSeq: request.snapshotSeq }) ===
1793
+ canonicalJson(request) &&
1794
+ (request.snapshotSeq === null ||
1795
+ request.snapshotSeq === page.request.snapshotSeq)
1796
+ );
1797
+ }
1798
+ export interface GraphReadRequest {
1799
+ navigation?: string;
1800
+ storeId: string;
1801
+ scope: Scope;
1802
+ snapshotSeq: string;
1803
+ graph: NonNullable<RunIdentity["graph"]>;
1804
+ }
1805
+ export interface GraphPage {
1806
+ request: GraphReadRequest;
1807
+ graph: Graph;
1808
+ }
1809
+ export function decodeGraphReadRequest(v: unknown): GraphReadRequest {
1810
+ const o = closed(v, [
1811
+ "storeId",
1812
+ "scope",
1813
+ "snapshotSeq",
1814
+ "graph",
1815
+ ...(Object.hasOwn(v ?? {}, "navigation") ? ["navigation"] : []),
1816
+ ]),
1817
+ g = closed(o.graph, ["recordId", "revision", "sha256"]);
1818
+ return {
1819
+ ...(Object.hasOwn(o, "navigation")
1820
+ ? { navigation: decodeNavigation(o.navigation) }
1821
+ : {}),
1822
+ storeId: decodeId(o.storeId),
1823
+ scope: decodeScope(o.scope),
1824
+ snapshotSeq: decodeSequence(o.snapshotSeq),
1825
+ graph: {
1826
+ recordId: decodeId(g.recordId),
1827
+ revision: integer(g.revision, 1),
1828
+ sha256: decodeHash(g.sha256),
1829
+ },
1830
+ };
1831
+ }
1832
+ export function decodeGraphPage(v: unknown): GraphPage {
1833
+ canonicalJson({ kind: "ok", value: v });
1834
+ const o = closed(v, ["request", "graph"]),
1835
+ request = decodeGraphReadRequest(o.request),
1836
+ graph = decodeGraph(o.graph);
1837
+ if (
1838
+ graph.revision !== request.graph.revision ||
1839
+ (request.scope.kind === "task" && request.scope.id !== graph.taskId)
1840
+ )
1841
+ invalid();
1842
+ // Stored-byte hash equality is W3's read-integrity check, not a new hashing algorithm here.
1843
+ return { request, graph };
1844
+ }
1845
+
1846
+ export type GraphReadResult = GraphPage | RequestProgress<GraphReadRequest>;
1847
+ export function decodeGraphReadResult(v: unknown): GraphReadResult {
1848
+ if (Object.getOwnPropertyDescriptor(v ?? {}, "kind")?.value !== "progress")
1849
+ return decodeGraphPage(v);
1850
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
1851
+ const o = closed(v, ["kind", "request", "next", "coverage"]),
1852
+ request = decodeGraphReadRequest(o.request),
1853
+ next = decodeGraphReadRequest(o.next);
1854
+ progressPair(request, next);
1855
+ return {
1856
+ kind: "progress",
1857
+ request,
1858
+ next,
1859
+ coverage: decodeCoverage(o.coverage),
1860
+ };
1861
+ }
1862
+
1863
+ export interface ExportRequest {
1864
+ storeId: string;
1865
+ scope: Scope;
1866
+ snapshotSeq: string;
1867
+ format: "ndjson" | "markdown";
1868
+ }
1869
+ export function decodeExportRequest(v: unknown): ExportRequest {
1870
+ const o = closed(v, ["storeId", "scope", "snapshotSeq", "format"]);
1871
+ return {
1872
+ storeId: decodeId(o.storeId),
1873
+ scope: decodeScope(o.scope),
1874
+ snapshotSeq: decodeSequence(o.snapshotSeq),
1875
+ format: oneOf(o.format, ["ndjson", "markdown"]),
1876
+ };
1877
+ }
1878
+ export type ExportEvent =
1879
+ | {
1880
+ kind: "manifest";
1881
+ schema: typeof EXPORT_SCHEMA;
1882
+ request: ExportRequest;
1883
+ coverage: CaptureCoverage;
1884
+ serviceProtocol: 1;
1885
+ workerProtocol: 2;
1886
+ provenance: {
1887
+ exporterBuildSha256: string;
1888
+ capturePolicy: "public-text-tools/2";
1889
+ };
1890
+ }
1891
+ | { kind: "record"; record: HistoryRecord }
1892
+ | { kind: "graph"; graph: GraphPage }
1893
+ | {
1894
+ kind: "detail";
1895
+ owner: RecordOwner;
1896
+ content: ContentRef;
1897
+ range: ByteRange | null;
1898
+ text: string;
1899
+ coverage: CaptureCoverage;
1900
+ }
1901
+ | { kind: "markdown"; text: string }
1902
+ | {
1903
+ kind: "trailer";
1904
+ complete: boolean;
1905
+ frames: number;
1906
+ encodedBytes: number;
1907
+ sha256: string;
1908
+ committedSeq: string;
1909
+ coverage: CaptureCoverage;
1910
+ };
1911
+ export interface ExportFrame {
1912
+ exportId: string;
1913
+ ordinal: number;
1914
+ event: ExportEvent;
1915
+ }
1916
+ export function decodeExportFrame(v: unknown): ExportFrame {
1917
+ canonicalJson(v, LIMITS.controlBytes);
1918
+ const o = closed(v, ["exportId", "ordinal", "event"]),
1919
+ kind = tag(o.event);
1920
+ let event: ExportEvent;
1921
+ switch (kind) {
1922
+ case "manifest": {
1923
+ const e = closed(o.event, [
1924
+ "kind",
1925
+ "schema",
1926
+ "request",
1927
+ "coverage",
1928
+ "serviceProtocol",
1929
+ "workerProtocol",
1930
+ "provenance",
1931
+ ]);
1932
+ const provenance = closed(e.provenance, [
1933
+ "exporterBuildSha256",
1934
+ "capturePolicy",
1935
+ ]);
1936
+ event = {
1937
+ kind,
1938
+ provenance: {
1939
+ exporterBuildSha256: decodeHash(provenance.exporterBuildSha256),
1940
+ capturePolicy: literal(
1941
+ provenance.capturePolicy,
1942
+ "public-text-tools/2",
1943
+ ),
1944
+ },
1945
+ schema: literal(e.schema, EXPORT_SCHEMA),
1946
+ request: decodeExportRequest(e.request),
1947
+ coverage: decodeCoverage(e.coverage),
1948
+ serviceProtocol: literal(e.serviceProtocol, SERVICE_PROTOCOL),
1949
+ workerProtocol: literal(e.workerProtocol, WORKER_PROTOCOL),
1950
+ };
1951
+ break;
1952
+ }
1953
+ case "record": {
1954
+ const e = closed(o.event, ["kind", "record"]);
1955
+ event = { kind, record: decodeHistoryRecord(e.record) };
1956
+ break;
1957
+ }
1958
+ case "graph": {
1959
+ const e = closed(o.event, ["kind", "graph"]);
1960
+ event = { kind, graph: decodeGraphPage(e.graph) };
1961
+ break;
1962
+ }
1963
+ case "detail": {
1964
+ const e = closed(o.event, [
1965
+ "kind",
1966
+ "owner",
1967
+ "content",
1968
+ "range",
1969
+ "text",
1970
+ "coverage",
1971
+ ]),
1972
+ content = decodeContentRef(e.content),
1973
+ selected = nullable(e.range, range),
1974
+ value = text(e.text, LIMITS.detailPageBytes);
1975
+ if (
1976
+ selected
1977
+ ? selected.end > content.bytes ||
1978
+ selected.end - selected.start !== utf8Bytes(value)
1979
+ : content.bytes !== 0 || value !== ""
1980
+ )
1981
+ invalid();
1982
+ event = {
1983
+ kind,
1984
+ owner: decodeRecordOwner(e.owner),
1985
+ content,
1986
+ range: selected,
1987
+ text: value,
1988
+ coverage: decodeCoverage(e.coverage),
1989
+ };
1990
+ break;
1991
+ }
1992
+ case "markdown": {
1993
+ const e = closed(o.event, ["kind", "text"]);
1994
+ event = { kind, text: text(e.text, LIMITS.detailPageBytes) };
1995
+ break;
1996
+ }
1997
+ case "trailer": {
1998
+ const e = closed(o.event, [
1999
+ "kind",
2000
+ "complete",
2001
+ "frames",
2002
+ "encodedBytes",
2003
+ "sha256",
2004
+ "committedSeq",
2005
+ "coverage",
2006
+ ]),
2007
+ coverage = decodeCoverage(e.coverage),
2008
+ complete = bool(e.complete);
2009
+ if (complete && coverage.observed !== "complete") invalid();
2010
+ event = {
2011
+ kind,
2012
+ complete,
2013
+ frames: integer(e.frames, 1),
2014
+ encodedBytes: integer(e.encodedBytes),
2015
+ sha256: decodeHash(e.sha256),
2016
+ committedSeq: decodeSequence(e.committedSeq),
2017
+ coverage,
2018
+ };
2019
+ break;
2020
+ }
2021
+ default:
2022
+ return invalid();
2023
+ }
2024
+ return {
2025
+ exportId: decodeId(o.exportId),
2026
+ ordinal: integer(o.ordinal, 1),
2027
+ event,
2028
+ };
2029
+ }
2030
+ /** Digest covers these exact UTF-8 bytes of ALL frames before the trailer.
2031
+ * Markdown prose is produced by W3 from the same retained reader, not the DOM.
2032
+ * The manifest/trailer are fenced JSON metadata, not executable HTML comments.
2033
+ */
2034
+ export function encodeExportFrame(
2035
+ frame: ExportFrame,
2036
+ format: ExportRequest["format"],
2037
+ ): string {
2038
+ const f = decodeExportFrame(frame);
2039
+ if (format === "ndjson") return `${canonicalJson(f)}\n`;
2040
+ if (f.event.kind === "markdown") return f.event.text;
2041
+ if (f.event.kind !== "manifest" && f.event.kind !== "trailer") invalid();
2042
+ return `\n\n\`\`\`json\n${canonicalJson(f)}\n\`\`\`\n`;
2043
+ }
2044
+ /** Sequence/count/scope guard only. The streaming adapter verifies the actual
2045
+ * digest and every access-generation check. EOF without trailer is incomplete. */
2046
+ export class ExportTracker {
2047
+ private exportId: string | null = null;
2048
+ private ordinal = 0;
2049
+ private bytes = 0;
2050
+ private ended = false;
2051
+ private complete = false;
2052
+ private readonly request: ExportRequest;
2053
+ constructor(request: ExportRequest) {
2054
+ this.request = decodeExportRequest(request);
2055
+ }
2056
+ receive(input: unknown): ExportFrame {
2057
+ const f = decodeExportFrame(input),
2058
+ e = f.event;
2059
+ if (
2060
+ this.ended ||
2061
+ f.ordinal !== this.ordinal + 1 ||
2062
+ (this.exportId !== null && this.exportId !== f.exportId)
2063
+ )
2064
+ invalid();
2065
+ if (this.ordinal === 0) {
2066
+ if (
2067
+ e.kind !== "manifest" ||
2068
+ canonicalJson(e.request) !== canonicalJson(this.request)
2069
+ )
2070
+ invalid();
2071
+ } else if (e.kind === "manifest") invalid();
2072
+ if (e.kind === "trailer") {
2073
+ if (
2074
+ e.frames !== this.ordinal ||
2075
+ e.encodedBytes !== this.bytes ||
2076
+ e.committedSeq !== this.request.snapshotSeq
2077
+ )
2078
+ invalid();
2079
+ this.ended = true;
2080
+ this.complete = e.complete;
2081
+ } else {
2082
+ if (
2083
+ this.request.format === "markdown" &&
2084
+ e.kind !== "manifest" &&
2085
+ e.kind !== "markdown"
2086
+ )
2087
+ invalid();
2088
+ if (this.request.format === "ndjson" && e.kind === "markdown") invalid();
2089
+ if (e.kind === "detail" && !ownerInScope(e.owner, this.request.scope))
2090
+ invalid();
2091
+ if (
2092
+ e.kind === "graph" &&
2093
+ (canonicalJson(e.graph.request.scope) !==
2094
+ canonicalJson(this.request.scope) ||
2095
+ e.graph.request.snapshotSeq !== this.request.snapshotSeq ||
2096
+ e.graph.request.storeId !== this.request.storeId)
2097
+ )
2098
+ invalid();
2099
+ if (
2100
+ e.kind === "record" &&
2101
+ !historyRecordInScope(e.record, this.request.scope)
2102
+ )
2103
+ invalid();
2104
+ this.bytes += utf8Bytes(encodeExportFrame(f, this.request.format));
2105
+ }
2106
+ this.exportId = f.exportId;
2107
+ this.ordinal = f.ordinal;
2108
+ return f;
2109
+ }
2110
+ get completion():
2111
+ | "missing-trailer"
2112
+ | "incomplete"
2113
+ | "complete-unverified-digest" {
2114
+ return !this.ended
2115
+ ? "missing-trailer"
2116
+ : this.complete
2117
+ ? "complete-unverified-digest"
2118
+ : "incomplete";
2119
+ }
2120
+ }
2121
+ function historyRecordInScope(r: HistoryRecord, scope: Scope): boolean {
2122
+ return r.kind === "run"
2123
+ ? runInScope(r.projection, scope)
2124
+ : r.kind === "owner-fact"
2125
+ ? scope.kind === "task"
2126
+ ? r.link.taskId === scope.id
2127
+ : scope.kind === "run" && r.link.runId === scope.id
2128
+ : r.kind === "capture-fact"
2129
+ ? scope.kind === "task"
2130
+ ? r.link.taskId === scope.id
2131
+ : scope.kind === "run"
2132
+ ? r.link.runId === scope.id
2133
+ : r.link.childRunId === scope.id
2134
+ : ownerInScope(r.owner, scope);
2135
+ }
2136
+
2137
+ export interface CheckObligations {
2138
+ taskId: string;
2139
+ revision: number;
2140
+ definitions: {
2141
+ selection: CheckSelection;
2142
+ status: "not-run" | "passed" | "failed" | "canceled" | "stale";
2143
+ checkId: string | null;
2144
+ }[];
2145
+ }
2146
+ export function decodeCheckObligations(v: unknown): CheckObligations {
2147
+ const o = closed(v, ["taskId", "revision", "definitions"]);
2148
+ const definitions = list(o.definitions, 32, (v) => {
2149
+ const d = closed(v, ["selection", "status", "checkId"]),
2150
+ selection = checkSelection(d.selection),
2151
+ status = oneOf(d.status, [
2152
+ "not-run",
2153
+ "passed",
2154
+ "failed",
2155
+ "canceled",
2156
+ "stale",
2157
+ ]),
2158
+ checkId = nullable(d.checkId, decodeId);
2159
+ if (
2160
+ selection.kind !== "configured" ||
2161
+ (status === "not-run") !== (checkId === null)
2162
+ )
2163
+ invalid();
2164
+ return { selection, status, checkId };
2165
+ });
2166
+ if (
2167
+ new Set(definitions.map((d) => d.selection.name)).size !==
2168
+ definitions.length
2169
+ )
2170
+ invalid();
2171
+ return {
2172
+ taskId: decodeId(o.taskId),
2173
+ revision: integer(o.revision),
2174
+ definitions,
2175
+ };
2176
+ }
2177
+ export interface EntityReadRequest {
2178
+ navigation?: string;
2179
+ storeId: string;
2180
+ scope: Scope;
2181
+ snapshotSeq: string;
2182
+ kind: "task" | "message" | "tool";
2183
+ id: string;
2184
+ version: number;
2185
+ }
2186
+ export function decodeEntityReadRequest(v: unknown): EntityReadRequest {
2187
+ const o = closed(v, [
2188
+ "storeId",
2189
+ "scope",
2190
+ "snapshotSeq",
2191
+ "kind",
2192
+ "id",
2193
+ "version",
2194
+ ...(Object.hasOwn(v ?? {}, "navigation") ? ["navigation"] : []),
2195
+ ]);
2196
+ return {
2197
+ ...(Object.hasOwn(o, "navigation")
2198
+ ? { navigation: decodeNavigation(o.navigation) }
2199
+ : {}),
2200
+ storeId: decodeId(o.storeId),
2201
+ scope: decodeScope(o.scope),
2202
+ snapshotSeq: decodeSequence(o.snapshotSeq),
2203
+ kind: oneOf(o.kind, ["task", "message", "tool"]),
2204
+ id: decodeId(o.id),
2205
+ version: integer(o.version, 1),
2206
+ };
2207
+ }
2208
+ export interface EntityPage {
2209
+ request: EntityReadRequest;
2210
+ record: Extract<HistoryRecord, { kind: "task" | "message" | "tool" }>;
2211
+ }
2212
+ export function decodeEntityPage(v: unknown): EntityPage {
2213
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
2214
+ const o = closed(v, ["request", "record"]),
2215
+ request = decodeEntityReadRequest(o.request),
2216
+ record = decodeHistoryRecord(o.record);
2217
+ if (
2218
+ (record.kind !== "task" &&
2219
+ record.kind !== "message" &&
2220
+ record.kind !== "tool") ||
2221
+ record.kind !== request.kind ||
2222
+ record.owner.entity.id !== request.id ||
2223
+ record.version !== request.version ||
2224
+ !ownerInScope(record.owner, request.scope)
2225
+ )
2226
+ invalid();
2227
+ return { request, record };
2228
+ }
2229
+
2230
+ export type EntityReadResult = EntityPage | RequestProgress<EntityReadRequest>;
2231
+ export function decodeEntityReadResult(v: unknown): EntityReadResult {
2232
+ if (Object.getOwnPropertyDescriptor(v ?? {}, "kind")?.value !== "progress")
2233
+ return decodeEntityPage(v);
2234
+ canonicalJson({ kind: "ok", value: v }, LIMITS.pageBytes);
2235
+ const o = closed(v, ["kind", "request", "next", "coverage"]),
2236
+ request = decodeEntityReadRequest(o.request),
2237
+ next = decodeEntityReadRequest(o.next);
2238
+ progressPair(request, next);
2239
+ return {
2240
+ kind: "progress",
2241
+ request,
2242
+ next,
2243
+ coverage: decodeCoverage(o.coverage),
2244
+ };
2245
+ }