@plurnk/plurnk-service 1.14.2 → 1.16.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 (308) hide show
  1. package/.env.defaults +2 -2
  2. package/SPEC.md +243 -173
  3. package/digest-sql/curation/curation.sql +1 -1
  4. package/dist/build-info.json +1 -1
  5. package/dist/content/byte-view.d.ts +14 -0
  6. package/dist/content/byte-view.d.ts.map +1 -0
  7. package/dist/content/byte-view.js +33 -0
  8. package/dist/content/byte-view.js.map +1 -0
  9. package/dist/content/edit-receipt.d.ts.map +1 -1
  10. package/dist/content/edit-receipt.js +12 -1
  11. package/dist/content/edit-receipt.js.map +1 -1
  12. package/dist/content/line-marker.d.ts +2 -1
  13. package/dist/content/line-marker.d.ts.map +1 -1
  14. package/dist/content/line-marker.js +1 -0
  15. package/dist/content/line-marker.js.map +1 -1
  16. package/dist/content/matcher.d.ts +1 -0
  17. package/dist/content/matcher.d.ts.map +1 -1
  18. package/dist/content/matcher.js +1 -0
  19. package/dist/content/matcher.js.map +1 -1
  20. package/dist/content/read-projector.d.ts +4 -1
  21. package/dist/content/read-projector.d.ts.map +1 -1
  22. package/dist/content/read-projector.js +80 -3
  23. package/dist/content/read-projector.js.map +1 -1
  24. package/dist/core/AdmittedTurnExecutor.d.ts +43 -0
  25. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -0
  26. package/dist/core/AdmittedTurnExecutor.js +245 -0
  27. package/dist/core/AdmittedTurnExecutor.js.map +1 -0
  28. package/dist/core/BareBatchRunner.d.ts +26 -0
  29. package/dist/core/BareBatchRunner.d.ts.map +1 -0
  30. package/dist/core/BareBatchRunner.js +104 -0
  31. package/dist/core/BareBatchRunner.js.map +1 -0
  32. package/dist/core/BudgetReadout.d.ts +1 -1
  33. package/dist/core/BudgetReadout.d.ts.map +1 -1
  34. package/dist/core/BudgetReadout.js +42 -47
  35. package/dist/core/BudgetReadout.js.map +1 -1
  36. package/dist/core/CapabilityResolver.d.ts +2 -1
  37. package/dist/core/CapabilityResolver.d.ts.map +1 -1
  38. package/dist/core/CapabilityResolver.js +8 -9
  39. package/dist/core/CapabilityResolver.js.map +1 -1
  40. package/dist/core/ChannelWrite.d.ts +1 -1
  41. package/dist/core/ChannelWrite.d.ts.map +1 -1
  42. package/dist/core/DataStatementRunner.d.ts +35 -0
  43. package/dist/core/DataStatementRunner.d.ts.map +1 -0
  44. package/dist/core/DataStatementRunner.js +252 -0
  45. package/dist/core/DataStatementRunner.js.map +1 -0
  46. package/dist/core/Dispatcher.d.ts +8 -3
  47. package/dist/core/Dispatcher.d.ts.map +1 -1
  48. package/dist/core/Dispatcher.js +88 -738
  49. package/dist/core/Dispatcher.js.map +1 -1
  50. package/dist/core/EditMutations.d.ts +39 -0
  51. package/dist/core/EditMutations.d.ts.map +1 -0
  52. package/dist/core/EditMutations.js +418 -0
  53. package/dist/core/EditMutations.js.map +1 -0
  54. package/dist/core/Engine.d.ts +2 -23
  55. package/dist/core/Engine.d.ts.map +1 -1
  56. package/dist/core/Engine.js +18 -240
  57. package/dist/core/Engine.js.map +1 -1
  58. package/dist/core/Engine.sql +24 -23
  59. package/dist/core/KillHandler.d.ts +32 -0
  60. package/dist/core/KillHandler.d.ts.map +1 -0
  61. package/dist/core/KillHandler.js +179 -0
  62. package/dist/core/KillHandler.js.map +1 -0
  63. package/dist/core/LogVisibility.d.ts +1 -1
  64. package/dist/core/LogVisibility.d.ts.map +1 -1
  65. package/dist/core/LogVisibility.js +3 -4
  66. package/dist/core/LogVisibility.js.map +1 -1
  67. package/dist/core/LogWriter.d.ts +42 -0
  68. package/dist/core/LogWriter.d.ts.map +1 -0
  69. package/dist/core/LogWriter.js +136 -0
  70. package/dist/core/LogWriter.js.map +1 -0
  71. package/dist/core/LoopDriver.d.ts +46 -0
  72. package/dist/core/LoopDriver.d.ts.map +1 -0
  73. package/dist/core/LoopDriver.js +263 -0
  74. package/dist/core/LoopDriver.js.map +1 -0
  75. package/dist/core/LoopLifecycle.d.ts +2 -1
  76. package/dist/core/LoopLifecycle.d.ts.map +1 -1
  77. package/dist/core/MembershipMaterialization.d.ts +7 -0
  78. package/dist/core/MembershipMaterialization.d.ts.map +1 -0
  79. package/dist/core/MembershipMaterialization.js +283 -0
  80. package/dist/core/MembershipMaterialization.js.map +1 -0
  81. package/dist/core/MutationEffects.d.ts +29 -0
  82. package/dist/core/MutationEffects.d.ts.map +1 -0
  83. package/dist/core/MutationEffects.js +318 -0
  84. package/dist/core/MutationEffects.js.map +1 -0
  85. package/dist/core/OverflowTurn.d.ts +4 -4
  86. package/dist/core/OverflowTurn.d.ts.map +1 -1
  87. package/dist/core/OverflowTurn.js +6 -7
  88. package/dist/core/OverflowTurn.js.map +1 -1
  89. package/dist/core/PacketBuilder.d.ts.map +1 -1
  90. package/dist/core/PacketBuilder.js +31 -9
  91. package/dist/core/PacketBuilder.js.map +1 -1
  92. package/dist/core/ResourceMutations.d.ts +12 -36
  93. package/dist/core/ResourceMutations.d.ts.map +1 -1
  94. package/dist/core/ResourceMutations.js +27 -1351
  95. package/dist/core/ResourceMutations.js.map +1 -1
  96. package/dist/core/ResourceSelector.d.ts +21 -0
  97. package/dist/core/ResourceSelector.d.ts.map +1 -0
  98. package/dist/core/ResourceSelector.js +233 -0
  99. package/dist/core/ResourceSelector.js.map +1 -0
  100. package/dist/core/ResourceTransfers.d.ts +55 -0
  101. package/dist/core/ResourceTransfers.d.ts.map +1 -0
  102. package/dist/core/ResourceTransfers.js +525 -0
  103. package/dist/core/ResourceTransfers.js.map +1 -0
  104. package/dist/core/SchemeRegistry.js +2 -2
  105. package/dist/core/SchemeRegistry.js.map +1 -1
  106. package/dist/core/SendBroadcastHandler.d.ts +40 -0
  107. package/dist/core/SendBroadcastHandler.d.ts.map +1 -0
  108. package/dist/core/SendBroadcastHandler.js +159 -0
  109. package/dist/core/SendBroadcastHandler.js.map +1 -0
  110. package/dist/core/StoredPacket.d.ts +11 -0
  111. package/dist/core/StoredPacket.d.ts.map +1 -1
  112. package/dist/core/StoredPacket.js +22 -1
  113. package/dist/core/StoredPacket.js.map +1 -1
  114. package/dist/core/StrikeRail.d.ts.map +1 -1
  115. package/dist/core/StrikeRail.js +19 -9
  116. package/dist/core/StrikeRail.js.map +1 -1
  117. package/dist/core/TokenCalibration.d.ts +16 -0
  118. package/dist/core/TokenCalibration.d.ts.map +1 -0
  119. package/dist/core/TokenCalibration.js +35 -0
  120. package/dist/core/TokenCalibration.js.map +1 -0
  121. package/dist/core/ToolResources.d.ts.map +1 -1
  122. package/dist/core/ToolResources.js +15 -12
  123. package/dist/core/ToolResources.js.map +1 -1
  124. package/dist/core/TurnMaterialization.d.ts +37 -0
  125. package/dist/core/TurnMaterialization.d.ts.map +1 -0
  126. package/dist/core/TurnMaterialization.js +296 -0
  127. package/dist/core/TurnMaterialization.js.map +1 -0
  128. package/dist/core/TurnOps.d.ts.map +1 -1
  129. package/dist/core/TurnOps.js +6 -8
  130. package/dist/core/TurnOps.js.map +1 -1
  131. package/dist/core/TurnRunner.d.ts +15 -27
  132. package/dist/core/TurnRunner.d.ts.map +1 -1
  133. package/dist/core/TurnRunner.js +76 -670
  134. package/dist/core/TurnRunner.js.map +1 -1
  135. package/dist/core/WorkerControlHandler.d.ts +15 -0
  136. package/dist/core/WorkerControlHandler.d.ts.map +1 -0
  137. package/dist/core/WorkerControlHandler.js +89 -0
  138. package/dist/core/WorkerControlHandler.js.map +1 -0
  139. package/dist/core/attachments.d.ts +14 -0
  140. package/dist/core/attachments.d.ts.map +1 -0
  141. package/dist/core/attachments.js +18 -0
  142. package/dist/core/attachments.js.map +1 -0
  143. package/dist/core/fork.d.ts.map +1 -1
  144. package/dist/core/fork.js +0 -4
  145. package/dist/core/fork.js.map +1 -1
  146. package/dist/core/fork.sql +5 -16
  147. package/dist/core/git-membership.d.ts +15 -0
  148. package/dist/core/git-membership.d.ts.map +1 -1
  149. package/dist/core/git-membership.js +4 -273
  150. package/dist/core/git-membership.js.map +1 -1
  151. package/dist/core/git-state.d.ts +2 -1
  152. package/dist/core/git-state.d.ts.map +1 -1
  153. package/dist/core/mutation-types.d.ts +102 -0
  154. package/dist/core/mutation-types.d.ts.map +1 -0
  155. package/dist/core/mutation-types.js +2 -0
  156. package/dist/core/mutation-types.js.map +1 -0
  157. package/dist/core/operation-target-groups.js +1 -1
  158. package/dist/core/operation-target-groups.js.map +1 -1
  159. package/dist/core/packet-wire.d.ts +7 -6
  160. package/dist/core/packet-wire.d.ts.map +1 -1
  161. package/dist/core/packet-wire.js +96 -68
  162. package/dist/core/packet-wire.js.map +1 -1
  163. package/dist/core/plurnk-uri.d.ts +0 -1
  164. package/dist/core/plurnk-uri.d.ts.map +1 -1
  165. package/dist/core/plurnk-uri.js +1 -1
  166. package/dist/core/plurnk-uri.js.map +1 -1
  167. package/dist/core/statement-primary.d.ts +4 -0
  168. package/dist/core/statement-primary.d.ts.map +1 -0
  169. package/dist/core/statement-primary.js +3 -0
  170. package/dist/core/statement-primary.js.map +1 -0
  171. package/dist/core/teaching-corpus.d.ts +0 -1
  172. package/dist/core/teaching-corpus.d.ts.map +1 -1
  173. package/dist/core/teaching-corpus.js +0 -9
  174. package/dist/core/teaching-corpus.js.map +1 -1
  175. package/dist/core/turn-scheduler.js +3 -3
  176. package/dist/core/turn-scheduler.js.map +1 -1
  177. package/dist/core/turn-signals.d.ts +15 -0
  178. package/dist/core/turn-signals.d.ts.map +1 -0
  179. package/dist/core/turn-signals.js +19 -0
  180. package/dist/core/turn-signals.js.map +1 -0
  181. package/dist/core/worker-settings.d.ts +0 -1
  182. package/dist/core/worker-settings.d.ts.map +1 -1
  183. package/dist/core/worker-settings.js +1 -1
  184. package/dist/core/worker-settings.js.map +1 -1
  185. package/dist/digest/Digest.d.ts +3 -16
  186. package/dist/digest/Digest.d.ts.map +1 -1
  187. package/dist/digest/Digest.js +10 -958
  188. package/dist/digest/Digest.js.map +1 -1
  189. package/dist/digest/DigestRender.d.ts +12 -0
  190. package/dist/digest/DigestRender.d.ts.map +1 -0
  191. package/dist/digest/DigestRender.js +641 -0
  192. package/dist/digest/DigestRender.js.map +1 -0
  193. package/dist/digest/DigestRequiem.d.ts +13 -0
  194. package/dist/digest/DigestRequiem.d.ts.map +1 -0
  195. package/dist/digest/DigestRequiem.js +332 -0
  196. package/dist/digest/DigestRequiem.js.map +1 -0
  197. package/dist/digest/digest-rows.d.ts +303 -0
  198. package/dist/digest/digest-rows.d.ts.map +1 -0
  199. package/dist/digest/digest-rows.js +2 -0
  200. package/dist/digest/digest-rows.js.map +1 -0
  201. package/dist/digest/digest.sql +1 -7
  202. package/dist/observe/api.d.ts +0 -2
  203. package/dist/observe/api.d.ts.map +1 -1
  204. package/dist/observe/api.js +2 -2
  205. package/dist/observe/api.js.map +1 -1
  206. package/dist/observe/metrics.d.ts +0 -1
  207. package/dist/observe/metrics.d.ts.map +1 -1
  208. package/dist/observe/metrics.js +0 -1
  209. package/dist/observe/metrics.js.map +1 -1
  210. package/dist/schemes/Exec.d.ts +4 -1
  211. package/dist/schemes/Exec.d.ts.map +1 -1
  212. package/dist/schemes/Exec.js +89 -48
  213. package/dist/schemes/Exec.js.map +1 -1
  214. package/dist/schemes/ExecOutputScheme.d.ts +2 -1
  215. package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
  216. package/dist/schemes/ExecOutputScheme.js +3 -3
  217. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  218. package/dist/schemes/File.d.ts +4 -1
  219. package/dist/schemes/File.d.ts.map +1 -1
  220. package/dist/schemes/File.js +100 -13
  221. package/dist/schemes/File.js.map +1 -1
  222. package/dist/schemes/Log.d.ts +4 -5
  223. package/dist/schemes/Log.d.ts.map +1 -1
  224. package/dist/schemes/Log.js +26 -118
  225. package/dist/schemes/Log.js.map +1 -1
  226. package/dist/schemes/Log.sql +1 -66
  227. package/dist/schemes/Prompt.js +1 -1
  228. package/dist/schemes/Prompt.js.map +1 -1
  229. package/dist/schemes/Worker.d.ts.map +1 -1
  230. package/dist/schemes/Worker.js +1 -13
  231. package/dist/schemes/Worker.js.map +1 -1
  232. package/dist/schemes/_entry-crud.d.ts +3 -1
  233. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  234. package/dist/schemes/_entry-crud.js +18 -3
  235. package/dist/schemes/_entry-crud.js.map +1 -1
  236. package/dist/schemes/_entry-find.d.ts +4 -2
  237. package/dist/schemes/_entry-find.d.ts.map +1 -1
  238. package/dist/schemes/_entry-find.js +79 -10
  239. package/dist/schemes/_entry-find.js.map +1 -1
  240. package/dist/schemes/_entry-manifest.d.ts +1 -2
  241. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  242. package/dist/schemes/_entry-ops.d.ts +2 -1
  243. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  244. package/dist/schemes/_entry-ops.js +20 -2
  245. package/dist/schemes/_entry-ops.js.map +1 -1
  246. package/dist/schemes/_entry-semantic.d.ts +1 -0
  247. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  248. package/dist/schemes/_entry-semantic.js +3 -0
  249. package/dist/schemes/_entry-semantic.js.map +1 -1
  250. package/dist/schemes/_entry-semantic.sql +2 -2
  251. package/dist/schemes/_entry-send.d.ts +0 -1
  252. package/dist/schemes/_entry-send.d.ts.map +1 -1
  253. package/dist/schemes/_entry-send.js +5 -80
  254. package/dist/schemes/_entry-send.js.map +1 -1
  255. package/dist/schemes/exec-runtime.d.ts +7 -0
  256. package/dist/schemes/exec-runtime.d.ts.map +1 -0
  257. package/dist/schemes/exec-runtime.js +2 -0
  258. package/dist/schemes/exec-runtime.js.map +1 -0
  259. package/dist/server/ClientReads.d.ts +34 -0
  260. package/dist/server/ClientReads.d.ts.map +1 -0
  261. package/dist/server/ClientReads.js +210 -0
  262. package/dist/server/ClientReads.js.map +1 -0
  263. package/dist/server/Daemon.d.ts +12 -22
  264. package/dist/server/Daemon.d.ts.map +1 -1
  265. package/dist/server/Daemon.js +31 -362
  266. package/dist/server/Daemon.js.map +1 -1
  267. package/dist/server/DaemonModule.d.ts +6 -5
  268. package/dist/server/DaemonModule.d.ts.map +1 -1
  269. package/dist/server/DrainSupervisor.d.ts +3 -2
  270. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  271. package/dist/server/DrainSupervisor.js +7 -3
  272. package/dist/server/DrainSupervisor.js.map +1 -1
  273. package/dist/server/Functionality.d.ts +0 -12
  274. package/dist/server/Functionality.d.ts.map +1 -1
  275. package/dist/server/Functionality.js.map +1 -1
  276. package/dist/server/FunctionalityManager.d.ts +0 -1
  277. package/dist/server/FunctionalityManager.d.ts.map +1 -1
  278. package/dist/server/FunctionalityManager.js +3 -3
  279. package/dist/server/FunctionalityManager.js.map +1 -1
  280. package/dist/server/MembersFunctionality.d.ts +2 -13
  281. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  282. package/dist/server/MembersFunctionality.js +4 -4
  283. package/dist/server/MembersFunctionality.js.map +1 -1
  284. package/dist/server/SkillsFunctionality.d.ts +1 -7
  285. package/dist/server/SkillsFunctionality.d.ts.map +1 -1
  286. package/dist/server/SkillsFunctionality.js +3 -3
  287. package/dist/server/SkillsFunctionality.js.map +1 -1
  288. package/dist/server/WorkerModelResolver.d.ts +17 -0
  289. package/dist/server/WorkerModelResolver.d.ts.map +1 -0
  290. package/dist/server/WorkerModelResolver.js +166 -0
  291. package/dist/server/WorkerModelResolver.js.map +1 -0
  292. package/dist/server/daemon-results.d.ts +5 -0
  293. package/dist/server/daemon-results.d.ts.map +1 -0
  294. package/dist/server/daemon-results.js +7 -0
  295. package/dist/server/daemon-results.js.map +1 -0
  296. package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
  297. package/dist/server/dispatch-as-plurnk.js +4 -6
  298. package/dist/server/dispatch-as-plurnk.js.map +1 -1
  299. package/dist/server/logEntry.d.ts +0 -1
  300. package/dist/server/logEntry.d.ts.map +1 -1
  301. package/dist/server/logEntry.js +1 -13
  302. package/dist/server/logEntry.js.map +1 -1
  303. package/dist/server/logEntry.sql +1 -5
  304. package/dist/server/loopDocs.d.ts.map +1 -1
  305. package/dist/server/loopDocs.js +11 -2
  306. package/dist/server/loopDocs.js.map +1 -1
  307. package/migrations/001_schema.sql +30 -347
  308. package/package.json +30 -30
@@ -0,0 +1,641 @@
1
+ // Digest renderers ({§digest-programmatic-surface}): the markdown, JSON, reasoning, and packet
2
+ // artifacts of one DigestModel. Pure projection of the model — no DB, no I/O except packetFiles.
3
+ import { writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import PacketWire from "../core/packet-wire.js";
6
+ import LogBody from "../core/LogBody.js";
7
+ import StoredPacket from "../core/StoredPacket.js";
8
+ import { renderTarget } from "../core/plurnk-uri.js";
9
+ import EntryManifest from "../schemes/_entry-manifest.js";
10
+ import { aggregateProviderAccounting, } from "@plurnk/plurnk-providers";
11
+ import { providerRequestFromStorageRow, } from "../core/provider-accounting.js";
12
+ import { Validator } from "@plurnk/plurnk-contracts";
13
+ export default class DigestRender {
14
+ static #summarize(text, n = 80) {
15
+ if (text === null || text === undefined)
16
+ return "";
17
+ const flat = String(text).replace(/\s+/g, " ").trim();
18
+ if (flat.length <= n)
19
+ return flat;
20
+ return `${flat.slice(0, n)}…`;
21
+ }
22
+ static parseJson(s, fallback = null) {
23
+ if (s === null || s === undefined)
24
+ return fallback;
25
+ try {
26
+ return JSON.parse(String(s));
27
+ }
28
+ catch {
29
+ return fallback;
30
+ }
31
+ }
32
+ static #requestAccounting(row) {
33
+ if (row.state !== "settled" || row.outcome === null || row.cost_kind === null) {
34
+ throw new TypeError(`digest: provider request ${row.id} is not settled`);
35
+ }
36
+ return providerRequestFromStorageRow(row);
37
+ }
38
+ static #accounting(rows) {
39
+ return rows.some((row) => row.state !== "settled")
40
+ ? null
41
+ : aggregateProviderAccounting(rows.map((row) => DigestRender.#requestAccounting(row)));
42
+ }
43
+ static usageSummary(accounting) {
44
+ if (accounting === null)
45
+ return "accounting=incomplete";
46
+ const usage = accounting.usage;
47
+ return [
48
+ `input=${usage?.inputTokens ?? "unknown"}`,
49
+ `output=${usage?.outputTokens ?? "unknown"}`,
50
+ `reasoning=${usage?.outputTokenDetails?.reasoningTokens ?? "unknown"}`,
51
+ `cache-read=${usage?.inputTokenDetails?.cacheReadTokens ?? "unknown"}`,
52
+ ].join(" ");
53
+ }
54
+ static #operationResult(raw, subject) {
55
+ try {
56
+ return Validator.assertOperationResult(DigestRender.parseJson(raw));
57
+ }
58
+ catch (cause) {
59
+ throw new Error(`digest: ${subject} does not contain a valid operation result`, { cause });
60
+ }
61
+ }
62
+ static #rowProblem(row) {
63
+ const result = DigestRender.#operationResult(row.rx, `failed log entry ${row.id}`);
64
+ if (result.problem === undefined) {
65
+ throw new Error(`digest: failed log entry ${row.id} does not contain Problem Details`);
66
+ }
67
+ return result.problem;
68
+ }
69
+ static #terminalResult(loop) {
70
+ return loop.terminal_result === null
71
+ ? null
72
+ : DigestRender.#operationResult(loop.terminal_result, `terminal loop ${loop.id}`);
73
+ }
74
+ static #renderTarget(le) {
75
+ return renderTarget(le);
76
+ }
77
+ static #renderStream(le) {
78
+ if (le.op !== "EXEC")
79
+ return null;
80
+ const stream = DigestRender.parseJson(le.attrs, {}).stream;
81
+ return typeof stream === "string" ? stream : null;
82
+ }
83
+ static #renderOpLine(le, label = le.op ?? "source artifact") {
84
+ const target = DigestRender.#renderTarget(le) ?? "—";
85
+ const stream = DigestRender.#renderStream(le);
86
+ const state = le.state !== "resolved" ? ` state=${le.state}` : "";
87
+ const outcome = le.outcome !== null ? ` outcome=${le.outcome}` : "";
88
+ const streamLink = stream === null ? "" : ` stream=${stream}`;
89
+ const source = le.source === null ? "" : ` source=${le.source}`;
90
+ const fail = le.status_rx >= 400 ? " ✗" : "";
91
+ // For failed outcomes, surface the Problem Details explanation from rx so
92
+ // the waterfall explains WHY each failure happened without opening packets.
93
+ let errLine = "";
94
+ if (le.status_rx >= 400) {
95
+ errLine = `\n -> ${DigestRender.#summarize(DigestRender.#rowProblem(le).detail, 140)}`;
96
+ }
97
+ return ` ← [${le.origin}] ${label}[${le.status_rx}] ${target}${source}${state}${outcome}${streamLink}${fail}${errLine}`;
98
+ }
99
+ static #renderGroupedOpLine(row) {
100
+ const attrs = DigestRender.parseJson(row.attrs, {});
101
+ const materialized = row.origin === "_plurnk" && row.op === "EDIT" && attrs.kind === "entry_materialized";
102
+ const actionlessKind = row.op === null
103
+ ? LogBody.actionlessKind({ op: row.op, attrs })
104
+ : null;
105
+ const label = actionlessKind === "emissionAttempt"
106
+ ? "emission attempt"
107
+ : actionlessKind ?? row.op ?? "actionless row";
108
+ return DigestRender.#renderOpLine(row, materialized ? "materialized entry" : label);
109
+ }
110
+ // Human triage is not a row dump. Preserve every row in digest.json, but
111
+ // collapse identical rendered outcomes in the Markdown waterfall. Using the
112
+ // rendered line itself as the key keeps actor, complete target, lifecycle,
113
+ // stream, and visible failure detail structurally aligned with the grouping.
114
+ static #renderOpLines(rows) {
115
+ const groups = new Map();
116
+ for (const row of rows) {
117
+ const line = DigestRender.#renderGroupedOpLine(row);
118
+ const group = groups.get(line);
119
+ if (group === undefined) {
120
+ groups.set(line, { line, count: 1, firstSeq: row.sequence, lastSeq: row.sequence });
121
+ }
122
+ else {
123
+ group.count++;
124
+ group.lastSeq = row.sequence;
125
+ }
126
+ }
127
+ return [...groups.values()].map(({ line, count, firstSeq, lastSeq }) => {
128
+ return count === 1 ? line : `${line} ×${count} (seq ${firstSeq}–${lastSeq})`;
129
+ });
130
+ }
131
+ // The "degenerate win" lens (owner ask): a loop's health = how many errors/strikes it earned
132
+ // vs whether it still concluded. A green that limped across on 16 errors is a FAILING artifact
133
+ // wearing a passing badge; the digest must make that impossible to miss. errors = ≥400 op rows;
134
+ // errorItems = minted op='error' rows (truncation/budget/steer/cycle — the strike signals).
135
+ // {§digest-executor-evidence} (#436) — a failed command's completion rows carry the
136
+ // executor's problem identity; a red test run is the loop's normal work, not a
137
+ // defect. They stay visible but never count as errors in health or the errs
138
+ // badge — the digest mirror of the strike rail's exemption (#425 F1).
139
+ static #isExecutorEvidence(le) {
140
+ if (le.status_rx < 400 || le.rx === null)
141
+ return false;
142
+ const rx = DigestRender.parseJson(le.rx, null);
143
+ return typeof rx?.problem?.type === "string"
144
+ && rx.problem.type.startsWith("https://problems.plurnk.xyz/executor/");
145
+ }
146
+ static #loopHealth(loop, m) {
147
+ let errors = 0;
148
+ let errorItems = 0;
149
+ for (const t of m.turnsByLoop.get(loop.id) ?? []) {
150
+ for (const le of m.logEntriesByTurn.get(t.id) ?? []) {
151
+ if (le.status_rx >= 400 && !DigestRender.#isExecutorEvidence(le))
152
+ errors += 1;
153
+ if (le.op === "error")
154
+ errorItems += 1;
155
+ }
156
+ }
157
+ const s = loop.status;
158
+ const verdict = s >= 400 ? `FAILED(${s})`
159
+ : s >= 200 && s < 300 ? (errors > 0 ? "DEGENERATE-WIN" : "CLEAN")
160
+ : `status=${s}`;
161
+ return { errors, errorItems, verdict };
162
+ }
163
+ static #renderTurnLine(turn, m) {
164
+ const packet = turn.packet;
165
+ const assistant = packet !== null && StoredPacket.isAdmitted(packet) ? packet.assistant : null;
166
+ const content = assistant?.content ?? "";
167
+ const reasoning = assistant?.reasoning ?? null;
168
+ const accounting = DigestRender.#accounting(m.requestsByTurn.get(turn.id) ?? []);
169
+ const tokens = DigestRender.usageSummary(accounting);
170
+ const cost = accounting === null || accounting.costUsd === null
171
+ ? " cost=unavailable"
172
+ : ` cost=$${accounting.costUsd}`;
173
+ const finishReason = turn.finish_reason ?? "—";
174
+ // Render only observed constraint metadata; absence makes no claim
175
+ // about endpoint-owned settings. {§rail-truth-engine-verdict}
176
+ const tm = DigestRender.parseJson(turn.meta ?? "null", null);
177
+ const attached = tm?.railsAttached;
178
+ const rails = attached === undefined || attached === false ? ""
179
+ : ` rails=${attached === true ? "client" : attached}:${tm?.railsVerdict ?? "attached"}`;
180
+ const model = turn.model ?? "—";
181
+ const errs = (m.logEntriesByTurn.get(turn.id) ?? [])
182
+ .filter((le) => le.status_rx >= 400 && !DigestRender.#isExecutorEvidence(le)).length;
183
+ const errBadge = errs > 0 ? ` ⚠ errs=${errs}` : "";
184
+ const attempts = m.attemptsByTurn.get(turn.id) ?? [];
185
+ const rejected = attempts.filter((attempt) => attempt.accepted === 0).length;
186
+ const errored = attempts.filter((attempt) => attempt.state === "error").length;
187
+ const pending = attempts.filter((attempt) => attempt.state === "pending").length;
188
+ const attemptConditions = [
189
+ rejected > 0 ? `rejected-emissions=${rejected}` : null,
190
+ errored > 0 ? `call-errors=${errored}` : null,
191
+ pending > 0 ? `open-calls=${pending}` : null,
192
+ ].filter((part) => part !== null);
193
+ const attemptBadge = attemptConditions.length === 0
194
+ ? ""
195
+ : ` ⚠ ${attemptConditions.join(" ")}/${attempts.length}`;
196
+ const packetBadge = turn.packetFailure === null ? "" : " ⚠ packet=invalid";
197
+ const lifecycle = `T${turn.sequence}: producer=${turn.producer} kind=${turn.kind} status=${turn.status}${turn.completed_at === null ? " state=open" : ""}`;
198
+ const head = turn.kind === "inference"
199
+ ? `${lifecycle} finish=${finishReason}${rails} model=${model} ${tokens}${cost}${errBadge}${attemptBadge}${packetBadge}`
200
+ : `${lifecycle}${errBadge}${packetBadge}`;
201
+ const summary = turn.packetFailure !== null
202
+ ? ` ↳ provider packet: invalid stored evidence (${turn.packetFailure.error.message})`
203
+ : packet === null
204
+ ? null
205
+ : content.length > 0
206
+ ? ` ↳ emission: ${DigestRender.#summarize(content, 100)}`
207
+ : assistant !== null
208
+ ? " ↳ emission: (admitted empty)"
209
+ : rejected > 0
210
+ ? ` ↳ emission: (none admitted; ${rejected} rejected)`
211
+ : " ↳ emission: (none admitted)";
212
+ const reasoningLine = reasoning && reasoning.length > 0
213
+ ? ` ↳ reasoning: ${DigestRender.#summarize(reasoning, 100)}`
214
+ : null;
215
+ const opLines = DigestRender.#renderOpLines(m.logEntriesByTurn.get(turn.id) ?? []);
216
+ return [head, ...(summary ? [summary] : []), ...(reasoningLine ? [reasoningLine] : []), ...opLines].join("\n");
217
+ }
218
+ static #renderWorkerShape(worker, m) {
219
+ // every worker has exactly one rollup row — digest_worker_rollups is FROM workers
220
+ const roll = m.workerRollups.get(worker.id);
221
+ const opMix = (m.opMixByWorker.get(worker.id) ?? []).map((o) => `${o.op}=${o.n}`).join(" ");
222
+ const requests = m.requestsByWorker.get(worker.id) ?? [];
223
+ const accounting = DigestRender.#accounting(requests);
224
+ const usageStr = requests.length === 0
225
+ ? "no provider requests"
226
+ : DigestRender.usageSummary(accounting);
227
+ const costStr = requests.length === 0
228
+ ? "n/a"
229
+ : accounting === null || accounting.costUsd === null
230
+ ? "unknown"
231
+ : `$${accounting.costUsd}`;
232
+ // {§digest-forensic-fidelity} (#461): settled exchanges that carry no usage at
233
+ // all (errored/aborted) billed server-side invisibly; say so, never price-as-zero.
234
+ const usageless = requests.filter((row) => row.state === "settled" && row.usage_total === null).length;
235
+ const usagelessStr = usageless > 0
236
+ ? ` (+${usageless} usage-less request${usageless === 1 ? "" : "s"} — server-side spend unrecorded)`
237
+ : "";
238
+ // {§digest-cost-kind} (#473): a dollar figure without its basis reads as billed
239
+ // truth; carry the kind so an estimate can never impersonate a charge.
240
+ const kinds = new Set(requests
241
+ .filter((row) => row.state === "settled" && row.cost_kind !== null)
242
+ .map((row) => row.cost_kind));
243
+ const kindStr = costStr === "n/a" || costStr === "unknown"
244
+ ? ""
245
+ : kinds.has("estimated")
246
+ ? " (estimated — catalog rates)"
247
+ : kinds.has("charged")
248
+ ? " (charged)"
249
+ : "";
250
+ // {§digest-wire-line} (#473): provider-level errors are absorbed by retries below
251
+ // the packet stream, so only an aggregate line makes a rate-limit storm visible.
252
+ const wireErrors = requests.filter((row) => row.outcome === "error").length;
253
+ const wireStr = requests.length === 0
254
+ ? "(no provider requests)"
255
+ : `${requests.length} request${requests.length === 1 ? "" : "s"} · ${wireErrors} error${wireErrors === 1 ? "" : "s"}${wireErrors > 0 ? ` (${Math.round((100 * wireErrors) / requests.length)}%)` : ""}`;
256
+ return [
257
+ `Loops: ${roll.loops}`,
258
+ `Turns: ${roll.turns}`,
259
+ `Last turn: ${roll.last_status !== null ? `status=${roll.last_status}` : "(none)"}`,
260
+ `Tokens: ${usageStr}`,
261
+ `Cost: ${costStr}${kindStr}${usagelessStr}`,
262
+ `Wire: ${wireStr}`,
263
+ `Op mix: ${opMix.length > 0 ? opMix : "(no ops)"}`,
264
+ ].join("\n");
265
+ }
266
+ static waterfall(m) {
267
+ const lines = [];
268
+ lines.push(`# plurnk-service digest`);
269
+ lines.push("");
270
+ lines.push(`DB: ${m.dbPath}`);
271
+ const rejectedAttempts = m.turnAttempts.filter((attempt) => attempt.accepted === 0).length;
272
+ const erroredCalls = m.inferenceCalls.filter((call) => call.state === "error").length;
273
+ const pendingCalls = m.inferenceCalls.filter((call) => call.state === "pending").length;
274
+ const bareCalls = m.modelCalls.filter((call) => call.kind === "bare").length;
275
+ const embeddingCalls = m.embeddingCalls.length;
276
+ const pendingRequests = m.providerRequests.filter((request) => request.state === "pending").length;
277
+ const packetFailures = m.turns.filter((turn) => turn.packetFailure !== null).length;
278
+ lines.push(`Workspaces: ${m.workspaces.length} Workers: ${m.workers.length} Loops: ${m.loops.length} Turns: ${m.turns.length} Inference calls: ${m.inferenceCalls.length} (${bareCalls} BARE, ${embeddingCalls} embedding, ${erroredCalls} errored, ${pendingCalls} open) Emission attempts: ${m.turnAttempts.length} (${rejectedAttempts} rejected) Provider requests: ${m.providerRequests.length} (${pendingRequests} open) Log entries: ${m.logEntries.length}`);
279
+ if (packetFailures > 0)
280
+ lines.push(`Stored packet failures: ${packetFailures}`);
281
+ lines.push(`Semantic: channels=${m.embeddings.channel_entries} attached=${m.embeddings.derivation_complete} (vector=${m.embeddings.vector_complete} lexical=${m.embeddings.lexical} excluded=${m.embeddings.excluded} nonsemantic=${m.embeddings.nonsemantic} failed=${m.embeddings.failed}) unattached=${m.embeddings.unfinished} artifacts=${m.embeddings.derivation_artifacts_complete} complete/${m.embeddings.derivation_artifacts_building} building chunks=${m.embeddings.chunk_rows} models=${m.embeddings.models} token-derivations=${m.embeddings.token_derivations}`);
282
+ if (m.embeddings.dispositions.length > 0) {
283
+ lines.push("Semantic dispositions:");
284
+ for (const row of m.embeddings.dispositions) {
285
+ const address = `${EntryManifest.toPath(row.scheme, row.authority, row.pathname)}#${row.channel}`;
286
+ lines.push(` ${row.disposition} ${address}${row.reason === null ? "" : ` — ${row.reason}`}`);
287
+ }
288
+ }
289
+ // Triage rollup up top: how many conversations limped vs died vs ran clean, and the total
290
+ // error/strike load. The whole point of the "degenerate win" lens — see it before scrolling.
291
+ const health = m.loops.map((l) => DigestRender.#loopHealth(l, m));
292
+ const clean = health.filter((h) => h.verdict === "CLEAN").length;
293
+ const degen = health.filter((h) => h.verdict === "DEGENERATE-WIN").length;
294
+ const failed = health.filter((h) => h.verdict.startsWith("FAILED")).length;
295
+ const totalErrs = health.reduce((s, h) => s + h.errors, 0);
296
+ const totalItems = health.reduce((s, h) => s + h.errorItems, 0);
297
+ lines.push(`Health: ${clean} clean · ${degen > 0 ? `⚠ ${degen} degenerate-win` : "0 degenerate-win"} · ${failed} failed (${m.loops.length} loops; ${totalErrs} error rows, ${totalItems} minted error-items total)`);
298
+ for (const workspace of m.workspaces) {
299
+ lines.push("");
300
+ lines.push(`## Workspace #${workspace.id} — ${workspace.name}`);
301
+ const workspaceWorkers = m.workersByWorkspace.get(workspace.id) ?? [];
302
+ for (const worker of workspaceWorkers) {
303
+ lines.push("");
304
+ lines.push(`### Worker #${worker.id} — ${worker.name}`);
305
+ lines.push("");
306
+ lines.push("```");
307
+ lines.push(DigestRender.#renderWorkerShape(worker, m));
308
+ lines.push("```");
309
+ const workerLoops = m.loopsByWorker.get(worker.id) ?? [];
310
+ for (const loop of workerLoops) {
311
+ const terminal = DigestRender.#terminalResult(loop);
312
+ lines.push("");
313
+ const h = DigestRender.#loopHealth(loop, m);
314
+ const badge = h.verdict === "CLEAN"
315
+ ? " — CLEAN"
316
+ : ` — ${h.verdict === "DEGENERATE-WIN" ? "⚠ DEGENERATE-WIN" : h.verdict} (${h.errors} errors, ${h.errorItems} error-items)`;
317
+ lines.push(`#### Loop ${loop.sequence} (id=${loop.id}, status=${loop.status})${badge}`);
318
+ lines.push("");
319
+ lines.push(`Prompt: ${DigestRender.#summarize(loop.prompt, 160)}`);
320
+ if (loop.status !== 200 && terminal?.problem?.detail !== undefined) {
321
+ lines.push(`Terminal${loop.terminated_by !== null ? ` (${loop.terminated_by})` : ""}: ${DigestRender.#summarize(terminal.problem.detail, 400)}`);
322
+ }
323
+ const policy = DigestRender.parseJson(loop.policy, {});
324
+ const policySummary = Object.entries(policy)
325
+ .map(([key, value]) => `${key}=${typeof value === "string" ? value : JSON.stringify(value)}`)
326
+ .join(" ");
327
+ if (policySummary.length > 0)
328
+ lines.push(`Policy: ${policySummary}`);
329
+ lines.push("");
330
+ lines.push("```");
331
+ for (const t of m.turnsByLoop.get(loop.id) ?? [])
332
+ lines.push(DigestRender.#renderTurnLine(t, m));
333
+ lines.push("```");
334
+ }
335
+ }
336
+ }
337
+ return lines.join("\n");
338
+ }
339
+ static reasoning(m) {
340
+ const lines = [];
341
+ lines.push(`# plurnk-service reasoning`);
342
+ lines.push("");
343
+ lines.push("Turn chronology with every provider attempt. Rejected attempts remain explicit forensic evidence.");
344
+ for (const t of m.turns) {
345
+ const loop = m.loopsById.get(t.loop_id);
346
+ const worker = loop ? m.workersById.get(loop.worker_id) : undefined;
347
+ lines.push("");
348
+ lines.push(`## Worker ${worker?.id ?? "?"} / Loop ${loop?.sequence ?? "?"} / Turn ${t.sequence} (id=${t.id}, producer=${t.producer}, kind=${t.kind})`);
349
+ if (t.kind !== "inference") {
350
+ lines.push("");
351
+ lines.push("(operation turn; no provider inference)");
352
+ continue;
353
+ }
354
+ const attempts = m.attemptsByTurn.get(t.id) ?? [];
355
+ if (attempts.length === 0) {
356
+ const reasoning = t.packet !== null && StoredPacket.isAdmitted(t.packet)
357
+ ? t.packet.assistant.reasoning
358
+ : null;
359
+ lines.push("");
360
+ if (t.packetFailure !== null)
361
+ lines.push("(stored provider packet is invalid; see its packet artifacts)");
362
+ else if (typeof reasoning === "string" && reasoning.length > 0)
363
+ lines.push(reasoning);
364
+ else
365
+ lines.push("(no admitted provider reasoning)");
366
+ continue;
367
+ }
368
+ for (const attempt of attempts) {
369
+ const response = DigestRender.parseJson(attempt.response, {});
370
+ const parseErrors = DigestRender.parseJson(attempt.parse_errors, []);
371
+ lines.push("");
372
+ const disposition = attempt.state === "error"
373
+ ? "call error"
374
+ : attempt.state === "pending"
375
+ ? "open at capture"
376
+ : attempt.accepted === 1
377
+ ? "admitted"
378
+ : "rejected";
379
+ lines.push(`### Attempt ${attempt.sequence} - ${disposition}`);
380
+ const attributions = DigestRender.parseJson(attempt.attributions, []);
381
+ lines.push(`Attributions: ${attributions.length === 0 ? "(none)" : attributions.join(", ")}`);
382
+ if (attempt.failure !== null) {
383
+ lines.push(`Failure: ${JSON.stringify(DigestRender.parseJson(attempt.failure, attempt.failure))}`);
384
+ }
385
+ if (attempt.accepted !== 1) {
386
+ for (const error of parseErrors) {
387
+ if (typeof error.message === "string")
388
+ lines.push(`- ${error.message}`);
389
+ }
390
+ lines.push("");
391
+ }
392
+ const reasoning = response.assistant?.reasoning ?? null;
393
+ if (typeof reasoning === "string" && reasoning.length > 0)
394
+ lines.push(reasoning);
395
+ else {
396
+ const reasoningTokens = DigestRender.#accounting(m.requestsByAttempt.get(attempt.id) ?? [])?.usage?.outputTokenDetails?.reasoningTokens;
397
+ lines.push(reasoningTokens !== undefined && reasoningTokens > 0
398
+ ? `(provider reported ${reasoningTokens} reasoning tokens; no readable reasoning content returned)`
399
+ : "(no reasoning content returned)");
400
+ }
401
+ }
402
+ }
403
+ return lines.join("\n");
404
+ }
405
+ static #turnOpsSource(m, turn) {
406
+ const rows = (m.logEntriesByTurn.get(turn.id) ?? []).filter((row) => row.op === null
407
+ && LogBody.actionlessKind({ op: row.op, attrs: row.attrs }) === "turnOps");
408
+ if (rows.length > 1) {
409
+ throw new TypeError(`digest: turn ${turn.id} has ${rows.length} turnOps rows; expected at most one`);
410
+ }
411
+ const row = rows[0];
412
+ if (row === undefined)
413
+ return null;
414
+ const body = LogBody.resolve({
415
+ op: row.op,
416
+ attrs: row.attrs,
417
+ tx: null,
418
+ rx: row.rx,
419
+ mimetypeRx: row.mimetype_rx,
420
+ });
421
+ if (body.mimetype !== "text/vnd.plurnk") {
422
+ throw new TypeError(`digest: turn ${turn.id} turnOps has mimetype ${JSON.stringify(body.mimetype)}; expected text/vnd.plurnk`);
423
+ }
424
+ return body.content;
425
+ }
426
+ // Per-turn forensic files. turnOps is the source authority; PacketWire
427
+ // reproduces provider request slots, and assistantRaw preserves provider bytes.
428
+ static packetFiles(m) {
429
+ const written = [];
430
+ m.turns
431
+ .map((turn) => ({ turn, source: DigestRender.#turnOpsSource(m, turn) }))
432
+ .filter(({ turn, source }) => turn.packet !== null || turn.packetFailure !== null || source !== null)
433
+ .toSorted((a, b) => a.turn.id - b.turn.id)
434
+ .forEach(({ turn, source }, ordinal) => {
435
+ const padded = String(ordinal).padStart(3, "0");
436
+ const files = [];
437
+ const packet = turn.packet;
438
+ if (turn.packetFailure !== null) {
439
+ files.push([`packet${padded}.packet.raw.txt`, turn.packetFailure.raw], [`packet${padded}.packet.invalid.json`, JSON.stringify({
440
+ turnId: turn.id,
441
+ error: turn.packetFailure.error,
442
+ }, null, 2)]);
443
+ }
444
+ if (packet !== null) {
445
+ files.push([`packet${padded}.system.md`, PacketWire.renderSlot(packet.sections, "system")], [`packet${padded}.user.md`, PacketWire.renderSlot(packet.sections, "user")]);
446
+ }
447
+ if (source !== null) {
448
+ files.push([`packet${padded}.assistant.md`, source]);
449
+ }
450
+ if (packet !== null && StoredPacket.isAdmitted(packet)) {
451
+ if (source !== null && packet.assistant.content !== source) {
452
+ throw new TypeError(`digest: turn ${turn.id} packet assistant differs from its turnOps source`);
453
+ }
454
+ files.push([`packet${padded}.assistantRaw.json`, JSON.stringify(packet.assistantRaw, null, 2)]);
455
+ }
456
+ else if (packet !== null) {
457
+ files.push([
458
+ `packet${padded}.response.md`,
459
+ `# Packet ${ordinal} — request only\n\nNo provider response was admitted. Rejected attempt evidence, when present, is written separately.\n`,
460
+ ]);
461
+ }
462
+ for (const [file, body] of files) {
463
+ writeFileSync(join(m.digestDir, file), body);
464
+ written.push(file);
465
+ }
466
+ for (const attempt of m.attemptsByTurn.get(turn.id) ?? []) {
467
+ if (attempt.accepted === 1)
468
+ continue;
469
+ const attemptPadded = String(attempt.sequence).padStart(3, "0");
470
+ if (attempt.state !== "response") {
471
+ const file = `packet${padded}.attempt${attemptPadded}.${attempt.state}.json`;
472
+ writeFileSync(join(m.digestDir, file), JSON.stringify({
473
+ state: attempt.state,
474
+ failure: DigestRender.parseJson(attempt.failure),
475
+ attributions: DigestRender.parseJson(attempt.attributions, []),
476
+ openedAt: attempt.timestamp,
477
+ completedAt: attempt.completed_at,
478
+ }, null, 2));
479
+ written.push(file);
480
+ continue;
481
+ }
482
+ const response = DigestRender.parseJson(attempt.response, {});
483
+ const prefix = `packet${padded}.attempt${attemptPadded}.rejected`;
484
+ const attemptFiles = [
485
+ [
486
+ `${prefix}.assistant.md`,
487
+ typeof response.assistant?.content === "string" ? response.assistant.content : "",
488
+ ],
489
+ [`${prefix}.response.json`, JSON.stringify(response, null, 2)],
490
+ [`${prefix}.parse-errors.json`, JSON.stringify(DigestRender.parseJson(attempt.parse_errors, []), null, 2)],
491
+ [`${prefix}.attributions.json`, JSON.stringify(DigestRender.parseJson(attempt.attributions, []), null, 2)],
492
+ ];
493
+ for (const [file, body] of attemptFiles) {
494
+ writeFileSync(join(m.digestDir, file), body);
495
+ written.push(file);
496
+ }
497
+ }
498
+ });
499
+ return written;
500
+ }
501
+ static json(m) {
502
+ return JSON.stringify({
503
+ dbPath: m.dbPath,
504
+ semantic: m.embeddings,
505
+ workspaces: m.workspaces.map((s) => ({
506
+ id: s.id,
507
+ name: s.name,
508
+ accounting: DigestRender.#accounting(m.requestsByWorkspace.get(s.id) ?? []),
509
+ })),
510
+ workers: m.workers.map((r) => ({
511
+ id: r.id,
512
+ workspace_id: r.workspace_id,
513
+ name: r.name,
514
+ accounting: DigestRender.#accounting(m.requestsByWorker.get(r.id) ?? []),
515
+ })),
516
+ loops: m.loops.map((l) => ({
517
+ id: l.id, worker_id: l.worker_id, sequence: l.sequence, status: l.status,
518
+ prompt: l.prompt, policy: DigestRender.parseJson(l.policy, {}),
519
+ terminated_by: l.terminated_by,
520
+ result: DigestRender.#terminalResult(l),
521
+ accounting: DigestRender.#accounting(m.requestsByLoop.get(l.id) ?? []),
522
+ })),
523
+ turns: m.turns.map((t) => ({
524
+ id: t.id, loop_id: t.loop_id, sequence: t.sequence,
525
+ producer: t.producer, kind: t.kind,
526
+ status: t.status, completed_at: t.completed_at,
527
+ accounting: DigestRender.#accounting(m.requestsByTurn.get(t.id) ?? []),
528
+ finish_reason: t.finish_reason, model: t.model,
529
+ attributions: t.packet?.attributions ?? [],
530
+ packet_failure: t.packetFailure,
531
+ // Preserve the opaque provider and engine metadata for aggregate
532
+ // tooling. {§meta-passthrough}, {§rail-truth-engine-verdict}
533
+ meta: DigestRender.parseJson(t.meta ?? "null", null),
534
+ })),
535
+ inference_calls: m.inferenceCalls.map((call) => ({
536
+ id: call.id,
537
+ workspace_id: call.workspace_id,
538
+ turn_id: call.turn_id,
539
+ sequence: call.sequence,
540
+ kind: call.kind,
541
+ state: call.state,
542
+ attributions: DigestRender.parseJson(call.attributions, []),
543
+ request_model: call.request_model,
544
+ accounting: DigestRender.#accounting(m.requestsByInferenceCall.get(call.id) ?? []),
545
+ timestamp: call.timestamp,
546
+ completed_at: call.completed_at,
547
+ })),
548
+ model_calls: m.modelCalls.map((call) => ({
549
+ id: call.id,
550
+ turn_id: call.turn_id,
551
+ sequence: call.sequence,
552
+ kind: call.kind,
553
+ state: call.state,
554
+ response: DigestRender.parseJson(call.response),
555
+ failure: DigestRender.parseJson(call.failure),
556
+ attributions: DigestRender.parseJson(call.attributions, []),
557
+ accounting: DigestRender.#accounting(m.requestsByInferenceCall.get(call.id) ?? []),
558
+ finish_reason: call.finish_reason,
559
+ model: call.model,
560
+ request_model: call.request_model,
561
+ response_model: call.response_model,
562
+ timestamp: call.timestamp,
563
+ completed_at: call.completed_at,
564
+ turn_attempt_id: call.turn_attempt_id,
565
+ accepted: call.accepted === null ? null : call.accepted === 1,
566
+ parse_errors: DigestRender.parseJson(call.parse_errors, []),
567
+ log_entry_id: call.log_entry_id,
568
+ })),
569
+ embedding_calls: m.embeddingCalls.map((call) => ({
570
+ id: call.id,
571
+ workspace_id: call.workspace_id,
572
+ turn_id: call.turn_id,
573
+ sequence: call.sequence,
574
+ kind: call.kind,
575
+ state: call.state,
576
+ model: call.model,
577
+ input_count: call.input_count,
578
+ output_count: call.output_count,
579
+ metadata: DigestRender.parseJson(call.metadata),
580
+ failure: DigestRender.parseJson(call.failure),
581
+ accounting: DigestRender.#accounting(m.requestsByInferenceCall.get(call.id) ?? []),
582
+ timestamp: call.timestamp,
583
+ completed_at: call.completed_at,
584
+ })),
585
+ turn_attempts: m.turnAttempts.map((attempt) => ({
586
+ id: attempt.id,
587
+ turn_id: attempt.turn_id,
588
+ sequence: attempt.sequence,
589
+ state: attempt.state,
590
+ accepted: attempt.accepted === null ? null : attempt.accepted === 1,
591
+ response: DigestRender.parseJson(attempt.response),
592
+ failure: DigestRender.parseJson(attempt.failure),
593
+ parse_errors: DigestRender.parseJson(attempt.parse_errors, []),
594
+ attributions: DigestRender.parseJson(attempt.attributions, []),
595
+ accounting: DigestRender.#accounting(m.requestsByAttempt.get(attempt.id) ?? []),
596
+ finish_reason: attempt.finish_reason,
597
+ model: attempt.model,
598
+ timestamp: attempt.timestamp,
599
+ completed_at: attempt.completed_at,
600
+ })),
601
+ provider_requests: m.providerRequests.map((request) => ({
602
+ id: request.id,
603
+ inference_call_id: request.inference_call_id,
604
+ turn_attempt_id: request.turn_attempt_id,
605
+ kind: request.kind,
606
+ sequence: request.sequence,
607
+ state: request.state,
608
+ accounting: request.state === "settled"
609
+ ? DigestRender.#requestAccounting(request)
610
+ : null,
611
+ started_at: request.started_at,
612
+ completed_at: request.completed_at,
613
+ })),
614
+ log_entries: m.logEntries.map((le) => ({
615
+ id: le.id, worker_id: le.worker_id, loop_id: le.loop_id,
616
+ turn_id: le.turn_id, sequence: le.sequence, origin: le.origin,
617
+ source: le.source, model_call_id: le.model_call_id,
618
+ attrs: DigestRender.parseJson(le.attrs, {}),
619
+ op: le.op, target: DigestRender.#renderTarget(le),
620
+ status_rx: le.status_rx, state: le.state, outcome: le.outcome,
621
+ initial_folded: DigestRender.parseJson(le.initial_folded, []),
622
+ projection: {
623
+ active: le.projection_active === 1,
624
+ folded: DigestRender.parseJson(le.projection_folded, []),
625
+ },
626
+ ...(DigestRender.#renderStream(le) === null
627
+ ? {}
628
+ : { stream: DigestRender.#renderStream(le) }),
629
+ ...(le.status_rx >= 400 ? { problem: DigestRender.#rowProblem(le) } : {}),
630
+ })),
631
+ log_curation_effects: m.curationEffects.map(({ active_before, active_after, folded_before, folded_after, ...effect }) => ({
632
+ ...effect,
633
+ active_before: active_before === 1,
634
+ active_after: active_after === 1,
635
+ folded_before: DigestRender.parseJson(folded_before, []),
636
+ folded_after: DigestRender.parseJson(folded_after, []),
637
+ })),
638
+ }, null, 2);
639
+ }
640
+ }
641
+ //# sourceMappingURL=DigestRender.js.map