@geraldmaron/construct 1.4.2 → 1.5.1

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 (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +57 -45
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +273 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +104 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -1,36 +1,35 @@
1
1
  /**
2
- * lib/embed/approval-queue.mjs — high-risk action approval queue.
2
+ * lib/embed/approval-queue.mjs — durable approval queue for MCP tool calls.
3
3
  *
4
- * Actions that match the approval.require list in embed config are held
5
- * here until approved, rejected, or timed out.
4
+ * Per ADR-0056 (LMCP-A6), every approval-required tool call creates a durable
5
+ * record with full identity, state machine, and file-backed persistence.
6
+ * Records survive process restart and can be resolved by any authorized actor
7
+ * with queue access.
6
8
  *
7
- * Persistence: in-memory by default; optionally writes to a JSONL file
8
- * at ~/.cx/approval-queue.jsonl for cross-session visibility.
9
+ * Persistence: JSONL file at `.cx/approvals/queue.jsonl` (team mode) or
10
+ * `~/.cx/approvals/queue.jsonl` (solo mode). Written on every state transition.
9
11
  */
10
12
 
13
+ import crypto from 'node:crypto';
11
14
  import fs from 'node:fs';
12
15
  import path from 'node:path';
13
16
 
14
17
  import { doctorRoot } from '../config/xdg.mjs';
15
18
 
19
+ const APPROVAL_TTL_MS = 3_600_000; // 1 hour default
20
+
16
21
  export class ApprovalQueue {
17
- #items = new Map(); // id → ApprovalItem
22
+ #items = new Map();
18
23
  #persistPath = null;
19
- #requirePatterns = [];
20
- #timeoutMs = 3_600_000;
21
- #fallback = 'reject';
24
+ #timeoutMs = APPROVAL_TTL_MS;
22
25
 
23
26
  /**
24
27
  * @param {object} opts
25
- * @param {string[]} opts.require - Action type patterns that need approval
26
- * @param {number} [opts.timeoutMs] - Auto-expiry after this many ms (default: 1h)
27
- * @param {string} [opts.fallback] - 'reject' | 'proceed' on timeout
28
- * @param {string} [opts.persistPath] - Path to JSONL persistence file
28
+ * @param {string} [opts.persistPath] - Path to JSONL persistence file
29
+ * @param {number} [opts.timeoutMs] - Auto-expiry after this many ms (default: 1h)
29
30
  */
30
- constructor({ require: requirePatterns = [], timeoutMs, fallback, persistPath } = {}) {
31
- this.#requirePatterns = requirePatterns;
31
+ constructor({ persistPath, timeoutMs } = {}) {
32
32
  if (timeoutMs != null) this.#timeoutMs = timeoutMs;
33
- if (fallback) this.#fallback = fallback;
34
33
  if (persistPath) {
35
34
  this.#persistPath = persistPath;
36
35
  this.#loadFromDisk();
@@ -38,113 +37,186 @@ export class ApprovalQueue {
38
37
  }
39
38
 
40
39
  /**
41
- * Check if an action type requires approval.
40
+ * Compute a deterministic hash from tool name and args.
42
41
  */
43
- requiresApproval(actionType) {
44
- return this.#requirePatterns.some((p) => {
45
- if (p.endsWith('*')) return actionType.startsWith(p.slice(0, -1));
46
- return p === actionType;
47
- });
42
+ static hashToolCall(tool, args) {
43
+ const stable = JSON.stringify(sortKeys({ tool, args: args ?? {} }));
44
+ return crypto.createHash('sha256').update(stable).digest('hex');
48
45
  }
49
46
 
50
47
  /**
51
- * Determine approval mode for an action type.
52
- * Returns 'auto' if autonomous execution is safe, 'human' if gating required.
48
+ * Enqueue a tool call for approval. Returns the full approval record.
53
49
  *
54
- * Hybrid model rules (in priority order):
55
- * 1. If requiresApproval() 'human'
56
- * 2. If action.autoApprove === true → 'auto'
57
- * 3. If action type matches low-risk patterns → 'auto'
58
- * 4. Default → 'human' (fail-safe)
59
- */
60
- approvalMode(actionType, { autoApprove = false } = {}) {
61
- if (this.requiresApproval(actionType)) return 'human';
62
- if (autoApprove) return 'auto';
63
- const LOW_RISK = ['webhook.', 'snapshot.', 'observation.', 'search.', 'read.'];
64
- if (LOW_RISK.some(prefix => actionType.startsWith(prefix))) return 'auto';
65
- return 'human';
66
- }
67
-
68
- /**
69
- * Enqueue an action for approval. Returns the item id.
70
- * @param {object} action - { type, provider, payload, requestedBy?, autoApprove? }
50
+ * If an identical tool call (same tool + argsHash) already exists with
51
+ * status `awaiting_approval`, returns the existing record instead of
52
+ * creating a duplicate.
71
53
  *
72
- * If approvalMode resolves to 'auto' the item is immediately auto-approved
73
- * and persisted with status 'auto-approved'. Callers can check the returned
74
- * item's status to decide whether to proceed immediately.
54
+ * @param {object} spec
55
+ * @param {string} spec.tool - Tool name
56
+ * @param {object} spec.args - Tool arguments
57
+ * @param {string} spec.surface - Interaction surface (e.g. 'mcp', 'cli')
58
+ * @param {string} spec.argsHash - Pre-computed hash (optional, computed if absent)
59
+ * @param {object} spec.requestedBy - Actor identity { userId, serviceId, tenantId, sessionId, role }
60
+ * @returns {object} Full approval record
75
61
  */
76
- enqueue(action) {
77
- const id = `appr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
78
- const mode = this.approvalMode(action.type ?? action.action ?? 'unknown', {
79
- autoApprove: action.autoApprove === true,
80
- });
81
- const item = {
82
- id,
83
- type: action.type ?? action.action ?? 'unknown',
84
- provider: action.provider ?? null,
85
- payload: action.payload ?? {},
86
- requestedBy: action.requestedBy ?? 'construct',
87
- approvalMode: mode,
88
- status: mode === 'auto' ? 'auto-approved' : 'pending',
89
- enqueuedAt: new Date().toISOString(),
90
- resolvedAt: mode === 'auto' ? new Date().toISOString() : null,
91
- expiresAt: new Date(Date.now() + this.#timeoutMs).toISOString(),
62
+ enqueue(spec) {
63
+ const { tool, args = {}, surface = 'mcp', requestedBy = {} } = spec;
64
+ const argsHash = spec.argsHash || ApprovalQueue.hashToolCall(tool, args);
65
+
66
+ const existing = this.#findByToolArgs(tool, argsHash);
67
+ if (existing && existing.state === 'awaiting_approval') {
68
+ return existing;
69
+ }
70
+
71
+ const approvalId = `appr-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
72
+ const resumeToken = crypto.randomBytes(32).toString('hex');
73
+ const now = new Date();
74
+
75
+ const record = {
76
+ approvalId,
77
+ toolCall: { tool, args, surface, argsHash },
78
+ requestedAt: now.toISOString(),
79
+ requestedBy: {
80
+ userId: requestedBy.userId ?? null,
81
+ serviceId: requestedBy.serviceId ?? null,
82
+ tenantId: requestedBy.tenantId ?? null,
83
+ sessionId: requestedBy.sessionId ?? null,
84
+ role: requestedBy.role ?? null,
85
+ },
86
+ state: 'awaiting_approval',
87
+ decidedAt: null,
88
+ decidedBy: null,
89
+ reason: null,
90
+ resumeToken,
91
+ expiresAt: new Date(now.getTime() + this.#timeoutMs).toISOString(),
92
92
  };
93
- this.#items.set(id, item);
93
+
94
+ this.#items.set(approvalId, record);
94
95
  this.#persist();
95
- return id;
96
+ return record;
96
97
  }
97
98
 
98
99
  /**
99
- * Approve a pending item. Returns the resolved item.
100
+ * Approve a pending approval. Returns the resolved record.
101
+ * @param {string} approvalId
102
+ * @param {object} [opts]
103
+ * @param {object} [opts.decidedBy] - Actor identity of the decider
104
+ * @param {string} [opts.reason]
100
105
  */
101
- approve(id, { approvedBy = 'human' } = {}) {
102
- return this.#resolve(id, 'approved', { approvedBy });
106
+ approve(approvalId, { decidedBy = {}, reason = null } = {}) {
107
+ return this.#resolve(approvalId, 'approved', { decidedBy, reason });
103
108
  }
104
109
 
105
110
  /**
106
- * Reject a pending item.
111
+ * Deny a pending approval. Returns the resolved record.
112
+ * @param {string} approvalId
113
+ * @param {object} [opts]
114
+ * @param {object} [opts.decidedBy] - Actor identity of the decider
115
+ * @param {string} [opts.reason]
107
116
  */
108
- reject(id, { reason = '' } = {}) {
109
- return this.#resolve(id, 'rejected', { reason });
117
+ deny(approvalId, { decidedBy = {}, reason = null } = {}) {
118
+ return this.#resolve(approvalId, 'denied', { decidedBy, reason });
110
119
  }
111
120
 
112
121
  /**
113
- * Get an item by id.
122
+ * Get a record by approvalId.
114
123
  */
124
+ getById(approvalId) {
125
+ return this.#items.get(approvalId) ?? null;
126
+ }
127
+
128
+ /** @deprecated Use getById() — kept for backward compat with old tests. */
115
129
  get(id) {
116
- return this.#items.get(id) ?? null;
130
+ if (typeof id === 'object' || id === null || id === undefined) return null;
131
+ return this.getById(id);
132
+ }
133
+
134
+ /** @deprecated Kept for backward compat; always returns false (policy decides). */
135
+ requiresApproval() {
136
+ return false;
137
+ }
138
+
139
+ /** @deprecated Use deny() — kept for backward compat with old tests. */
140
+ reject(id, opts = {}) {
141
+ const reason = opts?.reason ?? null;
142
+ return this.deny(id, { decidedBy: opts?.decidedBy ?? {}, reason });
117
143
  }
118
144
 
119
145
  /**
120
- * List all items, optionally filtered by status.
146
+ * Get all records with state `awaiting_approval`.
121
147
  */
122
- list(status = null) {
123
- const items = [...this.#items.values()];
124
- return status ? items.filter((i) => i.status === status) : items;
148
+ getPending() {
149
+ const results = [];
150
+ for (const item of this.#items.values()) {
151
+ if (item.state === 'awaiting_approval') results.push(item);
152
+ }
153
+ return results;
125
154
  }
126
155
 
127
156
  /**
128
- * Expire any pending items past their expiry time.
129
- * Returns the list of expired items.
157
+ * Look up a record by resumeToken.
158
+ */
159
+ getByResumeToken(resumeToken) {
160
+ for (const item of this.#items.values()) {
161
+ if (item.resumeToken === resumeToken) return item;
162
+ }
163
+ return null;
164
+ }
165
+
166
+ /**
167
+ * Find a record by tool + argsHash (for dedup).
168
+ */
169
+ #findByToolArgs(tool, argsHash) {
170
+ for (const item of this.#items.values()) {
171
+ if (item.toolCall?.tool === tool && item.toolCall?.argsHash === argsHash) {
172
+ return item;
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+
178
+ /**
179
+ * Find a record by tool + argsHash (public, for broker lookup).
180
+ */
181
+ findByToolArgs(tool, argsHash) {
182
+ return this.#findByToolArgs(tool, argsHash);
183
+ }
184
+
185
+ /**
186
+ * Expire any awaiting_approval records past their expiry time.
187
+ * Returns the list of expired records.
130
188
  */
131
189
  expireStale() {
132
190
  const now = Date.now();
133
191
  const expired = [];
134
192
  for (const item of this.#items.values()) {
135
- if (item.status === 'pending' && new Date(item.expiresAt).getTime() < now) {
136
- this.#resolve(item.id, this.#fallback === 'proceed' ? 'approved' : 'expired');
193
+ if (item.state === 'awaiting_approval' && new Date(item.expiresAt).getTime() < now) {
194
+ this.#resolve(item.approvalId, 'expired', { reason: 'auto-expired' });
137
195
  expired.push(item);
138
196
  }
139
197
  }
140
198
  return expired;
141
199
  }
142
200
 
143
- #resolve(id, status, extra = {}) {
144
- const item = this.#items.get(id);
145
- if (!item) throw new Error(`Approval item not found: ${id}`);
146
- if (item.status !== 'pending') throw new Error(`Item ${id} is already ${item.status}`);
147
- Object.assign(item, { status, resolvedAt: new Date().toISOString(), ...extra });
201
+ /**
202
+ * List all records, optionally filtered by state.
203
+ */
204
+ list(state = null) {
205
+ const items = [...this.#items.values()];
206
+ return state ? items.filter((i) => i.state === state) : items;
207
+ }
208
+
209
+ #resolve(approvalId, newState, extra = {}) {
210
+ const item = this.#items.get(approvalId);
211
+ if (!item) throw new Error(`Approval record not found: ${approvalId}`);
212
+ if (item.state !== 'awaiting_approval') {
213
+ throw new Error(`Approval ${approvalId} is already ${item.state}`);
214
+ }
215
+ const now = new Date();
216
+ item.state = newState;
217
+ item.decidedAt = now.toISOString();
218
+ if (extra.decidedBy) item.decidedBy = extra.decidedBy;
219
+ if (extra.reason) item.reason = extra.reason;
148
220
  this.#persist();
149
221
  return item;
150
222
  }
@@ -155,7 +227,9 @@ export class ApprovalQueue {
155
227
  fs.mkdirSync(path.dirname(this.#persistPath), { recursive: true });
156
228
  const lines = [...this.#items.values()].map((i) => JSON.stringify(i)).join('\n');
157
229
  fs.writeFileSync(this.#persistPath, lines + '\n', 'utf8');
158
- } catch (err) { process.stderr.write('[approval-queue.mjs] persist: ' + (err?.message ?? String(err)) + '\n'); }
230
+ } catch (err) {
231
+ process.stderr.write('[approval-queue.mjs] persist: ' + (err?.message ?? String(err)) + '\n');
232
+ }
159
233
  }
160
234
 
161
235
  #loadFromDisk() {
@@ -165,13 +239,35 @@ export class ApprovalQueue {
165
239
  for (const line of lines) {
166
240
  try {
167
241
  const item = JSON.parse(line);
168
- this.#items.set(item.id, item);
242
+ this.#items.set(item.approvalId || item.id, item);
169
243
  } catch { /* skip malformed */ }
170
244
  }
171
- } catch (err) { process.stderr.write('[approval-queue.mjs] load-from-disk: ' + (err?.message ?? String(err)) + '\n'); }
245
+ } catch (err) {
246
+ process.stderr.write('[approval-queue.mjs] load-from-disk: ' + (err?.message ?? String(err)) + '\n');
247
+ }
172
248
  }
173
249
 
174
- static defaultPersistPath() {
175
- return path.join(doctorRoot(), 'approval-queue.jsonl');
250
+ /**
251
+ * Compute the default persistence path.
252
+ * In team mode: `.cx/approvals/queue.jsonl` under the project root.
253
+ * In solo mode: `~/.cx/approvals/queue.jsonl` (via doctorRoot).
254
+ */
255
+ static resolvePersistPath(rootDir, deploymentMode) {
256
+ if (deploymentMode === 'team' || deploymentMode === 'enterprise') {
257
+ return path.join(rootDir, '.cx', 'approvals', 'queue.jsonl');
258
+ }
259
+ return path.join(doctorRoot(), 'approvals', 'queue.jsonl');
176
260
  }
177
261
  }
262
+
263
+ /**
264
+ * Recursively sort the keys of an object for deterministic JSON serialization.
265
+ */
266
+ function sortKeys(obj) {
267
+ if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) return obj;
268
+ const sorted = {};
269
+ for (const key of Object.keys(obj).sort()) {
270
+ sorted[key] = sortKeys(obj[key]);
271
+ }
272
+ return sorted;
273
+ }
@@ -20,6 +20,18 @@
20
20
  * const result = await guard.check('externalPost', { description: 'Post roadmap to Slack' });
21
21
  * if (result.allowed) { ... execute ... }
22
22
  * else { ... it's been queued or denied ... }
23
+ *
24
+ * Per-specialist binding enforcement (LMCP-E4): the operating-profile
25
+ * authority level answers "is this action *class* ever permitted" but has no
26
+ * per-specialist dimension — nothing stopped an embedded specialist from
27
+ * reading any provider or proposing any external write. When the caller
28
+ * passes `meta.proposal = { specialistId, providerId, writeKind }`, `check()`
29
+ * consults the specialist's `embedBindings` grant (lib/packs/manifest-schema.mjs)
30
+ * *before* the authority-level/queue logic runs: a `<providerId>.<writeKind>`
31
+ * token not present in that specialist's `proposals[]` is denied outright,
32
+ * never queued. Callers that omit `meta.proposal` are unaffected — this is
33
+ * additive, backward-compatible enforcement layered in front of the existing
34
+ * authority-level check.
23
35
  */
24
36
 
25
37
  // ─── Action-type → authority-key mapping ────────────────────────────────────
@@ -67,24 +79,75 @@ function resolveAuthorityKey(actionType) {
67
79
  export class AuthorityGuard {
68
80
  #authority;
69
81
  #approvalQueue;
82
+ #embedBindings;
70
83
 
71
84
  /**
72
85
  * @param {object} operatingProfile - The embed config's operatingProfile object
73
86
  * @param {ApprovalQueue} approvalQueue
87
+ * @param {object} [embedBindings] - Map of specialistId → { providers[], proposals[] }
88
+ * (lib/packs/manifest-schema.mjs embedBindings shape).
89
+ * Consulted only when a check's meta.proposal names
90
+ * a specialistId; omit for non-embed callers.
74
91
  */
75
- constructor(operatingProfile, approvalQueue) {
92
+ constructor(operatingProfile, approvalQueue, embedBindings = {}) {
76
93
  this.#authority = operatingProfile?.authority ?? {};
77
94
  this.#approvalQueue = approvalQueue;
95
+ this.#embedBindings = embedBindings ?? {};
96
+ }
97
+
98
+ /**
99
+ * Check a proposed embed action against the specialist's binding grant.
100
+ * Returns `null` when no binding-scoped check applies (no proposal meta,
101
+ * or no bindings configured) so the caller falls through to the ordinary
102
+ * authority-level check unaffected.
103
+ *
104
+ * @param {{specialistId: string, providerId: string, writeKind: string}} proposal
105
+ * @returns {{ allowed: boolean, mode: string, reason: string }|null}
106
+ */
107
+ #checkProposalBinding(proposal) {
108
+ if (!proposal || !proposal.specialistId) return null;
109
+
110
+ const { specialistId, providerId, writeKind } = proposal;
111
+ const binding = this.#embedBindings[specialistId];
112
+
113
+ if (!binding) {
114
+ return {
115
+ allowed: false,
116
+ mode: 'denied',
117
+ reason: `specialist "${specialistId}" has no embedBindings grant — no providers or proposals are authorized`,
118
+ };
119
+ }
120
+
121
+ const token = writeKind ? `${providerId}.${writeKind}` : providerId;
122
+ const grantedProposals = new Set(binding.proposals || []);
123
+
124
+ if (!grantedProposals.has(token)) {
125
+ return {
126
+ allowed: false,
127
+ mode: 'denied',
128
+ reason: `specialist "${specialistId}" is not granted to propose "${token}" (embedBindings.proposals: ${[...grantedProposals].join(', ') || '(none)'})`,
129
+ };
130
+ }
131
+
132
+ return null;
78
133
  }
79
134
 
80
135
  /**
81
136
  * Check whether an action is allowed under the current authority profile.
82
137
  *
83
138
  * @param {string} actionType - e.g. 'externalPost', 'artifact:prd', 'issue:create'
84
- * @param {object} [meta] - Optional metadata: { description, payload }
139
+ * @param {object} [meta] - Optional metadata: { description, payload, proposal }
140
+ * @param {{specialistId: string, providerId: string, writeKind: string}} [meta.proposal]
141
+ * Embed-originated proposal descriptor (LMCP-E4). When present, the
142
+ * proposal is checked against the specialist's embedBindings grant
143
+ * before any authority-level/queue logic runs; a proposal outside
144
+ * the grant is denied and never queued.
85
145
  * @returns {{ allowed: boolean, mode: string, queueId?: string, reason?: string }}
86
146
  */
87
147
  async check(actionType, meta = {}) {
148
+ const bindingDenial = this.#checkProposalBinding(meta.proposal);
149
+ if (bindingDenial) return bindingDenial;
150
+
88
151
  const key = resolveAuthorityKey(actionType);
89
152
  const level = this.#authority[key] ?? 'approval-queued'; // fail-safe default
90
153