@cohortapp/agent-sdk 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (731) hide show
  1. package/.claude/commands/init-agent.md +104 -0
  2. package/.claude/commands/init-maestro.md +1187 -0
  3. package/.claude/settings.json +161 -0
  4. package/.env.example +216 -0
  5. package/README.md +632 -0
  6. package/agents/browser-operator/agent.md +52 -0
  7. package/agents/calendar-ops/agent.md +50 -0
  8. package/agents/communications/agent.md +96 -0
  9. package/agents/decision-log/agent.md +65 -0
  10. package/agents/desktop-operator/agent.md +59 -0
  11. package/agents/gmail-operator/agent.md +62 -0
  12. package/agents/inbound-dispatcher/agent.md +66 -0
  13. package/agents/inbox-processor/agent.md +39 -0
  14. package/agents/pmo-execution/agent.md +60 -0
  15. package/agents/session-spawner/agent.md +64 -0
  16. package/agents/slack-operator/agent.md +60 -0
  17. package/agents/whatsapp-operator/agent.md +60 -0
  18. package/agents/workflow-automation/agent.md +61 -0
  19. package/archetypes/altitudes/c-suite.yaml +58 -0
  20. package/archetypes/altitudes/founder.yaml +68 -0
  21. package/archetypes/altitudes/senior-manager.yaml +63 -0
  22. package/archetypes/altitudes/svp.yaml +60 -0
  23. package/archetypes/altitudes/vp.yaml +50 -0
  24. package/archetypes/archetype.schema.json +77 -0
  25. package/archetypes/base.yaml +47 -0
  26. package/archetypes/capabilities/commercial-leader.yaml +159 -0
  27. package/archetypes/capabilities/compliance-officer.yaml +159 -0
  28. package/archetypes/capabilities/executive-operator.yaml +169 -0
  29. package/archetypes/capabilities/finance-leader.yaml +162 -0
  30. package/archetypes/capabilities/operations-leader.yaml +154 -0
  31. package/archetypes/capabilities/product-leader.yaml +148 -0
  32. package/archetypes/capabilities/technical-leader.yaml +146 -0
  33. package/archetypes/functions/commercial-leader.yaml +62 -0
  34. package/archetypes/functions/compliance-officer.yaml +64 -0
  35. package/archetypes/functions/executive-operator.yaml +70 -0
  36. package/archetypes/functions/finance-leader.yaml +70 -0
  37. package/archetypes/functions/operations-leader.yaml +62 -0
  38. package/archetypes/functions/product-leader.yaml +61 -0
  39. package/archetypes/functions/technical-leader.yaml +57 -0
  40. package/bin/cohort-mcp.mjs +81 -0
  41. package/bin/maestro.mjs +3516 -0
  42. package/bin/maestro.test.mjs +1015 -0
  43. package/desktop-control/README.md +56 -0
  44. package/desktop-control/app-profiles/gmail.yaml +120 -0
  45. package/desktop-control/app-profiles/slack.yaml +315 -0
  46. package/desktop-control/app-profiles/whatsapp.yaml +107 -0
  47. package/docs/architecture/agent-topology.md +2239 -0
  48. package/docs/architecture/archetype-agent-factory.md +110 -0
  49. package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
  50. package/docs/architecture/continuous-monitoring.md +221 -0
  51. package/docs/architecture/mcp-capability-map.md +585 -0
  52. package/docs/architecture/system-architecture.md +1272 -0
  53. package/docs/company-context/README.md +40 -0
  54. package/docs/guides/agent-persona-setup.md +600 -0
  55. package/docs/guides/agents-observe-setup.md +64 -0
  56. package/docs/guides/billing-console-keys.md +88 -0
  57. package/docs/guides/ccxray-diagnostics.md +65 -0
  58. package/docs/guides/channel-bus.md +127 -0
  59. package/docs/guides/claude-mem-setup.md +79 -0
  60. package/docs/guides/claude-pace-setup.md +56 -0
  61. package/docs/guides/claudraband-sessions.md +98 -0
  62. package/docs/guides/clawteam-swarm.md +116 -0
  63. package/docs/guides/code-review-graph-setup.md +86 -0
  64. package/docs/guides/email-setup.md +431 -0
  65. package/docs/guides/mac-mini.md +119 -0
  66. package/docs/guides/media-generation-setup.md +349 -0
  67. package/docs/guides/model-routing.md +162 -0
  68. package/docs/guides/observability-otel.md +265 -0
  69. package/docs/guides/org-onboarding.md +132 -0
  70. package/docs/guides/outbound-governance-setup.md +437 -0
  71. package/docs/guides/pdf-generation-setup.md +315 -0
  72. package/docs/guides/poller-daemon-setup.md +563 -0
  73. package/docs/guides/rag-context-setup.md +459 -0
  74. package/docs/guides/self-optimization-pattern.md +82 -0
  75. package/docs/guides/setup-wizard.md +178 -0
  76. package/docs/guides/slack-setup.md +350 -0
  77. package/docs/guides/telegram-setup.md +227 -0
  78. package/docs/guides/twilio-subaccounts-setup.md +223 -0
  79. package/docs/guides/verification.md +128 -0
  80. package/docs/guides/voice-mode.md +188 -0
  81. package/docs/guides/voice-sms-setup.md +698 -0
  82. package/docs/guides/webhook-relay-setup.md +349 -0
  83. package/docs/guides/whatsapp-setup.md +288 -0
  84. package/docs/prompts/board-pack-cover-template.md +36 -0
  85. package/docs/prompts/decision-recommendation-template.md +88 -0
  86. package/docs/prompts/followup-message-template.md +141 -0
  87. package/docs/prompts/investor-letter-template.md +52 -0
  88. package/docs/prompts/morning-brief-template.md +82 -0
  89. package/docs/prompts/presentation-template.md +58 -0
  90. package/docs/prompts/weekly-strategic-memo-template.md +104 -0
  91. package/docs/research/hallucinated-tool-output-investigation.md +151 -0
  92. package/docs/runbooks/backup-restore.md +205 -0
  93. package/docs/runbooks/cohort-cutover.md +129 -0
  94. package/docs/runbooks/fleet-operations.md +200 -0
  95. package/docs/runbooks/incident-response.md +226 -0
  96. package/docs/runbooks/mac-mini-bootstrap.md +431 -0
  97. package/docs/runbooks/perpetual-operations.md +509 -0
  98. package/docs/runbooks/recovery-and-failover.md +260 -0
  99. package/framework-features.json +267 -0
  100. package/ingest/README.md +87 -0
  101. package/lib/action-executor.js +689 -0
  102. package/lib/action-executor.test.mjs +871 -0
  103. package/lib/agent-root.mjs +37 -0
  104. package/lib/archetype.mjs +236 -0
  105. package/lib/archetype.test.mjs +132 -0
  106. package/lib/autonomy.mjs +114 -0
  107. package/lib/autonomy.test.mjs +66 -0
  108. package/lib/backlog.mjs +358 -0
  109. package/lib/backlog.test.mjs +266 -0
  110. package/lib/budget-guard.mjs +279 -0
  111. package/lib/budget-guard.test.mjs +291 -0
  112. package/lib/cadence-bus-schedule.test.mjs +194 -0
  113. package/lib/cadence-bus.mjs +1120 -0
  114. package/lib/cadence-bus.test.mjs +720 -0
  115. package/lib/cadences.mjs +205 -0
  116. package/lib/cadences.test.mjs +125 -0
  117. package/lib/capability.mjs +154 -0
  118. package/lib/capability.test.mjs +78 -0
  119. package/lib/channels/base-adapter.mjs +719 -0
  120. package/lib/channels/base-adapter.test.mjs +590 -0
  121. package/lib/channels/channel.mjs +128 -0
  122. package/lib/channels/channels.test.mjs +371 -0
  123. package/lib/channels/contract.mjs +215 -0
  124. package/lib/channels/contract.test.mjs +137 -0
  125. package/lib/channels/conversation-resolver.mjs +95 -0
  126. package/lib/channels/gmail/adapter.mjs +87 -0
  127. package/lib/channels/inbox-item.mjs +255 -0
  128. package/lib/channels/inbox-item.test.mjs +335 -0
  129. package/lib/channels/index.mjs +94 -0
  130. package/lib/channels/orgmail/adapter.mjs +353 -0
  131. package/lib/channels/orgmail/adapter.test.mjs +311 -0
  132. package/lib/channels/pairing.mjs +363 -0
  133. package/lib/channels/pairing.test.mjs +270 -0
  134. package/lib/channels/registry.mjs +164 -0
  135. package/lib/channels/slack/adapter.mjs +317 -0
  136. package/lib/channels/slack-adapter.test.mjs +212 -0
  137. package/lib/channels/sms/adapter.mjs +43 -0
  138. package/lib/channels/telegram/adapter.mjs +432 -0
  139. package/lib/channels/telegram-adapter.test.mjs +306 -0
  140. package/lib/channels/voice/adapter.mjs +301 -0
  141. package/lib/channels/voice/adapter.test.mjs +278 -0
  142. package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
  143. package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
  144. package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
  145. package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
  146. package/lib/charter.mjs +256 -0
  147. package/lib/charter.test.mjs +89 -0
  148. package/lib/claude-bin.mjs +134 -0
  149. package/lib/claude-bin.test.mjs +75 -0
  150. package/lib/collective/capture.mjs +185 -0
  151. package/lib/collective/capture.test.mjs +121 -0
  152. package/lib/collective/cards.mjs +201 -0
  153. package/lib/collective/cards.test.mjs +114 -0
  154. package/lib/collective/config.mjs +186 -0
  155. package/lib/collective/config.test.mjs +123 -0
  156. package/lib/collective/global-config.mjs +113 -0
  157. package/lib/collective/global-config.test.mjs +75 -0
  158. package/lib/collective/presence.mjs +201 -0
  159. package/lib/collective/presence.test.mjs +95 -0
  160. package/lib/collective/recall.mjs +215 -0
  161. package/lib/collective/recall.test.mjs +116 -0
  162. package/lib/comms/send-gate.mjs +554 -0
  163. package/lib/comms/send-gate.test.mjs +577 -0
  164. package/lib/comms.mjs +67 -0
  165. package/lib/comms.test.mjs +41 -0
  166. package/lib/diagnostics/alerts.mjs +424 -0
  167. package/lib/diagnostics/alerts.test.mjs +318 -0
  168. package/lib/diagnostics/backup-freshness.mjs +188 -0
  169. package/lib/diagnostics/backup-freshness.test.mjs +185 -0
  170. package/lib/diagnostics/counters.mjs +269 -0
  171. package/lib/diagnostics/counters.test.mjs +206 -0
  172. package/lib/diagnostics/events.mjs +188 -0
  173. package/lib/diagnostics/events.test.mjs +290 -0
  174. package/lib/diagnostics/otel.mjs +237 -0
  175. package/lib/diagnostics/otel.test.mjs +196 -0
  176. package/lib/diagnostics/trace.mjs +216 -0
  177. package/lib/diagnostics/trace.test.mjs +251 -0
  178. package/lib/env-compat.mjs +74 -0
  179. package/lib/env-compat.test.mjs +104 -0
  180. package/lib/feature-init.mjs +331 -0
  181. package/lib/fs-atomic.mjs +112 -0
  182. package/lib/fs-atomic.test.mjs +72 -0
  183. package/lib/fs-ownership.mjs +111 -0
  184. package/lib/fs-ownership.test.mjs +158 -0
  185. package/lib/hooks/bus.mjs +347 -0
  186. package/lib/hooks/bus.test.mjs +387 -0
  187. package/lib/index.js +16 -0
  188. package/lib/learning/config.mjs +106 -0
  189. package/lib/learning/config.test.mjs +75 -0
  190. package/lib/learning/counters.mjs +156 -0
  191. package/lib/learning/counters.test.mjs +69 -0
  192. package/lib/learning/curator-consolidate.test.mjs +238 -0
  193. package/lib/learning/curator.mjs +453 -0
  194. package/lib/learning/curator.test.mjs +106 -0
  195. package/lib/learning/log.mjs +40 -0
  196. package/lib/learning/reflect.mjs +534 -0
  197. package/lib/learning/reflect.test.mjs +0 -0
  198. package/lib/learning/session-index.mjs +352 -0
  199. package/lib/learning/session-index.test.mjs +125 -0
  200. package/lib/learning/skill-writer.mjs +474 -0
  201. package/lib/learning/skill-writer.test.mjs +210 -0
  202. package/lib/mcp/server.mjs +328 -0
  203. package/lib/mcp/server.test.mjs +400 -0
  204. package/lib/model-router/auth-profiles.mjs +758 -0
  205. package/lib/model-router/auth-profiles.test.mjs +580 -0
  206. package/lib/model-router/catalog/anthropic.yaml +153 -0
  207. package/lib/model-router/catalog/deepseek.yaml +86 -0
  208. package/lib/model-router/catalog/moonshot.yaml +81 -0
  209. package/lib/model-router/catalog/qwen.yaml +114 -0
  210. package/lib/model-router/catalog.mjs +925 -0
  211. package/lib/model-router/catalog.test.mjs +385 -0
  212. package/lib/model-router/economics.mjs +564 -0
  213. package/lib/model-router/economics.test.mjs +344 -0
  214. package/lib/model-router/failover.mjs +298 -0
  215. package/lib/model-router/failover.test.mjs +439 -0
  216. package/lib/model-router/health.mjs +453 -0
  217. package/lib/model-router/health.test.mjs +338 -0
  218. package/lib/model-router/integration-coverage.test.mjs +829 -0
  219. package/lib/model-router/integration.test.mjs +564 -0
  220. package/lib/model-router/ledger.mjs +402 -0
  221. package/lib/model-router/ledger.test.mjs +382 -0
  222. package/lib/model-router/llm-task.mjs +515 -0
  223. package/lib/model-router/llm-task.test.mjs +392 -0
  224. package/lib/model-router/org-credentials.mjs +260 -0
  225. package/lib/model-router/org-credentials.test.mjs +265 -0
  226. package/lib/model-router/pricing-refresh.mjs +463 -0
  227. package/lib/model-router/pricing-refresh.test.mjs +286 -0
  228. package/lib/model-router/reconcile.mjs +429 -0
  229. package/lib/model-router/reconcile.test.mjs +316 -0
  230. package/lib/model-router/repair.mjs +471 -0
  231. package/lib/model-router/repair.test.mjs +180 -0
  232. package/lib/model-router/resolve.mjs +1206 -0
  233. package/lib/model-router/spawn.mjs +497 -0
  234. package/lib/model-router/spawn.test.mjs +425 -0
  235. package/lib/model-router/taxonomy.mjs +893 -0
  236. package/lib/model-router/taxonomy.test.mjs +410 -0
  237. package/lib/model-router.mjs +677 -0
  238. package/lib/model-router.test.mjs +907 -0
  239. package/lib/org/activity.mjs +211 -0
  240. package/lib/org/activity.test.mjs +134 -0
  241. package/lib/org/approvals.mjs +448 -0
  242. package/lib/org/approvals.test.mjs +216 -0
  243. package/lib/org/awareness.mjs +222 -0
  244. package/lib/org/awareness.test.mjs +159 -0
  245. package/lib/org/board.mjs +229 -0
  246. package/lib/org/board.test.mjs +177 -0
  247. package/lib/org/bootstrap-context.mjs +169 -0
  248. package/lib/org/bootstrap-context.test.mjs +153 -0
  249. package/lib/org/client.mjs +1628 -0
  250. package/lib/org/client.test.mjs +1107 -0
  251. package/lib/org/cohort-client.mjs +67 -0
  252. package/lib/org/cohort-client.test.mjs +126 -0
  253. package/lib/org/cost-sync.mjs +227 -0
  254. package/lib/org/cost-sync.test.mjs +153 -0
  255. package/lib/org/doctor.mjs +212 -0
  256. package/lib/org/doctor.test.mjs +212 -0
  257. package/lib/org/handoff.mjs +293 -0
  258. package/lib/org/handoff.test.mjs +269 -0
  259. package/lib/org/integration-tools.mjs +182 -0
  260. package/lib/org/integration-tools.test.mjs +160 -0
  261. package/lib/org/keys.mjs +131 -0
  262. package/lib/org/keys.test.mjs +92 -0
  263. package/lib/org/knowledge.mjs +463 -0
  264. package/lib/org/knowledge.test.mjs +319 -0
  265. package/lib/org/leases.mjs +335 -0
  266. package/lib/org/leases.test.mjs +235 -0
  267. package/lib/org/mesh-integration.test.mjs +127 -0
  268. package/lib/org/mesh.mjs +459 -0
  269. package/lib/org/mesh.test.mjs +345 -0
  270. package/lib/org/messaging.mjs +503 -0
  271. package/lib/org/messaging.test.mjs +238 -0
  272. package/lib/org/policy.mjs +345 -0
  273. package/lib/org/policy.test.mjs +237 -0
  274. package/lib/org/protocol.checksum +1 -0
  275. package/lib/org/protocol.checksum.test.mjs +90 -0
  276. package/lib/org/protocol.mjs +967 -0
  277. package/lib/org/protocol.test.mjs +264 -0
  278. package/lib/org/registry.mjs +194 -0
  279. package/lib/org/registry.test.mjs +100 -0
  280. package/lib/org/tool-surface-integration.test.mjs +120 -0
  281. package/lib/org/tool-surface.mjs +2535 -0
  282. package/lib/org/tool-surface.test.mjs +589 -0
  283. package/lib/org/ui-parity.mjs +3236 -0
  284. package/lib/org/ui-parity.test.mjs +348 -0
  285. package/lib/org/verify.mjs +176 -0
  286. package/lib/org/verify.test.mjs +194 -0
  287. package/lib/rag/embed.mjs +188 -0
  288. package/lib/rag/indexer.mjs +425 -0
  289. package/lib/rag/rag.test.mjs +505 -0
  290. package/lib/rag/search.mjs +475 -0
  291. package/lib/rate-guard.mjs +246 -0
  292. package/lib/rate-guard.test.mjs +201 -0
  293. package/lib/render.mjs +112 -0
  294. package/lib/render.test.mjs +68 -0
  295. package/lib/resource-governor.mjs +297 -0
  296. package/lib/resource-governor.test.mjs +262 -0
  297. package/lib/scheduling/dynamic-jobs.mjs +675 -0
  298. package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
  299. package/lib/scheduling/jitter.mjs +0 -0
  300. package/lib/scheduling/jitter.test.mjs +140 -0
  301. package/lib/secrets/broker.mjs +315 -0
  302. package/lib/secrets/broker.test.mjs +280 -0
  303. package/lib/secrets/providers.mjs +461 -0
  304. package/lib/secrets/providers.test.mjs +274 -0
  305. package/lib/security/audit-engine.mjs +684 -0
  306. package/lib/security/audit-engine.test.mjs +389 -0
  307. package/lib/security/coerce-args.mjs +552 -0
  308. package/lib/security/coerce-args.test.mjs +281 -0
  309. package/lib/security/dangerous-tools.mjs +97 -0
  310. package/lib/security/dangerous-tools.test.mjs +68 -0
  311. package/lib/security/external-content.mjs +145 -0
  312. package/lib/security/external-content.test.mjs +67 -0
  313. package/lib/security/redact.mjs +592 -0
  314. package/lib/security/redact.test.mjs +441 -0
  315. package/lib/security/secret-equal.mjs +73 -0
  316. package/lib/security/secret-equal.test.mjs +55 -0
  317. package/lib/session-permissions.mjs +101 -0
  318. package/lib/session-permissions.test.mjs +100 -0
  319. package/lib/setup/claude-probe.mjs +74 -0
  320. package/lib/setup/completeness.mjs +175 -0
  321. package/lib/setup/completeness.test.mjs +110 -0
  322. package/lib/setup/context-pack.mjs +173 -0
  323. package/lib/setup/context-pack.test.mjs +89 -0
  324. package/lib/setup/enrich.mjs +277 -0
  325. package/lib/setup/enrich.test.mjs +115 -0
  326. package/lib/setup/enroll-from-cohort.mjs +441 -0
  327. package/lib/setup/enroll-from-cohort.test.mjs +233 -0
  328. package/lib/setup/integration.test.mjs +162 -0
  329. package/lib/setup/io.mjs +360 -0
  330. package/lib/setup/io.test.mjs +77 -0
  331. package/lib/setup/run-generator.mjs +81 -0
  332. package/lib/setup/runner.mjs +244 -0
  333. package/lib/setup/runner.test.mjs +132 -0
  334. package/lib/setup/sections/comms.mjs +173 -0
  335. package/lib/setup/sections/company.mjs +120 -0
  336. package/lib/setup/sections/enrich.mjs +138 -0
  337. package/lib/setup/sections/identity.mjs +182 -0
  338. package/lib/setup/sections/identity.test.mjs +140 -0
  339. package/lib/setup/sections/learning.mjs +153 -0
  340. package/lib/setup/sections/learning.test.mjs +81 -0
  341. package/lib/setup/sections/messaging.mjs +219 -0
  342. package/lib/setup/sections/messaging.test.mjs +127 -0
  343. package/lib/setup/sections/model.mjs +102 -0
  344. package/lib/setup/sections/operating-model.mjs +78 -0
  345. package/lib/setup/sections/org.mjs +475 -0
  346. package/lib/setup/sections/org.test.mjs +313 -0
  347. package/lib/setup/sections/orgmail.mjs +173 -0
  348. package/lib/setup/sections/orgmail.test.mjs +118 -0
  349. package/lib/setup/sections/recovery.mjs +159 -0
  350. package/lib/setup/sections/recovery.test.mjs +98 -0
  351. package/lib/setup/sections/tools.mjs +132 -0
  352. package/lib/setup/sections/verify.mjs +97 -0
  353. package/lib/setup/sot.mjs +205 -0
  354. package/lib/setup/sot.test.mjs +81 -0
  355. package/lib/setup/state.mjs +151 -0
  356. package/lib/setup/state.test.mjs +92 -0
  357. package/lib/singleton.js +229 -0
  358. package/lib/singleton.test.mjs +135 -0
  359. package/lib/telemetry/alerts.mjs +216 -0
  360. package/lib/telemetry/alerts.test.mjs +109 -0
  361. package/lib/telemetry/collect.mjs +512 -0
  362. package/lib/telemetry/collect.test.mjs +202 -0
  363. package/lib/tool-definitions-integration.test.mjs +83 -0
  364. package/lib/tool-definitions.js +738 -0
  365. package/lib/tool-definitions.test.mjs +437 -0
  366. package/lib/util/fetch-timeout.mjs +136 -0
  367. package/lib/util/fetch-timeout.test.mjs +202 -0
  368. package/lib/util/reconnect.mjs +343 -0
  369. package/lib/util/reconnect.test.mjs +369 -0
  370. package/lib/util/unhandled.mjs +205 -0
  371. package/lib/util/unhandled.test.mjs +216 -0
  372. package/lib/voice/context-loader.mjs +466 -0
  373. package/lib/voice/index.mjs +100 -0
  374. package/lib/voice/openai-realtime.mjs +510 -0
  375. package/lib/voice/outbound.mjs +542 -0
  376. package/lib/voice/outbound.test.mjs +69 -0
  377. package/lib/voice/post-call-brief.mjs +428 -0
  378. package/lib/voice/provider.mjs +52 -0
  379. package/lib/voice/session-rotation.mjs +257 -0
  380. package/lib/voice/stt.mjs +161 -0
  381. package/lib/voice/stt.test.mjs +226 -0
  382. package/lib/voice/tool-bridge.mjs +370 -0
  383. package/lib/voice/tts.mjs +104 -0
  384. package/lib/voice/twilio-sip-bridge.mjs +288 -0
  385. package/lib/voice/voice.test.mjs +990 -0
  386. package/mcp/README.md +80 -0
  387. package/package.json +151 -0
  388. package/plugins/maestro-skills/plugin.json +139 -0
  389. package/plugins/maestro-skills/skills/agents-observe.md +110 -0
  390. package/plugins/maestro-skills/skills/board-deck.md +68 -0
  391. package/plugins/maestro-skills/skills/books-close.md +77 -0
  392. package/plugins/maestro-skills/skills/brand-steward.md +121 -0
  393. package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
  394. package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
  395. package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
  396. package/plugins/maestro-skills/skills/claude-pace.md +61 -0
  397. package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
  398. package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
  399. package/plugins/maestro-skills/skills/decision-brief.md +89 -0
  400. package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
  401. package/plugins/maestro-skills/skills/draft-comms.md +84 -0
  402. package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
  403. package/plugins/maestro-skills/skills/files-find.md +65 -0
  404. package/plugins/maestro-skills/skills/generative-ui.md +228 -0
  405. package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
  406. package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
  407. package/plugins/maestro-skills/skills/mail-triage.md +86 -0
  408. package/plugins/maestro-skills/skills/morning-brief.md +54 -0
  409. package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
  410. package/plugins/maestro-skills/skills/org-board.md +133 -0
  411. package/plugins/maestro-skills/skills/org-credential.md +68 -0
  412. package/plugins/maestro-skills/skills/org-recall.md +81 -0
  413. package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
  414. package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
  415. package/plugins/maestro-skills/skills/router-why.md +78 -0
  416. package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
  417. package/plugins/maestro-skills/skills/session-search.md +71 -0
  418. package/plugins/maestro-skills/skills/set-reminder.md +93 -0
  419. package/plugins/maestro-skills/skills/slack-followup.md +64 -0
  420. package/plugins/maestro-skills/skills/team-activity.md +86 -0
  421. package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
  422. package/policies/action-classification.yaml +114 -0
  423. package/policies/ai-disclosure.yaml +294 -0
  424. package/policies/communication-style.md +139 -0
  425. package/policies/information-barriers.yaml +118 -0
  426. package/policies/prompt-injection-defence.yaml +138 -0
  427. package/public/assets/icon-dark.png +0 -0
  428. package/public/assets/icon-dark.svg +9 -0
  429. package/public/assets/icon-light.svg +9 -0
  430. package/public/assets/logo-dark.svg +15 -0
  431. package/public/assets/logo-light.svg +15 -0
  432. package/scaffold/.mcp.json +7 -0
  433. package/scaffold/CLAUDE.md +368 -0
  434. package/scaffold/config/agent.json +55 -0
  435. package/scaffold/config/agent.ts +76 -0
  436. package/scaffold/config/agent.ts.example +89 -0
  437. package/scaffold/config/alerts.yaml +23 -0
  438. package/scaffold/config/allowlist.yaml.example +25 -0
  439. package/scaffold/config/caller-id-map.yaml +46 -0
  440. package/scaffold/config/collective.yaml +49 -0
  441. package/scaffold/config/company.json +20 -0
  442. package/scaffold/config/known-agents.json +6 -0
  443. package/scaffold/config/learning.yaml +55 -0
  444. package/scaffold/config/model-routing.yaml.example +104 -0
  445. package/scaffold/config/org.yaml +25 -0
  446. package/scaffold/config/orgmail.yaml.example +19 -0
  447. package/scaffold/config/recovery.yaml +72 -0
  448. package/scaffold/config/secrets.yaml +27 -0
  449. package/scaffold/config/slack.yaml.example +35 -0
  450. package/scaffold/config/telegram.yaml.example +38 -0
  451. package/scaffold/config/voice.yaml.example +89 -0
  452. package/scaffold/config/whatsapp.yaml.example +39 -0
  453. package/schedules/README.md +49 -0
  454. package/schedules/triggers/backlog-executor.md +102 -0
  455. package/schedules/triggers/brand-steward.md +72 -0
  456. package/schedules/triggers/daily-evening-wrap.md +159 -0
  457. package/schedules/triggers/daily-midday-sweep.md +58 -0
  458. package/schedules/triggers/daily-morning-brief.md +55 -0
  459. package/schedules/triggers/directory-hygiene.md +81 -0
  460. package/schedules/triggers/dynamic-jobs.md +40 -0
  461. package/schedules/triggers/inbox-processor.md +115 -0
  462. package/schedules/triggers/meeting-action-capture.md +60 -0
  463. package/schedules/triggers/meeting-prep.md +69 -0
  464. package/schedules/triggers/messaging-inbound.md +50 -0
  465. package/schedules/triggers/org-pulse.md +24 -0
  466. package/schedules/triggers/quarterly-self-assessment.md +54 -0
  467. package/schedules/triggers/weekly-engineering-health.md +37 -0
  468. package/schedules/triggers/weekly-execution.md +65 -0
  469. package/schedules/triggers/weekly-hiring.md +53 -0
  470. package/schedules/triggers/weekly-priorities.md +38 -0
  471. package/schedules/triggers/weekly-strategic-memo.md +124 -0
  472. package/scripts/archive-email.sh +55 -0
  473. package/scripts/cadence/cadence-status.mjs +36 -0
  474. package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
  475. package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
  476. package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
  477. package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
  478. package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
  479. package/scripts/ci/check-docs-accuracy.mjs +493 -0
  480. package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
  481. package/scripts/ci/check-exports-exist.mjs +140 -0
  482. package/scripts/ci/check-files-exist.mjs +107 -0
  483. package/scripts/ci/check-no-build-artifacts.mjs +111 -0
  484. package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
  485. package/scripts/ci/check-no-confidential.mjs +198 -0
  486. package/scripts/ci/check-no-conflict-markers.mjs +169 -0
  487. package/scripts/ci/check-no-residual-identity.mjs +163 -0
  488. package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
  489. package/scripts/ci/check-tarball-fidelity.mjs +205 -0
  490. package/scripts/ci/check-unresolved-tokens.mjs +83 -0
  491. package/scripts/ci/check.mjs +109 -0
  492. package/scripts/ci/check.test.mjs +194 -0
  493. package/scripts/ci/run-coverage.mjs +82 -0
  494. package/scripts/ci/run-tests.mjs +71 -0
  495. package/scripts/cloud-relay/README.md +59 -0
  496. package/scripts/cloud-relay/index.mjs +233 -0
  497. package/scripts/cloud-relay/package.json +15 -0
  498. package/scripts/cloud-relay/railway.json +13 -0
  499. package/scripts/cloud-relay/voice/README.md +94 -0
  500. package/scripts/cloud-relay/voice/package-lock.json +39 -0
  501. package/scripts/cloud-relay/voice/package.json +16 -0
  502. package/scripts/cloud-relay/voice/railway.json +13 -0
  503. package/scripts/cloud-relay/voice/server.mjs +532 -0
  504. package/scripts/collective/hook-runner.mjs +211 -0
  505. package/scripts/collective/hook-runner.test.mjs +90 -0
  506. package/scripts/collective/org-pulse.mjs +72 -0
  507. package/scripts/collective/org-sync.mjs +61 -0
  508. package/scripts/collective/recall.mjs +45 -0
  509. package/scripts/collective/who.mjs +30 -0
  510. package/scripts/comms-monitor.sh +288 -0
  511. package/scripts/configure-whatsapp-sandbox.sh +201 -0
  512. package/scripts/continuous-monitor.sh +91 -0
  513. package/scripts/cost/fleet-digest.mjs +407 -0
  514. package/scripts/cost/fleet-digest.test.mjs +207 -0
  515. package/scripts/cost/track-claude-usage.mjs +169 -0
  516. package/scripts/daemon/agent-daemon.mjs +989 -0
  517. package/scripts/daemon/agent-daemon.test.mjs +525 -0
  518. package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
  519. package/scripts/daemon/cadence-consumer.mjs +1080 -0
  520. package/scripts/daemon/cadence-consumer.test.mjs +770 -0
  521. package/scripts/daemon/cadence-handlers.mjs +1121 -0
  522. package/scripts/daemon/cadence-handlers.test.mjs +617 -0
  523. package/scripts/daemon/classifier.mjs +704 -0
  524. package/scripts/daemon/classifier.test.mjs +238 -0
  525. package/scripts/daemon/classify-kind.mjs +54 -0
  526. package/scripts/daemon/classify-kind.test.mjs +40 -0
  527. package/scripts/daemon/context-compiler.mjs +605 -0
  528. package/scripts/daemon/context-compiler.test.mjs +300 -0
  529. package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
  530. package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
  531. package/scripts/daemon/dispatcher.mjs +1516 -0
  532. package/scripts/daemon/health.mjs +72 -0
  533. package/scripts/daemon/inbox-deferral.mjs +210 -0
  534. package/scripts/daemon/inbox-deferral.test.mjs +242 -0
  535. package/scripts/daemon/integration.test.mjs +149 -0
  536. package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
  537. package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
  538. package/scripts/daemon/launchd-wrapper.sh +91 -0
  539. package/scripts/daemon/lib/session-router.mjs +274 -0
  540. package/scripts/daemon/lib/session-router.test.mjs +295 -0
  541. package/scripts/daemon/maestro-daemon.mjs +275 -0
  542. package/scripts/daemon/prompt-builder.mjs +685 -0
  543. package/scripts/daemon/prompt-builder.test.mjs +213 -0
  544. package/scripts/daemon/responder.mjs +854 -0
  545. package/scripts/daemon/session-lock.mjs +721 -0
  546. package/scripts/daemon/session-lock.test.mjs +252 -0
  547. package/scripts/daemon/session-outcomes.mjs +640 -0
  548. package/scripts/daemon/session-outcomes.test.mjs +533 -0
  549. package/scripts/daemon/typing-registry.mjs +90 -0
  550. package/scripts/daemon/typing-registry.test.mjs +77 -0
  551. package/scripts/daemon/voice-webhook-server.mjs +804 -0
  552. package/scripts/decisions/capture-decision.mjs +116 -0
  553. package/scripts/disclosure_assessment.py +873 -0
  554. package/scripts/disclosure_boundaries.py +562 -0
  555. package/scripts/email-signature-principal.html +52 -0
  556. package/scripts/email-signature.html +60 -0
  557. package/scripts/email_quote_thread.py +167 -0
  558. package/scripts/email_thread_dedup.py +362 -0
  559. package/scripts/emergency-stop.sh +81 -0
  560. package/scripts/healthcheck.sh +116 -0
  561. package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
  562. package/scripts/hooks/block-mcp-slack-send.sh +7 -0
  563. package/scripts/hooks/post-action-log.sh +126 -0
  564. package/scripts/hooks/pre-send-audit.sh +174 -0
  565. package/scripts/hooks/pre-send-audit.test.mjs +215 -0
  566. package/scripts/hooks/session-end-log.sh +27 -0
  567. package/scripts/hooks/session-start-banner.sh +115 -0
  568. package/scripts/huddle/audio-bridge.mjs +664 -0
  569. package/scripts/huddle/boot-slack-cdp.sh +102 -0
  570. package/scripts/huddle/huddle-controller.mjs +942 -0
  571. package/scripts/huddle/huddle-server.mjs +1229 -0
  572. package/scripts/huddle/launch-slack.sh +232 -0
  573. package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
  574. package/scripts/huddle/package-lock.json +62 -0
  575. package/scripts/huddle/package.json +22 -0
  576. package/scripts/huddle/setup-audio.sh +239 -0
  577. package/scripts/huddle/start-call.mjs +318 -0
  578. package/scripts/huddle/test-pipeline.mjs +263 -0
  579. package/scripts/learning/consolidate-skills.mjs +72 -0
  580. package/scripts/learning/session-search.mjs +125 -0
  581. package/scripts/llm_email_dedup.py +442 -0
  582. package/scripts/local-triggers/generate-plists.sh +432 -0
  583. package/scripts/local-triggers/generate-plists.test.mjs +413 -0
  584. package/scripts/local-triggers/install-all.sh +49 -0
  585. package/scripts/local-triggers/plists/.gitkeep +0 -0
  586. package/scripts/local-triggers/run-trigger.sh +63 -0
  587. package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
  588. package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
  589. package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
  590. package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
  591. package/scripts/maintenance/backup-to-cloud.sh +124 -0
  592. package/scripts/maintenance/health-check.sh +377 -0
  593. package/scripts/media-generation/README.md +105 -0
  594. package/scripts/media-generation/gemini-image-client.mjs +173 -0
  595. package/scripts/media-generation/generate-assets.mjs +289 -0
  596. package/scripts/media-generation/veo-video-client.mjs +219 -0
  597. package/scripts/org/send-orgmail.mjs +227 -0
  598. package/scripts/outbound-dedup-cleanup.sh +43 -0
  599. package/scripts/outbound-dedup.sh +477 -0
  600. package/scripts/outbound_dedup.py +115 -0
  601. package/scripts/parse-voice-transcript.mjs +481 -0
  602. package/scripts/pdf-generation/README.md +63 -0
  603. package/scripts/pdf-generation/build-document.mjs +247 -0
  604. package/scripts/pdf-generation/templates/board-pack.latex +136 -0
  605. package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
  606. package/scripts/pdf-generation/templates/memo.latex +114 -0
  607. package/scripts/poll-slack-events.sh +35 -0
  608. package/scripts/poller/calendar-poller.mjs +12 -0
  609. package/scripts/poller/gmail-poller.mjs +192 -0
  610. package/scripts/poller/imap-client.mjs +289 -0
  611. package/scripts/poller/inbox-scan-poller.mjs +156 -0
  612. package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
  613. package/scripts/poller/index.mjs +73 -0
  614. package/scripts/poller/intra-session-check.mjs +285 -0
  615. package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
  616. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
  617. package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
  618. package/scripts/poller/secondary-gmail-poller.mjs +132 -0
  619. package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
  620. package/scripts/poller/slack-poller.mjs +854 -0
  621. package/scripts/poller/slack-socket-mode.mjs +917 -0
  622. package/scripts/poller/slack-socket-mode.test.mjs +753 -0
  623. package/scripts/poller/trigger.mjs +75 -0
  624. package/scripts/poller/utils.mjs +371 -0
  625. package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
  626. package/scripts/poller/voice-poller.mjs +236 -0
  627. package/scripts/poller-launchd/install.sh +66 -0
  628. package/scripts/poller-launchd/poller.plist.template +40 -0
  629. package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
  630. package/scripts/post-interaction-indexer.py +1598 -0
  631. package/scripts/pre-draft-context.py +994 -0
  632. package/scripts/pre_draft_lookup.py +258 -0
  633. package/scripts/rag/build-index.mjs +47 -0
  634. package/scripts/rag/ingest.mjs +111 -0
  635. package/scripts/rag/search.mjs +119 -0
  636. package/scripts/rag-indexer.py +629 -0
  637. package/scripts/restore-from-backup.sh +248 -0
  638. package/scripts/restore-from-backup.test.mjs +178 -0
  639. package/scripts/resume-operations.sh +80 -0
  640. package/scripts/search-secondary-inbox.py +181 -0
  641. package/scripts/secondary-inbox-poller.py +437 -0
  642. package/scripts/self-optimization/compute-metrics.py +398 -0
  643. package/scripts/send-email-as-principal.py +369 -0
  644. package/scripts/send-email-threaded.py +392 -0
  645. package/scripts/send-email-with-attachment.py +377 -0
  646. package/scripts/send-email.sh +131 -0
  647. package/scripts/send-sms.sh +175 -0
  648. package/scripts/send-whatsapp.sh +292 -0
  649. package/scripts/session-start.sh +106 -0
  650. package/scripts/setup/boot-claude-session.sh +94 -0
  651. package/scripts/setup/configure-macos.sh +674 -0
  652. package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
  653. package/scripts/setup/configure-voice-tunnel.mjs +182 -0
  654. package/scripts/setup/generate-agent-env.mjs +92 -0
  655. package/scripts/setup/generate-agent-package-json.mjs +222 -0
  656. package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
  657. package/scripts/setup/generate-autonomy.mjs +60 -0
  658. package/scripts/setup/generate-backlog.mjs +101 -0
  659. package/scripts/setup/generate-cadences.mjs +92 -0
  660. package/scripts/setup/generate-capability.mjs +162 -0
  661. package/scripts/setup/generate-charter.mjs +76 -0
  662. package/scripts/setup/generate-comms.mjs +59 -0
  663. package/scripts/setup/generate-company.mjs +92 -0
  664. package/scripts/setup/init-agent.sh +547 -0
  665. package/scripts/setup/init-agent.test.mjs +151 -0
  666. package/scripts/setup/init-archetype.mjs +74 -0
  667. package/scripts/setup/init-backup.mjs +54 -0
  668. package/scripts/setup/init-cadence-bus.mjs +60 -0
  669. package/scripts/setup/init-channel-bus.mjs +46 -0
  670. package/scripts/setup/init-cost-tracking.mjs +45 -0
  671. package/scripts/setup/init-decision-capture.mjs +66 -0
  672. package/scripts/setup/init-known-agents.mjs +57 -0
  673. package/scripts/setup/init-learning.mjs +70 -0
  674. package/scripts/setup/init-memory-executive.mjs +45 -0
  675. package/scripts/setup/init-model-router.mjs +124 -0
  676. package/scripts/setup/init-rag.mjs +174 -0
  677. package/scripts/setup/init-session-router.mjs +38 -0
  678. package/scripts/setup/init-slack-socket-mode.mjs +260 -0
  679. package/scripts/setup/init-telegram.mjs +165 -0
  680. package/scripts/setup/init-voice-realtime.mjs +204 -0
  681. package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
  682. package/scripts/setup/install-dev-tools.sh +150 -0
  683. package/scripts/setup/lib/install-plist.mjs +95 -0
  684. package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
  685. package/scripts/setup/render-environment-yaml.mjs +133 -0
  686. package/scripts/slack-events-ctl.sh +177 -0
  687. package/scripts/slack-events-server.mjs +1045 -0
  688. package/scripts/slack-react.mjs +89 -0
  689. package/scripts/slack-responded.sh +232 -0
  690. package/scripts/slack-send.sh +287 -0
  691. package/scripts/slack-typing.mjs +196 -0
  692. package/scripts/slack-upload-v2.py +95 -0
  693. package/scripts/sms-handler.mjs +450 -0
  694. package/scripts/spawn-session.sh +120 -0
  695. package/scripts/sync-protocol.mjs +217 -0
  696. package/scripts/system-verify.sh +184 -0
  697. package/scripts/test-email-thread-dedup.py +239 -0
  698. package/scripts/test-information-barriers.py +484 -0
  699. package/scripts/test-llm-email-dedup.py +251 -0
  700. package/scripts/test-pre-draft-integration.py +203 -0
  701. package/scripts/test-rag-phase2.sh +442 -0
  702. package/scripts/test-rag-search.sh +251 -0
  703. package/scripts/test-voice-parser.mjs +316 -0
  704. package/scripts/user-context-search.py +659 -0
  705. package/scripts/validate_outbound.py +1504 -0
  706. package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
  707. package/scripts/watchdog/force-reboot.sh +157 -0
  708. package/scripts/watchdog/memory-watchdog.sh +473 -0
  709. package/scripts/whatsapp-handler.mjs +538 -0
  710. package/teams/desktop-operations.yaml +34 -0
  711. package/teams/executive-office.yaml +27 -0
  712. package/teams/legal-and-regulatory.yaml +24 -0
  713. package/teams/platform-and-engineering.yaml +23 -0
  714. package/teams/strategy-and-growth.yaml +29 -0
  715. package/workflows/continuous/backlog-executor.yaml +141 -0
  716. package/workflows/continuous/inbound-monitor.yaml +168 -0
  717. package/workflows/daily/applicant-triage.yaml +197 -0
  718. package/workflows/daily/comms-triage.yaml +80 -0
  719. package/workflows/daily/evening-wrap.yaml +105 -0
  720. package/workflows/daily/morning-brief.yaml +164 -0
  721. package/workflows/daily/slack-followup-sweep.yaml +87 -0
  722. package/workflows/event-driven/README.md +50 -0
  723. package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
  724. package/workflows/event-driven/pr-review.yaml +107 -0
  725. package/workflows/monthly/board-readiness.yaml +76 -0
  726. package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
  727. package/workflows/session-protocol.md +171 -0
  728. package/workflows/weekly/engineering-health.yaml +154 -0
  729. package/workflows/weekly/hiring-review.yaml +169 -0
  730. package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
  731. package/workflows/weekly/strategic-memo.yaml +79 -0
@@ -0,0 +1,967 @@
1
+ /**
2
+ * lib/org/protocol.mjs — SHARED Cohort org-protocol contract (transport-agnostic).
3
+ *
4
+ * This is the single source of truth for the wire contract between maestro
5
+ * agents and the Cohort org server (SPEC-org-server §0.3 / §2.4): method names,
6
+ * the scope each method requires, which methods mutate state, the event families,
7
+ * the board status vocabulary, the error codes, and — critically — the
8
+ * `computeRowHash` function that defines the append-only `org_events` hash chain.
9
+ *
10
+ * Both sides import this module so they agree byte-for-byte:
11
+ * - the server (services/cohort) writes events and signs snapshots using these
12
+ * names/scopes and this hash function;
13
+ * - the agent client (lib/org/client.mjs) and the detective layer
14
+ * (lib/org/verify.mjs) re-derive the same hashes to detect tampering.
15
+ *
16
+ * The protocol is defined independently of HTTP vs WebSocket: phase 1 implements
17
+ * only the HTTP binding, but nothing here changes when the WS binding lands.
18
+ *
19
+ * Pure, Node-builtin only (node:crypto). ESM.
20
+ *
21
+ * @module lib/org/protocol
22
+ */
23
+
24
+ "use strict";
25
+
26
+ import { createHash } from "node:crypto";
27
+
28
+ /** Wire protocol version. Sent as the `X-Org-Protocol` header. */
29
+ export const PROTOCOL_VERSION = 1;
30
+
31
+ /**
32
+ * Event families. Every mutation appends to org_events under one of these, and
33
+ * every method's `family` must be one of them (CI-lintable invariant).
34
+ * @type {readonly string[]}
35
+ */
36
+ export const FAMILIES = Object.freeze([
37
+ "pairing", "registry", "presence", "board", "hierarchy", "admin",
38
+ "lease", "handoff", "governance",
39
+ "approval", "decision", "cost", "policy",
40
+ "knowledge", "contacts", "meetings",
41
+ "credential",
42
+ "messaging", "calling",
43
+ // agent UI-parity families
44
+ "charter", "member", "team", "channel", "persona", "profile", "sop", "escalation", "annotation", "contact", "file", "memory", "compact", "notification", "invitation", "user", "settings", "billing", "org", "integration",
45
+ // Brand Studio v2 write surface (branding-generation / brand-templates / brand-export)
46
+ "branding",
47
+ // agent workspace email (SPEC 2026-07-21 agent-email-server)
48
+ "email",
49
+ // generative-UI artifacts (GU program 2026-07): interactive cards in feeds
50
+ "artifact",
51
+ // agent desks (2026-07): Books desk, workspace calendar, CRM desk, and the
52
+ // workspace drive. NB "files" (Docs/Sheets/Decks drive) coexists with the
53
+ // UI-parity "file" family (pins/comments on chat attachments) — distinct.
54
+ "books", "calendar", "crm", "files",
55
+ // the Directory (2026-07): the canonical registry of external people and
56
+ // organisations every other desk resolves its counterparties through.
57
+ "directory",
58
+ ]);
59
+
60
+ /**
61
+ * Reserved method namespaces that ALWAYS resolve to the `admin` scope,
62
+ * regardless of a method descriptor — privileged control surfaces that an
63
+ * ordinary agent token must never reach (SPEC §2.4).
64
+ * @type {readonly string[]}
65
+ */
66
+ export const RESERVED_ADMIN_NAMESPACES = Object.freeze(["admin", "pairing", "policy", "governance"]);
67
+
68
+ /**
69
+ * The board work-item status vocabulary (SPEC org-server board kernel) plus its
70
+ * mapping onto the A2A Task lifecycle, so the board can later serialize to the
71
+ * A2A interop endpoints without a second enum.
72
+ * @type {readonly string[]}
73
+ */
74
+ export const BOARD_STATUS = Object.freeze([
75
+ "triage", "backlog", "todo", "scheduled", "ready", "running", "review", "blocked", "done", "archived",
76
+ ]);
77
+
78
+ /** Statuses an item can be claimed from (work is dispatchable). */
79
+ export const CLAIMABLE_STATUS = Object.freeze(["ready", "todo"]);
80
+
81
+ /** Terminal statuses (no further work; excluded from the ready feed). */
82
+ export const TERMINAL_STATUS = Object.freeze(["done", "archived"]);
83
+
84
+ /** A2A Task-state mapping for the board statuses (interop, phase 4). */
85
+ export const BOARD_STATUS_A2A = Object.freeze({
86
+ triage: "submitted", backlog: "submitted", todo: "submitted", scheduled: "submitted",
87
+ ready: "submitted", running: "working", review: "input-required", blocked: "input-required",
88
+ done: "completed", archived: "canceled",
89
+ });
90
+
91
+ /**
92
+ * Scopes (capabilities). Default-deny: a token carries a set of these; a method
93
+ * is permitted only if the actor holds its required scope (or `admin`).
94
+ * @type {readonly string[]}
95
+ */
96
+ export const SCOPES = Object.freeze([
97
+ "org.read", // read directory / hierarchy / snapshot / events
98
+ "registry.write", // register self, heartbeat
99
+ "presence.write", // presence beats
100
+ "board.read", // read board feeds
101
+ "board.write", // create / comment / decompose / link items
102
+ "board.work", // claim / heartbeat / complete items
103
+ "lease.write", // claim/heartbeat/release generic leases (thread ownership, singletons)
104
+ "handoff.send", // offer/accept/decline/cancel delegations (gated by governance_ready)
105
+ "approval.request",// request approval for a classified action; read own approvals
106
+ "approval.decide", // resolve (approve/reject) an approval — granted to approvers/humans only
107
+ "decision.write", // propose a decision into the registry
108
+ "cost.report", // report per-session usage into the rollup
109
+ "knowledge.read", // search ACL'd shared facts (RLS-floored; barrier cells deny-override)
110
+ "knowledge.write", // append episodes / replace facts / upsert contacts+meetings
111
+ "credential.use", // lease an org-level third-party credential (deepseek/kimi/...) — the
112
+ // single SoT: agents never hold the org key, they lease it short-lived
113
+ "messaging.read", // read channels + message history in the human messaging app
114
+ "messaging.write", // send / react / edit messages in the human messaging app
115
+ "calling.write", // start / join / end / invite — call lifecycle in the human app
116
+ "charter.write", // EDITOR-equivalent: author/amend member charters, clauses, sections, and colleague-charter fields. Reads use org.read.
117
+ "org.write", // EDITOR-equivalent: write org-structural content (members, teams, personas, profiles, SOPs, escalations, annotations, contacts, files, compacts, notification prefs, reporting lines, chart layout). Reads use org.read.
118
+ "email.read", // read own-mailbox inbox / messages / threads + the Inbox-app read surface (mailboxes/threads/messages/drafts/approvals/rules/signatures)
119
+ "email.write", // send workspace email, mark read + the Inbox-app write surface (triage/move/drafts/rules/folders/signatures; guardian verbs enforced in-domain)
120
+ // agent-desk scope pairs (2026-07): one read/write pair per desk family so a
121
+ // key can be minted for exactly one desk (e.g. a Books-only finance seat).
122
+ "files.read", // workspace drive reads: list/get + doc/sheet/deck reads; exportRequest is an AUDITED side-effecting export under this read scope
123
+ "files.write", // workspace drive writes: create/upload/rename/move/star/pin/delete/recover/shares + doc/sheet/deck revisions
124
+ "calendar.read", // workspace calendar reads: list/get + the deterministic findATime scan
125
+ "calendar.write",// workspace calendar writes: create/update/reschedule/rsvp/delete (queue real invite/update/cancel emails)
126
+ "crm.read", // CRM desk reads: deals/escalations/accounts/contacts/interactions/playbooks/targets + pure compile previews
127
+ "crm.write", // CRM desk writes: deal lifecycle, accounts/contacts, playbooks, targets — human-only verbs (signEscalation, targetCents) refuse agent actors in-domain
128
+ "books.read", // Books desk reads: accounts/journals/feed/invoices/bills/tax/reports/assets + grounded books.ask (AI-gateway self-audited)
129
+ "books.write", // Books desk writes: AR/AP/expenses/payroll/tax/assets — the signature law (PB-B1..B4) is enforced in-domain; scope alone cannot move money
130
+ "directory.read", // Directory reads: people/organisations/channels/lists/review queues + grounded directory.ask. AWARENESS-SCOPED: a named-only record is ABSENT from every result for a seat without a grant, never redacted
131
+ "directory.write", // Directory writes: create/update parties, channels, captures, links, lists. Structural verbs (merge execution, awareness change, archive) refuse an agent actor in-domain and create a review item instead
132
+ "design.read", // Design reads: the SAVED brand foundation snapshot (never a draft), voice, visible templates. Renders and voice rewrites are AUDITED side-effecting reads under this scope (files.exportRequest precedent)
133
+ "design.write", // Design writes: generate imagery, propose a foundation change. Foundation mutation itself is admin-only and human-gated -- a proposal creates a reviewable diff, it does not write
134
+ "admin", // pairing approval, deactivate (kill switch), governance, policy, credential put/revoke
135
+ ]);
136
+
137
+ /** Default scope set granted to a normal paired agent (no admin, no approval.decide). */
138
+ export const DEFAULT_AGENT_SCOPES = Object.freeze([
139
+ "org.read", "registry.write", "presence.write", "board.read", "board.write", "board.work",
140
+ "lease.write", "handoff.send", "approval.request", "decision.write", "cost.report",
141
+ "knowledge.read", "knowledge.write", "credential.use",
142
+ "messaging.read", "messaging.write", "calling.write",
143
+ "charter.write", "org.write", // agent UI-parity (EDITOR-equivalent)
144
+ "email.read", "email.write", // workspace email + Inbox app (messaging precedent)
145
+ // agent desks (2026-07): a normal paired agent works the product surfaces its
146
+ // seat's human role could — dangerous verbs stay held in the domain layer
147
+ // (guardian-only, human-only, signature law), never behind scope alone.
148
+ "files.read", "files.write", "calendar.read", "calendar.write",
149
+ "crm.read", "crm.write", "books.read", "books.write",
150
+ "directory.read", "directory.write", "design.read", "design.write",
151
+ ]);
152
+
153
+ /** Knowledge group scopes: org-wide, per-unit, or an information-barrier cell (deny-override). */
154
+ export const KNOWLEDGE_GROUP_KINDS = Object.freeze(["org", "unit", "barrier"]);
155
+
156
+ /** Generic lease scopes (one primitive, several scopes of use). */
157
+ export const LEASE_SCOPES = Object.freeze(["work-claim", "thread-ownership", "session-leadership", "singleton-cadence"]);
158
+
159
+ /**
160
+ * The method registry contract (the server globs one file per method; this is
161
+ * the authoritative name→descriptor map both sides validate against).
162
+ * `auth:false` ⇒ pre-auth method (pairing handshake). `sideEffecting:true` ⇒
163
+ * runs in a transaction and MUST append to the event chain.
164
+ * @type {Readonly<Record<string,{family:string,scope:string,sideEffecting:boolean,auth?:boolean,idempotent?:boolean}>>}
165
+ */
166
+ export const METHODS = Object.freeze({
167
+ // --- pairing (pre-auth handshake + admin approval) ---
168
+ "pairing.request": { family: "pairing", scope: "admin", sideEffecting: true, auth: false },
169
+ "pairing.approve": { family: "pairing", scope: "admin", sideEffecting: true },
170
+ // --- registry / presence ---
171
+ "registry.register": { family: "registry", scope: "registry.write", sideEffecting: true, idempotent: true },
172
+ "registry.heartbeat": { family: "registry", scope: "registry.write", sideEffecting: true },
173
+ "presence.beat": { family: "presence", scope: "presence.write", sideEffecting: true },
174
+ // --- board (kanban / backlog) ---
175
+ "board.create": { family: "board", scope: "board.write", sideEffecting: true, idempotent: true },
176
+ "board.claim": { family: "board", scope: "board.work", sideEffecting: true },
177
+ "board.heartbeat": { family: "board", scope: "board.work", sideEffecting: true },
178
+ "board.complete": { family: "board", scope: "board.work", sideEffecting: true },
179
+ "board.block": { family: "board", scope: "board.work", sideEffecting: true },
180
+ "board.comment": { family: "board", scope: "board.write", sideEffecting: true },
181
+ // --- board DAG (phase 2) — decompose/assign are governance-gated ---
182
+ "board.decompose": { family: "board", scope: "board.write", sideEffecting: true, governanceGated: true },
183
+ "board.assign": { family: "board", scope: "board.write", sideEffecting: true, governanceGated: true },
184
+ "board.link": { family: "board", scope: "board.write", sideEffecting: true },
185
+ // --- lease (phase 2): one generic primitive (work-claim / thread-ownership / singleton-cadence / session-leadership) ---
186
+ "lease.claim": { family: "lease", scope: "lease.write", sideEffecting: true },
187
+ "lease.heartbeat": { family: "lease", scope: "lease.write", sideEffecting: true },
188
+ "lease.release": { family: "lease", scope: "lease.write", sideEffecting: true },
189
+ // --- handoff (phase 2): signed delegation lifecycle — ALL governance-gated (§0.4) ---
190
+ "handoff.offer": { family: "handoff", scope: "handoff.send", sideEffecting: true, governanceGated: true },
191
+ "handoff.accept": { family: "handoff", scope: "handoff.send", sideEffecting: true, governanceGated: true },
192
+ "handoff.decline": { family: "handoff", scope: "handoff.send", sideEffecting: true, governanceGated: true },
193
+ "handoff.cancel": { family: "handoff", scope: "handoff.send", sideEffecting: true, governanceGated: true },
194
+ // --- hierarchy (org SoT — reads in phase 1; writes via governance later) ---
195
+ "hierarchy.set": { family: "hierarchy", scope: "admin", sideEffecting: true },
196
+ // --- governance (decision-rights ingest gates handoffs/assignment per §0.4) ---
197
+ "governance.set_decision_rights": { family: "governance", scope: "admin", sideEffecting: true },
198
+ "governance.status": { family: "governance", scope: "org.read", sideEffecting: false },
199
+ // --- approvals (phase 3): requester != approver enforced as a DB CHECK (G5) ---
200
+ "approval.request": { family: "approval", scope: "approval.request", sideEffecting: true, idempotent: true },
201
+ "approval.resolve": { family: "approval", scope: "approval.decide", sideEffecting: true },
202
+ "approval.get": { family: "approval", scope: "approval.request", sideEffecting: false },
203
+ "approval.list": { family: "approval", scope: "approval.request", sideEffecting: false },
204
+ // --- decisions registry (phase 3): adopt/supersede are governance-gated ---
205
+ "decision.propose": { family: "decision", scope: "decision.write", sideEffecting: true, idempotent: true },
206
+ "decision.adopt": { family: "decision", scope: "decision.write", sideEffecting: true, governanceGated: true },
207
+ "decision.supersede": { family: "decision", scope: "decision.write", sideEffecting: true, governanceGated: true },
208
+ // --- cost rollups (phase 3): agents report usage; rollups read org-wide ---
209
+ "cost.report": { family: "cost", scope: "cost.report", sideEffecting: true, idempotent: true },
210
+ // --- policy distribution (phase 3): publish is admin (reserved); agents fetch via the read surface ---
211
+ "policy.publish": { family: "policy", scope: "admin", sideEffecting: true },
212
+ // --- knowledge plane (phase 4): RLS-floored shared facts; barrier cells deny-override.
213
+ // Mutable-artifact semantics: append (commutes) / replace (CAS) / rewrite (owner-only).
214
+ // search is side-effecting because every retrieval is logged to org_events (read-audit). ---
215
+ "knowledge.append": { family: "knowledge", scope: "knowledge.write", sideEffecting: true, idempotent: true },
216
+ "knowledge.replace": { family: "knowledge", scope: "knowledge.write", sideEffecting: true },
217
+ "knowledge.rewrite": { family: "knowledge", scope: "knowledge.write", sideEffecting: true },
218
+ "knowledge.invalidate": { family: "knowledge", scope: "knowledge.write", sideEffecting: true },
219
+ "knowledge.search": { family: "knowledge", scope: "knowledge.read", sideEffecting: true },
220
+ "contacts.upsert": { family: "contacts", scope: "knowledge.write", sideEffecting: true, idempotent: true },
221
+ "meetings.record": { family: "meetings", scope: "knowledge.write", sideEffecting: true, idempotent: true },
222
+ // meetings.recapFile — file a held meeting's recap + transcript into the
223
+ // workspace drive (two DOCs + a MeetingRecord, links back); the
224
+ // notetaker's landing verb. Knowledge-capture like meetings.record, so it
225
+ // rides knowledge.write (the integration manifest's declared contract). ---
226
+ "meetings.recapFile": { family: "meetings", scope: "knowledge.write", sideEffecting: true, idempotent: true },
227
+ // --- org credential broker: the single source of truth for org-level third-party
228
+ // creds (DeepSeek/Kimi/OpenAI/...). Admin sets once; agents LEASE short-lived,
229
+ // role-scoped, audited — the org key never lives on the 50-100 agent machines. ---
230
+ "credential.put": { family: "credential", scope: "admin", sideEffecting: true },
231
+ "credential.lease": { family: "credential", scope: "credential.use", sideEffecting: true },
232
+ "credential.list": { family: "credential", scope: "admin", sideEffecting: false },
233
+ "credential.revoke": { family: "credential", scope: "admin", sideEffecting: true },
234
+ // --- messaging (SP3): agents participate in the human messaging app. send is
235
+ // idempotent via the client-supplied message id; audit rows are REDACTED
236
+ // (no message body lands in org_events). NOT a reserved-admin namespace. ---
237
+ "messaging.send": { family: "messaging", scope: "messaging.write", sideEffecting: true, idempotent: true },
238
+ "messaging.createPoll": { family: "messaging", scope: "messaging.write", sideEffecting: true, idempotent: true },
239
+ "messaging.votePoll": { family: "messaging", scope: "messaging.write", sideEffecting: true, idempotent: true },
240
+ "messaging.react": { family: "messaging", scope: "messaging.write", sideEffecting: true },
241
+ "messaging.edit": { family: "messaging", scope: "messaging.write", sideEffecting: true },
242
+ "messaging.history": { family: "messaging", scope: "messaging.read", sideEffecting: false },
243
+ "messaging.channels": { family: "messaging", scope: "messaging.read", sideEffecting: false },
244
+ // --- calling (SP3): call lifecycle in the human app. NOT reserved-admin. ---
245
+ "calling.start": { family: "calling", scope: "calling.write", sideEffecting: true },
246
+ "calling.join": { family: "calling", scope: "calling.write", sideEffecting: true },
247
+ "calling.end": { family: "calling", scope: "calling.write", sideEffecting: true },
248
+ "calling.invite": { family: "calling", scope: "calling.write", sideEffecting: true },
249
+ // --- calling agent-operability: in-call actions + reads for authorised agents.
250
+ // react/raiseHand/lowerHand are pure-event (the chain event IS the record,
251
+ // no projection row); getDetails/getTranscript are participation-gated reads. ---
252
+ "calling.react": { family: "calling", scope: "calling.write", sideEffecting: true },
253
+ "calling.raiseHand": { family: "calling", scope: "calling.write", sideEffecting: true },
254
+ "calling.lowerHand": { family: "calling", scope: "calling.write", sideEffecting: true },
255
+ "calling.getDetails": { family: "calling", scope: "org.read", sideEffecting: false },
256
+ "calling.getTranscript": { family: "calling", scope: "org.read", sideEffecting: false },
257
+ "calling.recordingStart": { family: "calling", scope: "calling.write", sideEffecting: true },
258
+ "calling.recordingStop": { family: "calling", scope: "calling.write", sideEffecting: true },
259
+ "calling.setMute": { family: "calling", scope: "calling.write", sideEffecting: true },
260
+ // --- calling agent transcript + in-call chat: authorised agents write finalized
261
+ // transcript lines and post to the in-call chat panel. Both persist a
262
+ // projection row (CallTranscriptLine / CallChatMessage) and append exactly
263
+ // one REDACTED calling event (no line/message body in the chain). ---
264
+ "calling.addTranscriptLine": { family: "calling", scope: "calling.write", sideEffecting: true },
265
+ "calling.chat": { family: "calling", scope: "calling.write", sideEffecting: true },
266
+ // --- calling host moderation (2026-08): remove a participant from the live
267
+ // room + lock the room against NEW joins (existing participants and
268
+ // reconnects are unaffected; start/end are unaffected). Host-gated in the
269
+ // hq domain (the call starter), and an agent may act only on AI targets.
270
+ // Governance refusals return as RESULTS with one audited *_refused event
271
+ // — never a thrown error. ---
272
+ "calling.removeParticipant": { family: "calling", scope: "calling.write", sideEffecting: true },
273
+ "calling.setLock": { family: "calling", scope: "calling.write", sideEffecting: true },
274
+ // --- calling working sessions (2026-08): an AI participant "presents" a Cohort
275
+ // surface (doc/sheet/channel/board/charter) on the call stage — an app
276
+ // share rendered locally by every client and driven by step frames over
277
+ // the room data lane. Lifecycle + coarse act batches persist a
278
+ // CallShareSession projection and append REDACTED share.* events (never
279
+ // step text/content). getAppShare hydrates late joiners; getVisualContext
280
+ // reads the narrated visual stream (screen/camera stills interpreted into
281
+ // text — frames are never persisted); getCallEvents reads recent
282
+ // non-speech call events (reactions/hands/joins/share lifecycle). ---
283
+ "calling.appShareStart": { family: "calling", scope: "calling.write", sideEffecting: true },
284
+ "calling.appShareAct": { family: "calling", scope: "calling.write", sideEffecting: true },
285
+ "calling.appShareEnd": { family: "calling", scope: "calling.write", sideEffecting: true },
286
+ "calling.getAppShare": { family: "calling", scope: "org.read", sideEffecting: false },
287
+ "calling.getVisualContext": { family: "calling", scope: "org.read", sideEffecting: false },
288
+ "calling.getCallEvents": { family: "calling", scope: "org.read", sideEffecting: false },
289
+ // --- admin (kill switch) ---
290
+ "admin.deactivate": { family: "admin", scope: "admin", sideEffecting: true },
291
+ "admin.reactivate": { family: "admin", scope: "admin", sideEffecting: true },
292
+
293
+ // ============================================================
294
+ // Agent UI-parity methods (full human-action mirror)
295
+ // ============================================================
296
+ // --- charter ---
297
+ "charter.updateClauses": { family: "charter", scope: "charter.write", sideEffecting: true },
298
+ "charter.update": { family: "charter", scope: "charter.write", sideEffecting: true },
299
+ "charter.addSection": { family: "charter", scope: "charter.write", sideEffecting: true, idempotent: true },
300
+ "charter.updateSection": { family: "charter", scope: "charter.write", sideEffecting: true },
301
+ "charter.deleteSection": { family: "charter", scope: "charter.write", sideEffecting: true },
302
+ "charter.reorderSections": { family: "charter", scope: "charter.write", sideEffecting: true },
303
+ "charter.amend": { family: "charter", scope: "charter.write", sideEffecting: true },
304
+ "charter.editColleagueField": { family: "charter", scope: "charter.write", sideEffecting: true },
305
+ "charter.get": { family: "charter", scope: "org.read", sideEffecting: false },
306
+ // --- member ---
307
+ "member.create": { family: "member", scope: "org.write", sideEffecting: true, idempotent: true },
308
+ "member.update": { family: "member", scope: "org.write", sideEffecting: true },
309
+ "member.archive": { family: "member", scope: "org.write", sideEffecting: true },
310
+ "member.assignTeam": { family: "member", scope: "org.write", sideEffecting: true },
311
+ "member.reassignReporting": { family: "member", scope: "org.write", sideEffecting: true },
312
+ "member.savePosition": { family: "member", scope: "org.write", sideEffecting: true },
313
+ "member.resetPositions": { family: "member", scope: "org.write", sideEffecting: true },
314
+ "member.get": { family: "member", scope: "org.read", sideEffecting: false },
315
+ "member.list": { family: "member", scope: "org.read", sideEffecting: false },
316
+ "member.getEditorOptions": { family: "member", scope: "org.read", sideEffecting: false },
317
+ // --- team ---
318
+ "team.create": { family: "team", scope: "org.write", sideEffecting: true, idempotent: true },
319
+ "team.update": { family: "team", scope: "org.write", sideEffecting: true },
320
+ "team.delete": { family: "team", scope: "org.write", sideEffecting: true },
321
+ "team.list": { family: "team", scope: "org.read", sideEffecting: false },
322
+ // --- channel ---
323
+ "channel.create": { family: "channel", scope: "messaging.write", sideEffecting: true, idempotent: true },
324
+ "channel.update": { family: "channel", scope: "messaging.write", sideEffecting: true },
325
+ "channel.addMember": { family: "channel", scope: "messaging.write", sideEffecting: true, idempotent: true },
326
+ "channel.removeMember": { family: "channel", scope: "messaging.write", sideEffecting: true },
327
+ "channel.updateMemberRole": { family: "channel", scope: "messaging.write", sideEffecting: true },
328
+ "channel.markRead": { family: "channel", scope: "messaging.write", sideEffecting: true },
329
+ "channel.markUnreadFrom": { family: "channel", scope: "messaging.write", sideEffecting: true },
330
+ "channel.resolveOrCreateDm": { family: "channel", scope: "messaging.write", sideEffecting: true, idempotent: true },
331
+ "channel.createConversation": { family: "channel", scope: "messaging.write", sideEffecting: true, idempotent: true },
332
+ // --- persona ---
333
+ "persona.editField": { family: "persona", scope: "org.write", sideEffecting: true },
334
+ "persona.regenerate": { family: "persona", scope: "org.write", sideEffecting: true },
335
+ "persona.generateAgentId": { family: "persona", scope: "org.write", sideEffecting: true, idempotent: true },
336
+ // --- profile ---
337
+ "profile.update": { family: "profile", scope: "org.write", sideEffecting: true },
338
+ "profile.addSection": { family: "profile", scope: "org.write", sideEffecting: true, idempotent: true },
339
+ "profile.updateSection": { family: "profile", scope: "org.write", sideEffecting: true },
340
+ "profile.deleteSection": { family: "profile", scope: "org.write", sideEffecting: true },
341
+ "profile.updateAvatar": { family: "profile", scope: "org.write", sideEffecting: true },
342
+ "profile.get": { family: "profile", scope: "org.read", sideEffecting: false },
343
+ // --- sop ---
344
+ "sop.create": { family: "sop", scope: "org.write", sideEffecting: true, idempotent: true },
345
+ "sop.update": { family: "sop", scope: "org.write", sideEffecting: true },
346
+ "sop.delete": { family: "sop", scope: "org.write", sideEffecting: true },
347
+ "sop.list": { family: "sop", scope: "org.read", sideEffecting: false },
348
+ // --- escalation ---
349
+ "escalation.create": { family: "escalation", scope: "org.write", sideEffecting: true, idempotent: true },
350
+ "escalation.resolve": { family: "escalation", scope: "org.write", sideEffecting: true },
351
+ "escalation.list": { family: "escalation", scope: "org.read", sideEffecting: false },
352
+ // --- annotation ---
353
+ "annotation.create": { family: "annotation", scope: "org.write", sideEffecting: true, idempotent: true },
354
+ "annotation.comment": { family: "annotation", scope: "org.write", sideEffecting: true },
355
+ "annotation.resolve": { family: "annotation", scope: "org.write", sideEffecting: true },
356
+ "annotation.dismiss": { family: "annotation", scope: "org.write", sideEffecting: true },
357
+ "annotation.list": { family: "annotation", scope: "org.read", sideEffecting: false },
358
+ "annotation.getThread": { family: "annotation", scope: "org.read", sideEffecting: false },
359
+ // --- contact ---
360
+ "contact.add": { family: "contact", scope: "org.write", sideEffecting: true, idempotent: true },
361
+ "contact.update": { family: "contact", scope: "org.write", sideEffecting: true },
362
+ "contact.delete": { family: "contact", scope: "org.write", sideEffecting: true },
363
+ "contact.list": { family: "contact", scope: "org.read", sideEffecting: false },
364
+ // --- file ---
365
+ "file.pin": { family: "file", scope: "org.write", sideEffecting: true, idempotent: true },
366
+ "file.unpin": { family: "file", scope: "org.write", sideEffecting: true },
367
+ "file.addComment": { family: "file", scope: "org.write", sideEffecting: true },
368
+ "file.listComments": { family: "file", scope: "org.read", sideEffecting: false },
369
+ "file.listPinned": { family: "file", scope: "org.read", sideEffecting: false },
370
+ // --- memory ---
371
+ "memory.author": { family: "memory", scope: "knowledge.write", sideEffecting: true, idempotent: true },
372
+ "memory.proposeAmendment": { family: "memory", scope: "knowledge.write", sideEffecting: true },
373
+ "memory.mergeAmendment": { family: "memory", scope: "knowledge.write", sideEffecting: true },
374
+ "memory.list": { family: "memory", scope: "knowledge.read", sideEffecting: false },
375
+ // --- compact ---
376
+ "compact.upsert": { family: "compact", scope: "org.write", sideEffecting: true, idempotent: true },
377
+ // --- notification ---
378
+ "notification.updatePreferences": { family: "notification", scope: "org.write", sideEffecting: true },
379
+ "notification.getPreferences": { family: "notification", scope: "org.read", sideEffecting: false },
380
+ // --- decision ---
381
+ "decision.comment": { family: "decision", scope: "decision.write", sideEffecting: true },
382
+ "decision.sign": { family: "decision", scope: "decision.write", sideEffecting: true },
383
+ "decision.reverse": { family: "decision", scope: "decision.write", sideEffecting: true },
384
+ "decision.requestAdjustment": { family: "decision", scope: "decision.write", sideEffecting: true },
385
+ "decision.listComments": { family: "decision", scope: "org.read", sideEffecting: false },
386
+ // --- messaging ---
387
+ "messaging.schedule": { family: "messaging", scope: "messaging.write", sideEffecting: true },
388
+ "messaging.delete": { family: "messaging", scope: "messaging.write", sideEffecting: true },
389
+ "messaging.pin": { family: "messaging", scope: "messaging.write", sideEffecting: true },
390
+ "messaging.unpin": { family: "messaging", scope: "messaging.write", sideEffecting: true },
391
+ "messaging.bookmark": { family: "messaging", scope: "messaging.write", sideEffecting: true },
392
+ "messaging.unbookmark": { family: "messaging", scope: "messaging.write", sideEffecting: true },
393
+ // --- board ---
394
+ "board.createWorkstream": { family: "board", scope: "board.write", sideEffecting: true, idempotent: true },
395
+ "board.updateWorkstream": { family: "board", scope: "board.write", sideEffecting: true },
396
+ "board.deleteWorkstream": { family: "board", scope: "board.write", sideEffecting: true },
397
+ "board.moveWorkstream": { family: "board", scope: "board.write", sideEffecting: true },
398
+ "board.createTask": { family: "board", scope: "board.write", sideEffecting: true, idempotent: true },
399
+ "board.updateTask": { family: "board", scope: "board.write", sideEffecting: true },
400
+ "board.moveTask": { family: "board", scope: "board.write", sideEffecting: true },
401
+ "board.assignTask": { family: "board", scope: "board.write", sideEffecting: true },
402
+ "board.addTaskComment": { family: "board", scope: "board.write", sideEffecting: true },
403
+ "board.addTaskAttachment": { family: "board", scope: "board.write", sideEffecting: true, idempotent: true },
404
+ "board.updateTaskAttachment": { family: "board", scope: "board.write", sideEffecting: true },
405
+ "board.removeTaskAttachment": { family: "board", scope: "board.write", sideEffecting: true },
406
+ "board.taskComments": { family: "board", scope: "board.read", sideEffecting: false },
407
+ "board.taskAttachments": { family: "board", scope: "board.read", sideEffecting: false },
408
+ // --- invitation ---
409
+ "invitation.create": { family: "invitation", scope: "admin", sideEffecting: true, idempotent: true },
410
+ "invitation.revoke": { family: "invitation", scope: "admin", sideEffecting: true },
411
+ "invitation.resend": { family: "invitation", scope: "admin", sideEffecting: true },
412
+ // --- user ---
413
+ "user.updateRole": { family: "user", scope: "admin", sideEffecting: true },
414
+ "user.deactivate": { family: "user", scope: "admin", sideEffecting: true },
415
+ // --- settings ---
416
+ "settings.mintApiKey": { family: "settings", scope: "admin", sideEffecting: true, idempotent: true },
417
+ "settings.renameApiKey": { family: "settings", scope: "admin", sideEffecting: true },
418
+ "settings.revokeApiKey": { family: "settings", scope: "admin", sideEffecting: true },
419
+ "settings.recordSecretRotation": { family: "settings", scope: "admin", sideEffecting: true },
420
+ "settings.upsertSecret": { family: "settings", scope: "admin", sideEffecting: true, idempotent: true },
421
+ "settings.deleteSecret": { family: "settings", scope: "admin", sideEffecting: true },
422
+ "settings.upsertOfficeLocation": { family: "settings", scope: "admin", sideEffecting: true, idempotent: true },
423
+ "settings.deleteOfficeLocation": { family: "settings", scope: "admin", sideEffecting: true },
424
+ "settings.saveCompanyProfile": { family: "settings", scope: "admin", sideEffecting: true, idempotent: true },
425
+ "settings.saveDesignTokens": { family: "settings", scope: "admin", sideEffecting: true, idempotent: true },
426
+ "settings.getDesignTokens": { family: "settings", scope: "org.read", sideEffecting: false },
427
+ "settings.listApiKeys": { family: "settings", scope: "admin", sideEffecting: false },
428
+ // --- billing ---
429
+ "billing.update": { family: "billing", scope: "admin", sideEffecting: true },
430
+ // --- org ---
431
+ "org.create": { family: "org", scope: "admin", sideEffecting: true, idempotent: true },
432
+ "org.delete": { family: "org", scope: "admin", sideEffecting: true },
433
+ "org.mintApiKey": { family: "org", scope: "admin", sideEffecting: true, idempotent: true },
434
+ "org.revokeApiKey": { family: "org", scope: "admin", sideEffecting: true },
435
+ "org.list": { family: "org", scope: "org.read", sideEffecting: false },
436
+ // --- integration ---
437
+ "integration.connect": { family: "integration", scope: "admin", sideEffecting: true, idempotent: true },
438
+ "integration.disconnect": { family: "integration", scope: "admin", sideEffecting: true },
439
+ "integration.list": { family: "integration", scope: "org.read", sideEffecting: false },
440
+ // Live Integrations SP1 — tool-set versioning + propagation. Read-only pulls a
441
+ // runtime consumer (hq responder, MCP server, maestro daemon) uses to hot-refresh
442
+ // its in-memory tool cache without a restart: toolsetVersion is the cheap ETag
443
+ // poll; listAgentTools is the authoritative granted-tool manifest (schemas only —
444
+ // never plugin code or secrets). Org-scoped by the credential.
445
+ "integration.toolsetVersion": { family: "integration", scope: "org.read", sideEffecting: false },
446
+ "integration.listAgentTools": { family: "integration", scope: "org.read", sideEffecting: false },
447
+ // Live Integrations SP5 — execute one GRANTED plugin tool as the authenticated
448
+ // seat, returning the tool's raw output. The org + seat are resolved from the
449
+ // credential server-side (never a param), and the per-seat grant check is the
450
+ // real authorization gate. scope org.write (EDITOR-tier — in DEFAULT_AGENT_SCOPES,
451
+ // so a normal paired agent can invoke; a read-only VIEWER agent cannot). NOT a
452
+ // reserved-admin namespace. sideEffecting:false at the DISPATCHER layer: the hq
453
+ // handler proxies to runIntegrationTool, which owns its own execution + self-heal
454
+ // + audit and may run a long sandbox/external call — so, like the branding write
455
+ // surface, it must NOT run inside the dispatcher's single-append transaction.
456
+ "integration.invokeTool": { family: "integration", scope: "org.write", sideEffecting: false },
457
+ // --- branding (Brand Studio v2 write surface) — the agent mirror of the human
458
+ // branding-generation / brand-templates / brand-export server actions.
459
+ // sideEffecting:false at the dispatcher layer: the branding surface owns its
460
+ // OWN versioned mutation + audit (the BrandVersion spine + self-appended
461
+ // CohortEvents) and may run long AI/render passes, so it must NOT execute
462
+ // inside the dispatcher's single-append transaction. Writes require ADMIN
463
+ // (mirroring requireWorkspaceRole("ADMIN")); the two reads use org.read. ---
464
+ // --- directory (2026-07): the canonical party registry. Reads are AWARENESS
465
+ // SCOPED at the query layer -- a named-only record is ABSENT from results
466
+ // for a seat without a grant (never redacted, never inferable). Structural
467
+ // verbs (executeMerge, revertMerge, setAwareness, archive*) are declared
468
+ // here so a human bearer can call them; an AGENT actor is refused in-domain
469
+ // and the call creates a review item instead. ---
470
+ "directory.search": { family: "directory", scope: "directory.read", sideEffecting: false },
471
+ "directory.listPeople": { family: "directory", scope: "directory.read", sideEffecting: false },
472
+ "directory.listOrgs": { family: "directory", scope: "directory.read", sideEffecting: false },
473
+ "directory.getPerson": { family: "directory", scope: "directory.read", sideEffecting: false },
474
+ "directory.getOrg": { family: "directory", scope: "directory.read", sideEffecting: false },
475
+ "directory.ask": { family: "directory", scope: "directory.read", sideEffecting: true },
476
+ "directory.exportVcard": { family: "directory", scope: "directory.read", sideEffecting: true },
477
+ "directory.listCaptures": { family: "directory", scope: "directory.read", sideEffecting: false },
478
+ "directory.listMerges": { family: "directory", scope: "directory.read", sideEffecting: false },
479
+ "directory.listAwarenessRequests": { family: "directory", scope: "directory.read", sideEffecting: false },
480
+ "directory.listLists": { family: "directory", scope: "directory.read", sideEffecting: false },
481
+ "directory.createPerson": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
482
+ "directory.updatePerson": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
483
+ "directory.archivePerson": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
484
+ "directory.createOrg": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
485
+ "directory.updateOrg": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
486
+ "directory.archiveOrg": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
487
+ "directory.addChannel": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
488
+ "directory.removeChannel": { family: "directory", scope: "directory.write", sideEffecting: true },
489
+ "directory.setPrimaryChannel": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
490
+ "directory.proposeCapture": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
491
+ "directory.resolveCapture": { family: "directory", scope: "directory.write", sideEffecting: true },
492
+ "directory.proposeMerge": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
493
+ "directory.executeMerge": { family: "directory", scope: "directory.write", sideEffecting: true },
494
+ "directory.revertMerge": { family: "directory", scope: "directory.write", sideEffecting: true },
495
+ "directory.requestAwareness": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
496
+ "directory.setAwareness": { family: "directory", scope: "directory.write", sideEffecting: true },
497
+ "directory.resolveAwarenessRequest": { family: "directory", scope: "directory.write", sideEffecting: true },
498
+ "directory.linkParty": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
499
+ "directory.unlinkParty": { family: "directory", scope: "directory.write", sideEffecting: true },
500
+ "directory.createList": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
501
+ "directory.addToList": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
502
+ "directory.removeFromList": { family: "directory", scope: "directory.write", sideEffecting: true },
503
+ "directory.shareList": { family: "directory", scope: "directory.write", sideEffecting: true, idempotent: true },
504
+ "branding.regenerate": { family: "branding", scope: "admin", sideEffecting: false },
505
+ "branding.regenerateTemplates": { family: "branding", scope: "admin", sideEffecting: false },
506
+ "branding.regenerateTemplate": { family: "branding", scope: "admin", sideEffecting: false },
507
+ "branding.regenerateImages": { family: "branding", scope: "admin", sideEffecting: false },
508
+ "branding.uploadImage": { family: "branding", scope: "admin", sideEffecting: false },
509
+ "branding.amendArtifact": { family: "branding", scope: "admin", sideEffecting: false },
510
+ "branding.amendTemplate": { family: "branding", scope: "admin", sideEffecting: false },
511
+ "branding.cloneTemplate": { family: "branding", scope: "admin", sideEffecting: false },
512
+ "branding.deleteTemplate": { family: "branding", scope: "admin", sideEffecting: false },
513
+ "branding.restoreVersion": { family: "branding", scope: "admin", sideEffecting: false },
514
+ "branding.undo": { family: "branding", scope: "admin", sideEffecting: false },
515
+ "branding.redo": { family: "branding", scope: "admin", sideEffecting: false },
516
+ "branding.listVersions": { family: "branding", scope: "org.read", sideEffecting: false },
517
+ "branding.exportTemplate": { family: "branding", scope: "org.read", sideEffecting: false },
518
+ // --- Design section (2026-07): the versioned brand foundation. The UI section is
519
+ // called "Design"; the wire family stays `branding` so enrolled agents keep
520
+ // working. Agents read the SAVED snapshot only -- never a draft -- and can
521
+ // render/generate within charter budgets. Foundation MUTATION is admin +
522
+ // human-gated: `proposeChange` creates a reviewable diff, it never writes. ---
523
+ "branding.getFoundation": { family: "branding", scope: "design.read", sideEffecting: false },
524
+ "branding.getVoice": { family: "branding", scope: "design.read", sideEffecting: false },
525
+ "branding.listTemplates": { family: "branding", scope: "design.read", sideEffecting: false },
526
+ "branding.renderTemplate": { family: "branding", scope: "design.read", sideEffecting: true, idempotent: true },
527
+ "branding.rewriteInVoice": { family: "branding", scope: "design.read", sideEffecting: true },
528
+ "branding.exportKit": { family: "branding", scope: "design.read", sideEffecting: true },
529
+ "branding.generateImage": { family: "branding", scope: "design.write", sideEffecting: true, idempotent: true },
530
+ "branding.proposeChange": { family: "branding", scope: "design.write", sideEffecting: true },
531
+ "branding.saveFoundation": { family: "branding", scope: "admin", sideEffecting: true, idempotent: true },
532
+ "branding.restoreFoundation": { family: "branding", scope: "admin", sideEffecting: true },
533
+ // --- email (agent workspace email; SPEC 2026-07-21) — send is outbox-queued;
534
+ // bodies NEVER enter org_events (redacted like messaging). Reads mirror
535
+ // messaging.history (sideEffecting:false METHODS, not READS). ---
536
+ "email.send": { family: "email", scope: "email.write", sideEffecting: true, idempotent: true },
537
+ "email.markRead": { family: "email", scope: "email.write", sideEffecting: true },
538
+ "email.inbox": { family: "email", scope: "email.read", sideEffecting: false },
539
+ "email.message": { family: "email", scope: "email.read", sideEffecting: false },
540
+ "email.thread": { family: "email", scope: "email.read", sideEffecting: false },
541
+ // --- email Inbox app (agent-lane mirror of the human Inbox; program
542
+ // upgrade A) — THIN wrappers over the email domain services, the SAME
543
+ // code the human server actions call, so mailbox entitlements, rules,
544
+ // per-thread holds and the guardian-only verbs (approve/amend/return,
545
+ // redirect) are enforced in the domain layer identically on both lanes.
546
+ // Bodies stay REDACTED on-chain (the email.send precedent); reads
547
+ // mirror email.inbox (sideEffecting:false METHODS, not READS). ---
548
+ "email.mailboxes": { family: "email", scope: "email.read", sideEffecting: false },
549
+ "email.threads": { family: "email", scope: "email.read", sideEffecting: false },
550
+ "email.messages": { family: "email", scope: "email.read", sideEffecting: false },
551
+ "email.drafts": { family: "email", scope: "email.read", sideEffecting: false },
552
+ "email.approvals": { family: "email", scope: "email.read", sideEffecting: false },
553
+ "email.rules": { family: "email", scope: "email.read", sideEffecting: false },
554
+ "email.signatures": { family: "email", scope: "email.read", sideEffecting: false },
555
+ "email.triage": { family: "email", scope: "email.write", sideEffecting: true },
556
+ "email.triageNow": { family: "email", scope: "email.write", sideEffecting: true },
557
+ "email.move": { family: "email", scope: "email.write", sideEffecting: true },
558
+ "email.markAllRead": { family: "email", scope: "email.write", sideEffecting: true, idempotent: true },
559
+ "email.delegate": { family: "email", scope: "email.write", sideEffecting: true },
560
+ "email.holdReplies": { family: "email", scope: "email.write", sideEffecting: true, idempotent: true },
561
+ "email.redirect": { family: "email", scope: "email.write", sideEffecting: true },
562
+ "email.draftSave": { family: "email", scope: "email.write", sideEffecting: true, idempotent: true },
563
+ "email.draftDiscard": { family: "email", scope: "email.write", sideEffecting: true },
564
+ "email.draftSend": { family: "email", scope: "email.write", sideEffecting: true },
565
+ "email.undoSend": { family: "email", scope: "email.write", sideEffecting: true },
566
+ "email.approve": { family: "email", scope: "email.write", sideEffecting: true },
567
+ "email.approveAll": { family: "email", scope: "email.write", sideEffecting: true },
568
+ "email.amend": { family: "email", scope: "email.write", sideEffecting: true },
569
+ "email.return": { family: "email", scope: "email.write", sideEffecting: true },
570
+ "email.ruleSave": { family: "email", scope: "email.write", sideEffecting: true },
571
+ "email.ruleSetEnabled": { family: "email", scope: "email.write", sideEffecting: true, idempotent: true },
572
+ "email.folderRename": { family: "email", scope: "email.write", sideEffecting: true },
573
+ "email.folderDelete": { family: "email", scope: "email.write", sideEffecting: true },
574
+ "email.signatureSave": { family: "email", scope: "email.write", sideEffecting: true },
575
+ "email.signatureDelete": { family: "email", scope: "email.write", sideEffecting: true },
576
+ "email.logDecision": { family: "email", scope: "email.write", sideEffecting: true },
577
+ "email.extractAction": { family: "email", scope: "email.write", sideEffecting: true },
578
+ "email.fileAttachment": { family: "email", scope: "email.write", sideEffecting: true },
579
+ // --- email mobile-program delta (2026-08): spam disposition (blocklist
580
+ // maintained in-domain), message reactions, cross-desk thread move, the
581
+ // stored thread brief (closes inbox-report gap #3), and the grounded
582
+ // mail ask (books.ask posture: read scope, gateway self-metered). ---
583
+ "email.reportSpam": { family: "email", scope: "email.write", sideEffecting: true },
584
+ "email.react": { family: "email", scope: "email.write", sideEffecting: true },
585
+ "email.moveMailbox": { family: "email", scope: "email.write", sideEffecting: true },
586
+ "email.summarise": { family: "email", scope: "email.write", sideEffecting: true },
587
+ "email.ask": { family: "email", scope: "email.read", sideEffecting: false },
588
+ // --- artifact (generative-UI artifact system; GU program 2026-07) — rich
589
+ // interactive cards agents post into conversational feeds, every rendered
590
+ // action equally invocable programmatically (the two-sided law). Writes are
591
+ // sideEffecting:false at the DISPATCHER: the hq artifact service owns its
592
+ // own multi-write (Message + Artifact rows + self-appended chain events +
593
+ // realtime fan-out + approval gating) exactly like branding.* /
594
+ // integration.invokeTool, so it must NOT run inside the dispatcher's
595
+ // single-append transaction. act idempotency is self-managed via an
596
+ // (artifact, actor, action, idempotencyKey) dedupe in the service. Scopes
597
+ // ride the messaging pair — artifacts live in conversation, and default
598
+ // agent tokens already hold them, so deployed SDKs pick the family up on
599
+ // protocol sync with no scope migration. ---
600
+ "artifact.create": { family: "artifact", scope: "messaging.write", sideEffecting: false },
601
+ "artifact.act": { family: "artifact", scope: "messaging.write", sideEffecting: false },
602
+ "artifact.patch": { family: "artifact", scope: "messaging.write", sideEffecting: false },
603
+ "artifact.get": { family: "artifact", scope: "messaging.read", sideEffecting: false },
604
+ "artifact.list": { family: "artifact", scope: "messaging.read", sideEffecting: false },
605
+ "artifact.catalog": { family: "artifact", scope: "messaging.read", sideEffecting: false },
606
+ // --- books (agent Books desk; PB-B1..B4) — THIN wrappers over the Books
607
+ // domain services, the SAME code the human UI calls, so floors, holds
608
+ // and the signature law behave identically on the agent lane. Writes run
609
+ // in the dispatcher transaction and append exactly one chain event via
610
+ // the domain op. The money/signature verbs (payrollSign, billScheduleRun,
611
+ // taxSignLodge; floor/new-payee billApprove releases) additionally
612
+ // require a verified HUMAN signature token inside the domain layer
613
+ // (SIGNATURE_REQUIRED otherwise) — books.write alone cannot move money.
614
+ // books.ask is a grounded ask-the-books via the shared AI gateway, which
615
+ // self-audits its own model call (sideEffecting:false, the branding
616
+ // precedent). Reads mirror email.inbox (sideEffecting:false METHODS).
617
+ // Writes are idempotent-by-state (a replayed transition no-ops) except
618
+ // the explicitly non-idempotent verbs (lineRecode, invoicePromise,
619
+ // statementRun, taxReturn, stpResubmit — each run is a new act). ---
620
+ "books.journalPost": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
621
+ "books.matchAccept": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
622
+ "books.matchHoldResolve": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
623
+ "books.receiptsRegister": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
624
+ "books.lineRecode": { family: "books", scope: "books.write", sideEffecting: true },
625
+ "books.invoiceDraft": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
626
+ "books.invoiceSend": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
627
+ "books.invoiceChaseStep": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
628
+ "books.invoiceRecordPayment": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
629
+ "books.invoiceCreditNote": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
630
+ "books.invoiceVoid": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
631
+ "books.invoicePromise": { family: "books", scope: "books.write", sideEffecting: true },
632
+ "books.statementRun": { family: "books", scope: "books.write", sideEffecting: true },
633
+ "books.billCapture": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
634
+ "books.billApprove": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
635
+ "books.billHold": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
636
+ "books.billReturn": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
637
+ "books.billScheduleRun": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
638
+ "books.expenseClaim": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
639
+ "books.expenseApprove": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
640
+ "books.payrollDraft": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
641
+ "books.payrollSign": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
642
+ "books.payrollRecall": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
643
+ "books.onboardingStart": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
644
+ "books.onboardingAdvance": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
645
+ "books.leaveRequest": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
646
+ "books.leaveApprove": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
647
+ "books.taxPrepare": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
648
+ "books.taxHold": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
649
+ "books.taxReturn": { family: "books", scope: "books.write", sideEffecting: true },
650
+ "books.taxSignLodge": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
651
+ "books.assetDepreciationRun": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
652
+ "books.assetDispose": { family: "books", scope: "books.write", sideEffecting: true, idempotent: true },
653
+ "books.stpResubmit": { family: "books", scope: "books.write", sideEffecting: true },
654
+ "books.accounts": { family: "books", scope: "books.read", sideEffecting: false },
655
+ "books.journals": { family: "books", scope: "books.read", sideEffecting: false },
656
+ "books.feedLines": { family: "books", scope: "books.read", sideEffecting: false },
657
+ "books.invoices": { family: "books", scope: "books.read", sideEffecting: false },
658
+ "books.bills": { family: "books", scope: "books.read", sideEffecting: false },
659
+ "books.taxObligations": { family: "books", scope: "books.read", sideEffecting: false },
660
+ "books.reports": { family: "books", scope: "books.read", sideEffecting: false },
661
+ "books.assets": { family: "books", scope: "books.read", sideEffecting: false },
662
+ "books.ask": { family: "books", scope: "books.read", sideEffecting: false },
663
+ // --- calendar (agent workspace calendar) — binds the same governance
664
+ // DomainCtx as files.* (one governance layer, two families); writes
665
+ // append exactly one chain event via the domain op. findATime is a
666
+ // deterministic free-slot scan — proposing times changes nothing. ---
667
+ "calendar.create": { family: "calendar", scope: "calendar.write", sideEffecting: true, idempotent: true },
668
+ "calendar.update": { family: "calendar", scope: "calendar.write", sideEffecting: true, idempotent: true },
669
+ "calendar.reschedule": { family: "calendar", scope: "calendar.write", sideEffecting: true, idempotent: true },
670
+ "calendar.rsvp": { family: "calendar", scope: "calendar.write", sideEffecting: true, idempotent: true },
671
+ "calendar.delete": { family: "calendar", scope: "calendar.write", sideEffecting: true, idempotent: true },
672
+ "calendar.get": { family: "calendar", scope: "calendar.read", sideEffecting: false },
673
+ "calendar.list": { family: "calendar", scope: "calendar.read", sideEffecting: false },
674
+ "calendar.findATime": { family: "calendar", scope: "calendar.read", sideEffecting: false },
675
+ // Grounded calendar ask (books.ask posture; visibility-floored in-domain).
676
+ "calendar.ask": { family: "calendar", scope: "calendar.read", sideEffecting: false },
677
+ // --- crm (agent CRM desk) — THIN wrappers over the CRM domain services
678
+ // (playbook holds, human-only signatures and ledger appends shared with
679
+ // the UI). signEscalation / returnEscalation REFUSE non-human actors in
680
+ // the domain layer — an agent-plane call fails exactly like the UI would
681
+ // never allow it; setTarget's targetCents is likewise human-only.
682
+ // compilePlaybook is a PURE compile preview (nothing persisted). ---
683
+ "crm.createDeal": { family: "crm", scope: "crm.write", sideEffecting: true },
684
+ "crm.updateDeal": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
685
+ "crm.deleteDeal": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
686
+ "crm.moveStage": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
687
+ "crm.closeDeal": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
688
+ "crm.signEscalation": { family: "crm", scope: "crm.write", sideEffecting: true },
689
+ "crm.returnEscalation": { family: "crm", scope: "crm.write", sideEffecting: true },
690
+ "crm.updateHeldTerms": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
691
+ "crm.delegate": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
692
+ "crm.holdNextStep": { family: "crm", scope: "crm.write", sideEffecting: true },
693
+ "crm.runNextBestAction": { family: "crm", scope: "crm.write", sideEffecting: true },
694
+ "crm.shareDeal": { family: "crm", scope: "crm.write", sideEffecting: true },
695
+ "crm.proposeTerms": { family: "crm", scope: "crm.write", sideEffecting: true },
696
+ "crm.revokeDealShare": { family: "crm", scope: "crm.write", sideEffecting: true },
697
+ "crm.linkFiles": { family: "crm", scope: "crm.write", sideEffecting: true },
698
+ "crm.linkContact": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
699
+ "crm.taskFollowThrough": { family: "crm", scope: "crm.write", sideEffecting: true },
700
+ "crm.logDealDecision": { family: "crm", scope: "crm.write", sideEffecting: true },
701
+ "crm.createAccount": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
702
+ "crm.updateAccount": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
703
+ "crm.archiveAccount": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
704
+ "crm.createContact": { family: "crm", scope: "crm.write", sideEffecting: true },
705
+ "crm.updateContact": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
706
+ "crm.deleteContact": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
707
+ "crm.logInteraction": { family: "crm", scope: "crm.write", sideEffecting: true },
708
+ "crm.addCallGuest": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
709
+ "crm.mintGuestToken": { family: "crm", scope: "crm.write", sideEffecting: true },
710
+ "crm.linkEmail": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
711
+ "crm.pinNote": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
712
+ "crm.redactInteraction": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
713
+ "crm.savePlaybook": { family: "crm", scope: "crm.write", sideEffecting: true },
714
+ "crm.togglePlaybook": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
715
+ "crm.deletePlaybook": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
716
+ "crm.setTarget": { family: "crm", scope: "crm.write", sideEffecting: true, idempotent: true },
717
+ "crm.listDeals": { family: "crm", scope: "crm.read", sideEffecting: false },
718
+ "crm.getDeal": { family: "crm", scope: "crm.read", sideEffecting: false },
719
+ "crm.listEscalations": { family: "crm", scope: "crm.read", sideEffecting: false },
720
+ "crm.listAccounts": { family: "crm", scope: "crm.read", sideEffecting: false },
721
+ "crm.listContacts": { family: "crm", scope: "crm.read", sideEffecting: false },
722
+ "crm.listInteractions": { family: "crm", scope: "crm.read", sideEffecting: false },
723
+ "crm.listPlaybooks": { family: "crm", scope: "crm.read", sideEffecting: false },
724
+ "crm.getTargets": { family: "crm", scope: "crm.read", sideEffecting: false },
725
+ "crm.compilePlaybook": { family: "crm", scope: "crm.read", sideEffecting: false },
726
+ // Grounded pipeline ask (books.ask posture; gateway self-metered).
727
+ "crm.ask": { family: "crm", scope: "crm.read", sideEffecting: false },
728
+ // --- files (agent workspace drive: Docs/Sheets/Decks/folders + binary
729
+ // uploads) — binds the same Workspace DomainCtx the server actions use,
730
+ // so shares/ACLs (VIEW/COMMENT/EDIT) govern agents exactly as humans
731
+ // ("agents obey what you grant"). NB distinct from the UI-parity "file"
732
+ // family (pins/comments on chat attachments) — both coexist. Reads
733
+ // mirror email.inbox (sideEffecting:false METHODS, not READS). ---
734
+ "files.create": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
735
+ "files.uploadRegister": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
736
+ "files.rename": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
737
+ "files.move": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
738
+ "files.duplicate": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
739
+ "files.star": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
740
+ "files.pin": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
741
+ "files.delete": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
742
+ "files.recover": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
743
+ "files.shareGrant": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
744
+ "files.shareRevoke": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
745
+ "files.docWriteRevision": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
746
+ "files.sheetWriteCells": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
747
+ "files.deckWriteSlides": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
748
+ "files.list": { family: "files", scope: "files.read", sideEffecting: false },
749
+ "files.get": { family: "files", scope: "files.read", sideEffecting: false },
750
+ "files.docRead": { family: "files", scope: "files.read", sideEffecting: false },
751
+ "files.sheetRead": { family: "files", scope: "files.read", sideEffecting: false },
752
+ "files.deckRead": { family: "files", scope: "files.read", sideEffecting: false },
753
+ // files.exportRequest is an AUDITED export: it rides the READ scope (export
754
+ // is reading content out) but is sideEffecting — every export lands on the
755
+ // chain (path DIRECT_CONTENT | PRINT_VIEW | SANDBOX_CONVERTER).
756
+ "files.exportRequest": { family: "files", scope: "files.read", sideEffecting: true, idempotent: true },
757
+ // --- files mobile-editors delta (2026-08): multi-tab structure ops (one
758
+ // method per surface, op-discriminated — the email.move precedent),
759
+ // held doc suggestions, the kind-agnostic comment lane, agent-fed
760
+ // sheet ranges, and the grounded drive ask (books.ask posture). ---
761
+ "files.sheetTabOp": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
762
+ "files.docTabOp": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
763
+ "files.docSuggestResolve": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
764
+ "files.docSuggestions": { family: "files", scope: "files.read", sideEffecting: false },
765
+ "files.commentAdd": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
766
+ "files.commentResolve": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
767
+ "files.comments": { family: "files", scope: "files.read", sideEffecting: false },
768
+ "files.sheetFedRange": { family: "files", scope: "files.write", sideEffecting: true, idempotent: true },
769
+ "files.ask": { family: "files", scope: "files.read", sideEffecting: false },
770
+ });
771
+
772
+ /**
773
+ * Is a method refused until the org server is `governance_ready` (decision_rights
774
+ * populated AND signing live)? A partially deployed control plane is worse than
775
+ * none (SPEC §0.4): handoffs and assignment/decomposition must not flow before
776
+ * the decision-rights matrix exists. Pure.
777
+ * @param {string} name @returns {boolean}
778
+ */
779
+ export function isGovernanceGated(name) {
780
+ const def = methodDef(name);
781
+ return !!(def && def.governanceGated);
782
+ }
783
+
784
+ /** Read endpoints (GET) and their required scope. */
785
+ export const READS = Object.freeze({
786
+ "snapshot": "org.read",
787
+ "directory": "org.read",
788
+ "events": "org.read",
789
+ "hierarchy": "org.read",
790
+ "board.ready": "board.read",
791
+ "board.context": "board.read",
792
+ "ops": "org.read",
793
+ // phase 3 reads
794
+ "approval.wait": "approval.request", // bounded long-poll for an approval outcome
795
+ "cost.rollup": "org.read", // org-wide cost rollup (per agent/model/cadence)
796
+ "decision.list": "org.read", // adopted decisions registry
797
+ "policy": "org.read", // agents fetch the current signed policy bundle
798
+ // phase 4 reads
799
+ "contacts.list": "knowledge.read", // org contacts registry (ACL-filtered)
800
+ "meetings.list": "knowledge.read", // org meetings registry (ACL-filtered)
801
+ });
802
+
803
+ /** Error codes (the error contract; HTTP status hints are advisory). */
804
+ export const ERROR_CODES = Object.freeze({
805
+ BAD_REQUEST: { http: 400 },
806
+ UNAUTHORIZED: { http: 401 },
807
+ FORBIDDEN_SCOPE: { http: 403 },
808
+ NOT_FOUND: { http: 404 },
809
+ CONFLICT: { http: 409 }, // e.g. claim race loser
810
+ IDEMPOTENT_REPLAY: { http: 200 }, // returned cached result, not an error per se
811
+ GOVERNANCE_NOT_READY: { http: 423 }, // §0.4 ordering gate
812
+ RATE_LIMITED: { http: 429 },
813
+ WEAK_SECRET: { http: 401 },
814
+ INTERNAL: { http: 500 },
815
+ });
816
+
817
+ /** Directive kinds the presence.beat response may carry (kill-switch + nudges). */
818
+ export const DIRECTIVES = Object.freeze(["halt", "rotate_token", "policy_stale", "resync"]);
819
+
820
+ // ---------------------------------------------------------------------------
821
+ // Helpers (shared, pure)
822
+ // ---------------------------------------------------------------------------
823
+
824
+ /** @param {string} name @returns {object|null} the method descriptor or null. */
825
+ export function methodDef(name) {
826
+ return Object.prototype.hasOwnProperty.call(METHODS, name) ? METHODS[name] : null;
827
+ }
828
+
829
+ /**
830
+ * Resolve the scope a method requires. A method in a reserved namespace ALWAYS
831
+ * requires `admin` (defence-in-depth: a missing/typo'd descriptor cannot
832
+ * accidentally widen access). Unknown methods resolve to `admin` (deny).
833
+ * @param {string} name
834
+ * @returns {string}
835
+ */
836
+ export function scopeForMethod(name) {
837
+ const ns = String(name || "").split(".")[0];
838
+ if (RESERVED_ADMIN_NAMESPACES.includes(ns)) return "admin";
839
+ const def = methodDef(name);
840
+ return def ? def.scope : "admin";
841
+ }
842
+
843
+ /** @param {string} name @returns {boolean} */
844
+ export function isReservedAdminNamespace(name) {
845
+ return RESERVED_ADMIN_NAMESPACES.includes(String(name || "").split(".")[0]);
846
+ }
847
+
848
+ /**
849
+ * Does an actor holding `scopes` satisfy the requirement for `methodName`?
850
+ * `admin` satisfies everything. Pure.
851
+ * @param {string[]} scopes
852
+ * @param {string} methodName
853
+ * @returns {boolean}
854
+ */
855
+ export function actorCan(scopes, methodName) {
856
+ const need = scopeForMethod(methodName);
857
+ if (!Array.isArray(scopes)) return false;
858
+ if (scopes.includes("admin")) return true;
859
+ return scopes.includes(need);
860
+ }
861
+
862
+ /**
863
+ * Canonical JSON for hashing/signing: keys sorted recursively so the same
864
+ * logical value always serializes to the same bytes on both sides.
865
+ * @param {unknown} value
866
+ * @returns {string}
867
+ */
868
+ export function canonicalize(value) {
869
+ return JSON.stringify(sortDeep(value));
870
+ }
871
+
872
+ function sortDeep(v) {
873
+ if (Array.isArray(v)) return v.map(sortDeep);
874
+ if (v && typeof v === "object") {
875
+ const out = {};
876
+ for (const k of Object.keys(v).sort()) out[k] = sortDeep(v[k]);
877
+ return out;
878
+ }
879
+ return v;
880
+ }
881
+
882
+ /**
883
+ * Compute the hash-chain row hash for an event. THE load-bearing function: the
884
+ * server writes `row_hash = computeRowHash(prevHash, row)` per event, and the
885
+ * detective layer recomputes it to prove the log was not rewritten. Any change
886
+ * to this function is a protocol break (bump PROTOCOL_VERSION).
887
+ *
888
+ * @param {string} prevHash hex hash of the previous event ("" for the genesis row)
889
+ * @param {{seq:number, family:string, kind:string, entity_id:string, payload:unknown, at:string, actor:string}} row
890
+ * @returns {string} lowercase hex sha-256
891
+ */
892
+ export function computeRowHash(prevHash, row) {
893
+ const material = canonicalize({
894
+ prev: prevHash || "",
895
+ seq: row.seq,
896
+ family: row.family,
897
+ kind: row.kind,
898
+ entity_id: row.entity_id,
899
+ actor: row.actor || "",
900
+ at: row.at,
901
+ payload: row.payload ?? null,
902
+ });
903
+ return createHash("sha256").update(material).digest("hex");
904
+ }
905
+
906
+ /**
907
+ * Verify a contiguous slice of the event chain links correctly. Returns the
908
+ * first break (index + reason) or null when the slice is internally consistent.
909
+ * `startPrev` is the row_hash of the event BEFORE events[0] ("" if from genesis).
910
+ * @param {Array<object>} events ordered by seq ascending
911
+ * @param {string} [startPrev=""]
912
+ * @returns {{ index:number, reason:string }|null}
913
+ */
914
+ export function verifyChain(events, startPrev = "") {
915
+ let prev = startPrev;
916
+ for (let i = 0; i < events.length; i++) {
917
+ const e = events[i];
918
+ if ((e.prev_hash || "") !== prev) return { index: i, reason: "prev_hash mismatch" };
919
+ const expect = computeRowHash(prev, e);
920
+ if (e.row_hash !== expect) return { index: i, reason: "row_hash mismatch" };
921
+ prev = e.row_hash;
922
+ }
923
+ return null;
924
+ }
925
+
926
+ /**
927
+ * Build a request frame for the RPC binding. Side-effecting methods require an
928
+ * idempotency key (the caller supplies a stable one for safe retries).
929
+ * @param {string} method @param {object} params @param {object} [opts] @param {string} [opts.idempotencyKey]
930
+ * @returns {{method:string, params:object, idempotencyKey?:string}}
931
+ */
932
+ export function buildReqFrame(method, params = {}, opts = {}) {
933
+ const frame = { method, params: params || {} };
934
+ if (opts.idempotencyKey) frame.idempotencyKey = String(opts.idempotencyKey);
935
+ return frame;
936
+ }
937
+
938
+ /**
939
+ * Validate an inbound request frame against the registry. Returns
940
+ * {ok:true, def} or {ok:false, code, message}. Pure — no auth check here
941
+ * (that needs the actor's scopes; see actorCan).
942
+ * @param {any} frame
943
+ * @returns {{ok:true, def:object}|{ok:false, code:string, message:string}}
944
+ */
945
+ export function validateReqFrame(frame) {
946
+ if (!frame || typeof frame !== "object") return { ok: false, code: "BAD_REQUEST", message: "frame not an object" };
947
+ if (typeof frame.method !== "string") return { ok: false, code: "BAD_REQUEST", message: "method missing" };
948
+ const def = methodDef(frame.method);
949
+ if (!def) return { ok: false, code: "NOT_FOUND", message: `unknown method ${frame.method}` };
950
+ if (frame.params != null && typeof frame.params !== "object") return { ok: false, code: "BAD_REQUEST", message: "params must be an object" };
951
+ if (def.sideEffecting && def.auth !== false && def.idempotent && !frame.idempotencyKey) {
952
+ // idempotent side-effecting methods want a key for safe retries; warn-not-fatal
953
+ // (the server may synthesize one), so this is advisory, not a hard reject.
954
+ }
955
+ return { ok: true, def };
956
+ }
957
+
958
+ /** Build a success response frame. */
959
+ export function okFrame(result) {
960
+ return { ok: true, result: result ?? null };
961
+ }
962
+
963
+ /** Build an error response frame. */
964
+ export function errFrame(code, message) {
965
+ const known = ERROR_CODES[code] ? code : "INTERNAL";
966
+ return { ok: false, error: { code: known, message: String(message || known) } };
967
+ }