@geraldmaron/construct 1.4.1 → 1.5.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 (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -0,0 +1,42 @@
1
+ /**
2
+ * lib/policy/audit-gate.mjs — mandatory-audit enforcement for enterprise mode (LMCP-H5).
3
+ *
4
+ * ADR-0057 (A7): enterprise mode promises every mutating action is audited.
5
+ * A broker/policy/write path that silently swallows an audit-sink failure
6
+ * (as lib/mcp/broker.mjs's best-effort catch does today) makes that promise
7
+ * false under exactly the condition it exists to cover. enforceMandatoryAudit
8
+ * is a policy-decision precondition: in enterprise mode, when the audit sink
9
+ * (lib/audit-trail.mjs#checkAuditSinkAvailable) is down, every action is
10
+ * denied before any role/manifest logic runs — fail closed, no
11
+ * CONSTRUCT_SKIP_* escape hatch. Solo and team modes never call the sink
12
+ * check and are unaffected.
13
+ */
14
+
15
+ import { checkAuditSinkAvailable } from '../audit-trail.mjs';
16
+
17
+ export const AUDIT_GATE_SOURCE = 'mandatory-audit-gate';
18
+
19
+ /**
20
+ * Enterprise-only precondition: deny everything if the audit sink is down.
21
+ * Returns null (no opinion) for solo/team or when the sink is healthy, so
22
+ * callers can compose this before their own decision logic without changing
23
+ * behavior outside enterprise mode.
24
+ *
25
+ * @param {object} input
26
+ * @param {'solo'|'team'|'enterprise'} input.deploymentMode
27
+ * @param {function} [input.checkSink] - injectable for tests; defaults to the real fs probe
28
+ * @returns {{allowed: boolean, reason: string, approvalRequired: boolean, source: string}|null}
29
+ */
30
+ export function enforceMandatoryAudit({ deploymentMode, checkSink = checkAuditSinkAvailable } = {}) {
31
+ if (deploymentMode !== 'enterprise') return null;
32
+
33
+ const sink = checkSink();
34
+ if (sink.available) return null;
35
+
36
+ return {
37
+ allowed: false,
38
+ reason: `enterprise mode requires a healthy audit sink; audit sink unavailable (${sink.reason || 'unknown-error'}) — action refused fail-closed`,
39
+ approvalRequired: false,
40
+ source: AUDIT_GATE_SOURCE,
41
+ };
42
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * lib/policy/consumption-budget.mjs — durable per-actor/run consumption budgets (LMCP-N5).
3
+ *
4
+ * Nothing durably bounded token/tool-call/external-write/queue-submission
5
+ * consumption per actor or run before this: lib/mcp/broker.mjs's BrokerStore
6
+ * (LMCP-I1) covers only a fixed-window tool-call rate per actor+tool, reset
7
+ * on every window rather than accumulating for the life of a run. This store
8
+ * persists cumulative consumption per actor+run to
9
+ * `<rootDir>/.cx/consumption-budgets.json`, survives process restarts the
10
+ * same way BrokerStore does, and is consulted pre-execution so an exhausted
11
+ * run halts with a clear, durable budget-denied record instead of a
12
+ * surprise bill.
13
+ *
14
+ * Budgets default per deployment mode (lib/deployment-mode.mjs): solo is
15
+ * `null` (unbounded) — a single local operator should never hit a wall a
16
+ * team/enterprise multi-actor deployment needs; team/enterprise apply real
17
+ * per-kind caps. An explicit `budget` object always overrides the mode
18
+ * default.
19
+ */
20
+
21
+ import fs from 'node:fs';
22
+ import path from 'node:path';
23
+
24
+ import { getDeploymentMode } from '../deployment-mode.mjs';
25
+
26
+ const BUDGET_STORE_SUBPATH = path.join('.cx', 'consumption-budgets.json');
27
+
28
+ export const CONSUMPTION_KINDS = ['tokens', 'toolCalls', 'externalWrites', 'queueSubmissions'];
29
+
30
+ export const DEFAULT_BUDGETS_BY_MODE = Object.freeze({
31
+ solo: null,
32
+ team: Object.freeze({ tokens: 2_000_000, toolCalls: 2000, externalWrites: 200, queueSubmissions: 500 }),
33
+ enterprise: Object.freeze({ tokens: 5_000_000, toolCalls: 5000, externalWrites: 500, queueSubmissions: 1000 }),
34
+ });
35
+
36
+ export function resolveDefaultBudget(mode) {
37
+ // `??` treats solo's deliberate `null` (unbounded) as absent and would fall
38
+ // through to the team default, defeating the solo-is-unbounded default —
39
+ // an explicit `in` check is required so null is a real, honored value.
40
+ return mode in DEFAULT_BUDGETS_BY_MODE ? DEFAULT_BUDGETS_BY_MODE[mode] : DEFAULT_BUDGETS_BY_MODE.team;
41
+ }
42
+
43
+ export function budgetStorePath(rootDir) {
44
+ return path.join(rootDir, BUDGET_STORE_SUBPATH);
45
+ }
46
+
47
+ function emptyConsumption() {
48
+ return { tokens: 0, toolCalls: 0, externalWrites: 0, queueSubmissions: 0 };
49
+ }
50
+
51
+ function readStoreFile(file) {
52
+ try {
53
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
54
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
55
+ } catch {
56
+ return {};
57
+ }
58
+ }
59
+
60
+ export class BudgetExceeded extends Error {
61
+ constructor({ actor, runId, kind, cap, projected }) {
62
+ super(`consumption budget exceeded: actor '${actor}' run '${runId}' would reach ${projected} ${kind} against a budget of ${cap}`);
63
+ this.name = 'BudgetExceeded';
64
+ this.actor = actor;
65
+ this.runId = runId;
66
+ this.kind = kind;
67
+ this.cap = cap;
68
+ this.projected = projected;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * ConsumptionBudgetStore — file-backed durable per-actor/run consumption.
74
+ *
75
+ * Read/write mirrors lib/mcp/broker.mjs's BrokerStore: in-memory `_data` is
76
+ * the write buffer, `save()` persists the whole map, and every disk
77
+ * operation is best-effort so a disk failure never breaks the caller.
78
+ */
79
+ export class ConsumptionBudgetStore {
80
+ constructor({ rootDir, file, env = process.env } = {}) {
81
+ if (!rootDir && !file) throw new Error('ConsumptionBudgetStore: rootDir or file is required');
82
+ this.rootDir = rootDir ?? null;
83
+ this.env = env;
84
+ this._storePath = file || budgetStorePath(rootDir);
85
+ this._data = readStoreFile(this._storePath);
86
+ }
87
+
88
+ _key(actor, runId) {
89
+ return `${actor}::${runId}`;
90
+ }
91
+
92
+ _save() {
93
+ try {
94
+ fs.mkdirSync(path.dirname(this._storePath), { recursive: true });
95
+ fs.writeFileSync(this._storePath, JSON.stringify(this._data), 'utf8');
96
+ } catch { /* best-effort, mirrors BrokerStore.save */ }
97
+ }
98
+
99
+ /** Resolves the effective budget: an explicit override, else the deployment-mode default. */
100
+ resolveBudget(budgetOverride) {
101
+ if (budgetOverride !== undefined) return budgetOverride;
102
+ const mode = getDeploymentMode(this.env, { cwd: this.rootDir ?? process.cwd() });
103
+ return resolveDefaultBudget(mode);
104
+ }
105
+
106
+ /** Cumulative consumption recorded so far for actor+run, zeroed if none. */
107
+ consumptionFor(actor, runId) {
108
+ return { ...emptyConsumption(), ...(this._data[this._key(actor, runId)] || {}) };
109
+ }
110
+
111
+ /**
112
+ * check(actor, runId, kind, amount, { budget }) — would recording `amount`
113
+ * more of `kind` exceed the budget? Read-only, no state mutated.
114
+ */
115
+ check(actor, runId, kind, amount = 1, { budget } = {}) {
116
+ const resolved = this.resolveBudget(budget);
117
+ const consumption = this.consumptionFor(actor, runId);
118
+ if (!resolved) return { allowed: true, cap: null, projected: consumption[kind] + amount, consumption };
119
+ const cap = resolved[kind];
120
+ if (!Number.isFinite(cap)) return { allowed: true, cap: null, projected: consumption[kind] + amount, consumption };
121
+ const projected = consumption[kind] + amount;
122
+ return { allowed: projected <= cap, cap, projected, consumption };
123
+ }
124
+
125
+ /** Durably record `amount` more of `kind` for actor+run. Returns the updated row. */
126
+ record(actor, runId, kind, amount = 1) {
127
+ const key = this._key(actor, runId);
128
+ const current = { ...emptyConsumption(), ...(this._data[key] || {}) };
129
+ current[kind] = (current[kind] || 0) + amount;
130
+ this._data[key] = current;
131
+ this._save();
132
+ return current;
133
+ }
134
+
135
+ /** Every actor+run row with its resolved budget, for status surfacing. */
136
+ allEntries() {
137
+ return Object.entries(this._data).map(([key, consumption]) => {
138
+ const sep = key.indexOf('::');
139
+ const actor = sep === -1 ? key : key.slice(0, sep);
140
+ const runId = sep === -1 ? null : key.slice(sep + 2);
141
+ return {
142
+ actor,
143
+ runId,
144
+ consumption: { ...emptyConsumption(), ...consumption },
145
+ budget: this.resolveBudget(undefined),
146
+ };
147
+ });
148
+ }
149
+ }
@@ -8,16 +8,24 @@
8
8
  * engine returns allow-without-approval everywhere.
9
9
  *
10
10
  * Decision precedence:
11
- * 1. Team-level decisions gate specialist decisions forbidden by team blocks the action.
12
- * 2. Explicit deny in the role's fence denied + typed reason.
13
- * 3. Action falls in the role's approvalRequired list → allowed but
11
+ * 0. Mandatory-audit gate (LMCP-H5)enterprise only: if the audit sink is
12
+ * down, every action is denied fail-closed before any other check runs.
13
+ * 1. Identity-anchored role authority (LMCP-I6) in team/enterprise, an
14
+ * env-claimed role not among the identity's registered grants is
15
+ * denied before any manifest is consulted.
16
+ * 2. Team-level decisions gate specialist decisions — forbidden by team blocks the action.
17
+ * 3. Explicit deny in the role's fence → denied + typed reason.
18
+ * 4. Action falls in the role's approvalRequired list → allowed but
14
19
  * approvalRequired = true (UI must collect human consent).
15
- * 4. risk === 'high' and role is not in HIGH_RISK_AUTONOMOUS → approval required.
16
- * 5. Otherwiseallowed.
20
+ * 5. risk === 'high' and role is not in HIGH_RISK_AUTONOMOUS → approval required.
21
+ * 6. Deny-by-default: team deny unless explicit grant; enterprise → deny always.
22
+ * 7. solo → allowed.
17
23
  */
18
24
 
19
25
  import { readFileSync, existsSync } from 'node:fs';
20
26
  import { loadRegistry } from '../registry/loader.mjs';
27
+ import { authorizeRoleClaim } from './role-authority.mjs';
28
+ import { enforceMandatoryAudit } from './audit-gate.mjs';
21
29
 
22
30
  const HIGH_RISK_AUTONOMOUS = new Set(['security', 'sre', 'release-manager']);
23
31
 
@@ -97,6 +105,25 @@ function manifestFor(role, manifests) {
97
105
  return manifests?.personas?.[role] || null;
98
106
  }
99
107
 
108
+ /**
109
+ * Return the default decision for unclassified (no matching explicit rule) tool calls.
110
+ *
111
+ * - solo → 'allow' (unchanged permissive behaviour)
112
+ * - team → 'deny-unclassified' (deny unless an explicit grant exists or the
113
+ * user escapes with an approval flow)
114
+ * - enterprise → 'deny' (hard deny regardless of approval escape)
115
+ *
116
+ * @param {'solo'|'team'|'enterprise'} deploymentMode
117
+ * @returns {'allow'|'deny-unclassified'|'deny'}
118
+ */
119
+ export function getDefaultDecision(deploymentMode) {
120
+ switch (deploymentMode) {
121
+ case 'enterprise': return 'deny';
122
+ case 'team': return 'deny-unclassified';
123
+ default: return 'allow'; // solo and unknown → permissive
124
+ }
125
+ }
126
+
100
127
  function actionMatches(pattern, action) {
101
128
  if (!pattern || !action) return false;
102
129
  if (pattern === action) return true;
@@ -125,15 +152,41 @@ function needsApprovalFromManifest(action, manifest) {
125
152
  * @param {string} input.action
126
153
  * @param {string} [input.decision] — optional decision id for team-level gate
127
154
  * @param {string} [input.risk]
155
+ * @param {'solo'|'team'|'enterprise'} [input.deploymentMode='solo'] — controls deny-by-default behaviour
156
+ * @param {object} [input.identity] — resolved actor/service identity (lib/identity.mjs). In
157
+ * team/enterprise, `input.role` is validated as a claim against this identity's registered
158
+ * grants (see lib/policy/role-authority.mjs) before any manifest lookup.
128
159
  * @param {object} [opts]
160
+ * @param {function} [opts.checkSink] — injectable audit-sink probe for LMCP-H5 tests;
161
+ * defaults to the real fs-backed checkAuditSinkAvailable.
129
162
  * @returns {{allowed: boolean, reason: string, approvalRequired: boolean, source: string}}
130
163
  */
131
164
  export function policyDecision(input = {}, opts = {}) {
132
- const { role, tool, action, decision, risk = 'low' } = input;
165
+ const { role, tool, action, decision, risk = 'low', deploymentMode = 'solo', identity } = input;
133
166
  if (!role) return { allowed: false, reason: 'role is required', approvalRequired: false, source: 'engine' };
134
167
  if (!tool) return { allowed: false, reason: 'tool is required', approvalRequired: false, source: 'engine' };
135
168
  if (!action) return { allowed: false, reason: 'action is required', approvalRequired: false, source: 'engine' };
136
169
 
170
+ const auditGateDecision = enforceMandatoryAudit({
171
+ deploymentMode,
172
+ ...(opts.checkSink ? { checkSink: opts.checkSink } : {}),
173
+ });
174
+ if (auditGateDecision) return auditGateDecision;
175
+
176
+ if ((deploymentMode === 'team' || deploymentMode === 'enterprise') && identity) {
177
+ if (identity.source === 'implicit-solo') {
178
+ return { allowed: false, reason: `Identity not resolved for ${deploymentMode} mode`, approvalRequired: false, source: 'identity-boundary' };
179
+ }
180
+
181
+ // Identity-anchored authority (LMCP-I6): the role claim under evaluation
182
+ // must be among the identity's registered grants — an env-declared role
183
+ // with no matching grant is denied before any manifest lookup runs.
184
+ const authority = authorizeRoleClaim({ identity, role, deploymentMode });
185
+ if (!authority.authorized) {
186
+ return { allowed: false, reason: authority.reason, approvalRequired: false, source: authority.source };
187
+ }
188
+ }
189
+
137
190
  const manifests = opts.manifests || loadRoleManifests(opts.manifestPath);
138
191
  const manifest = manifestFor(role, manifests);
139
192
 
@@ -186,6 +239,28 @@ export function policyDecision(input = {}, opts = {}) {
186
239
  };
187
240
  }
188
241
 
242
+ // Deny-by-default for team and enterprise modes.
243
+ // No explicit rule granted this action — consult the deployment-mode default.
244
+ const defaultDecision = getDefaultDecision(deploymentMode);
245
+ if (defaultDecision === 'deny') {
246
+ return {
247
+ allowed: false,
248
+ reason: 'deny-by-default',
249
+ approvalRequired: false,
250
+ mode: deploymentMode,
251
+ source: 'deny-by-default',
252
+ };
253
+ }
254
+ if (defaultDecision === 'deny-unclassified') {
255
+ return {
256
+ allowed: false,
257
+ reason: 'deny-by-default',
258
+ approvalRequired: false,
259
+ mode: deploymentMode,
260
+ source: 'deny-by-default',
261
+ };
262
+ }
263
+
189
264
  return {
190
265
  allowed: true,
191
266
  reason: `action "${action}" permitted for role "${role}"`,
@@ -0,0 +1,89 @@
1
+ /**
2
+ * lib/policy/role-authority.mjs — identity-anchored role claim authority.
3
+ *
4
+ * A6/ADR-0056 requires policy decisions to bind to actor/service identity
5
+ * (H2, lib/identity.mjs), not to a self-declared env var. Prior to this
6
+ * module, `CONSTRUCT_ROLE` alone selected the role manifest a caller was
7
+ * evaluated against — a worker could export the env var and claim any
8
+ * role's authority, including `security` or `release-manager`, with no
9
+ * check against who the identity actually is.
10
+ *
11
+ * `authorizeRoleClaim` closes that gap: it takes the resolved identity
12
+ * (from resolveIdentity()) and the role claim under evaluation, and
13
+ * decides whether the identity is entitled to act as that role.
14
+ *
15
+ * Registered-grants source: `identity.grants` — a list of role ids the
16
+ * identity is registered for. This is the seam LMCP-G5 (worker
17
+ * registration/heartbeat) and pack `toolGrantsRequested` (LMCP-E1) populate;
18
+ * neither exists as a live registration store yet, so until G5 lands the
19
+ * only way an identity acquires a grant is an explicit `identity.grants`
20
+ * array set by the caller that resolved the identity (e.g. from a signed
21
+ * header, a worker registration record, or test fixture). No implicit
22
+ * grants are invented here — an identity with no `grants` array has none.
23
+ *
24
+ * Mode behavior:
25
+ * - solo: always authorized. CONSTRUCT_ROLE remains a documented
26
+ * solo-only convenience (lib/identity.mjs env fallback); there is no
27
+ * registration authority to check against in single-user mode.
28
+ * - team / enterprise: the claimed role must be present in the
29
+ * identity's `grants`. A role claim sourced from `env-fallback` (an
30
+ * env var with no actor identity headers at all) is never itself
31
+ * trusted as a grant — it is a claim, and claims are checked, not
32
+ * trusted. Missing or non-matching grants are DENIED (fail closed).
33
+ */
34
+
35
+ export class RoleAuthorityError extends Error {
36
+ constructor(message) { super(message); this.name = 'RoleAuthorityError'; }
37
+ }
38
+
39
+ /**
40
+ * Return the set of role ids an identity is registered for.
41
+ *
42
+ * `identity.grants` is the only source read today (see file header for
43
+ * why). Absent/malformed values resolve to an empty set — never to "all
44
+ * roles" — so a caller who forgets to populate grants fails closed rather
45
+ * than silently trusting the claim.
46
+ *
47
+ * @param {object} identity
48
+ * @returns {Set<string>}
49
+ */
50
+ export function resolveIdentityGrants(identity) {
51
+ const grants = identity?.grants;
52
+ if (!Array.isArray(grants)) return new Set();
53
+ return new Set(grants.filter((g) => typeof g === 'string' && g.length > 0));
54
+ }
55
+
56
+ /**
57
+ * Decide whether `role` is an authorized claim for `identity` under
58
+ * `deploymentMode`.
59
+ *
60
+ * @param {object} input
61
+ * @param {object} input.identity - resolved identity (lib/identity.mjs shape)
62
+ * @param {string} input.role - the role claim under evaluation
63
+ * @param {'solo'|'team'|'enterprise'} [input.deploymentMode='solo']
64
+ * @returns {{authorized: boolean, reason: string, source: string}}
65
+ */
66
+ export function authorizeRoleClaim({ identity, role, deploymentMode = 'solo' } = {}) {
67
+ if (!role) {
68
+ return { authorized: false, reason: 'role claim is required', source: 'role-authority' };
69
+ }
70
+
71
+ if (deploymentMode !== 'team' && deploymentMode !== 'enterprise') {
72
+ return { authorized: true, reason: 'solo mode: env-claimed role permitted (documented convenience)', source: 'solo-ergonomics' };
73
+ }
74
+
75
+ if (!identity) {
76
+ return { authorized: false, reason: `no identity resolved to validate role claim "${role}" in ${deploymentMode} mode`, source: 'role-authority' };
77
+ }
78
+
79
+ const grants = resolveIdentityGrants(identity);
80
+ if (grants.has(role)) {
81
+ return { authorized: true, reason: `role "${role}" is a registered grant for this identity`, source: 'identity-grant' };
82
+ }
83
+
84
+ return {
85
+ authorized: false,
86
+ reason: `role claim "${role}" is not in this identity's registered grants in ${deploymentMode} mode`,
87
+ source: 'role-authority.ungranted',
88
+ };
89
+ }
@@ -39,7 +39,7 @@ import { estimateTokens, estimatePromptTokens, estimateTokensSync } from './toke
39
39
  import { cxDir } from './paths.mjs';
40
40
  import { getStrategyDigestSync } from './strategy-store.mjs';
41
41
  import { loadRegistry } from './registry/loader.mjs';
42
- import { bindingForSpecialist, resolveRoleOverlayId } from './roles/flavor-bindings.mjs';
42
+ import { bindingForSpecialist, bindingsForSpecialistId, resolveRoleOverlayId } from './roles/flavor-bindings.mjs';
43
43
 
44
44
  const MAX_OBSERVATIONS = 3;
45
45
 
@@ -254,12 +254,23 @@ export function composePrompt(agentName, {
254
254
  }
255
255
 
256
256
  const shortName = String(agentName).replace(/^cx-/, '');
257
- const flavorMapping = bindingForSpecialist(shortName);
258
257
 
259
258
  // When workspaceType is provided and roleFlavors doesn't already specify this agent's flavor,
260
259
  // auto-select the matching overlay so agents get domain guidance without requiring explicit caller config.
261
260
  const effectiveRoleFlavors = resolveRoleFlavors(roleFlavors, workspaceType, shortName);
262
261
 
262
+ // Since construct-rf26.11, one specialist can carry several flavor bindings
263
+ // (e.g. cx-engineer owns engineer/ai-engineer/platform-engineer/data-engineer
264
+ // overlays). Prefer whichever candidate binding's classifierKey the caller
265
+ // actually named in roleFlavors over the specialist's own bare-name binding,
266
+ // so a flavor-specific request (roleFlavors.platformEngineer) still resolves
267
+ // even though bindingForSpecialist(shortName) alone would only ever return
268
+ // the base 'engineer' entry.
269
+ const candidates = bindingsForSpecialistId(agentName);
270
+ const flavorMapping = (effectiveRoleFlavors
271
+ && candidates.find((b) => effectiveRoleFlavors[b.classifierKey]))
272
+ || bindingForSpecialist(shortName);
273
+
263
274
  if (flavorMapping && effectiveRoleFlavors) {
264
275
  const flavor = effectiveRoleFlavors[flavorMapping.classifierKey];
265
276
  if (flavor) {
@@ -276,7 +287,12 @@ export function composePrompt(agentName, {
276
287
  type: 'role-flavor',
277
288
  priority: PRIORITY['role-flavor'],
278
289
  label: roleId,
279
- content: `### ${flavor === 'core' ? shortName : flavor} domain guidance\n\n${overlayContent}`,
290
+ // flavorMapping.rolePrefix (not the agent's own shortName) names the
291
+ // domain guidance: since construct-rf26.11 one specialist can carry
292
+ // several flavor bindings (e.g. cx-engineer), and shortName only
293
+ // ever reflects the specialist's own bare name, not which of its
294
+ // several possible overlays actually matched.
295
+ content: `### ${flavor === 'core' ? flavorMapping.rolePrefix : flavor} domain guidance\n\n${overlayContent}`,
280
296
  tokenBudget: selectedProfile.roleFlavorTokens,
281
297
  });
282
298
  }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * lib/providers/contract/adapters/confluence/governed-write.mjs —
3
+ * envelope-shaped Confluence write adapter.
4
+ *
5
+ * Wraps a Confluence REST transport in the provider shape
6
+ * lib/writes/envelope.mjs expects: write(config, payload), meta.id,
7
+ * search(). This is the only exposed entry point for Confluence writes —
8
+ * specialists and CLI callers route through writeWithEnvelope(), never
9
+ * through a raw Confluence client, so dedup, retry, dry-run, approval, and
10
+ * audit stay centralized in the envelope (LMCP-J2) rather than being
11
+ * reimplemented per-adapter.
12
+ *
13
+ * Adds the three behaviors a generic envelope cannot provide:
14
+ *
15
+ * - Duplicate detection by title+space: before create, searches the
16
+ * target space for an existing page with the same title (via the
17
+ * injected `confluenceTransport.searchPagesByTitle`). A match returns a
18
+ * `page-duplicate` result carrying a `linkback` to the existing page
19
+ * instead of creating a second page — mirrors the GitHub adapter's
20
+ * marker-search dedup (LMCP-J-github) but keyed on title+space rather
21
+ * than a hidden marker, since Confluence page search has no analogue to
22
+ * GitHub's full-text issue search over a hidden comment.
23
+ * - Version-conflict recovery on update: Confluence page updates require
24
+ * the current `version.number`; if the page changed since the caller
25
+ * last fetched it, the API returns 409. On 409 this adapter refetches
26
+ * the live page, re-renders the caller's update against the fresh
27
+ * version, and returns a `version-conflict` result carrying the fresh
28
+ * page and a `retryPayload` — the caller (or a human via the envelope's
29
+ * approval path) re-approves before the adapter retries the write. The
30
+ * adapter never blindly overwrites a page that changed underneath it.
31
+ * - Linkback comment: after a successful create or update, posts a footer
32
+ * comment on the page recording the write's idempotency key, so anyone
33
+ * reading the page in Confluence can trace which governed write
34
+ * produced or last touched it.
35
+ */
36
+
37
+ import { AuthError } from '../../errors.mjs';
38
+
39
+ const LINKBACK_PREFIX = 'construct:write:';
40
+
41
+ function linkbackComment(idempotencyKey) {
42
+ return `<p>Published via Construct governed write (${LINKBACK_PREFIX}${idempotencyKey}).</p>`;
43
+ }
44
+
45
+ /**
46
+ * @param {object} opts
47
+ * @param {object} opts.confluenceTransport - underlying Confluence REST transport; must implement
48
+ * `searchPagesByTitle(spaceId, title)`, `getPage(pageId)`, `createPage(fields)`,
49
+ * `updatePage(fields)`, `createFooterComment(pageId, body)`.
50
+ */
51
+ export function createGovernedConfluenceProvider({ confluenceTransport } = {}) {
52
+ if (!confluenceTransport) throw new Error('createGovernedConfluenceProvider: confluenceTransport is required');
53
+
54
+ async function postLinkback(pageId, idempotencyKey) {
55
+ if (!idempotencyKey) return;
56
+ try {
57
+ await confluenceTransport.createFooterComment(pageId, linkbackComment(idempotencyKey));
58
+ } catch {
59
+ // Linkback is best-effort audit trail on the page itself; the
60
+ // envelope's sent-log is the authoritative record, so a comment
61
+ // failure must not fail an otherwise-successful publish.
62
+ }
63
+ }
64
+
65
+ async function writePage(payload) {
66
+ const { spaceId, title, body, parentId, idempotencyKey } = payload;
67
+ if (!spaceId) throw new Error('confluence governed write: payload.spaceId is required for type "page"');
68
+ if (!title) throw new Error('confluence governed write: payload.title is required for type "page"');
69
+
70
+ let existing;
71
+ try {
72
+ existing = await confluenceTransport.searchPagesByTitle(spaceId, title);
73
+ } catch (err) {
74
+ throw mapWriteTransportError(err, { spaceId });
75
+ }
76
+
77
+ const match = (existing ?? []).find((p) => p.title === title);
78
+ if (match) {
79
+ return {
80
+ type: 'page-duplicate',
81
+ id: match.id,
82
+ title: match.title,
83
+ url: match.url,
84
+ linkback: match.url,
85
+ };
86
+ }
87
+
88
+ try {
89
+ const result = await confluenceTransport.createPage({ spaceId, title, body, parentId });
90
+ await postLinkback(result.id, idempotencyKey);
91
+ return { type: 'page-created', id: result.id, title: result.title, version: result.version, url: result.url };
92
+ } catch (err) {
93
+ throw mapWriteTransportError(err, { spaceId });
94
+ }
95
+ }
96
+
97
+ async function writePageUpdate(payload) {
98
+ const { pageId, title, body, version, idempotencyKey } = payload;
99
+ if (!pageId) throw new Error('confluence governed write: payload.pageId is required for type "page-update"');
100
+ if (version === undefined || version === null) {
101
+ throw new Error('confluence governed write: payload.version is required for type "page-update"');
102
+ }
103
+
104
+ try {
105
+ const result = await confluenceTransport.updatePage({ pageId, title, body, version });
106
+ await postLinkback(pageId, idempotencyKey);
107
+ return { type: 'page-updated', id: result.id, title: result.title, version: result.version, url: result.url };
108
+ } catch (err) {
109
+ if (err?.status === 409) {
110
+ const fresh = await confluenceTransport.getPage(pageId);
111
+ return {
112
+ type: 'version-conflict',
113
+ id: pageId,
114
+ url: fresh.url,
115
+ currentVersion: fresh.version,
116
+ currentBody: fresh.body,
117
+ currentTitle: fresh.title,
118
+ retryPayload: {
119
+ type: 'page-update',
120
+ pageId,
121
+ title: title ?? fresh.title,
122
+ body,
123
+ version: fresh.version,
124
+ idempotencyKey,
125
+ },
126
+ };
127
+ }
128
+ throw mapWriteTransportError(err, { pageId });
129
+ }
130
+ }
131
+
132
+ return {
133
+ meta: {
134
+ id: 'confluence',
135
+ displayName: 'Confluence (governed)',
136
+ capabilities: ['write', 'search'],
137
+ description: 'Envelope-routed Confluence writes with title+space dedup, version-conflict recovery, and linkback comments.',
138
+ },
139
+
140
+ async write(config, payload) {
141
+ if (payload?.type === 'page') return writePage(payload);
142
+ if (payload?.type === 'page-update') return writePageUpdate(payload);
143
+ throw new Error(`confluence governed write: unsupported type "${payload?.type}" (only 'page' and 'page-update' are supported)`);
144
+ },
145
+
146
+ async search(config, query) {
147
+ const spaceId = typeof query === 'object' ? query?.spaceId : undefined;
148
+ const title = typeof query === 'object' ? query?.title : query;
149
+ return confluenceTransport.searchPagesByTitle(spaceId, title);
150
+ },
151
+
152
+ /**
153
+ * Render the payload the envelope would submit, without calling search,
154
+ * getPage, or the transport. Feeds the envelope's dry-run path
155
+ * (lib/writes/envelope.mjs `dryRun: true`), giving a human reviewing a
156
+ * pending write the exact storage-format body and target coordinates
157
+ * before anything is sent.
158
+ *
159
+ * @param {object} payload
160
+ * @returns {{ type: string, fields: object }}
161
+ */
162
+ renderDryRun(payload) {
163
+ if (payload?.type === 'page') {
164
+ return {
165
+ type: 'page',
166
+ fields: {
167
+ spaceId: payload.spaceId,
168
+ title: payload.title,
169
+ parentId: payload.parentId,
170
+ body: { representation: 'storage', value: payload.body },
171
+ },
172
+ };
173
+ }
174
+ if (payload?.type === 'page-update') {
175
+ return {
176
+ type: 'page-update',
177
+ fields: {
178
+ pageId: payload.pageId,
179
+ title: payload.title,
180
+ version: { number: payload.version },
181
+ body: { representation: 'storage', value: payload.body },
182
+ },
183
+ };
184
+ }
185
+ throw new Error(`confluence governed write: cannot render dry-run for unsupported type "${payload?.type}"`);
186
+ },
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Map a create/update/search transport failure (401/403/404) to an
192
+ * actionable message. 409 (version conflict) is handled by the caller
193
+ * directly and never reaches this mapper.
194
+ *
195
+ * @param {Error & {status?: number}} err
196
+ * @param {{ spaceId?: string, pageId?: string }} ctx
197
+ * @returns {Error}
198
+ */
199
+ function mapWriteTransportError(err, ctx) {
200
+ const status = err?.status;
201
+ if (status === 401) {
202
+ return new AuthError('Confluence authentication failed. Check CONFLUENCE_EMAIL / CONFLUENCE_TOKEN.', { provider: 'confluence' });
203
+ }
204
+ if (status === 403) {
205
+ const target = ctx.spaceId ? `space "${ctx.spaceId}"` : `page "${ctx.pageId}"`;
206
+ return new Error(`Forbidden: the authenticated account lacks permission to write to ${target}. Check the confluence-content:write scope.`);
207
+ }
208
+ if (status === 404) {
209
+ const target = ctx.spaceId ? `Space "${ctx.spaceId}"` : `Page "${ctx.pageId}"`;
210
+ return new Error(`${target} was not found, or the authenticated account cannot access it.`);
211
+ }
212
+ return err instanceof Error ? err : new Error(String(err));
213
+ }
214
+
215
+ export default createGovernedConfluenceProvider;