@skrr-ai/cli 0.1.119 → 0.1.120

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 (276) hide show
  1. package/dist/base-command.d.ts +31 -0
  2. package/dist/base-command.js +106 -4
  3. package/dist/commands/commands.js +11 -0
  4. package/dist/commands/commitments/action-proposal.d.ts +13 -0
  5. package/dist/commands/commitments/action-proposal.js +39 -0
  6. package/dist/commands/commitments/action-proposals/decide.js +8 -1
  7. package/dist/commands/commitments/action-proposals/execute.js +11 -1
  8. package/dist/commands/commitments/create.js +2 -2
  9. package/dist/commands/commitments/doctor.d.ts +8 -0
  10. package/dist/commands/commitments/doctor.js +22 -0
  11. package/dist/commands/commitments/execute-remediation.js +12 -1
  12. package/dist/commands/commitments/observations.js +2 -2
  13. package/dist/commands/commitments/pack/bind.d.ts +18 -1
  14. package/dist/commands/commitments/pack/bind.js +66 -8
  15. package/dist/commands/commitments/pack/plan.d.ts +4 -1
  16. package/dist/commands/commitments/pack/plan.js +30 -2
  17. package/dist/commands/commitments/update.js +8 -2
  18. package/dist/commands/completion/index.js +1 -1
  19. package/dist/commands/completion/values.js +1 -1
  20. package/dist/commands/computer-use/audio.d.ts +1 -0
  21. package/dist/commands/computer-use/audio.js +1 -0
  22. package/dist/commands/computer-use/browser.d.ts +1 -0
  23. package/dist/commands/computer-use/browser.js +1 -0
  24. package/dist/commands/computer-use/disable.d.ts +1 -0
  25. package/dist/commands/computer-use/disable.js +1 -0
  26. package/dist/commands/computer-use/enable.d.ts +1 -0
  27. package/dist/commands/computer-use/enable.js +1 -0
  28. package/dist/commands/computer-use/index.d.ts +1 -0
  29. package/dist/commands/computer-use/index.js +1 -0
  30. package/dist/commands/computer-use/install.d.ts +1 -0
  31. package/dist/commands/computer-use/install.js +1 -0
  32. package/dist/commands/computer-use/recover.d.ts +1 -0
  33. package/dist/commands/computer-use/recover.js +1 -0
  34. package/dist/commands/computer-use/status.d.ts +1 -0
  35. package/dist/commands/computer-use/status.js +1 -0
  36. package/dist/commands/computer-use/stop.d.ts +1 -0
  37. package/dist/commands/computer-use/stop.js +1 -0
  38. package/dist/commands/context.js +16 -11
  39. package/dist/commands/convos/recovery.js +1 -1
  40. package/dist/commands/daemon/diagnostics.d.ts +9 -0
  41. package/dist/commands/daemon/diagnostics.js +53 -0
  42. package/dist/commands/daemon/index.js +2 -0
  43. package/dist/commands/daemon/maintenance/attest.d.ts +15 -0
  44. package/dist/commands/daemon/maintenance/attest.js +52 -0
  45. package/dist/commands/daemon/maintenance/hold.d.ts +17 -0
  46. package/dist/commands/daemon/maintenance/hold.js +70 -0
  47. package/dist/commands/daemon/maintenance/index.d.ts +12 -0
  48. package/dist/commands/daemon/maintenance/index.js +30 -0
  49. package/dist/commands/daemon/maintenance/resume.d.ts +13 -0
  50. package/dist/commands/daemon/maintenance/resume.js +36 -0
  51. package/dist/commands/daemon/maintenance/status.d.ts +13 -0
  52. package/dist/commands/daemon/maintenance/status.js +36 -0
  53. package/dist/commands/doctor.d.ts +4 -1
  54. package/dist/commands/doctor.js +50 -6
  55. package/dist/commands/followups/plans/cancel-batch.js +6 -3
  56. package/dist/commands/followups/plans/capacity.js +7 -3
  57. package/dist/commands/followups/plans/observability.js +11 -3
  58. package/dist/commands/followups/plans/preferences/set.js +26 -8
  59. package/dist/commands/followups/plans/preferences.js +1 -1
  60. package/dist/commands/followups/plans/show.js +1 -1
  61. package/dist/commands/goals/key-results/assess.d.ts +36 -0
  62. package/dist/commands/goals/key-results/assess.js +110 -0
  63. package/dist/commands/goals/key-results/history.d.ts +30 -0
  64. package/dist/commands/goals/key-results/history.js +104 -0
  65. package/dist/commands/goals/key-results/list.d.ts +5 -4
  66. package/dist/commands/goals/key-results/list.js +12 -6
  67. package/dist/commands/goals/key-results/show.d.ts +4 -2
  68. package/dist/commands/goals/key-results/show.js +25 -4
  69. package/dist/commands/goals/key-results/update.d.ts +4 -0
  70. package/dist/commands/goals/key-results/update.js +18 -24
  71. package/dist/commands/goals/plan-now.js +12 -1
  72. package/dist/commands/goals/planner-compare.d.ts +30 -0
  73. package/dist/commands/goals/planner-compare.js +136 -0
  74. package/dist/commands/machines/dedicated/service/exec.d.ts +3 -0
  75. package/dist/commands/machines/dedicated/service/exec.js +70 -11
  76. package/dist/commands/machines/hosted/destroy.d.ts +1 -0
  77. package/dist/commands/machines/hosted/destroy.js +11 -1
  78. package/dist/commands/machines/hosted/exec.d.ts +1 -0
  79. package/dist/commands/machines/hosted/exec.js +12 -1
  80. package/dist/commands/machines/hosted/index.js +1 -0
  81. package/dist/commands/machines/hosted/operation.d.ts +14 -0
  82. package/dist/commands/machines/hosted/operation.js +51 -0
  83. package/dist/commands/machines/hosted/pull.js +1 -1
  84. package/dist/commands/machines/hosted/start.js +6 -2
  85. package/dist/commands/spaces/auto-executor/compare.d.ts +18 -0
  86. package/dist/commands/spaces/auto-executor/compare.js +139 -0
  87. package/dist/commands/spaces/auto-executor/runs.d.ts +3 -0
  88. package/dist/commands/spaces/auto-executor/runs.js +13 -1
  89. package/dist/commands/spaces/charter/create-from-description.d.ts +21 -0
  90. package/dist/commands/spaces/charter/create-from-description.js +77 -0
  91. package/dist/commands/spaces/charter/show.d.ts +22 -0
  92. package/dist/commands/spaces/charter/show.js +61 -0
  93. package/dist/commands/spaces/heartbeats/list.d.ts +1 -16
  94. package/dist/commands/spaces/heartbeats/list.js +13 -0
  95. package/dist/commands/spaces/install-template.js +3 -0
  96. package/dist/commands/spaces/loops/migrate.d.ts +32 -0
  97. package/dist/commands/spaces/loops/migrate.js +103 -0
  98. package/dist/commands/spaces/loops/release.d.ts +14 -0
  99. package/dist/commands/spaces/loops/release.js +59 -0
  100. package/dist/commands/spaces/notebook.d.ts +25 -0
  101. package/dist/commands/spaces/notebook.js +75 -0
  102. package/dist/commands/spaces/proposals/discard.d.ts +17 -0
  103. package/dist/commands/spaces/proposals/discard.js +17 -0
  104. package/dist/commands/spaces/proposals/keep.d.ts +17 -0
  105. package/dist/commands/spaces/proposals/keep.js +17 -0
  106. package/dist/commands/spaces/proposals/list.d.ts +24 -0
  107. package/dist/commands/spaces/proposals/list.js +89 -0
  108. package/dist/commands/spaces/skills/propose.d.ts +29 -0
  109. package/dist/commands/spaces/skills/propose.js +82 -0
  110. package/dist/commands/spaces/templates/fork.js +3 -0
  111. package/dist/commands/spaces/triggers/show.js +6 -0
  112. package/dist/commands/spaces/workflows/retry.d.ts +1 -0
  113. package/dist/commands/spaces/workflows/retry.js +8 -1
  114. package/dist/commands/spaces/workflows/triggers/create.js +3 -2
  115. package/dist/commands/spaces/workflows/triggers/fire.js +4 -0
  116. package/dist/commands/spaces/workflows/triggers/update.js +2 -1
  117. package/dist/commands/spaces/write-policy/set.d.ts +22 -0
  118. package/dist/commands/spaces/write-policy/set.js +82 -0
  119. package/dist/commands/spaces/write-policy/show.d.ts +19 -0
  120. package/dist/commands/spaces/write-policy/show.js +49 -0
  121. package/dist/commands/store/artifacts/receipt.d.ts +14 -0
  122. package/dist/commands/store/artifacts/receipt.js +34 -0
  123. package/dist/commands/store/artifacts/release.d.ts +2 -0
  124. package/dist/commands/store/artifacts/release.js +11 -0
  125. package/dist/commands/store/governance/publish-release.d.ts +2 -0
  126. package/dist/commands/store/governance/publish-release.js +30 -1
  127. package/dist/commands/store/governance/release-receipt.d.ts +12 -0
  128. package/dist/commands/store/governance/release-receipt.js +32 -0
  129. package/dist/commands/tasks/approvals/list.d.ts +11 -5
  130. package/dist/commands/tasks/approvals/list.js +13 -6
  131. package/dist/commands/tasks/attachments/facts.d.ts +12 -0
  132. package/dist/commands/tasks/attachments/facts.js +29 -0
  133. package/dist/commands/tasks/attachments/upload.d.ts +1 -0
  134. package/dist/commands/tasks/attachments/upload.js +16 -0
  135. package/dist/commands/tasks/bulk-update.js +35 -1
  136. package/dist/commands/tasks/context.d.ts +15 -0
  137. package/dist/commands/tasks/context.js +69 -0
  138. package/dist/commands/tasks/copy.d.ts +18 -0
  139. package/dist/commands/tasks/copy.js +85 -0
  140. package/dist/commands/tasks/create-tree.js +97 -23
  141. package/dist/commands/tasks/create.js +9 -1
  142. package/dist/commands/tasks/deliverable/add.js +20 -10
  143. package/dist/commands/tasks/move.js +8 -1
  144. package/dist/commands/tasks/operations/show.d.ts +16 -0
  145. package/dist/commands/tasks/operations/show.js +98 -0
  146. package/dist/commands/tasks/operations/wait.d.ts +22 -0
  147. package/dist/commands/tasks/operations/wait.js +114 -0
  148. package/dist/commands/tasks/preflight.d.ts +1 -0
  149. package/dist/commands/tasks/preflight.js +11 -1
  150. package/dist/commands/tasks/rearm-recurrence.js +4 -0
  151. package/dist/commands/tasks/result/show.js +22 -0
  152. package/dist/commands/tasks/result/submit.d.ts +1 -0
  153. package/dist/commands/tasks/result/submit.js +3 -0
  154. package/dist/commands/tasks/retry-receipt.d.ts +15 -0
  155. package/dist/commands/tasks/retry-receipt.js +89 -0
  156. package/dist/commands/tasks/retry-review.d.ts +16 -0
  157. package/dist/commands/tasks/retry-review.js +66 -0
  158. package/dist/commands/tasks/retry.d.ts +4 -8
  159. package/dist/commands/tasks/retry.js +34 -68
  160. package/dist/commands/tasks/runs.d.ts +21 -0
  161. package/dist/commands/tasks/runs.js +95 -0
  162. package/dist/commands/tasks/show.d.ts +2 -0
  163. package/dist/commands/tasks/show.js +15 -1
  164. package/dist/commands/tasks/start-receipt.d.ts +15 -0
  165. package/dist/commands/tasks/start-receipt.js +95 -0
  166. package/dist/commands/tasks/start.d.ts +4 -0
  167. package/dist/commands/tasks/start.js +49 -3
  168. package/dist/commands/tasks/update.js +3 -0
  169. package/dist/commands/tasks/upsert.js +17 -3
  170. package/dist/commands/tasks/watch.d.ts +3 -0
  171. package/dist/commands/tasks/watch.js +85 -20
  172. package/dist/commands/wiki/history.d.ts +33 -0
  173. package/dist/commands/wiki/history.js +134 -0
  174. package/dist/commands/wiki/ls.d.ts +2 -0
  175. package/dist/commands/wiki/ls.js +8 -0
  176. package/dist/commands/wiki/update.d.ts +3 -1
  177. package/dist/commands/wiki/update.js +24 -2
  178. package/dist/commands/wiki/write.d.ts +22 -0
  179. package/dist/commands/wiki/write.js +144 -6
  180. package/dist/generated/agent-guides.json +81 -81
  181. package/dist/generated/managed-instruction-guides.json +1 -1
  182. package/dist/lib/agent-guides.d.ts +9 -0
  183. package/dist/lib/agent-guides.js +23 -1
  184. package/dist/lib/agentic-stream.d.ts +5 -0
  185. package/dist/lib/agentic-stream.js +122 -3
  186. package/dist/lib/auth-diagnostics.d.ts +39 -0
  187. package/dist/lib/auth-diagnostics.js +71 -0
  188. package/dist/lib/cli-client-identity.d.ts +6 -0
  189. package/dist/lib/cli-client-identity.js +16 -0
  190. package/dist/lib/cli-installation-diagnostics.d.ts +52 -0
  191. package/dist/lib/cli-installation-diagnostics.js +254 -0
  192. package/dist/lib/command-hygiene.d.ts +45 -0
  193. package/dist/lib/command-hygiene.js +413 -0
  194. package/dist/lib/commitments.d.ts +64 -3
  195. package/dist/lib/commitments.js +34 -6
  196. package/dist/lib/completion-index.d.ts +16 -4
  197. package/dist/lib/completion-index.js +50 -14
  198. package/dist/lib/config.js +7 -1
  199. package/dist/lib/daemon-maintenance-command.d.ts +22 -0
  200. package/dist/lib/daemon-maintenance-command.js +39 -0
  201. package/dist/lib/daemon-maintenance.d.ts +275 -0
  202. package/dist/lib/daemon-maintenance.js +1035 -0
  203. package/dist/lib/dedicated-service.d.ts +3 -2
  204. package/dist/lib/dedicated-service.js +11 -3
  205. package/dist/lib/governed-store.d.ts +8 -0
  206. package/dist/lib/governed-store.js +5 -0
  207. package/dist/lib/hosted-machines.d.ts +33 -2
  208. package/dist/lib/hosted-machines.js +19 -1
  209. package/dist/lib/json-output.d.ts +1 -1
  210. package/dist/lib/machines.d.ts +9 -1
  211. package/dist/lib/machines.js +15 -1
  212. package/dist/lib/node-adapter.js +13 -0
  213. package/dist/lib/record-output.d.ts +22 -0
  214. package/dist/lib/record-output.js +78 -0
  215. package/dist/lib/roles.d.ts +3 -3
  216. package/dist/lib/scoped-memory-command.d.ts +18 -0
  217. package/dist/lib/scoped-memory-command.js +53 -2
  218. package/dist/lib/space-charter.d.ts +49 -0
  219. package/dist/lib/space-charter.js +78 -0
  220. package/dist/lib/space-loop-migration.d.ts +18 -0
  221. package/dist/lib/space-loop-migration.js +128 -0
  222. package/dist/lib/space-notebook.d.ts +22 -0
  223. package/dist/lib/space-notebook.js +140 -0
  224. package/dist/lib/space-proposal-decision-command.d.ts +36 -0
  225. package/dist/lib/space-proposal-decision-command.js +108 -0
  226. package/dist/lib/space-proposals.d.ts +44 -0
  227. package/dist/lib/space-proposals.js +186 -0
  228. package/dist/lib/space-templates.d.ts +8 -0
  229. package/dist/lib/space-templates.js +17 -0
  230. package/dist/lib/space-write-policy.d.ts +24 -0
  231. package/dist/lib/space-write-policy.js +103 -0
  232. package/dist/lib/task-agent-context-capabilities.d.ts +10 -0
  233. package/dist/lib/task-agent-context-capabilities.js +22 -0
  234. package/dist/lib/task-agent-context-compatibility.d.ts +9 -0
  235. package/dist/lib/task-agent-context-compatibility.js +30 -0
  236. package/dist/lib/task-asset-upload.d.ts +22 -0
  237. package/dist/lib/task-asset-upload.js +257 -29
  238. package/dist/lib/task-batch.d.ts +15 -1
  239. package/dist/lib/task-batch.js +35 -2
  240. package/dist/lib/task-operation-watch.d.ts +66 -0
  241. package/dist/lib/task-operation-watch.js +159 -0
  242. package/dist/lib/task-resolver.d.ts +1 -1
  243. package/dist/lib/task-resolver.js +4 -3
  244. package/dist/lib/task-work.d.ts +1 -1
  245. package/dist/lib/task-work.js +35 -1
  246. package/dist/lib/task-write-attribution.d.ts +14 -0
  247. package/dist/lib/task-write-attribution.js +35 -2
  248. package/dist/lib/triggers.d.ts +11 -0
  249. package/dist/lib/triggers.js +7 -0
  250. package/dist/lib/wiki-update.d.ts +1 -0
  251. package/dist/lib/wiki-update.js +10 -1
  252. package/dist/lib/wiki.js +4 -1
  253. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +9 -1
  254. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +31 -15
  255. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +1 -0
  256. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +13 -7
  257. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.d.ts +370 -0
  258. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.js +315 -0
  259. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.d.ts +178 -0
  260. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.js +262 -0
  261. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.d.ts +51 -0
  262. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.js +106 -0
  263. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +9 -1
  264. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +32 -5
  265. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +1 -0
  266. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +33 -14
  267. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.d.ts +370 -0
  268. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.js +404 -0
  269. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.d.ts +178 -0
  270. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.js +342 -0
  271. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.d.ts +51 -0
  272. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.js +138 -0
  273. package/dist/node_modules/@skrr-ai/auth-core/package.json +20 -0
  274. package/dist/node_modules/@skrr-ai/data-provider/index.js +27085 -20250
  275. package/oclif.manifest.json +10566 -7247
  276. package/package.json +20 -2
@@ -0,0 +1,404 @@
1
+ /**
2
+ * The pure core of a GUIDED update (OSK-19107, Space OSK-S-524, tasks M51 to M53):
3
+ * hold the machine, wait for it to be quiet, update, verify, and give the machine
4
+ * back. The Desktop app and a standalone `skrr` install both run it, so what they
5
+ * decide on, and the words they use for what is in the way, cannot drift apart.
6
+ *
7
+ * This file is DECISIONS ONLY. It performs no I/O, reads no clock and holds no
8
+ * state, so every rule below is a table test. The orchestration (a persisted flow
9
+ * record, polling, the installer, the supervisor) lives with each caller, because
10
+ * that is the part that differs between a Desktop that quits mid-flow and a CLI that
11
+ * runs in the foreground.
12
+ *
13
+ * ## Why these rules (each names the failure it prevents)
14
+ *
15
+ * - **`prepare` is a probe that briefly refuses admitted work.** While it runs the
16
+ * daemon refuses admitted tool calls with an infra-class error, and a tool-proxy Run
17
+ * that sees one drops its tools to the Hosted Machine for good. So a guided flow
18
+ * calls it ONLY when the server's reading says there is nothing admitted to refuse
19
+ * ({@link prepareBasis}), never to find out whether there is.
20
+ * - **`unknown` is an answer.** A source nobody could read is never zero and never
21
+ * ready. {@link noServerVeto} is deliberately strict: every server source must have
22
+ * been read, and none may list active or unproven work.
23
+ * - **A server source can only veto.** Only the daemon's own positively announced
24
+ * report grants `ready`; the owner's attestation and the daemon's own `prepare`
25
+ * (labelled `no_veto`, D38) are the two other bases, and each is labelled as what
26
+ * it is wherever it is shown.
27
+ * - **Blockers are shown in the words of whoever knows them.** The daemon's blocker
28
+ * `reason` and the server's holder `label` are the vocabulary; nothing here
29
+ * re-spells them. They are bounded and never a raw path, because a diagnostic can
30
+ * carry one.
31
+ * - **A new PID, an exit code or a cloud connection is not a verified update.**
32
+ * {@link verifyReplacement} requires the daemon's own `verified`, from a process
33
+ * other than the one that was replaced, for the build that was asked for, and the
34
+ * same machine identity afterwards. A different identity is the reinstall case: the
35
+ * hold is left in place and shown, never resumed.
36
+ *
37
+ * The shapes are STRUCTURAL SUBSETS of `DaemonMaintenanceView` in
38
+ * `@skrr-ai/data-provider` (`daemonMaintenance.ts`), which is the one vocabulary.
39
+ * This package cannot import it (it sits below it), so `guidedUpdate.test.ts` reads
40
+ * that file and fails when a list mirrored here drifts from it.
41
+ */
42
+ /** The server sources that can only veto. `daemon_report` is the only one that grants. */
43
+ export const GUIDED_SERVER_SOURCES = [
44
+ 'claims',
45
+ 'receipts',
46
+ 'sessions',
47
+ 'runs',
48
+ 'leases',
49
+ 'custody',
50
+ ];
51
+ /** The refusals the owner API names, mirrored from `DAEMON_MAINTENANCE_REFUSALS`. */
52
+ export const GUIDED_REFUSALS = [
53
+ 'managed_machine',
54
+ 'no_stable_identity',
55
+ 'not_found',
56
+ 'ambiguous',
57
+ 'generation_conflict',
58
+ 'not_held',
59
+ 'already_held',
60
+ 'attestation_not_applicable',
61
+ 'not_owner',
62
+ 'held_by_other',
63
+ ];
64
+ export function isGuidedRefusal(value) {
65
+ return typeof value === 'string' && GUIDED_REFUSALS.includes(value);
66
+ }
67
+ /** The daemon handoff phases, mirrored from `daemon/src/update/handoff.ts`. */
68
+ export const GUIDED_HANDOFF_PHASES = [
69
+ 'ready',
70
+ 'deferred',
71
+ 'reconnecting',
72
+ 'verified',
73
+ 'failed',
74
+ 'rolled-back',
75
+ ];
76
+ /**
77
+ * Whether the server holds a reading that nothing vetoes, for a daemon that cannot
78
+ * report (D38). Strict on purpose: a source that is absent from the list, unreadable,
79
+ * truncated, or that names active or unproven work, is a veto. `daemon_report` must
80
+ * be `not_announced`: any other answer means the reading is not the
81
+ * `no_inventory_capability` case this is for.
82
+ */
83
+ export function noServerVeto(sources) {
84
+ // `Array.isArray` does not narrow a readonly array, so the list is named explicitly.
85
+ if (!Array.isArray(sources))
86
+ return false;
87
+ const list = sources;
88
+ for (const name of GUIDED_SERVER_SOURCES) {
89
+ const source = list.find((s) => s && s.source === name);
90
+ if (!source || source.read !== 'ok' || source.truncated === true)
91
+ return false;
92
+ const holders = Array.isArray(source.holders)
93
+ ? source.holders
94
+ : null;
95
+ if (!holders)
96
+ return false;
97
+ if (holders.some((h) => h && (h.class === 'active' || h.class === 'unproven')))
98
+ return false;
99
+ }
100
+ const report = list.find((s) => s && s.source === 'daemon_report');
101
+ return report !== undefined && report.read === 'not_announced';
102
+ }
103
+ /**
104
+ * Read the owner API's view for a flow that took `generation`.
105
+ *
106
+ * Order matters and is the rule set (each is a test):
107
+ * 1. No live hold: `not_held`. A different live generation: `generation_changed`.
108
+ * 2. The fence is not confirmed (`pausing`): nothing below it means anything.
109
+ * 3. The reading is `ready` (the daemon's own report): `ready` by evidence.
110
+ * 4. `draining`: work is running. Evidence, never overridden.
111
+ * 5. `unknown`: ready by attestation if the Journey says the owner vouched for this
112
+ * cause; `no_report` if the daemon cannot report and no source vetoes; else
113
+ * `unknown`. No reading at all is `unknown (source_unreadable)`, never ready.
114
+ */
115
+ export function evaluateReadiness(view, expectedGeneration) {
116
+ const hold = view?.hold;
117
+ if (!view || !hold || hold.lifecycle !== 'held')
118
+ return { kind: 'not_held' };
119
+ if (expectedGeneration !== null && hold.generation !== expectedGeneration) {
120
+ return { kind: 'generation_changed', currentGeneration: hold.generation };
121
+ }
122
+ if (view.journey?.phase === 'pausing')
123
+ return { kind: 'pausing' };
124
+ const reading = view.reading;
125
+ if (!reading)
126
+ return { kind: 'unknown', cause: 'source_unreadable' };
127
+ if (reading.state === 'ready')
128
+ return { kind: 'ready', basis: 'evidence' };
129
+ if (reading.state === 'draining')
130
+ return { kind: 'draining' };
131
+ if (reading.state !== 'unknown')
132
+ return { kind: 'unknown', cause: 'source_unreadable' };
133
+ if (view.journey?.phase === 'ready' && view.journey.modifiers?.includes('attested')) {
134
+ return { kind: 'ready', basis: 'attested' };
135
+ }
136
+ if (reading.cause === 'no_inventory_capability' && noServerVeto(reading.sources)) {
137
+ return { kind: 'no_report' };
138
+ }
139
+ return { kind: 'unknown', cause: reading.cause ?? null };
140
+ }
141
+ /**
142
+ * Whether a guided flow may call the daemon's `prepare`, and on what basis. `null`
143
+ * means NO: `prepare` is never a way to find out. A legacy daemon never gets one:
144
+ * it has no endpoint, and its restart is the owner's attested decision, not a probe.
145
+ */
146
+ export function prepareBasis(readiness, opts) {
147
+ if (opts.legacy)
148
+ return null;
149
+ if (readiness.kind === 'ready')
150
+ return readiness.basis;
151
+ if (readiness.kind === 'no_report')
152
+ return 'no_veto';
153
+ return null;
154
+ }
155
+ /**
156
+ * Whether a legacy daemon (no handoff endpoint) may be restarted by the supervisor.
157
+ * Only on the owner's attestation: the daemon cannot report what it is doing and a
158
+ * SIGTERM ends its wrappers, so nothing but the owner's word can vouch for it.
159
+ */
160
+ export function legacyRestartAllowed(readiness) {
161
+ return readiness.kind === 'ready' && readiness.basis === 'attested';
162
+ }
163
+ export const MAX_OWNER_BLOCKERS = 20;
164
+ const BLOCKER_TEXT_MAX = 200;
165
+ const ID_PATTERN = /^[A-Za-z0-9_-]{1,120}$/;
166
+ // A diagnostic can carry a local path or a URL. Showing one would leak the layout of
167
+ // someone's machine into a notice, so a reason that looks like one is dropped whole.
168
+ const PATHLIKE = /(?:^|[\s("'=])(?:~|\.{1,2})?[\\/](?:[\w.-]+[\\/])+|[A-Za-z]:[\\/]|\\\\|file:\/\/|\/(?:Users|home|var|tmp|private|opt|etc|usr)\//i;
169
+ /** A sentence safe to put in front of the owner, or `undefined` when it is not. */
170
+ export function sanitizeBlockerText(value, max = BLOCKER_TEXT_MAX) {
171
+ if (typeof value !== 'string')
172
+ return undefined;
173
+ const cleaned = Array.from(value, (ch) => {
174
+ const code = ch.charCodeAt(0);
175
+ return code < 0x20 || code === 0x7f ? ' ' : ch;
176
+ })
177
+ .join('')
178
+ .replace(/\s+/g, ' ')
179
+ .trim();
180
+ if (!cleaned || PATHLIKE.test(cleaned))
181
+ return undefined;
182
+ return cleaned.length > max ? `${cleaned.slice(0, max - 1)}…` : cleaned;
183
+ }
184
+ function boundedId(value) {
185
+ return typeof value === 'string' && ID_PATTERN.test(value) ? value : undefined;
186
+ }
187
+ /**
188
+ * Everything in the way, from the three places that know: the server's holders
189
+ * (active and unproven work: `pending_delivery` and `forensic` never block), the
190
+ * daemon's own refusal from `prepare`, and the work the Desktop app itself hosts and
191
+ * ends when it quits (`LocalSessionService` processes and agent browser windows,
192
+ * which the daemon's inventory cannot see).
193
+ *
194
+ * A daemon blocker whose id equals a server holder's ref is the same work seen twice:
195
+ * it is listed once, with the daemon's reason and the server's label. Whether the two
196
+ * ids are in fact comparable was never established (review §6.2 item 2), so a match
197
+ * only MERGES the wording and never hides a blocker that has no match.
198
+ */
199
+ export function describeBlockers(input) {
200
+ const out = [];
201
+ const serverByRef = new Map();
202
+ const serverHolders = [];
203
+ for (const source of input.view?.reading?.sources ?? []) {
204
+ for (const holder of source?.holders ?? []) {
205
+ if (!holder || (holder.class !== 'active' && holder.class !== 'unproven'))
206
+ continue;
207
+ serverHolders.push(holder);
208
+ if (typeof holder.ref === 'string')
209
+ serverByRef.set(holder.ref, holder);
210
+ }
211
+ }
212
+ const merged = new Set();
213
+ for (const blocker of input.daemon ?? []) {
214
+ if (!blocker || typeof blocker.kind !== 'string')
215
+ continue;
216
+ const id = boundedId(blocker.id);
217
+ const holder = blocker.id ? serverByRef.get(blocker.id) : undefined;
218
+ if (holder)
219
+ merged.add(holder);
220
+ const label = holder ? sanitizeBlockerText(holder.label, 120) : undefined;
221
+ out.push({
222
+ source: 'daemon',
223
+ kind: blocker.kind.slice(0, 64),
224
+ text: sanitizeBlockerText(blocker.reason),
225
+ ...(label ? { label } : {}),
226
+ ...(id ? { id } : {}),
227
+ ...(holder?.since !== undefined ? { since: holder.since } : {}),
228
+ });
229
+ }
230
+ for (const holder of serverHolders) {
231
+ if (merged.has(holder))
232
+ continue;
233
+ const id = boundedId(holder.ref);
234
+ out.push({
235
+ source: 'server',
236
+ kind: String(holder.kind || 'work').slice(0, 64),
237
+ text: sanitizeBlockerText(holder.label, 120),
238
+ ...(id ? { id } : {}),
239
+ ...(holder.since !== undefined ? { since: holder.since } : {}),
240
+ });
241
+ }
242
+ const desktop = input.desktop;
243
+ if (desktop && desktop.localSessions > 0) {
244
+ out.push({ source: 'desktop', kind: 'desktop_local_sessions', count: desktop.localSessions });
245
+ }
246
+ if (desktop && desktop.agentBrowserWindows > 0) {
247
+ out.push({
248
+ source: 'desktop',
249
+ kind: 'desktop_agent_browser',
250
+ count: desktop.agentBrowserWindows,
251
+ });
252
+ }
253
+ return out.slice(0, MAX_OWNER_BLOCKERS);
254
+ }
255
+ /**
256
+ * A string that changes whenever the set of things in the way changes. The "update
257
+ * now anyway" confirmation carries the one it was shown, and the action is refused
258
+ * when it no longer matches: a confirmation that names what WAS running is not a
259
+ * confirmation of what is.
260
+ */
261
+ export function blockersFingerprint(blockers) {
262
+ return blockers
263
+ .map((b) => [b.source, b.kind, b.id ?? '', b.count ?? ''].join('|'))
264
+ .sort()
265
+ .join(';');
266
+ }
267
+ /**
268
+ * The labels the owner is shown, as the server records them in the outcome
269
+ * (`acknowledged`): each bounded to the server's 160-character limit.
270
+ */
271
+ export function acknowledgedLabels(blockers, max = 20) {
272
+ const labels = [];
273
+ for (const b of blockers) {
274
+ const detail = b.label ?? b.text;
275
+ const text = b.count ? `${b.kind} (${b.count})` : detail ? `${b.kind}: ${detail}` : b.kind;
276
+ labels.push(text.slice(0, 160));
277
+ if (labels.length >= max)
278
+ break;
279
+ }
280
+ return labels;
281
+ }
282
+ function isHandoffStatus(value) {
283
+ if (!value || typeof value !== 'object')
284
+ return false;
285
+ const v = value;
286
+ return (v.protocolVersion === 1 &&
287
+ typeof v.handoffId === 'string' &&
288
+ typeof v.phase === 'string' &&
289
+ GUIDED_HANDOFF_PHASES.includes(v.phase) &&
290
+ typeof v.sourceProcessId === 'string' &&
291
+ !!v.target &&
292
+ typeof v.target.sha256 === 'string' &&
293
+ Array.isArray(v.blockers) &&
294
+ Array.isArray(v.expectedSessions));
295
+ }
296
+ export function classifyHandoffReply(input) {
297
+ const { statusCode, body } = input;
298
+ if (statusCode === null)
299
+ return { kind: 'unreachable' };
300
+ if (statusCode === 200) {
301
+ return isHandoffStatus(body) ? { kind: 'ok', status: body } : { kind: 'unreachable' };
302
+ }
303
+ if (statusCode === 404)
304
+ return { kind: 'absent' };
305
+ if (statusCode === 401 || statusCode === 403)
306
+ return { kind: 'unauthorized' };
307
+ if (statusCode === 400 || statusCode === 409 || statusCode === 413 || statusCode === 405) {
308
+ const code = body && typeof body === 'object' && typeof body.error === 'string'
309
+ ? String(body.error).slice(0, 64)
310
+ : undefined;
311
+ return { kind: 'refused', ...(code ? { code } : {}) };
312
+ }
313
+ return { kind: 'unreachable' };
314
+ }
315
+ /**
316
+ * Decide whether a committed replacement is complete. All of (1) and (2) must hold
317
+ * before anything else is asked of the server (3, {@link serverSawNewProcess}), and
318
+ * nothing here resumes: the verdict only says what was seen.
319
+ *
320
+ * An identity that is merely UNREADABLE (the bootstrap is mid-rotation, the machine
321
+ * payload could not be fetched) is `not_yet`, never a reinstall and never verified.
322
+ */
323
+ export function verifyReplacement(evidence) {
324
+ const { handoff, expect, identity } = evidence;
325
+ if (!handoff)
326
+ return { kind: 'not_yet', missing: 'daemon_status' };
327
+ if (handoff.phase === 'failed' || handoff.phase === 'rolled-back') {
328
+ return {
329
+ kind: 'failed',
330
+ phase: handoff.phase,
331
+ reason: String(handoff.reason || handoff.phase).slice(0, 64),
332
+ // A failed handoff observed by the SAME process is a replacement that never
333
+ // happened: the old daemon is still the one running, and a retry is meaningful.
334
+ sameProcess: handoff.observer?.processId === expect.sourceProcessId,
335
+ };
336
+ }
337
+ if (handoff.phase !== 'verified')
338
+ return { kind: 'not_yet', missing: 'verified' };
339
+ const by = handoff.verifiedBy;
340
+ if (!by ||
341
+ by.processId === expect.sourceProcessId ||
342
+ handoff.target.sha256 !== expect.targetSha256 ||
343
+ (expect.version !== null && by.version !== expect.version)) {
344
+ return { kind: 'not_yet', missing: 'verified' };
345
+ }
346
+ if (identity.bootstrapDaemonId === null || identity.runtimeKeyNow === null) {
347
+ return { kind: 'not_yet', missing: 'identity_unreadable' };
348
+ }
349
+ if (identity.bootstrapDaemonId !== identity.recordedDaemonId) {
350
+ return { kind: 'reinstall', what: 'daemon_id' };
351
+ }
352
+ if (identity.runtimeKeyNow !== identity.recordedRuntimeKey) {
353
+ return { kind: 'reinstall', what: 'runtime_key' };
354
+ }
355
+ return { kind: 'verified' };
356
+ }
357
+ /**
358
+ * The legacy counterpart: a daemon with no handoff cannot say `verified`, so the copy
359
+ * claims only what was seen ("restarted on version X"), never "adopted N sessions".
360
+ * A new process (a different start time and, where known, a different PID), the same
361
+ * identity, and the version it announces equal to the bundled one.
362
+ */
363
+ export function verifyLegacyRestart(evidence) {
364
+ const { before, after, expect } = evidence;
365
+ if (after.daemonId === null || after.startedAt === null || evidence.runtimeKeyNow === null) {
366
+ return { kind: 'not_yet', missing: 'identity_unreadable' };
367
+ }
368
+ const newProcess = before.startedAt === null
369
+ ? after.processId !== null && after.processId !== before.processId
370
+ : after.startedAt > before.startedAt &&
371
+ (after.processId === null ||
372
+ before.processId === null ||
373
+ after.processId !== before.processId);
374
+ if (!newProcess)
375
+ return { kind: 'not_yet', missing: 'verified' };
376
+ if (after.daemonId !== expect.daemonId)
377
+ return { kind: 'reinstall', what: 'daemon_id' };
378
+ if (evidence.runtimeKeyNow !== evidence.recordedRuntimeKey) {
379
+ return { kind: 'reinstall', what: 'runtime_key' };
380
+ }
381
+ if (expect.version !== null && after.version !== expect.version) {
382
+ return { kind: 'not_yet', missing: 'verified' };
383
+ }
384
+ return { kind: 'verified' };
385
+ }
386
+ /**
387
+ * Whether the server independently saw a new process register under the held key
388
+ * (condition 3). The Journey says `back_held` for ANY process that registered since
389
+ * the fence, so it can only corroborate THIS replacement when it did not already say
390
+ * so before the commit. When it did, the answer is `not_independent`: the flow rests
391
+ * on (1) and (2) and says so.
392
+ */
393
+ export function serverSawNewProcess(input) {
394
+ if (input.backHeldAtCommit)
395
+ return 'not_independent';
396
+ return input.journeyPhase === 'back_held' ? 'seen' : 'not_yet';
397
+ }
398
+ /** Whether an observed hold is the one this flow took, and still live. */
399
+ export function isOurLiveHold(view, generation) {
400
+ return (generation !== null &&
401
+ !!view?.hold &&
402
+ view.hold.lifecycle === 'held' &&
403
+ view.hold.generation === generation);
404
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * The maintenance report wire contract (Machine Maintenance, OSK-19107, task M24;
3
+ * `docs/architecture/machine-maintenance/implementation-plan-2026-10-24.md` §3.5,
4
+ * `phase0/m04-quiescence-evidence.md` §2.5 and §2.6).
5
+ *
6
+ * Declared ONCE, here. The daemon is published and cannot import the API, so the
7
+ * capability name, the payload shape and its bounds live in this package, which
8
+ * every end can import (`@skrr-ai/auth-core/maintenance-report-wire`).
9
+ * `maintenanceReportWire.test.ts` fails on a second spelling of the capability
10
+ * anywhere else in the repo. The shape follows `dedicatedRuntimeBusyWire.ts`, the
11
+ * precedent for a positively announced, fail-closed report, and differs from it
12
+ * only where the owner's pause needs more.
13
+ *
14
+ * WHAT THE REPORT SAYS
15
+ * ---------------------------------------------------------------------------
16
+ * "What does this daemon process hold right now that an update would take from
17
+ * the owner, and has anything been admitted since the last time you asked?" It is
18
+ * a LOCAL read summarised into counts, the handoff's own restart blockers, and
19
+ * the daemon's own update handoff phase.
20
+ *
21
+ * It is EVIDENCE, never authority. The fence does not depend on it: a daemon that
22
+ * lies, or one that sends nothing, cannot make a held machine accept work. Only
23
+ * this report can grant `ready` (a server-side source can only veto it), and a
24
+ * daemon that never announces the capability can only reach `ready` by the
25
+ * owner's attestation.
26
+ *
27
+ * FAIL CLOSED, in three places
28
+ * ---------------------------------------------------------------------------
29
+ * 1. A count is `0` only when the daemon positively counted. A source that could
30
+ * not be read is `complete: 'unknown'`; a daemon that is leaving is
31
+ * `complete: false`. A reader MUST treat both as busy, whatever the counts
32
+ * say (`maintenanceReportIsQuiet` is the one place that says so).
33
+ * 2. A report that is malformed in ANY field is `null` from
34
+ * `parseMaintenanceReport`, and the caller treats the machine as not quiet.
35
+ * A count that is missing, negative, fractional or not a safe integer is not
36
+ * "zero": it is a different, broken report. A version this parser does not
37
+ * know is `null` too: a newer shape needs its own announced capability.
38
+ * 3. Bounds. The blocker list and every string are clamped, so one report can
39
+ * never be large enough to matter, and a field that is NOT in the contract is
40
+ * never copied into what the parser returns (the result is built from a
41
+ * fresh object, so `__proto__` and its kin cannot ride along).
42
+ *
43
+ * `admittedTotal`
44
+ * ---------------------------------------------------------------------------
45
+ * A MONOTONIC counter of admissions since this process started. The server proves
46
+ * "nothing was admitted between two reports" by comparing two values, without
47
+ * trusting the daemon's clock. It counts at every place the daemon admits work
48
+ * and OVER-counts on purpose (one admission may be noted at the gate and again
49
+ * where the work registers): a second increment can only make the proof harder,
50
+ * never easier. A path that the daemon cannot count must say so in
51
+ * `daemon/src/maintenance-report.ts`, not hide in a zero.
52
+ */
53
+ export declare const MAINTENANCE_REPORT_CAPABILITY = "maintenance_report_v1";
54
+ /** The heartbeat (and register) key the report rides under. Sent only by a daemon that announced the capability. */
55
+ export declare const MAINTENANCE_REPORT_HEARTBEAT_KEY = "maintenanceReport";
56
+ export declare const MAINTENANCE_REPORT_VERSION = 1;
57
+ /**
58
+ * The daemon's own update handoff phases (`daemon/src/update/handoff.ts`), and the
59
+ * members of `DaemonMaintenanceHandoff.phase` in `@skrr-ai/data-provider`. A phase
60
+ * outside this list is a newer protocol and makes the whole report `null`.
61
+ */
62
+ export declare const MAINTENANCE_REPORT_HANDOFF_PHASES: readonly ["ready", "deferred", "reconnecting", "verified", "failed", "rolled-back"];
63
+ export type MaintenanceReportHandoffPhase = (typeof MAINTENANCE_REPORT_HANDOFF_PHASES)[number];
64
+ export declare const MAINTENANCE_REPORT_LIMITS: {
65
+ /** The handoff's blockers carried for the "waiting on" text; the counts, not this list, decide busy. */
66
+ readonly maxBlockers: 32;
67
+ readonly maxKindLength: 32;
68
+ readonly maxIdLength: 128;
69
+ readonly maxReasonLength: 240;
70
+ readonly maxHandoffIdLength: 64;
71
+ readonly maxHandoffReasonLength: 64;
72
+ readonly maxProcessStartIdLength: 64;
73
+ /** Hard ceiling on the serialized report. The clamps above keep a valid one well under it. */
74
+ readonly maxSerializedBytes: 20000;
75
+ };
76
+ /** What `complete` can be. Anything but `true` means the counts prove nothing. */
77
+ export type MaintenanceReportComplete = boolean | 'unknown';
78
+ /** The handoff's own `RestartBlocker` shape (`daemon/src/update/handoff.ts`). Reason text is the handoff's, never re-spelled. */
79
+ export interface MaintenanceReportBlocker {
80
+ kind: string;
81
+ id?: string;
82
+ reason: string;
83
+ }
84
+ export interface MaintenanceReportInflight {
85
+ /** Provider sessions and their wrapper processes: in-process turns, preparing dispatches, live wrappers. */
86
+ sessions: number;
87
+ /** Tool calls running, plus background commands still running. */
88
+ tools: number;
89
+ /** Terminals the daemon holds, preservable or not. A person's open shell is work an update would interrupt. */
90
+ terminals: number;
91
+ /** Provider turns that still depend on credentials, permissions, a browser or a model connection the service owns. */
92
+ providerDependencies: number;
93
+ /** Write-ahead entries whose durability the daemon has not confirmed. */
94
+ walUnconfirmed: number;
95
+ }
96
+ export interface MaintenanceReportHandoff {
97
+ phase: MaintenanceReportHandoffPhase;
98
+ handoffId: string;
99
+ reason?: string;
100
+ /** ISO time the daemon's lease on this phase ends. */
101
+ expiresAt?: string;
102
+ }
103
+ export interface MaintenanceReport {
104
+ version: typeof MAINTENANCE_REPORT_VERSION;
105
+ /** The daemon process that read this. The server drops a report that is not its registered process's. */
106
+ processStartId: string;
107
+ /** ISO time of the daemon's own read. Informational: freshness is the server's receipt time. */
108
+ readAt: string;
109
+ /** `true`: every source was read. `'unknown'`: one could not be. `false`: the process is leaving. */
110
+ complete: MaintenanceReportComplete;
111
+ /** Monotonic count of admissions since this process started. */
112
+ admittedTotal: number;
113
+ inflight: MaintenanceReportInflight;
114
+ blockers: MaintenanceReportBlocker[];
115
+ /** True when the daemon had more blockers than it sent. */
116
+ blockersTruncated: boolean;
117
+ /** The daemon's own update handoff, only while it is live or unresolved; otherwise `null`. */
118
+ handoff: MaintenanceReportHandoff | null;
119
+ }
120
+ /**
121
+ * The reader's view of a daemon's heartbeat section. Anything malformed, from a
122
+ * daemon that did not announce the capability, an unknown version, or oversized
123
+ * is `null`, and the caller must then treat the machine as NOT quiet. Never
124
+ * trusts a field it did not validate and never copies one it does not know.
125
+ */
126
+ export declare function parseMaintenanceReport(raw: unknown): MaintenanceReport | null;
127
+ /**
128
+ * A report as it was stored (JSON text on the presence row). Anything that does
129
+ * not parse, or does not validate, is `null`: a stored value is an input like any
130
+ * other and is read with the same suspicion.
131
+ */
132
+ export declare function parseStoredMaintenanceReport(stored: unknown): MaintenanceReport | null;
133
+ /**
134
+ * The one definition of a POSITIVE ZERO: the daemon read every source and found
135
+ * nothing. `complete: 'unknown'` and `complete: false` are never zero whatever
136
+ * the counts say, because the counts were not (all) read.
137
+ *
138
+ * A blocker that is not work (a missing restart owner, say) does not make a
139
+ * report non-quiet; readiness is "nothing is running", not "the daemon can
140
+ * restart itself".
141
+ */
142
+ export declare function maintenanceReportIsQuiet(report: MaintenanceReport | null): boolean;
143
+ export interface BuildMaintenanceReportInput {
144
+ processStartId: string;
145
+ /** Epoch ms of the daemon's own read. */
146
+ readAtMs: number;
147
+ complete: MaintenanceReportComplete;
148
+ admittedTotal: number;
149
+ inflight: MaintenanceReportInflight;
150
+ blockers: ReadonlyArray<{
151
+ kind: string;
152
+ id?: string;
153
+ reason: string;
154
+ }>;
155
+ /** `expiresAtMs` is epoch ms; the wire carries ISO. */
156
+ handoff: {
157
+ phase: MaintenanceReportHandoffPhase;
158
+ handoffId: string;
159
+ reason?: string;
160
+ expiresAtMs?: number;
161
+ } | null;
162
+ }
163
+ /**
164
+ * A report that says nothing was counted. The shape the daemon sends when it
165
+ * could not read an inventory source at all, and the shape `buildMaintenanceReport`
166
+ * falls back to when it was handed something the contract cannot carry.
167
+ */
168
+ export declare function unknownMaintenanceReport(processStartId: string, readAtMs: number, admittedTotal?: number): MaintenanceReport;
169
+ /**
170
+ * Build the wire report from what the daemon read. Input the contract cannot
171
+ * carry (a negative count, a blocker with no kind, a time that is not a time)
172
+ * becomes an `unknown` report rather than a throw or a report that parses to
173
+ * something else, so the result parses whenever `processStartId` is a real id (the
174
+ * daemon's always is). A builder that threw would turn a bad read into silence,
175
+ * and silence is the absence of a report, which the server reads the same way,
176
+ * but later.
177
+ */
178
+ export declare function buildMaintenanceReport(input: BuildMaintenanceReportInput): MaintenanceReport;