@cohortapp/agent-sdk 2.17.0 → 2.18.4

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 (526) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +18 -5
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/setup-wizard.md +1 -1
  9. package/docs/runbooks/fleet-rollout.md +156 -0
  10. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  11. package/lib/action-executor.js +19 -3
  12. package/lib/budget-guard.mjs +279 -3
  13. package/lib/channels/base-adapter.mjs +3 -1
  14. package/lib/channels/contract.mjs +2 -1
  15. package/lib/channels/inbox-item.mjs +8 -0
  16. package/lib/claude-bin.mjs +5 -6
  17. package/lib/cli/doctor-checks.mjs +141 -10
  18. package/lib/cli/global-setup-extras.mjs +5 -1
  19. package/lib/cli/inbox.mjs +100 -15
  20. package/lib/cli/seat-auth.mjs +463 -0
  21. package/lib/cli/session.mjs +80 -12
  22. package/lib/collective/capture.mjs +8 -6
  23. package/lib/collective/global-config.mjs +63 -1
  24. package/lib/collective/presence.mjs +142 -5
  25. package/lib/comms/send-gate.mjs +559 -1
  26. package/lib/diagnostics/alerts.mjs +49 -0
  27. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  28. package/lib/engine/agents/definitions.mjs +343 -0
  29. package/lib/engine/agents/persist.mjs +275 -0
  30. package/lib/engine/agents/runtime.mjs +748 -0
  31. package/lib/engine/agents/usage.mjs +95 -0
  32. package/lib/engine/auth-status.mjs +139 -0
  33. package/lib/engine/budget.mjs +194 -0
  34. package/lib/engine/cli.mjs +1204 -0
  35. package/lib/engine/commands/index.mjs +269 -0
  36. package/lib/engine/context/budget.mjs +219 -0
  37. package/lib/engine/context/cache.mjs +125 -0
  38. package/lib/engine/context/child-env.mjs +215 -0
  39. package/lib/engine/context/compaction.mjs +342 -0
  40. package/lib/engine/context/images.mjs +90 -0
  41. package/lib/engine/context/instructions.mjs +327 -0
  42. package/lib/engine/context/lazy-instructions.mjs +169 -0
  43. package/lib/engine/context/manager.mjs +182 -0
  44. package/lib/engine/context/real-path.mjs +91 -0
  45. package/lib/engine/context/secret-values.mjs +163 -0
  46. package/lib/engine/context/settings.mjs +274 -0
  47. package/lib/engine/context/stream-input.mjs +159 -0
  48. package/lib/engine/guard.mjs +152 -0
  49. package/lib/engine/hooks.mjs +713 -0
  50. package/lib/engine/loop.mjs +560 -0
  51. package/lib/engine/mcp/client.mjs +254 -0
  52. package/lib/engine/mcp/config.mjs +301 -0
  53. package/lib/engine/mcp/http.mjs +201 -0
  54. package/lib/engine/mcp/index.mjs +146 -0
  55. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  56. package/lib/engine/mcp/naming.mjs +66 -0
  57. package/lib/engine/mcp/resources.mjs +89 -0
  58. package/lib/engine/mcp/results.mjs +133 -0
  59. package/lib/engine/mcp/stdio.mjs +137 -0
  60. package/lib/engine/mcp/supervisor.mjs +116 -0
  61. package/lib/engine/messages.mjs +104 -0
  62. package/lib/engine/output/json.mjs +164 -0
  63. package/lib/engine/output/stream-json.mjs +266 -0
  64. package/lib/engine/permissions.mjs +845 -0
  65. package/lib/engine/process-identity.mjs +164 -0
  66. package/lib/engine/process-tree.mjs +551 -0
  67. package/lib/engine/prompt.mjs +60 -0
  68. package/lib/engine/session/store.mjs +299 -0
  69. package/lib/engine/session-runtime/args.mjs +97 -0
  70. package/lib/engine/session-runtime/host.mjs +143 -0
  71. package/lib/engine/session-runtime/inbox.mjs +122 -0
  72. package/lib/engine/session-runtime/notifications.mjs +129 -0
  73. package/lib/engine/session-runtime/registry.mjs +328 -0
  74. package/lib/engine/session-runtime/runner.mjs +344 -0
  75. package/lib/engine/session-runtime/socket.mjs +212 -0
  76. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  77. package/lib/engine/skills/index.mjs +321 -0
  78. package/lib/engine/tools/bash-background.mjs +533 -0
  79. package/lib/engine/tools/bash.mjs +216 -0
  80. package/lib/engine/tools/edit.mjs +97 -0
  81. package/lib/engine/tools/glob.mjs +81 -0
  82. package/lib/engine/tools/grep.mjs +224 -0
  83. package/lib/engine/tools/index.mjs +84 -0
  84. package/lib/engine/tools/list-agents.mjs +32 -0
  85. package/lib/engine/tools/ls.mjs +127 -0
  86. package/lib/engine/tools/monitor.mjs +82 -0
  87. package/lib/engine/tools/notebook-edit.mjs +218 -0
  88. package/lib/engine/tools/read.mjs +103 -0
  89. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  90. package/lib/engine/tools/schema.mjs +144 -0
  91. package/lib/engine/tools/send-message.mjs +77 -0
  92. package/lib/engine/tools/session.mjs +70 -0
  93. package/lib/engine/tools/todo.mjs +144 -0
  94. package/lib/engine/tools/toolsearch.mjs +217 -0
  95. package/lib/engine/tools/walk.mjs +193 -0
  96. package/lib/engine/tools/web-switch.mjs +31 -0
  97. package/lib/engine/tools/webfetch-html.mjs +387 -0
  98. package/lib/engine/tools/webfetch-net.mjs +340 -0
  99. package/lib/engine/tools/webfetch.mjs +198 -0
  100. package/lib/engine/tools/websearch.mjs +91 -0
  101. package/lib/engine/tools/workflow.mjs +95 -0
  102. package/lib/engine/tools/write.mjs +76 -0
  103. package/lib/engine/tui/line-editor.mjs +137 -0
  104. package/lib/engine/tui/render.mjs +86 -0
  105. package/lib/engine/tui/tui.mjs +274 -0
  106. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  107. package/lib/engine/wire/effort.mjs +36 -0
  108. package/lib/engine/wire/errors.mjs +496 -0
  109. package/lib/engine/wire/http.mjs +441 -0
  110. package/lib/engine/wire/index.mjs +76 -0
  111. package/lib/engine/wire/openai-chat.mjs +332 -0
  112. package/lib/engine/wire/prompt-cache.mjs +79 -0
  113. package/lib/engine/wire/search.mjs +140 -0
  114. package/lib/engine/wire/sse.mjs +114 -0
  115. package/lib/engine/wire/stall.mjs +349 -0
  116. package/lib/engine/wire/token-provider.mjs +175 -0
  117. package/lib/engine/wire/usage.mjs +192 -0
  118. package/lib/engine/workflow/host.mjs +524 -0
  119. package/lib/engine/workflow/journal.mjs +188 -0
  120. package/lib/engine/workflow/json-schema.mjs +171 -0
  121. package/lib/engine/workflow/meta.mjs +329 -0
  122. package/lib/engine/workflow/notifications.mjs +52 -0
  123. package/lib/engine/workflow/runtime.mjs +447 -0
  124. package/lib/engine/workflow/sandbox.mjs +534 -0
  125. package/lib/engine/workflow/worker.mjs +141 -0
  126. package/lib/engine/workflow/worktree.mjs +74 -0
  127. package/lib/execution/disposition.mjs +1 -1
  128. package/lib/execution/intake.mjs +10 -0
  129. package/lib/execution/surface-policy.mjs +15 -0
  130. package/lib/learning/curator.mjs +8 -6
  131. package/lib/learning/reflect.mjs +8 -6
  132. package/lib/model-router/catalog/cohort.yaml +137 -0
  133. package/lib/model-router/catalog.mjs +118 -1
  134. package/lib/model-router/failover.mjs +67 -16
  135. package/lib/model-router/llm-task.mjs +39 -3
  136. package/lib/model-router/resolve.mjs +89 -3
  137. package/lib/model-router/spawn.mjs +46 -47
  138. package/lib/model-router/taxonomy.mjs +126 -4
  139. package/lib/org/cost-sync.mjs +141 -11
  140. package/lib/org/inbound/broadcast.mjs +289 -0
  141. package/lib/org/inbound/collective.mjs +375 -0
  142. package/lib/org/inbound/directedness.mjs +96 -8
  143. package/lib/org/inbound/facts.mjs +78 -2
  144. package/lib/org/inbound/project.mjs +22 -0
  145. package/lib/org/inbound/surfaces.mjs +14 -0
  146. package/lib/org/llm-token.mjs +879 -0
  147. package/lib/org/mesh.mjs +61 -0
  148. package/lib/org/messaging.mjs +3 -1
  149. package/lib/org/protocol.checksum +1 -1
  150. package/lib/org/protocol.mjs +15 -0
  151. package/lib/org/quota.mjs +520 -0
  152. package/lib/org/tool-surface.mjs +104 -16
  153. package/lib/org/ui-parity.mjs +16 -1
  154. package/lib/org/work-ledger.mjs +37 -6
  155. package/lib/rate-guard.mjs +114 -1
  156. package/lib/resource-governor.mjs +41 -6
  157. package/lib/runtime/adapter.mjs +823 -0
  158. package/lib/runtime/child-env.mjs +191 -0
  159. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  160. package/lib/runtime/seat-engine.mjs +162 -0
  161. package/lib/session/ask-ledger.mjs +271 -0
  162. package/lib/session/current-work.mjs +676 -0
  163. package/lib/session/feed-core.mjs +40 -3
  164. package/lib/session/launch-args.mjs +56 -4
  165. package/lib/session/status-summary.mjs +26 -9
  166. package/lib/session/upgrade-notice.mjs +42 -0
  167. package/lib/setup/claude-probe.mjs +117 -13
  168. package/lib/setup/enrich.mjs +13 -10
  169. package/lib/setup/sections/model.mjs +39 -13
  170. package/lib/telemetry/collect.mjs +208 -9
  171. package/lib/upgrade/ignored-drift.mjs +105 -0
  172. package/lib/voice/post-call-brief.mjs +30 -17
  173. package/package.json +13 -3
  174. package/plugins/maestro-skills/skills/board-work.md +5 -0
  175. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  176. package/plugins/maestro-skills/skills/main-session.md +18 -7
  177. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  178. package/scripts/ci/run-tests.mjs +47 -19
  179. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  180. package/scripts/collective/hook-runner.mjs +29 -2
  181. package/scripts/continuous-monitor.sh +13 -0
  182. package/scripts/cost/track-claude-usage.mjs +15 -0
  183. package/scripts/daemon/agent-daemon.mjs +408 -20
  184. package/scripts/daemon/assurance.mjs +48 -12
  185. package/scripts/daemon/cadence-consumer.mjs +218 -68
  186. package/scripts/daemon/cadence-handlers.mjs +73 -4
  187. package/scripts/daemon/classifier.mjs +75 -26
  188. package/scripts/daemon/context-compiler.mjs +51 -37
  189. package/scripts/daemon/deliver.mjs +30 -1
  190. package/scripts/daemon/dispatcher.mjs +595 -149
  191. package/scripts/daemon/health.mjs +14 -1
  192. package/scripts/daemon/maestro-daemon.mjs +11 -0
  193. package/scripts/daemon/prompt-builder.mjs +24 -0
  194. package/scripts/daemon/responder.mjs +246 -79
  195. package/scripts/daemon/sdk-version.mjs +98 -16
  196. package/scripts/eval/probe-gateway.mjs +635 -0
  197. package/scripts/eval/replay/extract.mjs +270 -0
  198. package/scripts/eval/replay/grade.mjs +260 -0
  199. package/scripts/eval/replay/lib/config.mjs +50 -0
  200. package/scripts/eval/replay/lib/effects.mjs +65 -0
  201. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  202. package/scripts/eval/replay/lib/judge.mjs +72 -0
  203. package/scripts/eval/replay/lib/redact.mjs +136 -0
  204. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  205. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  206. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  207. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  208. package/scripts/eval/replay/report.mjs +185 -0
  209. package/scripts/eval/replay/run.mjs +404 -0
  210. package/scripts/fleet/rollout.mjs +1094 -0
  211. package/scripts/hooks/pre-send-audit.sh +36 -245
  212. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  213. package/scripts/hooks/validate-state-yaml.sh +190 -0
  214. package/scripts/huddle/huddle-llm.mjs +361 -0
  215. package/scripts/huddle/huddle-server.mjs +46 -121
  216. package/scripts/local-triggers/autoupdate.sh +448 -78
  217. package/scripts/local-triggers/run-trigger.sh +13 -0
  218. package/scripts/maintenance/pin-integrity.mjs +364 -0
  219. package/scripts/poll-slack-events.sh +41 -9
  220. package/scripts/poller/slack-socket-mode.mjs +28 -3
  221. package/scripts/session/supervisor.mjs +80 -13
  222. package/scripts/spawn-session.sh +13 -0
  223. package/bin/maestro.test.mjs +0 -1574
  224. package/lib/action-executor.test.mjs +0 -871
  225. package/lib/archetype.test.mjs +0 -132
  226. package/lib/assurance/plan-note.test.mjs +0 -234
  227. package/lib/assurance/room-budget.test.mjs +0 -486
  228. package/lib/assurance/tier.test.mjs +0 -174
  229. package/lib/autonomy.test.mjs +0 -66
  230. package/lib/backlog.test.mjs +0 -302
  231. package/lib/backup/policy.test.mjs +0 -305
  232. package/lib/budget-escalate.test.mjs +0 -232
  233. package/lib/budget-guard.envelope.test.mjs +0 -476
  234. package/lib/budget-guard.test.mjs +0 -427
  235. package/lib/cadence-bus-requeue.test.mjs +0 -83
  236. package/lib/cadence-bus-schedule.test.mjs +0 -194
  237. package/lib/cadence-bus.test.mjs +0 -720
  238. package/lib/cadences.test.mjs +0 -230
  239. package/lib/capability/inventory.test.mjs +0 -232
  240. package/lib/capability.test.mjs +0 -78
  241. package/lib/channels/base-adapter.test.mjs +0 -590
  242. package/lib/channels/channels.test.mjs +0 -371
  243. package/lib/channels/contract.test.mjs +0 -162
  244. package/lib/channels/inbox-item.test.mjs +0 -368
  245. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  246. package/lib/channels/pairing.test.mjs +0 -270
  247. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  248. package/lib/channels/slack-adapter.test.mjs +0 -212
  249. package/lib/channels/telegram-adapter.test.mjs +0 -306
  250. package/lib/channels/voice/adapter.test.mjs +0 -278
  251. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  252. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  253. package/lib/charter.test.mjs +0 -89
  254. package/lib/claude-bin.test.mjs +0 -131
  255. package/lib/cli/board.test.mjs +0 -227
  256. package/lib/cli/design.test.mjs +0 -270
  257. package/lib/cli/doctor-checks.test.mjs +0 -336
  258. package/lib/cli/global-setup-extras.test.mjs +0 -462
  259. package/lib/cli/inbox.test.mjs +0 -230
  260. package/lib/cli/session-ack.test.mjs +0 -63
  261. package/lib/cli/session.test.mjs +0 -613
  262. package/lib/collective/capture.test.mjs +0 -121
  263. package/lib/collective/cards.test.mjs +0 -114
  264. package/lib/collective/config.test.mjs +0 -123
  265. package/lib/collective/global-config.test.mjs +0 -220
  266. package/lib/collective/global-skills.test.mjs +0 -126
  267. package/lib/collective/presence.test.mjs +0 -95
  268. package/lib/collective/recall.test.mjs +0 -116
  269. package/lib/collective/vendor-skills.test.mjs +0 -306
  270. package/lib/comms/send-gate.test.mjs +0 -770
  271. package/lib/comms.test.mjs +0 -41
  272. package/lib/context/budget.test.mjs +0 -252
  273. package/lib/context/history-scope.test.mjs +0 -79
  274. package/lib/cost/ledger-row.test.mjs +0 -183
  275. package/lib/design/design-md.test.mjs +0 -318
  276. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  277. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  278. package/lib/design/fixtures/foundation.json +0 -133
  279. package/lib/design/refresh-gate.test.mjs +0 -144
  280. package/lib/design/write.test.mjs +0 -241
  281. package/lib/diagnostics/alerts.test.mjs +0 -318
  282. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  283. package/lib/diagnostics/counters.test.mjs +0 -206
  284. package/lib/diagnostics/events.test.mjs +0 -290
  285. package/lib/diagnostics/otel.test.mjs +0 -196
  286. package/lib/diagnostics/trace.test.mjs +0 -251
  287. package/lib/env-compat.test.mjs +0 -104
  288. package/lib/execution/disposition.test.mjs +0 -553
  289. package/lib/execution/drive.test.mjs +0 -270
  290. package/lib/execution/effects.test.mjs +0 -344
  291. package/lib/execution/intake.test.mjs +0 -389
  292. package/lib/execution/journal.test.mjs +0 -261
  293. package/lib/execution/match.test.mjs +0 -235
  294. package/lib/execution/pipeline.test.mjs +0 -392
  295. package/lib/execution/route.test.mjs +0 -186
  296. package/lib/execution/surface-policy.test.mjs +0 -162
  297. package/lib/fs-atomic.test.mjs +0 -72
  298. package/lib/fs-ownership.test.mjs +0 -158
  299. package/lib/goals/admission.test.mjs +0 -164
  300. package/lib/goals/classify.test.mjs +0 -167
  301. package/lib/goals/collaborate.test.mjs +0 -336
  302. package/lib/goals/gaps.test.mjs +0 -284
  303. package/lib/goals/loop.test.mjs +0 -845
  304. package/lib/hooks/bus.test.mjs +0 -387
  305. package/lib/identity/persona.test.mjs +0 -142
  306. package/lib/kpi-sensors.test.mjs +0 -278
  307. package/lib/kpi.test.mjs +0 -244
  308. package/lib/learning/config.test.mjs +0 -75
  309. package/lib/learning/counters.test.mjs +0 -69
  310. package/lib/learning/curator-consolidate.test.mjs +0 -238
  311. package/lib/learning/curator.test.mjs +0 -106
  312. package/lib/learning/reflect.test.mjs +0 -0
  313. package/lib/learning/session-index.test.mjs +0 -125
  314. package/lib/learning/skill-writer.test.mjs +0 -210
  315. package/lib/mandate/audit.test.mjs +0 -195
  316. package/lib/mandate/contract.test.mjs +0 -185
  317. package/lib/mandate/derive.test.mjs +0 -274
  318. package/lib/mandate/model.test.mjs +0 -164
  319. package/lib/mandate/refresh.test.mjs +0 -389
  320. package/lib/mcp/server.test.mjs +0 -426
  321. package/lib/model-router/auth-profiles.test.mjs +0 -580
  322. package/lib/model-router/catalog.test.mjs +0 -385
  323. package/lib/model-router/economics.test.mjs +0 -438
  324. package/lib/model-router/failover.test.mjs +0 -439
  325. package/lib/model-router/health.test.mjs +0 -338
  326. package/lib/model-router/integration-coverage.test.mjs +0 -831
  327. package/lib/model-router/integration.test.mjs +0 -564
  328. package/lib/model-router/ledger.test.mjs +0 -415
  329. package/lib/model-router/llm-task.test.mjs +0 -392
  330. package/lib/model-router/org-credentials.test.mjs +0 -265
  331. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  332. package/lib/model-router/reconcile.test.mjs +0 -316
  333. package/lib/model-router/repair.test.mjs +0 -180
  334. package/lib/model-router/spawn.test.mjs +0 -446
  335. package/lib/model-router/taxonomy.test.mjs +0 -410
  336. package/lib/model-router.test.mjs +0 -1207
  337. package/lib/org/activity.test.mjs +0 -134
  338. package/lib/org/approvals.test.mjs +0 -216
  339. package/lib/org/awareness.test.mjs +0 -159
  340. package/lib/org/board-mine-cache.test.mjs +0 -53
  341. package/lib/org/board.test.mjs +0 -187
  342. package/lib/org/bootstrap-context.test.mjs +0 -153
  343. package/lib/org/client.test.mjs +0 -1206
  344. package/lib/org/cohort-client.test.mjs +0 -126
  345. package/lib/org/cost-sync.test.mjs +0 -153
  346. package/lib/org/doctor.test.mjs +0 -346
  347. package/lib/org/engagement-ledger.test.mjs +0 -112
  348. package/lib/org/engagement.test.mjs +0 -739
  349. package/lib/org/handoff.test.mjs +0 -269
  350. package/lib/org/inbound/directedness.test.mjs +0 -668
  351. package/lib/org/inbound/facts.test.mjs +0 -471
  352. package/lib/org/inbound/hydrate.test.mjs +0 -908
  353. package/lib/org/inbound/index.test.mjs +0 -429
  354. package/lib/org/inbound/project.test.mjs +0 -287
  355. package/lib/org/integration-tools.test.mjs +0 -160
  356. package/lib/org/keys.test.mjs +0 -92
  357. package/lib/org/knowledge.test.mjs +0 -326
  358. package/lib/org/leases.test.mjs +0 -235
  359. package/lib/org/mesh-directives.test.mjs +0 -110
  360. package/lib/org/mesh-integration.test.mjs +0 -127
  361. package/lib/org/mesh.test.mjs +0 -400
  362. package/lib/org/messaging.test.mjs +0 -471
  363. package/lib/org/param-contract.test.mjs +0 -477
  364. package/lib/org/policy.test.mjs +0 -237
  365. package/lib/org/protocol.checksum.test.mjs +0 -90
  366. package/lib/org/protocol.test.mjs +0 -323
  367. package/lib/org/push.test.mjs +0 -792
  368. package/lib/org/registry.test.mjs +0 -100
  369. package/lib/org/resource-tools.test.mjs +0 -361
  370. package/lib/org/tool-access.test.mjs +0 -144
  371. package/lib/org/tool-surface-integration.test.mjs +0 -120
  372. package/lib/org/tool-surface.test.mjs +0 -1268
  373. package/lib/org/typing.test.mjs +0 -291
  374. package/lib/org/ui-parity.test.mjs +0 -560
  375. package/lib/org/verify.test.mjs +0 -194
  376. package/lib/org/work-ledger.test.mjs +0 -273
  377. package/lib/plan/adoption-e2e.test.mjs +0 -366
  378. package/lib/plan/budget-enforcement.test.mjs +0 -400
  379. package/lib/plan/compile.test.mjs +0 -382
  380. package/lib/plan/emit.test.mjs +0 -269
  381. package/lib/plan/explain.test.mjs +0 -188
  382. package/lib/prompts/parallelism.test.mjs +0 -177
  383. package/lib/rag/rag.test.mjs +0 -505
  384. package/lib/rate-guard.test.mjs +0 -272
  385. package/lib/reactive-gate.test.mjs +0 -57
  386. package/lib/render.test.mjs +0 -68
  387. package/lib/resource-governor.test.mjs +0 -488
  388. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  389. package/lib/scheduling/jitter.test.mjs +0 -140
  390. package/lib/secrets/broker.test.mjs +0 -280
  391. package/lib/secrets/providers.test.mjs +0 -274
  392. package/lib/security/audit-engine.test.mjs +0 -424
  393. package/lib/security/coerce-args.test.mjs +0 -281
  394. package/lib/security/dangerous-tools.test.mjs +0 -68
  395. package/lib/security/external-content.test.mjs +0 -84
  396. package/lib/security/redact.test.mjs +0 -441
  397. package/lib/security/secret-equal.test.mjs +0 -55
  398. package/lib/session/config.test.mjs +0 -92
  399. package/lib/session/feed-core.test.mjs +0 -198
  400. package/lib/session/first-run.test.mjs +0 -121
  401. package/lib/session/frontdoor.test.mjs +0 -205
  402. package/lib/session/handoffs.test.mjs +0 -183
  403. package/lib/session/identity.test.mjs +0 -180
  404. package/lib/session/inbox-claims.test.mjs +0 -286
  405. package/lib/session/launch-args.test.mjs +0 -157
  406. package/lib/session/liveness.test.mjs +0 -100
  407. package/lib/session/status-summary.test.mjs +0 -118
  408. package/lib/session-permissions.test.mjs +0 -120
  409. package/lib/setup/claude-probe.test.mjs +0 -187
  410. package/lib/setup/completeness.test.mjs +0 -110
  411. package/lib/setup/context-pack.test.mjs +0 -89
  412. package/lib/setup/enrich.test.mjs +0 -115
  413. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  414. package/lib/setup/integration.test.mjs +0 -162
  415. package/lib/setup/io.test.mjs +0 -77
  416. package/lib/setup/runner.test.mjs +0 -132
  417. package/lib/setup/sections/identity.test.mjs +0 -234
  418. package/lib/setup/sections/inventory.test.mjs +0 -198
  419. package/lib/setup/sections/learning.test.mjs +0 -81
  420. package/lib/setup/sections/mandate.test.mjs +0 -388
  421. package/lib/setup/sections/messaging.test.mjs +0 -127
  422. package/lib/setup/sections/model.test.mjs +0 -240
  423. package/lib/setup/sections/org.test.mjs +0 -346
  424. package/lib/setup/sections/orgmail.test.mjs +0 -118
  425. package/lib/setup/sections/recovery.test.mjs +0 -98
  426. package/lib/setup/sections/subagents.test.mjs +0 -429
  427. package/lib/setup/sections/verify.test.mjs +0 -175
  428. package/lib/setup/sot.test.mjs +0 -81
  429. package/lib/setup/state.test.mjs +0 -115
  430. package/lib/singleton.test.mjs +0 -151
  431. package/lib/subagents/cli.test.mjs +0 -389
  432. package/lib/subagents/client.test.mjs +0 -309
  433. package/lib/subagents/gap.test.mjs +0 -234
  434. package/lib/subagents/lock.test.mjs +0 -248
  435. package/lib/subagents/manifest.test.mjs +0 -175
  436. package/lib/subagents/refs.test.mjs +0 -204
  437. package/lib/subagents/resolve.test.mjs +0 -422
  438. package/lib/subagents/schema.test.mjs +0 -328
  439. package/lib/telemetry/alerts.test.mjs +0 -109
  440. package/lib/telemetry/collect.test.mjs +0 -1274
  441. package/lib/tool-definitions-integration.test.mjs +0 -83
  442. package/lib/tool-definitions.test.mjs +0 -437
  443. package/lib/upgrade/global-refresh.test.mjs +0 -65
  444. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  445. package/lib/upgrade/post-steps.test.mjs +0 -200
  446. package/lib/upgrade/verify.test.mjs +0 -164
  447. package/lib/util/fetch-timeout.test.mjs +0 -202
  448. package/lib/util/reconnect.test.mjs +0 -369
  449. package/lib/util/unhandled.test.mjs +0 -216
  450. package/lib/voice/outbound.test.mjs +0 -69
  451. package/lib/voice/session-rotation.test.mjs +0 -114
  452. package/lib/voice/stt.test.mjs +0 -226
  453. package/lib/voice/voice.test.mjs +0 -990
  454. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  455. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  456. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  457. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  458. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  459. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  460. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  461. package/scripts/ci/check.test.mjs +0 -194
  462. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  463. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  464. package/scripts/collective/hook-runner.test.mjs +0 -173
  465. package/scripts/cost/fleet-digest.test.mjs +0 -207
  466. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  467. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  468. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  469. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  470. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  471. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  472. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  473. package/scripts/daemon/assurance.test.mjs +0 -1791
  474. package/scripts/daemon/board-mirror.test.mjs +0 -165
  475. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  476. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  477. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  478. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  479. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  480. package/scripts/daemon/classifier.test.mjs +0 -266
  481. package/scripts/daemon/classify-kind.test.mjs +0 -40
  482. package/scripts/daemon/context-compiler.test.mjs +0 -406
  483. package/scripts/daemon/deliver.test.mjs +0 -564
  484. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  485. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  486. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  487. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  488. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  489. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  490. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  491. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  492. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  493. package/scripts/daemon/integration.test.mjs +0 -149
  494. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  495. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  496. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  497. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  498. package/scripts/daemon/responder-cost.test.mjs +0 -68
  499. package/scripts/daemon/responder-history.test.mjs +0 -221
  500. package/scripts/daemon/sdk-version.test.mjs +0 -31
  501. package/scripts/daemon/session-lock.test.mjs +0 -252
  502. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  503. package/scripts/daemon/typing-registry.test.mjs +0 -102
  504. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  505. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  506. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  507. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  508. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  509. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  510. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  511. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  512. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  513. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  514. package/scripts/poller-launchd/install.test.mjs +0 -243
  515. package/scripts/restore-from-backup.test.mjs +0 -181
  516. package/scripts/session/feed.test.mjs +0 -196
  517. package/scripts/session/supervisor-sh.test.mjs +0 -218
  518. package/scripts/session/supervisor.test.mjs +0 -482
  519. package/scripts/setup/configure-macos.test.mjs +0 -306
  520. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  521. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  522. package/scripts/setup/generate-capability.test.mjs +0 -134
  523. package/scripts/setup/init-agent.test.mjs +0 -370
  524. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  525. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  526. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -122,6 +122,15 @@
122
122
  "command": "./scripts/hooks/block-mcp-cohort-send.sh"
123
123
  }
124
124
  ]
125
+ },
126
+ {
127
+ "matcher": "Write|Edit",
128
+ "hooks": [
129
+ {
130
+ "type": "command",
131
+ "command": "node ./scripts/hooks/pre-write-yaml-validate.mjs"
132
+ }
133
+ ]
125
134
  }
126
135
  ],
127
136
  "PostToolUse": [
@@ -133,6 +142,15 @@
133
142
  "command": "./scripts/hooks/post-action-log.sh"
134
143
  }
135
144
  ]
145
+ },
146
+ {
147
+ "matcher": "Bash|Write|Edit",
148
+ "hooks": [
149
+ {
150
+ "type": "command",
151
+ "command": "./scripts/hooks/validate-state-yaml.sh"
152
+ }
153
+ ]
136
154
  }
137
155
  ],
138
156
  "Stop": [
package/.env.example CHANGED
@@ -63,7 +63,7 @@ COHORT_AGENT_EMAIL=
63
63
  # is in effect and what `claude auth status` reports (presence of a credential,
64
64
  # not its validity — a bad token surfaces on the first real spawn).
65
65
  #
66
- # Option A — subscription OAuth token ← THE FLEET DEFAULT (seat machines)
66
+ # Option A — subscription OAuth token (NOT the fleet posture — see Option C)
67
67
  # On ANY machine where you are logged in to Claude Code, run
68
68
  # claude setup-token
69
69
  # and paste the long-lived token below, together with
@@ -72,25 +72,38 @@ COHORT_AGENT_EMAIL=
72
72
  # keychain login is NOT relied on because it expires headlessly. The token is
73
73
  # per seat; mint a fresh one with the same command when it is rejected.
74
74
  # Headless setup reads it from the environment: CLAUDE_CODE_OAUTH_TOKEN=… maestro setup --headless
75
+ # It still RUNS, but it has no per-agent ceiling and no central spend
76
+ # visibility: do NOT provision a new agent seat on it (gate G4).
75
77
  CLAUDE_CODE_OAUTH_TOKEN=
76
78
 
77
79
  # Option B — keychain login on this machine (interactive / dev boxes only)
78
80
  # `claude login` here, leave CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY
79
81
  # empty, set MAESTRO_PREFER_SUBSCRIPTION_AUTH=1. Doctor warns: the login
80
- # expires headlessly and a seat machine should hold the token instead.
82
+ # expires headlessly and an agent seat belongs on Option C.
81
83
  #
82
84
  # Set to 1 for Option A and Option B: every claude spawn strips
83
85
  # ANTHROPIC_API_KEY from its env so claude rides the subscription (token or
84
86
  # keychain) — routine cadence ticks then cost zero API credits.
85
87
  MAESTRO_PREFER_SUBSCRIPTION_AUTH=
86
88
 
87
- # Option C — API key (pay-per-token)
88
- # Set ANTHROPIC_API_KEY to a valid sk-ant-api03-... key and leave the two
89
- # above empty. Get one: https://console.anthropic.com/settings/keys
89
+ # Option C — API key (pay-per-token) ← THE FLEET POSTURE (agent seats)
90
+ # Set ANTHROPIC_API_KEY to a valid sk-ant-api03-... key created INSIDE a
91
+ # Console workspace with a monthly spend cap, one workspace per agent, and
92
+ # leave the two above empty. Get one:
93
+ # https://console.anthropic.com/settings/keys
90
94
  # Doctor validates it against api.anthropic.com on every run; an invalid
91
95
  # key cascades 401s through every sub-session spawn.
96
+ # Runbook: docs/guides/billing-console-keys.md
92
97
  ANTHROPIC_API_KEY=
93
98
 
99
+ # OPTIONAL — Arm the G4 guard. `maestro doctor` and `maestro seat-auth` WARN on
100
+ # a seat still using subscription OAuth (and on a seat carrying both rails,
101
+ # which is ambiguous about where its spend lands). Set this to 1 and those
102
+ # warnings become failures — do that once the fleet has migrated, so a seat that
103
+ # regresses to a subscription token announces itself. Never fails an
104
+ # engine=cohort seat that holds no Anthropic credential: that is its posture.
105
+ MAESTRO_REQUIRE_CONSOLE_KEY=
106
+
94
107
  # OPTIONAL — Supplemental model access (GPT-4, embeddings)
95
108
  # Get your key: https://platform.openai.com/api-keys
96
109
  # Subscription: OpenAI API plan (pay-per-token)
package/README.md CHANGED
@@ -161,6 +161,7 @@ Verifies the agent installation end-to-end. Checks include:
161
161
  - **Config:** `config/environment.yaml`, `config/contacts.yaml`, `config/priorities.yaml`, `config/sla-defaults.yaml`.
162
162
  - **State:** `state/dashboards/executive-summary.yaml`, `state/queues/action-stack.yaml`, `knowledge/decisions/decision-schema.yaml`.
163
163
  - **Environment:** `.env` file with `ANTHROPIC_API_KEY` (required), `SLACK_USER_TOKEN`, `GMAIL_APP_PASSWORD` (optional).
164
+ - **Billing rail (G4):** which Claude rail this seat is on — `api-key` (the fleet posture: a per-agent Console key in a capped workspace), `subscription`, `both` (ambiguous, flagged loudly) or `neither`. Variable **names** only; no credential value is ever printed. A warning today; `MAESTRO_REQUIRE_CONSOLE_KEY=1` turns it into a failure once the fleet has migrated. `maestro seat-auth --row` prints the paste-able inventory row and `maestro seat-auth --fleet` the template it pastes into — see [billing: Console API keys](docs/guides/billing-console-keys.md).
164
165
  - **Dependencies:** `node_modules` installed, Claude CLI available, jq available, emergency-stop script present.
165
166
 
166
167
  Doctor exits non-zero when issues are found and prints actionable remediation (most commonly: `npx @cohortapp/agent-sdk upgrade`). Doctor also verifies the collective-memory wiring (see below).
package/bin/maestro.mjs CHANGED
@@ -37,6 +37,7 @@ import { runAudit, applyFixes, buildAttestation } from "../lib/security/audit-en
37
37
  import { selectProvider } from "../lib/secrets/providers.mjs";
38
38
  import { syncSecrets, rotateSecret, makeBroker, auditLogPath } from "../lib/secrets/broker.mjs";
39
39
  import { applyBrandEnvCompat } from "../lib/env-compat.mjs";
40
+ import { buildIgnoredDriftReport, formatIgnoredLine, IGNORED_DRIFT_REL } from "../lib/upgrade/ignored-drift.mjs";
40
41
 
41
42
  // Fleet back-compat FIRST: bridge NEOLITH_* ⇄ COHORT_* env names before any
42
43
  // env-first resolution below — installed hosts still export the legacy names.
@@ -1249,6 +1250,8 @@ Per-file behaviour:
1249
1250
  const counts = { added: 0, updated: 0, same: 0, ignored: 0, preserved: 0, mergeKept: 0, forced: 0, pruned: 0, pruneKept: 0 };
1250
1251
  const preservedFiles = [];
1251
1252
  const ignoredFiles = [];
1253
+ // repoRel → the upstream file the protection kept out (null when upstream ships nothing there): the drift report's input.
1254
+ const ignoredSources = new Map();
1252
1255
  const prunedFiles = [];
1253
1256
  const pruneKeptFiles = [];
1254
1257
  // What upstream ships right now — becomes this machine's provenance record.
@@ -1276,6 +1279,7 @@ Per-file behaviour:
1276
1279
  if (isIgnored) {
1277
1280
  counts.ignored++;
1278
1281
  ignoredFiles.push(repoRel);
1282
+ ignoredSources.set(repoRel, srcFile);
1279
1283
  if (flags.verbose) console.log(` · ${repoRel} (ignored via .maestroignore)`);
1280
1284
  continue;
1281
1285
  }
@@ -1414,6 +1418,7 @@ Per-file behaviour:
1414
1418
  if (matchesIgnore(repoRel, ignorePatterns)) {
1415
1419
  counts.ignored++;
1416
1420
  ignoredFiles.push(repoRel);
1421
+ ignoredSources.set(repoRel, null);
1417
1422
  if (flags.verbose) console.log(` · ${repoRel} (orphan, ignored via .maestroignore)`);
1418
1423
  continue;
1419
1424
  }
@@ -1504,6 +1509,7 @@ Per-file behaviour:
1504
1509
  if (matchesIgnore(repoRel, ignorePatterns)) {
1505
1510
  counts.ignored++;
1506
1511
  ignoredFiles.push(repoRel);
1512
+ ignoredSources.set(repoRel, src);
1507
1513
  if (flags.verbose) console.log(` · ${repoRel} (ignored via .maestroignore)`);
1508
1514
  continue;
1509
1515
  }
@@ -1596,6 +1602,26 @@ Per-file behaviour:
1596
1602
  if (counts.updated) ok(`${counts.updated} updated (vendored, no local edits)`);
1597
1603
  if (counts.same) console.log(` = ${counts.same} already up-to-date`);
1598
1604
  if (counts.ignored) console.log(` · ${counts.ignored} ignored (.maestroignore protected)`);
1605
+ // A protected file is a fork the operator owns; "N ignored" hid how far
1606
+ // upstream had moved under each one. Say it per file, and keep the answer
1607
+ // in .maestro/ignored-drift.json for doctor and the next reader.
1608
+ let ignoredDrift = null;
1609
+ if (ignoredFiles.length) {
1610
+ const readOrNull = (p) => { try { return p && existsSync(p) ? readFileSync(p, "utf8") : null; } catch { return null; } };
1611
+ ignoredDrift = buildIgnoredDriftReport(
1612
+ [...new Set(ignoredFiles)].sort().map((repoRel) => ({ path: repoRel, local: readOrNull(join(cwd, repoRel)), upstream: readOrNull(ignoredSources.get(repoRel)) })),
1613
+ { sdkVersion: readFrameworkVersion(), at: new Date().toISOString() },
1614
+ );
1615
+ for (const f of ignoredDrift.files) console.log(formatIgnoredLine(f));
1616
+ if (ignoredDrift.counts.drifts) warn(`${ignoredDrift.counts.drifts} protected file(s) drift from upstream — every upstream fix to those paths stops here until you port it (diff each against node_modules/@cohortapp/agent-sdk/<path>)`);
1617
+ if (!flags.dryRun) {
1618
+ try {
1619
+ const out = join(cwd, IGNORED_DRIFT_REL);
1620
+ mkdirSync(dirname(out), { recursive: true });
1621
+ writeFileSync(out, JSON.stringify(ignoredDrift, null, 2) + "\n");
1622
+ } catch (e) { warn(`could not write ${IGNORED_DRIFT_REL}: ${e && e.message ? e.message : e}`); }
1623
+ }
1624
+ }
1599
1625
  if (counts.mergeKept) console.log(` ~ ${counts.mergeKept} merge-mode kept (agents/ custom files preserved)`);
1600
1626
  if (counts.preserved) warn(`${counts.preserved} preserved (local edits — kept your version)`);
1601
1627
  if (counts.forced) warn(`${counts.forced} force-overwritten (backups in .maestro/backup/)`);
@@ -3341,6 +3367,38 @@ async function globalSetup(args = []) {
3341
3367
  ok("global hooks already present (no change)");
3342
3368
  }
3343
3369
 
3370
+ // 1b) Per-seat .claude/settings.json: the state-YAML gate hooks (PreToolUse on
3371
+ // Write|Edit, PostToolUse on Bash|Write|Edit). These belong in the SEAT file,
3372
+ // not ~/.claude — their commands are seat-relative and only mean anything
3373
+ // inside an agent repo. A hand edit there does not survive a fresh checkout
3374
+ // or an upgrade, which is why the scaffold copy carries them for new seats
3375
+ // and this merge carries them onto existing ones (upgrade runs global-setup
3376
+ // as a post-step). Deep-merged, idempotent, backed up before write.
3377
+ {
3378
+ const seatSettingsPath = join(cwd, ".claude", "settings.json");
3379
+ let seat = {};
3380
+ if (existsSync(seatSettingsPath)) {
3381
+ try { seat = JSON.parse(readFileSync(seatSettingsPath, "utf-8")); } catch { seat = null; }
3382
+ }
3383
+ if (seat === null) {
3384
+ warn(`${seatSettingsPath} is not valid JSON — state-YAML gate hooks not merged (repair it, then re-run global-setup)`);
3385
+ } else {
3386
+ const merged = gc.mergeStateYamlGateHooks(seat);
3387
+ if (merged.added.length > 0) {
3388
+ if (!dryRun) {
3389
+ mkdirSync(dirname(seatSettingsPath), { recursive: true });
3390
+ if (existsSync(seatSettingsPath)) {
3391
+ try { copyFileSync(seatSettingsPath, `${seatSettingsPath}.backup.${tsStamp()}`); } catch { /* */ }
3392
+ }
3393
+ writeFileSync(seatSettingsPath, JSON.stringify(merged.settings, null, 2) + "\n");
3394
+ }
3395
+ ok(`${dryRun ? "[dry-run] " : ""}seat hooks added to .claude/settings.json: ${merged.added.join(", ")}`);
3396
+ } else {
3397
+ ok("seat state-YAML gate hooks already present (no change)");
3398
+ }
3399
+ }
3400
+ }
3401
+
3344
3402
  // 2) global CLAUDE.md directive (only our delimited block is touched).
3345
3403
  let mdContent = "";
3346
3404
  if (existsSync(claudeMdPath)) { try { mdContent = readFileSync(claudeMdPath, "utf-8"); } catch { /* */ } }
@@ -3806,9 +3864,11 @@ switch (command) {
3806
3864
  case "who-owns": case "who": await whoOwnsCmd(args); break;
3807
3865
  case "inbox": process.exitCode = await (await import("../lib/cli/inbox.mjs")).runInbox(args); break;
3808
3866
  case "session-ack": process.exitCode = await (await import("../lib/cli/session-ack.mjs")).runSessionAck(args); break;
3867
+ case "seat-auth": process.exitCode = await (await import("../lib/cli/seat-auth.mjs")).runSeatAuth(args); break;
3809
3868
  case "session": { const r = await (await import("../lib/cli/session.mjs")).run(args); process.exitCode = r.code; break; }
3810
3869
  case "board": await (await import("../lib/cli/board.mjs")).boardCmd(args); break;
3811
3870
  case "design": await (await import("../lib/cli/design.mjs")).designCmd(args); break;
3871
+ case "run": process.exitCode = await (await import("../lib/engine/cli.mjs")).runFromProcess(args); break;
3812
3872
  case "init":
3813
3873
  case "update-init":
3814
3874
  case "init-update":
@@ -3836,9 +3896,11 @@ Usage:
3836
3896
  npx @cohortapp/agent-sdk who-owns <scope> Who owns a scope / escalate-to / reports (org context, zero-LLM)
3837
3897
  npx @cohortapp/agent-sdk inbox list|show|claim|reply|done|defer The main session's inbox (JSON; shares the daemon's markers)
3838
3898
  npx @cohortapp/agent-sdk session-ack <tickId> Ack a cadence tick handed to the main session
3899
+ npx @cohortapp/agent-sdk seat-auth [--row|--fleet] Which Claude billing rail this seat is on (G4) — variable NAMES only, never a value
3839
3900
  npx @cohortapp/agent-sdk session <cmd> Front-door session: status|attach|start|stop|restart|spawn|peers|handoffs|ack
3840
3901
  npx @cohortapp/agent-sdk board mine|track|claim|complete Your work across every board (maestro board --help)
3841
3902
  npx @cohortapp/agent-sdk design sync [--out <dir>] Pull the brand foundation → DESIGN.md + PRODUCT.md
3903
+ npx @cohortapp/agent-sdk run -p "<prompt>" [flags] Cohort Engine, headless (cohort run --help)
3842
3904
 
3843
3905
  Upgrade flags:
3844
3906
  --dry-run, -n Preview changes without writing
@@ -65,6 +65,7 @@ make per-agent spend attribution and revocation possible).
65
65
 
66
66
  ```sh
67
67
  maestro doctor # validates ANTHROPIC_API_KEY against api.anthropic.com
68
+ maestro seat-auth # which rail this seat is on, by variable NAME (see below)
68
69
  ```
69
70
 
70
71
  After the cost-telemetry work (roadmap item 0.3) lands, `maestro doctor` also
@@ -72,6 +73,65 @@ turns RED when sessions have run but recorded spend is $0, and a nightly digest
72
73
  reconciles the local ledger against the Admin Cost API. Until then, confirm spend
73
74
  is accruing in the Console workspace view for a canary agent.
74
75
 
76
+ ## The tooling: `maestro seat-auth` (G4)
77
+
78
+ A seat can state, about itself, which rail it is on — and the answer is a
79
+ **verdict over variable names**, never a value. Nothing here prints, logs or
80
+ stores a credential, not even truncated.
81
+
82
+ | Verdict | Meaning | Doctor level |
83
+ | -------------- | ---------------------------------------------------------------------- | -------------------------------- |
84
+ | `api-key` | `ANTHROPIC_API_KEY` only — the fleet posture | ok |
85
+ | `subscription` | `MAESTRO_PREFER_SUBSCRIPTION_AUTH` and/or `CLAUDE_CODE_OAUTH_TOKEN` | warn (fail when armed, below) |
86
+ | `both` | a Console key **and** a subscription credential on the same seat | warn (fail when armed) — loud |
87
+ | `neither` | no Claude credential at all | fail — except on an engine-cohort seat, where it is expected |
88
+
89
+ **`both` is its own verdict and is worth flagging loudly.** It is not "half
90
+ migrated": it is ambiguous. The runtime adapter blanks `ANTHROPIC_API_KEY` on a
91
+ seat that `subscriptionAuth()` calls true (`lib/runtime/adapter.mjs`), so the
92
+ subscription rail wins the lanes that go through the adapter while a lane that
93
+ passes the environment straight through may use the key. Spend then lands in
94
+ two places and neither is authoritative. Migrate, do not stack.
95
+
96
+ ```sh
97
+ maestro seat-auth # the verdict, the names present, the remediation
98
+ maestro seat-auth --json # the same, machine-readable
99
+ maestro seat-auth --row # ONE markdown row to paste into the fleet inventory
100
+ maestro seat-auth --fleet # the inventory template, one row per org member
101
+ ```
102
+
103
+ The same verdict appears as a row in `maestro doctor`, so an operator who runs
104
+ only doctor still sees it.
105
+
106
+ ### The fleet inventory
107
+
108
+ A seat's environment lives on the seat's own machine. There is no remote
109
+ inventory mechanism and this tooling does not invent one. So the fleet report is
110
+ a **template plus paste-able rows**: run `maestro seat-auth --fleet` once (it
111
+ pre-fills one row per member from the org directory), then on each machine run
112
+ `maestro seat-auth --row` and paste the line over that seat's row. The measured
113
+ columns — seat, agent id, machine, verdict, which variable names are present,
114
+ when it was checked — fill themselves; the Console-side columns (account,
115
+ workspace, cap, key id last 4, revocation date) stay blank, because only a human
116
+ with Console access knows them and a tool that guessed them would teach people
117
+ to trust a guess.
118
+
119
+ ### Arming the guard
120
+
121
+ The check **warns** by default, deliberately: seats are live, and a check that
122
+ failed on the day it shipped would break the seats it exists to move. Once every
123
+ row of the inventory reads `api-key`, set
124
+
125
+ ```sh
126
+ MAESTRO_REQUIRE_CONSOLE_KEY=1
127
+ ```
128
+
129
+ in each seat's `.env` (or the fleet's environment). `subscription` and `both`
130
+ then FAIL `maestro doctor` and exit 1 from `maestro seat-auth`, so a seat that
131
+ regresses to a subscription token announces itself instead of quietly billing a
132
+ personal plan. Record the date it was set fleet-wide — that, plus the last token
133
+ revocation, is what closes this gate.
134
+
75
135
  ## What is automated vs human
76
136
 
77
137
  maestro cannot create Console workspaces or keys for you — that requires a human
@@ -177,15 +177,60 @@ summary and in `.maestro/upgrade-result.json` `{from, to, at, steps}`:
177
177
  | `globalInstall` | `npm i -g @cohortapp/agent-sdk@<this version>` so `maestro` / `cohort` / `cohort-mcp` on PATH match the agent dir (`MAESTRO_SKIP_GLOBAL_INSTALL=1` skips) |
178
178
  | `verify` | the `maestro upgrade --verify` report, below |
179
179
 
180
- autoupdate then kickstarts the daemon and health-gates it: the daemon
181
- process must be alive with a fresh `org-mesh connected` / `[daemon] Running`
182
- line, and — when a `-session` plist was generated — the session label must be
183
- in `launchctl list` with a live supervisor process. A missing session is
184
- logged as `reconcile-failed` and does **not** roll back (the daemon's
185
- `--print` lane is the front door meanwhile; doctor says what to do). Every
186
- launchd and `pgrep` question is scoped to *this* agent (`ai.maestro.<first>-*`
187
- and its own agent dir), so two seats on one Mac never gate on — or restart —
188
- each other.
180
+ autoupdate then kickstarts the daemon and health-gates it. **The gate means
181
+ health, not "running"** — it used to be "pid alive + a fresh `org-mesh
182
+ connected` line", and a crash-looping daemon satisfies both (launchd respawns
183
+ it, and every respawn prints the line again): one seat logged `up to date
184
+ (2.17.0)` hourly for six days, 2026-09-15..21, while its daemon died ~25 s
185
+ after every start, and the org saw no presence beat from it for ten days. The
186
+ daemon now has to answer all of:
187
+
188
+ - **pid stability** — the daemon pid set is unchanged across two samples 90 s
189
+ apart and every pid's `ps etime` is ≥ 90 s (3× the observed crash
190
+ interval, well inside the hourly cadence; `MAESTRO_AUTOUPDATE_STABLE_S`);
191
+ - **no fatal line** in its own log since the restart (`[DAEMON]
192
+ uncaughtException:`, `[daemon] Fatal:`, `[wrapper] FATAL:`,
193
+ `ERR_MODULE_NOT_FOUND`) — the log has no per-line timestamps and the
194
+ wrapper names the file for the day the process started, so "since the
195
+ restart" is a line offset taken before the kickstart;
196
+ - **an org-acknowledged beat** — `state/org/last-beat.json` `{at, ok, code?}`,
197
+ which `lib/org/mesh.mjs` overwrites on every presence beat (`ok:true` only
198
+ when the org answered 2xx), must say `ok:true`, post-date the daemon's start
199
+ and be younger than 5 min (`MAESTRO_AUTOUPDATE_BEAT_FRESH_S`, the same bar
200
+ after which hq's presence view calls a seat offline). Skipped only for a
201
+ seat `config/org.yaml` does not enrol.
202
+
203
+ When a `-session` plist was generated, the session label must also be loaded
204
+ with a live supervisor process; a missing session is logged as
205
+ `reconcile-failed` and does **not** roll back (the daemon's `--print` lane is
206
+ the front door meanwhile; doctor says what to do). Every launchd and `pgrep`
207
+ question is scoped to *this* agent (`ai.maestro.<first>-*` and its own agent
208
+ dir), so two seats on one Mac never gate on — or restart — each other.
209
+
210
+ **Being up to date is not an exemption.** When the installed version is
211
+ already `@latest` the run still runs the same gate on the current version.
212
+ An unhealthy daemon is logged loudly, recorded in `state/autoupdate/last.json`
213
+ as `{ok:false, healthy:false, reason:"unhealthy-current: …"}` (the beat
214
+ carries it as `machine.upgrade`), and announced once per episode through
215
+ `state/session/upgrade-notice.json` `{from:CUR, to:CUR, healthy:false,
216
+ reason}` so the front-door session's feed surfaces it as a `daemon-unhealthy`
217
+ directive — a distinct action from `upgrade-available`, so no session restarts
218
+ on a health report. Nothing is rolled back (there is no upgrade to undo), an
219
+ `unhealthy-current` entry never holds a later release, and a recovery restores
220
+ `last.json`.
221
+
222
+ **A stale daemon is reconciled on the same path.** A hand-run `maestro
223
+ upgrade` installs the package and reconciles the seat but never restarts the
224
+ daemon (its verify row says the new version "takes effect when autoupdate
225
+ kickstarts it"). When `state/dashboards/daemon-health.yaml` shows the live
226
+ daemon started on a different `sdk_version` than the installed one, the hourly
227
+ run kickstarts it, waits, and runs the gate exactly as after an automatic
228
+ install — a healthy outcome writes the same `{from:<old>, to:<new>}` notice, so
229
+ the session restarts itself onto the new code when idle. The fatal scan starts
230
+ at the running daemon's own boot marker (`[DAEMON] boot pid=<pid>`, the first
231
+ line `maestro-daemon.mjs` prints), never at the top of the shared start-day
232
+ log, so a one-off crash earlier the same day that launchd already recovered
233
+ from is history, not health.
189
234
 
190
235
  An unhealthy daemon **rolls back**: the previous SDK is reinstalled into the
191
236
  agent dir and *that* package's `upgrade` runs, the global install is put back
@@ -32,20 +32,17 @@ npm i -g @cohortapp/agent-sdk # GLOBAL on purpose: every Claude Code sess
32
32
  # this seat needs `maestro` + `cohort-mcp` on PATH
33
33
  ```
34
34
 
35
- ## 3. The Claude token — one auth story for the whole seat
35
+ ## 3. The Claude credential — one Console API key per seat
36
36
 
37
37
  Every `claude` the seat runs (the daemon's `--print` lane, the main session,
38
- cadence sub-sessions) authenticates with a **long-lived subscription OAuth
39
- token**, never the interactive keychain login (which expires headlessly and
40
- has stranded seats before). Mint it on **any** machine where you are logged
41
- in to Claude Code with the Max subscription — your laptop is fine:
38
+ cadence sub-sessions) authenticates with an **Anthropic Console API key**
39
+ created for this agent, in a Console workspace with its own spend limit. Do
40
+ not use a Claude subscription on an agent seat: neither a `claude setup-token`
41
+ token nor the keychain login (which also expires headlessly). Creating the
42
+ workspace and the key: [billing-console-keys.md](billing-console-keys.md).
42
43
 
43
- ```bash
44
- claude setup-token # prints one token; copy it
45
- ```
46
-
47
- Keep it in your clipboard / password manager for step 5. It is per seat: mint
48
- a fresh one whenever `cohort doctor` reports the token as rejected.
44
+ Keep the key in your password manager for step 5. It is per seat: rotate it in
45
+ the Console whenever `cohort doctor` reports it as rejected.
49
46
 
50
47
  ## 4. Create the agent repo
51
48
 
@@ -59,7 +56,7 @@ including `.mcp.json`, which exposes the `cohort-mcp` org tool surface to
59
56
  interactive Claude sessions, and the PreToolUse hooks that keep its outbound
60
57
  writes on the CLI lane).
61
58
 
62
- ## 5. Enroll against `https://os.cohortapp.com` — and paste the token
59
+ ## 5. Enroll against `https://os.cohortapp.com` — and paste the API key
63
60
 
64
61
  Two lanes — pick one:
65
62
 
@@ -68,13 +65,13 @@ Two lanes — pick one:
68
65
 
69
66
  ```bash
70
67
  export COHORT_API_KEY=nlk_… COHORT_ORG_ID=<org-ID> COHORT_AGENT_ID=<member-slug>
71
- cohort setup # the model section (order 30) asks for the token from step 3
72
- # headless / one-paste bootstrap: the token comes from the environment instead of a prompt
73
- CLAUDE_CODE_OAUTH_TOKEN=<token> cohort setup --headless
68
+ cohort setup # the model section (order 30) asks for the API key from step 3
69
+ # headless / one-paste bootstrap: the key comes from the environment instead of a prompt
70
+ ANTHROPIC_API_KEY=<key> cohort setup --headless
74
71
  ```
75
72
 
76
- Either way `.env` ends up with `CLAUDE_CODE_OAUTH_TOKEN` +
77
- `MAESTRO_PREFER_SUBSCRIPTION_AUTH=1`, mode 600. No `ANTHROPIC_API_KEY`.
73
+ Either way `.env` ends up with `ANTHROPIC_API_KEY`, mode 600, and no
74
+ `CLAUDE_CODE_OAUTH_TOKEN` or `MAESTRO_PREFER_SUBSCRIPTION_AUTH`.
78
75
 
79
76
  > **`COHORT_ORG_ID` is the org's ID, not its slug** — this line said
80
77
  > `<org-slug>` and that would 401 every call. The value rides as `x-org-id`
@@ -98,7 +95,7 @@ Two lanes — pick one:
98
95
 
99
96
  | Order | Section | What it does |
100
97
  | --- | --- | --- |
101
- | 30 | `model` | Claude auth — **`oauth-token` by default**: paste the `claude setup-token` output; writes `CLAUDE_CODE_OAUTH_TOKEN` + `MAESTRO_PREFER_SUBSCRIPTION_AUTH=1`, chmods `.env` 600 |
98
+ | 30 | `model` | Claude auth — **`api-key` by default**: paste the agent's Console API key; writes `ANTHROPIC_API_KEY`, chmods `.env` 600 |
102
99
  | 75 | `org` | endpoint + token (the enrollment SoT) |
103
100
  | 76 | `messaging` | messaging.read/write + calling.write scopes, home channels |
104
101
  | 77 | `orgmail` | **workspace mailbox** — writes `config/orgmail.yaml` |
@@ -144,13 +141,11 @@ cohort doctor
144
141
 
145
142
  All green includes, in this order:
146
143
 
147
- - **Claude auth** — the mode in effect (`subscription OAuth token` is the
148
- expected line) and what `claude auth status` reports. The CLI reports that
149
- a credential is PRESENT — it answers "logged in" for any token value — so a
150
- bad token is only caught by the first real spawn (`logs/sessions`, and the
151
- beat flips to `relogin_required`); a `claude -p "ping"` after doctor is the
152
- cheap way to prove it. "keychain login"
153
- or "nothing configured" means step 3/5 was skipped.
144
+ - **Claude auth** — the mode in effect (`ANTHROPIC_API_KEY` is the expected
145
+ line for an agent seat). In API-key mode the probe makes a real call, so a
146
+ rejected key shows here. A subscription line (OAuth token or keychain login)
147
+ on an agent seat means it is still on the pre-migration setup — see step 3.
148
+ "nothing configured" means step 3/5 was skipped.
154
149
  - **Cohort sources** — for each of `base` / `orgId` / `token` / `agentId`,
155
150
  which file won (`config/org.yaml` beats the environment beats `.env`) and
156
151
  every value that lost. If you edited `.env` and nothing changed, this line
@@ -52,7 +52,7 @@ core runner.
52
52
  |---------|-------|-------------|
53
53
  | `identity` | 10 | Agent name and title, the **function × altitude** archetype, and the principal. Writes `config/agent.json`. |
54
54
  | `company` | 20 | Company-context interview — name, website, industry, one-line + detailed overview, stage/size, footprint, regulation, top priorities, and key people. Writes `config/company.json`. |
55
- | `model` | 30 | Claude auth. Three modes, **`oauth-token` the default**: run `claude setup-token` on any logged-in machine and paste the result (headless: `CLAUDE_CODE_OAUTH_TOKEN` in the environment) → `.env` gets `CLAUDE_CODE_OAUTH_TOKEN` + `MAESTRO_PREFER_SUBSCRIPTION_AUTH=1`; or this machine's keychain login; or a pasted Anthropic API key. Chmods `.env` 600. |
55
+ | `model` | 30 | Claude auth. Three modes, **`api-key` the default**: paste an Anthropic Console API key created for this agent in a Console workspace with a spend limit (headless: `ANTHROPIC_API_KEY` in the environment) → `.env` gets `ANTHROPIC_API_KEY`. Setup runbook: [billing-console-keys.md](billing-console-keys.md). The other two modes — a `claude setup-token` subscription token, or this machine's keychain login — are for a person's own development machine, not an agent seat. Chmods `.env` 600. |
56
56
  | `comms` | 45 | Wires each messaging channel — Slack, Gmail, SMS, WhatsApp, Telegram, voice — into `.env`/gate files and verifies inbound. |
57
57
  | `tools` | 50 | Selects which channels and MCP servers the agent should run. |
58
58
  | `operating-model` | 60 | Deterministically generates the operating charter, a seeded WBS backlog (≥5 open items), 40–60 sub-agents + skills + workflows + MCP servers + event-routing, archetype cadences, the autonomy policy, the communication profile, and the launchd plists. |
@@ -0,0 +1,156 @@
1
+ # Fleet rollout — publish one version, then prove the fleet took it
2
+
3
+ `scripts/fleet/rollout.mjs` is the whole propagation path in one command.
4
+
5
+ ```
6
+ node scripts/fleet/rollout.mjs [--dry-run] [--verify-only]
7
+ [--deadline <min>] [--interval <s>]
8
+ [--seat-window <min>] [--agent-root <path>] [--json]
9
+ ```
10
+
11
+ There is exactly one way an SDK change reaches the fleet: it is published to
12
+ npm, and each seat's hourly `scripts/local-triggers/autoupdate.sh` installs
13
+ `@latest`. Nothing is pushed to a machine. **This tool never ssh's anywhere**,
14
+ and it takes **no credential as an argument or environment variable** — npm auth
15
+ comes from the operator's own `npm login`, org auth from the agent config the
16
+ rest of `lib/org` reads.
17
+
18
+ Both promises are enforced **twice**, because a source-reading test only catches
19
+ the shapes it was taught — `execSync("ssh …")` and `deps.exec(cmd, [])` both
20
+ slipped past the first version of it. `runCommand()` refuses any command but
21
+ `npm` and `git` at runtime, on the value; `parseArgs()` drops everything after
22
+ the first `=` before an error string is built, so no branch can echo a
23
+ credential back into your scrollback. The structural tests in
24
+ `scripts/fleet/rollout.test.mjs` sit on top, pinning the import surface of
25
+ `node:child_process` so nothing can reach a process without that guarded seam.
26
+
27
+ ## The three stages
28
+
29
+ **1 — pre-flight.** Refuses the publish unless all of:
30
+
31
+ | gate | why it is here |
32
+ | --- | --- |
33
+ | `npm whoami` succeeds | a publish with no auth fails halfway and leaves you guessing |
34
+ | local version > registry `@latest` | the registry, not a changelog, is the authority on what is published |
35
+ | working tree clean | you can only publish what is committed |
36
+ | `v<version>` exists locally **and** on the remote | published bytes stay findable |
37
+ | `npm run check` passes | … |
38
+ | `npm test` passes | … on the bytes about to be published |
39
+
40
+ The last two are **re-run**, never read from a log: a green run from an hour ago
41
+ is a fact about a different tree. They are skipped only when a cheaper gate has
42
+ already blocked the publish — and the run says so in that case, so "pre-flight
43
+ passed" can never quietly mean "was not run".
44
+
45
+ **2 — publish.** `npm publish`, then poll the registry until `@latest` **is** the
46
+ new version. A 404 or a stale answer in the first seconds is **read lag, not
47
+ failure** — that has been misread as a failed publish before, so the poll
48
+ retries and only fails when its attempts run out.
49
+
50
+ **3 — verify propagation.** Poll the org directory — hq's server-stamped
51
+ presence-beat derivation, the same signal the human fleet page paints — and
52
+ print a per-seat table every round until every seat reports the new version or
53
+ the deadline passes. The run exits non-zero naming exactly which seats are
54
+ behind.
55
+
56
+ A **seat** is an AI colleague with a server-stamped beat inside `--seat-window`
57
+ (default 30 min). An agent with no machine (responder-only) takes no SDK version
58
+ and is counted separately, never as "behind".
59
+
60
+ ## Exit codes
61
+
62
+ | code | meaning |
63
+ | --- | --- |
64
+ | 0 | published (or already published) **and** every seat reports the version |
65
+ | 1 | pre-flight refusal — nothing was published, nothing changed |
66
+ | 2 | publish failed, or the registry never served the new version |
67
+ | 3 | deadline passed with seats behind or unverifiable |
68
+
69
+ ### `3` is three different things — do not gate on the exit code yet
70
+
71
+ **`rollout` cannot be used as a success gate today.** Every seat classifies as
72
+ `unverifiable` (see below), an unverified seat is deliberately not counted as
73
+ done, so a flawless publish onto a fully propagated fleet **still exits 3**.
74
+ A cron, a CI step or a wrapper script that treats `0` as "shipped" will never
75
+ fire; one that treats `3` as "seats are behind" will page you for nothing.
76
+
77
+ Branch on `--json` instead. It prints one object — `{code, reason, note,
78
+ summary}` — whose `reason` separates the cases the exit code merges:
79
+
80
+ | `reason` | meaning |
81
+ | --- | --- |
82
+ | `verified` | every seat reports the target version (`code` 0) |
83
+ | `behind` | at least one seat is **provably** on an older version |
84
+ | `unverifiable` | no seat is known to be behind; nothing available can prove any seat is current |
85
+ | `empty-fleet` | no beating seats were found — never treated as done |
86
+ | `no-fleet-read` | the org read never succeeded; says nothing about the fleet |
87
+
88
+ `unverifiable` becomes `verified` the moment the one-line hq change below lands,
89
+ with no change to this script. `propagationOutcome()` is pure and exported, so
90
+ the mapping is pinned by test rather than by this table.
91
+
92
+ Idempotent: when `@latest` already equals the local version the run prints
93
+ `already published, verifying propagation` and goes straight to stage 3.
94
+ Re-running it is the supported way to watch a rollout land. `--verify-only`
95
+ does stage 3 alone, against any version, without touching npm.
96
+
97
+ ## Today's blocker, and the one honest gap
98
+
99
+ **npm auth.** `npm whoami` is a 401 on this machine, so stage 1 refuses. Only a
100
+ human can clear it (`npm login` as the publisher). The moment it is cleared,
101
+ this command is the entire rollout.
102
+
103
+ **`unverifiable` seats.** Stage 3 wants each seat's *running* version. Every seat
104
+ already reports it — the presence beat carries `status.machine.sdkVersion` and
105
+ hq persists it to `AgentStatus.machine` — but **no read an agent can make gives
106
+ it back**: hq's `directory` read (`src/server/rpc/reads.ts#directory`) selects
107
+ five `AgentStatus` columns and `machine` is not among them, and `member.get`
108
+ does not read `AgentStatus` at all. So every seat currently reads `unknown` and
109
+ the run exits 3.
110
+
111
+ That is deliberate. The tool does **not** count a beating seat as up to date:
112
+ treating "running" as "healthy" is exactly what let one seat sit six days behind
113
+ while its own log said "up to date" every hour. It proves liveness, says plainly
114
+ that it cannot prove the version, and fails.
115
+
116
+ **To close it (hq side, one lane):** add `machine: true` to the `agentStatus`
117
+ select in `directory()` and echo `sdkVersion` (only that key — `machine` is an
118
+ open record and the rest is seat telemetry) into the per-member `status` object,
119
+ with a test pinning the field. `seatVersion()` in the script already reads
120
+ `status.machine.sdkVersion` first and will light up with no change here.
121
+
122
+ Until it does, the run says so in words on every all-unverifiable finish — that
123
+ exit 3 does **not** mean a seat is behind — rather than leaving the reader to
124
+ infer it from a count.
125
+
126
+ ## One wrinkle: the gates dirty the tree
127
+
128
+ `npm test` appends to a tracked runtime ledger (`.claude-flow/policy/state.json`),
129
+ so a second run in a row would refuse on a file the *first* run wrote. The
130
+ clean-tree gate is not relaxed for it — the run names those paths and tells you
131
+ to `git checkout --` them. The gate order also means a first run is unaffected:
132
+ cleanliness is read before the gates run.
133
+
134
+ ## Reading a round
135
+
136
+ ```
137
+ Fleet vs 2.18.1 (round 2 · 46s elapsed)
138
+ SEAT ID BEAT REPORTS VERDICT
139
+ -------------- ---- ---- ------- ------------
140
+ <name> A0xx 6s 2.18.1 current
141
+ <name> A0xx 15s 2.17.0 stale
142
+ <name> A0xx 20s unknown unverifiable
143
+ 15 seat(s): 1 current · 1 stale · 13 unverifiable (+32 agent(s) with no beating machine — not rollout targets)
144
+ ```
145
+
146
+ `BEAT` is the age of the seat's last **server-stamped** beat — the only honest
147
+ liveness signal there is. `REPORTS` is what that seat says it is running.
148
+
149
+ ## What the suite covers
150
+
151
+ `npm test` discovers every `*.test.mjs` under `lib/`, `scripts/`, `bin/`,
152
+ `services/` and `test/` — there is no list to append to. `npm run test:list`
153
+ asks the same runner what it found and prints it without running anything; it
154
+ used to be a hand-typed enumeration that had drifted to 46 of 82 files, and
155
+ `scripts/ci/run-tests.test.mjs` now fails the build if any `package.json` script
156
+ starts enumerating the suite again.