@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
@@ -1,382 +0,0 @@
1
- /**
2
- * compile.test.mjs — the plan compiler + obligation schema (SPEC §5.6).
3
- * Run: node --test lib/plan/compile.test.mjs
4
- *
5
- * The compiler is PURE, so these tests are pure too: fixture mandate, fixture
6
- * manifest, fixture cadence list. No fs, no network, no clock.
7
- */
8
- "use strict";
9
-
10
- import { test } from "node:test";
11
- import assert from "node:assert/strict";
12
-
13
- import { compilePlan, solveSlot, busyWindows, allowedToolsFor, actionClassesFor, toolNameFor, STANDARD_REACTS, COMPILER_VERSION } from "./compile.mjs";
14
- import { validatePlan, validateObligation } from "./schema.mjs";
15
- import { deriveMandate, toMandateBody, pickSensor, slug } from "../mandate/derive.mjs";
16
-
17
- /** A small, explicit capability manifest. */
18
- function manifest(ids = ["board_ready", "task_create", "task_update", "board_claim", "board_complete", "crm_list_deals", "messaging_send", "knowledge_search", "email_inbox", "email_triage", "messaging_history"], unreachable = []) {
19
- return {
20
- schemaVersion: 1,
21
- checksum: "fixture",
22
- entries: [
23
- ...ids.map((id) => ({ id, plane: "org", kind: "method_tool", reachable: true, blastRadius: id === "messaging_send" ? "external" : id.endsWith("_create") ? "irreversible" : "internal" })),
24
- ...unreachable.map((id) => ({ id, plane: "org", kind: "method_tool", reachable: false, blastRadius: "internal" })),
25
- { id: "builtin:Read", plane: "builtin", kind: "tool", reachable: true, blastRadius: "internal" },
26
- ],
27
- };
28
- }
29
-
30
- const STANDARD = [
31
- { id: "inbox-processor", scope: "standard", mode: "guarded", interval: 300, prompt: "schedules/triggers/inbox-processor.md" },
32
- { id: "daily-morning-brief", scope: "standard", mode: "escalate", calendar: { hour: 6, minute: 30 }, prompt: "schedules/triggers/daily-morning-brief.md" },
33
- { id: "nightly-cost-reconcile", scope: "standard", mode: "inline", calendar: { hour: 3, minute: 30 } },
34
- ];
35
-
36
- const ARCHETYPE = [
37
- { id: "weekly-operating-review", scope: "function", mode: "escalate", cadence: "weekly", calendar: { weekday: 2, hour: 10, minute: 0 }, prompt: "schedules/triggers/weekly-operating-review.md", purpose: "Run the leadership operating review" },
38
- ];
39
-
40
- function objective(over = {}) {
41
- return {
42
- key: "pipeline-coverage", kind: "OBJECTIVE", text: "Pipeline coverage", state: "active",
43
- metric: "pipeline_coverage_x", unit: "x", direction: "up", target: 3, tolerance: 0.2,
44
- weight: 1, cadence: "weekly", sensor: { capability: "crm_list_deals", params: { stage: "open" }, source: "method" },
45
- // `adoptedById` is part of the fixture because it is part of the LAW: an
46
- // active objective with no adopter on record compiles nothing (see
47
- // lib/mandate/model.adoptionDefect). It must differ from the body's memberId.
48
- collaborators: [], id: "obj_7Kx", charterSectionId: "cs_12", adoptedById: "M-SUPERVISOR", ...over,
49
- };
50
- }
51
-
52
- test("standard cadences compile to SCHEDULE obligations with VERBATIM triggers", () => {
53
- const p = compilePlan({ mandate: {}, manifest: manifest(), standardCadences: STANDARD });
54
- const inbox = p.obligations.find((o) => o.key === "schedule.inbox-processor");
55
- assert.equal(inbox.kind, "SCHEDULE");
56
- assert.deepEqual(inbox.schedule, { interval: 300 });
57
- assert.equal(inbox.source.origin, "standard");
58
- assert.equal(inbox.scope, "standard");
59
- const brief = p.obligations.find((o) => o.key === "schedule.daily-morning-brief");
60
- assert.deepEqual(brief.schedule, { calendar: { hour: 6, minute: 30 } }, "trigger copied byte-for-byte so plists do not churn");
61
- });
62
-
63
- test("PROPOSED objectives compile NOTHING — the schedule only grows on human adoption", () => {
64
- const proposed = { objectives: [objective({ state: "proposed" })] };
65
- const p = compilePlan({ mandate: proposed, manifest: manifest(), standardCadences: STANDARD });
66
- assert.equal(p.counts.OUTCOME, 0);
67
- assert.equal(p.counts.proposedObjectives, 1);
68
- assert.ok(!p.obligations.some((o) => o.key.startsWith("schedule.measure.")));
69
- });
70
-
71
- test("an ADOPTED objective compiles measure + outcome, provenance-linked to the objective and charter clause", () => {
72
- const p = compilePlan({ mandate: { objectives: [objective()] }, manifest: manifest(), standardCadences: STANDARD });
73
- const measure = p.obligations.find((o) => o.key === "schedule.measure.pipeline-coverage");
74
- const outcome = p.obligations.find((o) => o.key === "outcome.pipeline-coverage");
75
- assert.ok(measure && outcome);
76
- assert.equal(measure.cadence_id, "measure-pipeline-coverage");
77
- assert.equal(outcome.objective_id, "obj_7Kx");
78
- assert.equal(outcome.source.charter_section, "cs_12");
79
- assert.equal(outcome.sensor.capability, "crm_list_deals");
80
- assert.equal(outcome.target, 3);
81
- // This is what makes it "not a generic cron list":
82
- assert.equal(measure.source.origin, "mandate");
83
- assert.deepEqual(measure.uses, ["crm_list_deals"]);
84
- });
85
-
86
- test("a REVIEWER collaborator compiles a review cadence", () => {
87
- const obj = objective({ collaborators: [{ member_id: "mem_A03", role: "REVIEWER" }] });
88
- const p = compilePlan({ mandate: { objectives: [obj] }, manifest: manifest(), standardCadences: [] });
89
- const review = p.obligations.find((o) => o.key === "schedule.review.pipeline-coverage");
90
- assert.ok(review, "review obligation emitted");
91
- assert.equal(review.cadence_id, "review-pipeline-coverage");
92
- assert.equal(review.mode, "escalate");
93
- });
94
-
95
- test("an objective whose sensor is UNREACHABLE emits no OUTCOME and opens drift", () => {
96
- const p = compilePlan({ mandate: { objectives: [objective()] }, manifest: manifest(["board_ready"], ["crm_list_deals"]), standardCadences: [] });
97
- assert.equal(p.counts.OUTCOME, 0);
98
- const d = p.drift.find((x) => x.kind === "unreachable_capability");
99
- assert.ok(d, `expected unreachable_capability drift, got ${JSON.stringify(p.drift)}`);
100
- assert.equal(d.detail.sensor, "crm_list_deals");
101
- });
102
-
103
- test("an objective with a HUMAN sensor emits no OUTCOME and opens stale_sensor drift", () => {
104
- const obj = objective({ sensor: { capability: null, params: {}, source: "human" } });
105
- const p = compilePlan({ mandate: { objectives: [obj] }, manifest: manifest(), standardCadences: [] });
106
- assert.equal(p.counts.OUTCOME, 0);
107
- assert.ok(p.drift.some((d) => d.kind === "stale_sensor"));
108
- });
109
-
110
- test("uses[] is filtered against the manifest and every drop is recorded as drift", () => {
111
- const p = compilePlan({ mandate: {}, manifest: manifest(["email_inbox"]), standardCadences: STANDARD });
112
- const inbox = p.obligations.find((o) => o.key === "schedule.inbox-processor");
113
- assert.deepEqual(inbox.uses, ["email_inbox"], "unreachable capabilities are dropped, not cited");
114
- assert.ok(p.drift.filter((d) => d.kind === "unreachable_capability" && d.key === "schedule.inbox-processor").length >= 2);
115
- });
116
-
117
- test("allowed_tools is COMPUTED from uses[] — a session-spawning cadence can never be unscoped", () => {
118
- const p = compilePlan({ mandate: {}, manifest: manifest(), standardCadences: STANDARD, archetypeCadences: ARCHETYPE });
119
- for (const ob of p.obligations.filter((o) => o.kind === "SCHEDULE" && o.mode !== "inline")) {
120
- assert.ok(ob.allowed_tools.length > 0, `${ob.key} must carry a tool scope`);
121
- assert.ok(ob.allowed_tools.includes("Read"), "baseline read primitives present");
122
- }
123
- const inline = p.obligations.find((o) => o.key === "schedule.nightly-cost-reconcile");
124
- assert.deepEqual(inline.allowed_tools, [], "an inline cadence spawns no session and needs no scope");
125
- });
126
-
127
- test("action_classes are derived from the manifest's blast radii", () => {
128
- assert.deepEqual(actionClassesFor(["messaging_send", "board_ready"], manifest()), ["external"]);
129
- assert.deepEqual(actionClassesFor(["board_ready"], manifest()), []);
130
- });
131
-
132
- test("toolNameFor maps every plane onto a session tool name", () => {
133
- assert.equal(toolNameFor("builtin:Read"), "Read");
134
- assert.equal(toolNameFor("skill:agent-skills:morning-brief"), "Skill");
135
- assert.equal(toolNameFor("workflow:weekly-operating-review"), "Task");
136
- assert.equal(toolNameFor("mcp:notion"), "mcp__notion__*");
137
- assert.equal(toolNameFor("board_ready"), "board_ready");
138
- assert.deepEqual(allowedToolsFor(["board_ready", "builtin:Read"]), ["Glob", "Grep", "LS", "Read", "board_ready"]);
139
- });
140
-
141
- test("the four standard REACT obligations always compile", () => {
142
- const p = compilePlan({ mandate: {}, manifest: manifest(["email_message", "email_thread", "email_send", "board_ready", "board_claim", "task_update", "approval_wait", "decision_list"]), standardCadences: [] });
143
- for (const r of STANDARD_REACTS) assert.ok(p.obligations.some((o) => o.key === r.key), `${r.key} missing`);
144
- assert.equal(p.counts.REACT, STANDARD_REACTS.length);
145
- });
146
-
147
- test("mandate reactsTo[] adds REACT obligations bound to an objective", () => {
148
- const mandate = { objectives: [], reactsTo: [{ key: "react.mandate.deal-stalled", topic: "task", kind: "stalled", uses: ["crm_list_deals"], objectiveId: "obj_7Kx", offlineSafe: true }] };
149
- const p = compilePlan({ mandate, manifest: manifest(), standardCadences: [] });
150
- const r = p.obligations.find((o) => o.key === "react.mandate.deal-stalled");
151
- assert.equal(r.kind, "REACT");
152
- assert.equal(r.objective_id, "obj_7Kx");
153
- assert.equal(r.offline_safe, true);
154
- assert.equal(r.source.origin, "mandate");
155
- });
156
-
157
- test("DETERMINISM: identical inputs → identical obligationsHash; a changed mandate changes it", () => {
158
- const a = compilePlan({ mandate: { objectives: [objective()] }, manifest: manifest(), standardCadences: STANDARD, archetypeCadences: ARCHETYPE });
159
- const b = compilePlan({ mandate: { objectives: [objective()] }, manifest: manifest(), standardCadences: STANDARD, archetypeCadences: ARCHETYPE });
160
- assert.equal(a.obligationsHash, b.obligationsHash);
161
- assert.equal(a.inputsHash, b.inputsHash);
162
- const c = compilePlan({ mandate: { objectives: [objective({ target: 4 })] }, manifest: manifest(), standardCadences: STANDARD, archetypeCadences: ARCHETYPE });
163
- assert.notEqual(a.inputsHash, c.inputsHash, "a different mandate is a different plan input");
164
- });
165
-
166
- test("DETERMINISM: objective input ORDER does not change the emitted schedule", () => {
167
- const o1 = objective();
168
- const o2 = objective({ key: "decision-latency", id: "obj_2", metric: "decision_days", cadence: "monthly", weight: 1 });
169
- const a = compilePlan({ mandate: { objectives: [o1, o2] }, manifest: manifest(), standardCadences: [] });
170
- const b = compilePlan({ mandate: { objectives: [o2, o1] }, manifest: manifest(), standardCadences: [] });
171
- assert.equal(a.obligationsHash, b.obligationsHash);
172
- });
173
-
174
- test("slot solver: deterministic, avoids the principal's busy hours, respects the concurrency cap", () => {
175
- const busy = busyWindows([{ weekday: 1, hour: 9 }, { weekday: 1, hour: 10 }]);
176
- const state = { busy, taken: new Map(), concurrency: 1 };
177
- const a = solveSlot("schedule.measure.alpha", "weekly", state);
178
- const b = solveSlot("schedule.measure.beta", "weekly", state);
179
- assert.notDeepEqual(a.calendar, b.calendar, "the cap forces distinct slots");
180
- for (const s of [a, b]) assert.ok(!busy.has(`${s.calendar.weekday}:${s.calendar.hour}`), "never lands in a busy window");
181
- // Same key, fresh state → same slot, forever.
182
- const again = solveSlot("schedule.measure.alpha", "weekly", { busy, taken: new Map(), concurrency: 1 });
183
- assert.deepEqual(again.calendar, a.calendar);
184
- });
185
-
186
- test("budget envelope: objectives past the envelope are SUSPENDED with a breach record, never dropped", () => {
187
- const objs = [
188
- objective({ key: "a", id: "a", weight: 3 }),
189
- objective({ key: "b", id: "b", weight: 2 }),
190
- objective({ key: "c", id: "c", weight: 1 }),
191
- ];
192
- const p = compilePlan({ mandate: { objectives: objs, budgetCentsPerPeriod: 500 }, manifest: manifest(), standardCadences: [], seatBudgetCents: 1000 });
193
- const suspended = p.obligations.filter((o) => o.status === "suspended");
194
- assert.ok(suspended.length > 0, "the third objective exceeds the envelope");
195
- assert.ok(p.drift.some((d) => d.kind === "budget_breach"));
196
- assert.equal(p.obligations.filter((o) => o.kind === "OUTCOME").length, 3, "all three still exist — suspended, not dropped");
197
- });
198
-
199
- // ---------------------------------------------------------------------------
200
- // THE MANDATE BODY JOINT — no invented money, ever
201
- // ---------------------------------------------------------------------------
202
-
203
- test("THE BUG: a mandate with no budget compiles UNMETERED, never 500", () => {
204
- // This is the exact failure. hq sent no `budgetCentsPerPeriod`, the compiler
205
- // read `undefined`, `Number.isFinite(undefined)` was false, and every single
206
- // obligation in the fleet silently took 500c against a 10000c envelope that
207
- // no column anywhere produced. Nothing errored, so nothing was ever noticed.
208
- const logs = [];
209
- const p = compilePlan({
210
- mandate: { objectives: [objective()] },
211
- manifest: manifest(),
212
- standardCadences: STANDARD,
213
- log: (level, msg) => logs.push([level, msg]),
214
- });
215
-
216
- for (const ob of p.obligations) {
217
- assert.equal(ob.budget_cents_per_period, null, `${ob.key} must be unmetered, not a made-up number`);
218
- assert.notEqual(ob.budget_cents_per_period, 500, `${ob.key} must never be 500`);
219
- }
220
- // Unmetered means NO ceiling is invented either — nothing is suspended for
221
- // breaching a 10000c envelope that never existed.
222
- assert.equal(p.obligations.filter((o) => o.status === "suspended").length, 0);
223
- assert.ok(!p.drift.some((d) => d.kind === "budget_breach"));
224
- // FAIL-OPEN IS FINE; SILENT IS NOT.
225
- assert.ok(logs.length > 0, "the degradation is announced");
226
- assert.ok(logs.some(([l]) => l === "error"), "a pre-contract body is an error-level event");
227
- assert.equal(p.contract.ok, false);
228
- assert.equal(p.contract.unmetered, true);
229
- assert.ok(p.warnings.length > 0);
230
- });
231
-
232
- test("a funded hq body puts the MANDATE's number on each obligation", () => {
233
- const objs = [
234
- objective({ key: "a", id: "a", weight: 3, budgetCentsPerPeriod: 18_750 }),
235
- objective({ key: "b", id: "b", weight: 1, budgetCentsPerPeriod: 6_250 }),
236
- ];
237
- const p = compilePlan({
238
- mandate: {
239
- contractVersion: 1, memberId: "mem_self", objectives: objs, proposed: [],
240
- budgetCentsPerPeriod: null, seatBudgetCents: 25_000, budgetPeriod: "monthly",
241
- budgetSource: "employee.payBasis.meteredBudget", collaborators: [], reactsTo: [],
242
- degradations: [], generatedAt: "2026-08-11T00:00:00.000Z",
243
- },
244
- manifest: manifest(), standardCadences: [], log: () => {},
245
- });
246
- assert.equal(p.contract.ok, true);
247
- assert.equal(p.contract.unmetered, false);
248
- const byKey = Object.fromEntries(p.obligations.filter((o) => o.kind === "OUTCOME").map((o) => [o.key, o.budget_cents_per_period]));
249
- assert.equal(byKey["outcome.a"], 18_750);
250
- assert.equal(byKey["outcome.b"], 6_250);
251
- // Obligations with no objective behind them stay unmetered — hq holds no
252
- // per-obligation column, and `budgetCentsPerPeriod:null` says exactly that.
253
- for (const ob of p.obligations.filter((o) => o.kind === "REACT")) {
254
- assert.equal(ob.budget_cents_per_period, null);
255
- }
256
- });
257
-
258
- test("budgets are NOT part of obligationsHash — funding a seat must not churn every plist", () => {
259
- const base = { objectives: [objective({ key: "a", id: "a" })] };
260
- const unfunded = compilePlan({ mandate: base, manifest: manifest(), standardCadences: STANDARD, log: () => {} });
261
- const funded = compilePlan({
262
- mandate: { ...base, objectives: [objective({ key: "a", id: "a", budgetCentsPerPeriod: 9_000 })], seatBudgetCents: 9_000 },
263
- manifest: manifest(), standardCadences: STANDARD, log: () => {},
264
- });
265
- assert.equal(funded.obligationsHash, unfunded.obligationsHash, "funding changes money, not identity");
266
- });
267
-
268
- test("an explicit figure in a MALFORMED body is still used — only invention is forbidden", () => {
269
- // The contract failing does not license throwing away a number the caller
270
- // actually stated. It licenses refusing to make one up.
271
- const logs = [];
272
- const p = compilePlan({
273
- mandate: { objectives: [objective({ key: "a", id: "a" })], budgetCentsPerPeriod: 700 },
274
- manifest: manifest(), standardCadences: [], log: (l, m) => logs.push([l, m]),
275
- });
276
- assert.equal(p.contract.ok, false, "still a pre-contract body");
277
- assert.ok(logs.some(([l]) => l === "error"), "and still loud about it");
278
- assert.equal(p.obligations.find((o) => o.kind === "OUTCOME").budget_cents_per_period, 700);
279
- });
280
-
281
- test("validatePlan is FAIL-CLOSED on an unreachable citation and on an llm-sourced OUTCOME", () => {
282
- const reachable = new Set(["board_ready"]);
283
- const bad = {
284
- obligations: [{
285
- key: "outcome.x", kind: "OUTCOME", source: { origin: "mandate", ref: "x" }, status: "active",
286
- uses: ["ghost_tool"], allowed_tools: ["Read"], action_classes: [], offline_safe: true,
287
- objective_id: "obj", sensor: { source: "llm", capability: null },
288
- }],
289
- };
290
- const v = validatePlan(bad, { reachable });
291
- assert.equal(v.ok, false);
292
- assert.ok(v.errors.some((e) => /not reachable:true/.test(e)));
293
- assert.ok(v.errors.some((e) => /"llm" cannot back an OUTCOME/.test(e)));
294
- });
295
-
296
- test("validateObligation rejects a session-spawning SCHEDULE with no tool scope", () => {
297
- const errs = validateObligation({
298
- key: "schedule.x", kind: "SCHEDULE", source: { origin: "archetype", ref: "x" }, status: "active",
299
- mode: "escalate", schedule: { interval: 600 }, uses: [], allowed_tools: [], action_classes: [], offline_safe: false,
300
- });
301
- assert.ok(errs.some((e) => /non-empty allowed_tools/.test(e)));
302
- });
303
-
304
- test("a full compiled plan validates against the manifest", () => {
305
- const m = manifest();
306
- const p = compilePlan({ mandate: { objectives: [objective()] }, manifest: m, standardCadences: STANDARD, archetypeCadences: ARCHETYPE });
307
- const v = validatePlan(p, { manifest: m });
308
- assert.equal(v.ok, true, v.errors.join("; "));
309
- assert.equal(p.compilerVersion, COMPILER_VERSION);
310
- });
311
-
312
- // ── derivation (the "per-agent and justified, not a static list" property) ───
313
-
314
- test("deriveMandate: charter pillars become PILLARs, KPI categories become OBJECTIVEs parented to them", () => {
315
- const d = deriveMandate({
316
- charterSections: [
317
- { kind: "PILLAR", order: 0, body: "1) Own the agent-platform substrate — runtime, memory, tools." },
318
- { kind: "PILLAR", order: 1, body: "2) Make the fleet governable: every capability observable." },
319
- ],
320
- kpiCategories: [
321
- { id: "execution", name: "Fleet execution & follow-through", examples: ["Commitment-closure rate"] },
322
- // Overlaps neither pillar's words. It is CARRIED but left unplaced —
323
- // the old deriver silently parented it onto pillar #1.
324
- { id: "warehouse-latency", name: "Warehouse latency", examples: ["p95 ms"] },
325
- ],
326
- manifest: manifest(),
327
- });
328
- assert.equal(d.counts.pillars, 2);
329
- assert.equal(d.counts.objectives, 2);
330
- const exec = d.objectives.find((o) => o.key === "execution");
331
- assert.equal(exec.kind, "OBJECTIVE");
332
- assert.ok(exec.parentKey, "every PLACEABLE objective hangs off a pillar (provenance)");
333
- assert.ok(exec.charterSectionId !== undefined);
334
- const orphan = d.objectives.find((o) => o.key === "warehouse-latency");
335
- assert.equal(orphan.parentKey, null, "no fabricated pillar edge");
336
- assert.ok(d.degradations.some((x) => x.kind === "no_pillar_trace" && x.key === "warehouse-latency"));
337
- assert.equal(exec.state, "proposed", "NOTHING is adopted by the agent");
338
- assert.equal(exec.sensor.source, "method");
339
- assert.equal(exec.sensor.capability, "board_ready", "bound to a REACHABLE capability");
340
- });
341
-
342
- test("deriveMandate: two different agents (different charters/KPIs) get different mandates", () => {
343
- const a = deriveMandate({ charterSections: [{ kind: "PILLAR", order: 0, body: "Own revenue growth" }], kpiCategories: [{ id: "pipeline", name: "Pipeline coverage" }], manifest: manifest() });
344
- const b = deriveMandate({ charterSections: [{ kind: "PILLAR", order: 0, body: "Own compliance posture" }], kpiCategories: [{ id: "audit", name: "Audit findings closed" }], manifest: manifest() });
345
- assert.notDeepEqual(a.objectives.map((o) => o.key), b.objectives.map((o) => o.key));
346
- });
347
-
348
- test("deriveMandate: a metric with no REACHABLE sensor is proposed source:human AND flagged", () => {
349
- const d = deriveMandate({ kpiCategories: [{ id: "pipeline", name: "Pipeline coverage", examples: ["Pipeline coverage x"] }], manifest: manifest(["board_ready"]) });
350
- const o = d.objectives[0];
351
- assert.equal(o.sensor.source, "human");
352
- assert.ok(d.degradations.some((x) => x.kind === "no_reachable_sensor"));
353
- });
354
-
355
- test("deriveMandate is deterministic and slug keys are stable", () => {
356
- const input = { charterSections: [{ kind: "PILLAR", order: 0, body: "3) Scale the platform with the roster, not after it." }], kpiCategories: [], manifest: manifest() };
357
- assert.deepEqual(deriveMandate(input).objectives, deriveMandate(input).objectives);
358
- assert.equal(slug("3) Scale the platform with the roster, not after it."), "scale-the-platform-with-the-roster-not-after-it");
359
- });
360
-
361
- test("pickSensor prefers the most specific reachable capability", () => {
362
- assert.equal(pickSensor("weekly pipeline coverage", new Set(["crm_list_deals"])).capability, "crm_list_deals");
363
- assert.equal(pickSensor("decision cycle time", new Set(["decision_list"])).capability, "decision_list");
364
- assert.equal(pickSensor("something unmodelled", new Set(["board_ready"])).source, "human");
365
- });
366
-
367
- test("end to end: derive → compile produces a plan whose obligations trace back to the charter", () => {
368
- const d = deriveMandate({
369
- charterSections: [{ kind: "PILLAR", order: 0, id: "cs_1", body: "Own execution follow-through" }],
370
- kpiCategories: [{ id: "execution", name: "Execution & Follow-Through", examples: ["Commitment-closure rate"] }],
371
- manifest: manifest(),
372
- });
373
- // Simulate the human adoption step in hq.
374
- // Simulating hq's adopt handler means simulating ALL of it — including the
375
- // `adoptedById` column it stamps. Without one the compiler refuses the row.
376
- const adopted = d.objectives.map((o) => (o.kind === "OBJECTIVE" ? { ...o, state: "active", target: 0.95, id: `obj_${o.key}`, adoptedById: "M-SUPERVISOR" } : o));
377
- const p = compilePlan({ mandate: toMandateBody({ objectives: adopted }), manifest: manifest(), standardCadences: STANDARD });
378
- const outcome = p.obligations.find((o) => o.kind === "OUTCOME");
379
- assert.ok(outcome, "adoption turns the proposal into a real obligation");
380
- assert.equal(outcome.target, 0.95);
381
- assert.equal(validatePlan(p, { manifest: manifest() }).ok, true);
382
- });
@@ -1,269 +0,0 @@
1
- /**
2
- * emit.test.mjs — obligations → the files the runtime already reads (SPEC §5.7).
3
- * Run: node --test lib/plan/emit.test.mjs
4
- *
5
- * The contract under test is compatibility: cadences.yaml, .cadence-registry.json
6
- * and .cadence-plists.tsv must keep the EXACT shapes their existing consumers
7
- * (cadence-handlers.mjs, session-permissions.mjs, generate-plists.sh) already
8
- * parse — new producer, unchanged formats.
9
- */
10
- "use strict";
11
-
12
- import { test } from "node:test";
13
- import assert from "node:assert/strict";
14
- import { mkdtempSync, readFileSync, existsSync } from "node:fs";
15
- import { tmpdir } from "node:os";
16
- import { join } from "node:path";
17
- import yaml from "js-yaml";
18
-
19
- import { emitPlan, toCadenceEntries, toRegistry, toTsvLines, scheduleXml, readLock, PLAN_REL, CADENCES_REL, REGISTRY_REL, TSV_REL, LOCK_REL } from "./emit.mjs";
20
- import { compilePlan } from "./compile.mjs";
21
-
22
- const AT = "2026-08-11T09:00:00.000Z";
23
-
24
- function manifest() {
25
- return {
26
- schemaVersion: 1,
27
- checksum: "fixture",
28
- entries: ["board_ready", "task_create", "task_update", "board_claim", "board_complete", "crm_list_deals", "knowledge_search", "email_inbox", "email_triage", "messaging_history", "messaging_send", "email_message", "email_thread", "email_send", "approval_wait", "decision_list"]
29
- .map((id) => ({ id, plane: "org", kind: "method_tool", reachable: true, blastRadius: "internal" })),
30
- };
31
- }
32
-
33
- const STANDARD = [
34
- { id: "inbox-processor", scope: "standard", mode: "guarded", interval: 300, prompt: "schedules/triggers/inbox-processor.md" },
35
- { id: "daily-morning-brief", scope: "standard", mode: "escalate", calendar: { hour: 6, minute: 30 }, prompt: "schedules/triggers/daily-morning-brief.md" },
36
- ];
37
- const ARCHETYPE = [
38
- { id: "weekly-operating-review", scope: "function", mode: "escalate", cadence: "weekly", calendar: { weekday: 2, hour: 10, minute: 0 }, prompt: "schedules/triggers/weekly-operating-review.md", purpose: "Operating review" },
39
- { id: "org-cost-sync-fn", scope: "function", mode: "inline", interval: 3600 },
40
- ];
41
-
42
- function adoptedObjective() {
43
- return {
44
- key: "pipeline-coverage", kind: "OBJECTIVE", text: "Pipeline coverage", state: "active", id: "obj_7Kx",
45
- adoptedById: "M-SUPERVISOR",
46
- metric: "pipeline_coverage_x", direction: "up", target: 3, tolerance: 0.2, weight: 1, cadence: "weekly",
47
- sensor: { capability: "crm_list_deals", params: { stage: "open" }, source: "method" }, collaborators: [],
48
- };
49
- }
50
-
51
- function planWith(objectives = []) {
52
- return compilePlan({ mandate: { objectives }, manifest: manifest(), standardCadences: STANDARD, archetypeCadences: ARCHETYPE });
53
- }
54
-
55
- function tmpRoot() { return mkdtempSync(join(tmpdir(), "maestro-emit-")); }
56
-
57
- test("emitPlan writes all five artefacts", async () => {
58
- const root = tmpRoot();
59
- const r = await emitPlan({ agentRoot: root, plan: planWith([adoptedObjective()]), manifest: manifest(), archetype: "executive-operator@c-suite", at: AT });
60
- assert.equal(r.ok, true, r.errors.join("; "));
61
- for (const rel of [PLAN_REL, CADENCES_REL, REGISTRY_REL, TSV_REL, LOCK_REL]) {
62
- assert.ok(existsSync(join(root, rel)), `${rel} written`);
63
- }
64
- });
65
-
66
- test("config/cadences.yaml keeps the shape cadence-handlers already parses", async () => {
67
- const root = tmpRoot();
68
- await emitPlan({ agentRoot: root, plan: planWith([adoptedObjective()]), manifest: manifest(), archetype: "executive-operator@c-suite", at: AT });
69
- const doc = yaml.load(readFileSync(join(root, CADENCES_REL), "utf8"));
70
- assert.equal(doc.archetype, "executive-operator@c-suite");
71
- const inbox = doc.cadences.find((c) => c.id === "inbox-processor");
72
- assert.equal(inbox.interval, 300);
73
- assert.equal(inbox.mode, "guarded");
74
- assert.equal(inbox.prompt, "schedules/triggers/inbox-processor.md");
75
- assert.equal(inbox.scope, "standard");
76
- // the plan-derived measure cadence is a first-class cadence id
77
- assert.ok(doc.cadences.some((c) => c.id === "measure-pipeline-coverage"));
78
- });
79
-
80
- test(".cadence-registry.json carries the EXTENDED shape session-permissions can scope on", async () => {
81
- const root = tmpRoot();
82
- await emitPlan({ agentRoot: root, plan: planWith([adoptedObjective()]), manifest: manifest(), at: AT });
83
- const reg = JSON.parse(readFileSync(join(root, REGISTRY_REL), "utf8"));
84
- const m = reg["measure-pipeline-coverage"];
85
- assert.equal(m.mode, "guarded");
86
- assert.equal(m.guardModule, "lib/goals/loop.mjs");
87
- assert.equal(m.obligationKey, "schedule.measure.pipeline-coverage");
88
- // PRESENT, and null when the mandate funded nothing — `planWith` builds from a
89
- // mandate with no budget, so null is the honest reading. The key must exist
90
- // either way: `getCadenceDef` branches on Number.isFinite, and an ABSENT key
91
- // and a null one both mean "no cap" while only one of them can be logged.
92
- assert.ok(Object.prototype.hasOwnProperty.call(m, "budgetCents"));
93
- assert.equal(m.budgetCents, null);
94
- assert.ok(Array.isArray(m.allowedTools) && m.allowedTools.includes("crm_list_deals"));
95
- // Back-compat: {mode, prompt} still present for every entry.
96
- for (const [id, def] of Object.entries(reg)) {
97
- assert.ok(def.mode, `${id} has a mode`);
98
- assert.ok(def.prompt, `${id} has a prompt`);
99
- }
100
- });
101
-
102
- test(".cadence-plists.tsv is pipe-delimited, EXCLUDES standard cadences, and decodes to launchd XML", async () => {
103
- const root = tmpRoot();
104
- await emitPlan({ agentRoot: root, plan: planWith([adoptedObjective()]), manifest: manifest(), at: AT });
105
- const lines = readFileSync(join(root, TSV_REL), "utf8").trim().split("\n");
106
- assert.ok(lines.length > 0);
107
- assert.ok(!lines.some((l) => l.startsWith("inbox-processor|")), "generate-plists.sh emits the standard set itself");
108
- const interval = lines.find((l) => l.startsWith("org-cost-sync-fn|"));
109
- assert.equal(interval, "org-cost-sync-fn|3600|");
110
- const cal = lines.find((l) => l.startsWith("weekly-operating-review|"));
111
- const [, empty, b64] = cal.split("|");
112
- assert.equal(empty, "");
113
- const xml = Buffer.from(b64, "base64").toString("utf8");
114
- assert.match(xml, /<key>Weekday<\/key>\n\s+<integer>2<\/integer>/);
115
- assert.match(xml, /<key>Hour<\/key>\n\s+<integer>10<\/integer>/);
116
- // the derived measure cadence becomes a REAL launchd job
117
- assert.ok(lines.some((l) => l.startsWith("measure-pipeline-coverage||")));
118
- });
119
-
120
- test("scheduleXml output is byte-identical to the legacy generator's shape", () => {
121
- assert.equal(
122
- scheduleXml({ weekday: 2, hour: 10, minute: 0 }),
123
- " <key>Weekday</key>\n <integer>2</integer>\n <key>Hour</key>\n <integer>10</integer>\n <key>Minute</key>\n <integer>0</integer>",
124
- );
125
- });
126
-
127
- test("IDEMPOTENT: re-emitting an unchanged plan produces byte-identical files (no plist churn)", async () => {
128
- const root = tmpRoot();
129
- const plan = planWith([adoptedObjective()]);
130
- await emitPlan({ agentRoot: root, plan, manifest: manifest(), at: AT });
131
- const before = [CADENCES_REL, REGISTRY_REL, TSV_REL].map((r) => readFileSync(join(root, r), "utf8"));
132
- await emitPlan({ agentRoot: root, plan: planWith([adoptedObjective()]), manifest: manifest(), at: AT });
133
- const after = [CADENCES_REL, REGISTRY_REL, TSV_REL].map((r) => readFileSync(join(root, r), "utf8"));
134
- assert.deepEqual(after, before);
135
- });
136
-
137
- test("FAIL-CLOSED: an invalid plan writes NOTHING", async () => {
138
- const root = tmpRoot();
139
- const bad = {
140
- compilerVersion: 1, mandateVersion: 0, inputsHash: "x", obligationsHash: "y", counts: {},
141
- obligations: [{
142
- key: "schedule.ghost", kind: "SCHEDULE", source: { origin: "archetype", ref: "ghost" }, status: "active",
143
- mode: "escalate", schedule: { interval: 600 }, cadence_id: "ghost",
144
- uses: ["ghost_tool"], allowed_tools: ["Read"], action_classes: [], offline_safe: false,
145
- }],
146
- };
147
- const r = await emitPlan({ agentRoot: root, plan: bad, manifest: manifest(), at: AT });
148
- assert.equal(r.ok, false);
149
- assert.ok(r.errors.some((e) => /not reachable:true/.test(e)));
150
- for (const rel of [PLAN_REL, CADENCES_REL, REGISTRY_REL, TSV_REL, LOCK_REL]) {
151
- assert.equal(existsSync(join(root, rel)), false, `${rel} must NOT exist after a failed validation`);
152
- }
153
- });
154
-
155
- test("compile.lock carries the hashes verify() checks", async () => {
156
- const root = tmpRoot();
157
- const plan = planWith([adoptedObjective()]);
158
- await emitPlan({ agentRoot: root, plan, manifest: manifest(), at: AT });
159
- const lock = readLock(root);
160
- assert.equal(lock.obligationsHash, plan.obligationsHash);
161
- assert.equal(lock.inputsHash, plan.inputsHash);
162
- assert.equal(lock.compilerVersion, plan.compilerVersion);
163
- assert.equal(lock.at, AT);
164
- });
165
-
166
- test("config/plan.yaml is the reviewable obligation set (provenance + sensor + tool scope)", async () => {
167
- const root = tmpRoot();
168
- await emitPlan({ agentRoot: root, plan: planWith([adoptedObjective()]), manifest: manifest(), enforcement: "observe-only", at: AT });
169
- const doc = yaml.load(readFileSync(join(root, PLAN_REL), "utf8"));
170
- assert.equal(doc.enforcement, "observe-only");
171
- const outcome = doc.obligations.find((o) => o.key === "outcome.pipeline-coverage");
172
- assert.equal(outcome.objective_id, "obj_7Kx");
173
- assert.equal(outcome.sensor.capability, "crm_list_deals");
174
- assert.equal(outcome.source.origin, "mandate");
175
- assert.ok(Array.isArray(outcome.allowed_tools));
176
- });
177
-
178
- test("SUSPENDED obligations are in plan.yaml but never in the schedule artefacts", () => {
179
- const obs = [
180
- { key: "schedule.a", kind: "SCHEDULE", cadence_id: "a", scope: "mandate", mode: "guarded", prompt: "p.md", schedule: { interval: 60 }, status: "suspended", uses: [], allowed_tools: ["Read"], action_classes: [], offline_safe: true, source: { origin: "mandate", ref: "a" }, budget_cents_per_period: 100 },
181
- { key: "schedule.b", kind: "SCHEDULE", cadence_id: "b", scope: "mandate", mode: "guarded", prompt: "p.md", schedule: { interval: 60 }, status: "active", uses: [], allowed_tools: ["Read"], action_classes: [], offline_safe: true, source: { origin: "mandate", ref: "b" }, budget_cents_per_period: 100 },
182
- ];
183
- assert.deepEqual(toCadenceEntries(obs).map((c) => c.id), ["b"]);
184
- assert.deepEqual(Object.keys(toRegistry(obs)), ["b"]);
185
- assert.deepEqual(toTsvLines(obs), ["b|60|"]);
186
- });
187
-
188
- test("the registry carries the provenance the RUNTIME budget gate needs", () => {
189
- const obs = [
190
- { key: "schedule.measure.gm", kind: "SCHEDULE", cadence_id: "measure-gm", scope: "mandate", objective_id: "obj_7Kx", mode: "guarded", prompt: "p.md", schedule: { interval: 60 }, status: "active", uses: [], allowed_tools: ["Read"], action_classes: [], offline_safe: true, source: { origin: "mandate", ref: "gm" }, budget_cents_per_period: 100 },
191
- { key: "schedule.cadence-bus-heartbeat", kind: "SCHEDULE", cadence_id: "cadence-bus-heartbeat", scope: "standard", mode: "inline", prompt: "p.md", schedule: { interval: 300 }, status: "active", uses: [], allowed_tools: [], action_classes: [], offline_safe: false, source: { origin: "standard", ref: "cadence-bus-heartbeat" }, budget_cents_per_period: null },
192
- ];
193
- const reg = toRegistry(obs);
194
- // Without these three the consumer cannot tell a funded objective's cadence
195
- // from the seat's own heartbeat, so a budget breach would suspend either
196
- // everything or nothing.
197
- assert.equal(reg["measure-gm"].scope, "mandate");
198
- assert.equal(reg["measure-gm"].objectiveId, "obj_7Kx");
199
- assert.equal(reg["measure-gm"].offlineSafe, true);
200
- assert.equal(reg["cadence-bus-heartbeat"].scope, "standard");
201
- assert.equal(reg["cadence-bus-heartbeat"].objectiveId, null);
202
- assert.equal(reg["cadence-bus-heartbeat"].offlineSafe, false);
203
- });
204
-
205
- // ---------------------------------------------------------------------------
206
- // The no-op guard (SPEC §6.4: "if obligationsHash is unchanged, nothing on disk
207
- // moves"). config/plan.yaml is TRACKED, so a recompile that changes nothing must
208
- // not rewrite it — otherwise the one artefact meant to make a schedule change
209
- // legible in a PR diff churns on every org-pulse tick and proves nothing.
210
- // ---------------------------------------------------------------------------
211
-
212
- test("a re-emit with identical inputs moves NOTHING on disk", async () => {
213
- const root = tmpRoot();
214
- const opts = { agentRoot: root, manifest: manifest(), archetype: "executive-operator@c-suite" };
215
-
216
- const first = await emitPlan({ ...opts, plan: planWith([adoptedObjective()]), at: AT });
217
- assert.equal(first.ok, true);
218
- assert.equal(first.files.length, 5);
219
- const before = [PLAN_REL, CADENCES_REL, REGISTRY_REL, TSV_REL].map((rel) => readFileSync(join(root, rel), "utf-8"));
220
-
221
- // Same inputs, LATER wall clock — the only thing that would otherwise differ.
222
- const second = await emitPlan({ ...opts, plan: planWith([adoptedObjective()]), at: "2026-09-01T12:00:00.000Z" });
223
- assert.equal(second.ok, true);
224
- assert.equal(second.unchanged, true, "the re-emit is reported as a no-op");
225
- assert.deepEqual(second.files, [], "no file was rewritten");
226
-
227
- const after = [PLAN_REL, CADENCES_REL, REGISTRY_REL, TSV_REL].map((rel) => readFileSync(join(root, rel), "utf-8"));
228
- assert.deepEqual(after, before, "every artefact is byte-identical");
229
- // The derived views are still returned, so a caller can report on them.
230
- assert.ok(second.cadences.length > 0 && Object.keys(second.registry).length > 0);
231
- });
232
-
233
- test("a CHANGED mandate still re-emits (the guard is not a freeze)", async () => {
234
- const root = tmpRoot();
235
- const opts = { agentRoot: root, manifest: manifest(), archetype: "executive-operator@c-suite", at: AT };
236
-
237
- await emitPlan({ ...opts, plan: planWith([]) });
238
- const beforeTsv = readFileSync(join(root, TSV_REL), "utf-8");
239
-
240
- // Adopting an objective adds measure/outcome obligations → new hashes.
241
- const changed = await emitPlan({ ...opts, plan: planWith([adoptedObjective()]) });
242
- assert.equal(changed.unchanged, undefined, "a real change is not a no-op");
243
- assert.equal(changed.files.length, 5);
244
- assert.notEqual(readFileSync(join(root, TSV_REL), "utf-8"), beforeTsv, "the launchd TSV was updated");
245
- assert.equal(readLock(root).obligationsHash, planWith([adoptedObjective()]).obligationsHash);
246
- });
247
-
248
- test("the guard re-emits when a tracked artefact was deleted underneath it", async () => {
249
- const root = tmpRoot();
250
- const opts = { agentRoot: root, manifest: manifest(), at: AT };
251
- await emitPlan({ ...opts, plan: planWith([adoptedObjective()]) });
252
-
253
- // Hashes still match, but the file is gone — a missing launchd TSV must be
254
- // rebuilt, not skipped because the lock looks satisfied.
255
- const { rmSync } = await import("node:fs");
256
- rmSync(join(root, TSV_REL));
257
- const res = await emitPlan({ ...opts, plan: planWith([adoptedObjective()]) });
258
- assert.equal(res.unchanged, undefined);
259
- assert.ok(existsSync(join(root, TSV_REL)), "the deleted artefact is restored");
260
- });
261
-
262
- test("force:true overrides the guard", async () => {
263
- const root = tmpRoot();
264
- const opts = { agentRoot: root, manifest: manifest(), at: AT };
265
- await emitPlan({ ...opts, plan: planWith([adoptedObjective()]) });
266
- const res = await emitPlan({ ...opts, plan: planWith([adoptedObjective()]), force: true });
267
- assert.equal(res.unchanged, undefined);
268
- assert.equal(res.files.length, 5);
269
- });