@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
@@ -0,0 +1,71 @@
1
+ /**
2
+ * lib/workflows/manifest-schema.mjs — canonical workflow manifest schema descriptor.
3
+ *
4
+ * Defines the vocabulary for workflow manifests as per ADR-0054 (LMCP-A4).
5
+ * A plain JS descriptor, not JSON Schema — the validator in validate.mjs
6
+ * consumes it directly. The schema is intentionally additive: new optional fields
7
+ * may be added in a minor version bump; new required fields or type removals are
8
+ * breaking and require a COMPAT_VERSION increment.
9
+ */
10
+
11
+ /**
12
+ * All valid workflow type values. These correspond to the execution topologies
13
+ * the orchestration runtime can dispatch. `embed` (ADR-0061, LMCP-P1/P2) is a
14
+ * workflow-manifest specialization carrying an `embed` block rather than a
15
+ * dispatchable topology — it reuses this schema's id/version/tiering rules
16
+ * instead of forking a second manifest system.
17
+ */
18
+ export const WORKFLOW_TYPES = ['linear', 'routing', 'orchestrator-worker', 'evaluator-loop', 'pipeline', 'embed'];
19
+
20
+ /**
21
+ * Fields every workflow manifest must carry. Absence of any required field is a
22
+ * hard validation error and the manifest is rejected.
23
+ */
24
+ export const WORKFLOW_REQUIRED_FIELDS = ['id', 'version', 'type', 'defaultApprovalMode'];
25
+
26
+ /**
27
+ * Fields a workflow manifest may carry. These are not validated for shape — the
28
+ * validator checks only presence of required fields and semantic constraints.
29
+ * Manifests that add fields outside this list are accepted but their extra
30
+ * fields are ignored by core consumers.
31
+ *
32
+ * `intakeType` (LMCP-D3) lets a pack or project manifest claim a triage
33
+ * classifier label without editing the hardcoded INTAKE_TO_WORKFLOW table in
34
+ * lib/embedded-contract/workflow-defs.mjs — the shim rebuilds that table from
35
+ * every loaded manifest's `intakeType` field, project/pack entries applied
36
+ * after builtins so a contributed manifest can add or remap a label.
37
+ */
38
+ export const WORKFLOW_OPTIONAL_FIELDS = [
39
+ 'modes', 'surfaces', 'inputSchema', 'outputSchema',
40
+ 'roleChain', 'packRequirements', 'providerRequirements',
41
+ 'toolGrants', 'policyGates', 'durableStateModel',
42
+ 'telemetryEvents', 'tests', 'docs', 'examples',
43
+ 'degradation', 'owner', 'compatVersion', 'description', 'tier',
44
+ 'intakeType',
45
+ ];
46
+
47
+ /**
48
+ * Current schema version. A manifest with compatVersion > WORKFLOW_COMPAT_VERSION
49
+ * was written for a newer schema than this runtime understands — load is refused
50
+ * to prevent silent misinterpretation. Forward compatibility is not guaranteed.
51
+ */
52
+ export const WORKFLOW_COMPAT_VERSION = 1;
53
+
54
+ /**
55
+ * All valid approval mode values. These govern whether a workflow run proceeds
56
+ * without human intervention or requires explicit approval at one or more gates.
57
+ */
58
+ export const APPROVAL_MODES = ['proposal-only', 'requires-human-approval', 'allow-durable-write'];
59
+
60
+ /**
61
+ * All valid durable state model values. These describe how the workflow runtime
62
+ * persists intermediate state across invocations for resumability and audit.
63
+ */
64
+ export const DURABLE_STATE_MODELS = ['none', 'git-queue', 'in-process'];
65
+
66
+ /**
67
+ * All valid model tier values. These hint at the minimum model capability the
68
+ * orchestrator should assign to the workflow, from fastest/cheapest to most
69
+ * capable/expensive.
70
+ */
71
+ export const TIERS = ['fast', 'standard', 'reasoning'];
@@ -0,0 +1,118 @@
1
+ /**
2
+ * lib/workflows/surface-parity.mjs — workflow manifest surface-parity check (LMCP-D4).
3
+ *
4
+ * Compares each workflow manifest's declared `surfaces` field against its
5
+ * ACTUAL registration, derived from the real dispatch code rather than
6
+ * inferred:
7
+ *
8
+ * - lib/embedded-contract/workflow-invoke.mjs exports one `invokeWorkflow`
9
+ * core that CLI (`construct workflow invoke --workflow-type`, bin/construct
10
+ * cmdWorkflow), MCP (`workflow_invoke`, lib/mcp/tools/embedded-contract.mjs),
11
+ * and SDK (`invokeWorkflow`, lib/embedded-contract/index.mjs) all wrap
12
+ * unchanged (lib/embedded-contract/envelope.mjs even documents the three
13
+ * surfaces as "structurally identical"). That core resolves a workflow id
14
+ * through `getWorkflowDef` (lib/embedded-contract/workflow-defs.mjs),
15
+ * whose DEFS table is generated from every loaded manifest with
16
+ * `type !== 'embed'`.
17
+ * - A manifest with `type !== 'embed'` is therefore registered on cli, mcp,
18
+ * AND sdk simultaneously — there is no dispatch path that can register a
19
+ * workflow id on one of the three without the other two, so per-manifest
20
+ * divergence between them is structurally impossible today. sdk carries
21
+ * no separate opt-in and is reported as an info line, never a checkable
22
+ * declared/actual pair — flagging it as a per-manifest mismatch would
23
+ * fail every manifest for a fact no author can change.
24
+ * - A manifest with `type === 'embed'` (ADR-0061) is scheduled by the embed
25
+ * daemon on a cadence; it is never reached through workflow_invoke /
26
+ * `construct workflow invoke` / SDK invokeWorkflow at all, so its actual
27
+ * registration is the empty set on cli and mcp.
28
+ *
29
+ * checkSurfaceParity() flags two failure shapes as errors:
30
+ * 1. A manifest declares a surface ('cli' or 'mcp') it is not actually
31
+ * registered on (an embed manifest declaring 'cli'/'mcp', or a non-embed
32
+ * manifest whose id is absent from WORKFLOW_TYPES because it failed to
33
+ * load into workflow-defs.mjs).
34
+ * 2. A non-embed manifest omits a surface ('cli' or 'mcp') it IS actually
35
+ * registered on.
36
+ * Both shapes are suppressed, with a passing info line instead, when the
37
+ * manifest carries a `surfaceExceptions` map with a non-empty `reason` string
38
+ * for that surface — `surfaceExceptions` is an optional, unvalidated field
39
+ * (manifests load with strict:false in this caller, so unknown fields pass
40
+ * through) rather than a schema addition, keeping this rule additive.
41
+ */
42
+
43
+ import { WORKFLOW_TYPES } from '../embedded-contract/workflow-defs.mjs';
44
+
45
+ /** The two surfaces a manifest can meaningfully declare/omit per-workflow. */
46
+ export const DECLARABLE_SURFACES = ['cli', 'mcp'];
47
+
48
+ /**
49
+ * Actual registration for a manifest, derived from workflow-defs.mjs's DEFS
50
+ * table (built from loadAllWorkflows() — the same loader this check itself
51
+ * consumes elsewhere) rather than re-implemented here.
52
+ *
53
+ * @param {object} manifest
54
+ * @returns {string[]} subset of DECLARABLE_SURFACES the manifest is actually
55
+ * dispatchable on; sdk is always additionally true for non-embed manifests
56
+ * but is not part of this declarable set (see module header).
57
+ */
58
+ export function actualSurfaces(manifest) {
59
+ if (!manifest || manifest.type === 'embed') return [];
60
+ return WORKFLOW_TYPES.includes(manifest.id) ? [...DECLARABLE_SURFACES] : [];
61
+ }
62
+
63
+ /**
64
+ * Look up a manifest's declared exception reason for a surface, if any.
65
+ *
66
+ * @param {object} manifest
67
+ * @param {string} surface
68
+ * @returns {string|null} the reason string, or null when no valid exception is declared
69
+ */
70
+ function exceptionReason(manifest, surface) {
71
+ const exceptions = manifest?.surfaceExceptions;
72
+ if (!exceptions || typeof exceptions !== 'object') return null;
73
+ const entry = exceptions[surface];
74
+ if (typeof entry === 'string' && entry.trim()) return entry;
75
+ if (entry && typeof entry === 'object' && typeof entry.reason === 'string' && entry.reason.trim()) return entry.reason;
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * checkSurfaceParity(manifests)
81
+ *
82
+ * Runs the surface-parity check over an already-loaded manifest list (each
83
+ * carrying `_filePath` and `id`, per lib/workflows/loader.mjs). Never throws.
84
+ *
85
+ * @param {object[]} manifests
86
+ * @returns {{ errors: string[], infos: string[] }}
87
+ */
88
+ export function checkSurfaceParity(manifests = []) {
89
+ const errors = [];
90
+ const infos = [];
91
+
92
+ for (const manifest of manifests) {
93
+ const label = manifest._filePath || manifest.id || '(unknown manifest)';
94
+ const declared = new Set(Array.isArray(manifest.surfaces) ? manifest.surfaces : []);
95
+ const actual = new Set(actualSurfaces(manifest));
96
+
97
+ for (const surface of DECLARABLE_SURFACES) {
98
+ const isDeclared = declared.has(surface);
99
+ const isActual = actual.has(surface);
100
+ if (isDeclared === isActual) continue;
101
+
102
+ const reason = exceptionReason(manifest, surface);
103
+ if (reason) {
104
+ const shape = isDeclared ? 'declared but not registered' : 'registered but not declared';
105
+ infos.push(`${label}: workflow '${manifest.id}' surface '${surface}' is ${shape} — declared exception: ${reason}`);
106
+ continue;
107
+ }
108
+
109
+ if (isDeclared && !isActual) {
110
+ errors.push(`${label}: workflow '${manifest.id}' declares surface '${surface}' but is not actually registered there (no reason given — add a 'surfaceExceptions.${surface}' reason or remove the declaration)`);
111
+ } else {
112
+ errors.push(`${label}: workflow '${manifest.id}' is registered on surface '${surface}' but does not declare it in 'surfaces' (no reason given — add '${surface}' to 'surfaces' or a 'surfaceExceptions.${surface}' reason)`);
113
+ }
114
+ }
115
+ }
116
+
117
+ return { errors, infos };
118
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * lib/workflows/validate.mjs — workflow manifest validator.
3
+ *
4
+ * Validates a plain-JS manifest object against the canonical schema defined in
5
+ * manifest-schema.mjs. All errors are returned in a structured result; this
6
+ * function never throws. Error messages are single-line, actionable, and
7
+ * prefixed with the filePath (when provided) so consumers can surface them
8
+ * directly to users.
9
+ */
10
+
11
+ import {
12
+ WORKFLOW_TYPES, WORKFLOW_REQUIRED_FIELDS, WORKFLOW_OPTIONAL_FIELDS,
13
+ WORKFLOW_COMPAT_VERSION, APPROVAL_MODES,
14
+ } from './manifest-schema.mjs';
15
+
16
+ /** Basic semver pattern: MAJOR.MINOR.PATCH with optional pre-release/build metadata. */
17
+ const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+].+)?$/;
18
+
19
+ /** Valid id characters: lowercase alphanumeric, hyphens, dots, forward slashes. */
20
+ const ID_RE = /^[a-z0-9\-./]+$/;
21
+
22
+ /**
23
+ * validateWorkflowManifest(manifest, { filePath, strict } = {})
24
+ *
25
+ * Returns { valid: true } on success, or { valid: false, errors: string[] }
26
+ * on failure. Every error string is a self-contained, single-line description
27
+ * of the problem. The filePath prefix is included in every error so callers
28
+ * can surface messages without additional decoration.
29
+ *
30
+ * Checks performed (in order):
31
+ * 1. Manifest is a non-null object.
32
+ * 2. Required fields are present.
33
+ * 3. `type` is one of the canonical WORKFLOW_TYPES.
34
+ * 4. `version` matches the semver pattern.
35
+ * 5. `id` is a non-empty string matching [a-z0-9-./]+.
36
+ * 6. `compatVersion` (if present) does not exceed WORKFLOW_COMPAT_VERSION.
37
+ * 7. `defaultApprovalMode` is one of APPROVAL_MODES.
38
+ *
39
+ * When strict is true, unknown fields (outside REQUIRED + OPTIONAL) are rejected.
40
+ *
41
+ * @param {unknown} manifest - The parsed manifest object to validate.
42
+ * @param {{ filePath?: string, strict?: boolean }} [opts]
43
+ * @returns {{ valid: true } | { valid: false, errors: string[] }}
44
+ */
45
+ export function validateWorkflowManifest(manifest, { filePath, strict = false } = {}) {
46
+ const prefix = filePath ? `${filePath}: ` : '';
47
+ const errors = [];
48
+
49
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
50
+ return { valid: false, errors: [`${prefix}manifest must be a JSON object`] };
51
+ }
52
+
53
+ // Required fields
54
+ for (const field of WORKFLOW_REQUIRED_FIELDS) {
55
+ if (!(field in manifest) || manifest[field] === undefined || manifest[field] === null) {
56
+ errors.push(`${prefix}missing required field: ${field}`);
57
+ }
58
+ }
59
+
60
+ // type check (only if type is present — otherwise already flagged above)
61
+ if ('type' in manifest && manifest.type !== undefined && manifest.type !== null) {
62
+ if (!WORKFLOW_TYPES.includes(manifest.type)) {
63
+ errors.push(
64
+ `${prefix}unknown type '${manifest.type}'; expected one of: ${WORKFLOW_TYPES.join(', ')}`
65
+ );
66
+ }
67
+ }
68
+
69
+ // version semver check
70
+ if ('version' in manifest && manifest.version !== undefined && manifest.version !== null) {
71
+ if (typeof manifest.version !== 'string' || !SEMVER_RE.test(manifest.version)) {
72
+ errors.push(`${prefix}version must be a semver string`);
73
+ }
74
+ }
75
+
76
+ // id format check
77
+ if ('id' in manifest && manifest.id !== undefined && manifest.id !== null) {
78
+ if (typeof manifest.id !== 'string' || manifest.id.length === 0) {
79
+ errors.push(`${prefix}id must be a non-empty string`);
80
+ } else if (!ID_RE.test(manifest.id)) {
81
+ errors.push(`${prefix}id must match [a-z0-9-./]+ (got '${manifest.id}')`);
82
+ }
83
+ }
84
+
85
+ // Forward-compat guard
86
+ if (
87
+ manifest.compatVersion !== undefined &&
88
+ manifest.compatVersion !== null
89
+ ) {
90
+ if (typeof manifest.compatVersion !== 'number' || !Number.isInteger(manifest.compatVersion)) {
91
+ errors.push(`${prefix}compatVersion must be an integer`);
92
+ } else if (manifest.compatVersion > WORKFLOW_COMPAT_VERSION) {
93
+ errors.push(
94
+ `${prefix}compatVersion ${manifest.compatVersion} exceeds supported version ${WORKFLOW_COMPAT_VERSION}; upgrade Construct to use this manifest`
95
+ );
96
+ }
97
+ }
98
+
99
+ // defaultApprovalMode check
100
+ if (
101
+ 'defaultApprovalMode' in manifest &&
102
+ manifest.defaultApprovalMode !== undefined &&
103
+ manifest.defaultApprovalMode !== null
104
+ ) {
105
+ if (!APPROVAL_MODES.includes(manifest.defaultApprovalMode)) {
106
+ errors.push(
107
+ `${prefix}defaultApprovalMode must be one of: ${APPROVAL_MODES.join(', ')} (got '${manifest.defaultApprovalMode}')`
108
+ );
109
+ }
110
+ }
111
+
112
+ // Strict-mode hardening: reject unknown fields
113
+ if (strict) {
114
+ const knownFields = new Set([...WORKFLOW_REQUIRED_FIELDS, ...WORKFLOW_OPTIONAL_FIELDS]);
115
+ for (const key of Object.keys(manifest)) {
116
+ if (key.startsWith('_')) continue;
117
+ if (!knownFields.has(key)) {
118
+ errors.push(`${prefix}unknown field '${key}' in strict mode`);
119
+ }
120
+ }
121
+ }
122
+
123
+ if (errors.length > 0) {
124
+ return { valid: false, errors };
125
+ }
126
+ return { valid: true };
127
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * lib/writes/control-plane.mjs — the only entry point that turns a queued
3
+ * writeIntent into an executed write (LMCP-J6).
4
+ *
5
+ * A specialist (in-process, embed-invoked, or MCP-invoked) may only ever
6
+ * *recommend* a write: it produces a writeIntent (lib/writes/write-intent.mjs)
7
+ * and hands it to an ApprovalQueue (lib/embed/approval-queue.mjs, the F5/I2
8
+ * durable queue). Nothing else in this file, nor anywhere outside it, is
9
+ * permitted to resolve a governed-write adapter and call
10
+ * lib/writes/envelope.mjs's writeWithEnvelope() for a queue-sourced intent —
11
+ * that resolution + call happens exclusively in executeApprovedWriteIntent()
12
+ * below, gated on the queue record's own state.
13
+ *
14
+ * Concretely, the state machine this module enforces:
15
+ * 1. ApprovalQueue.enqueue() records state 'awaiting_approval' — this
16
+ * module never calls a governed adapter for a record in that state.
17
+ * 2. A human or policy actor calls ApprovalQueue.approve()/deny() out of
18
+ * band (CLI, dashboard, MCP resume) — this module does not decide
19
+ * approval, it only checks the decision already recorded.
20
+ * 3. drainApprovedWriteIntents() scans the queue for state === 'approved'
21
+ * records this module has not yet executed, resolves the matching
22
+ * governed adapter (lib/providers/contract/adapters/*\/governed-write.mjs,
23
+ * built by J3-J5), and calls writeWithEnvelope() exactly once per
24
+ * record — the envelope's own idempotency key + sent-log dedup still
25
+ * apply on top, so a re-drain of an already-sent record is a cache hit,
26
+ * not a second external write.
27
+ *
28
+ * No dependency on lib/embed/daemon.mjs or lib/embed/capability-jobs.mjs —
29
+ * only the ApprovalQueue class shape is required, so a queue instance handed
30
+ * over by the daemon, a CLI command, or a test drains the same way, without
31
+ * reaching into daemon internals.
32
+ */
33
+
34
+ import { writeWithEnvelope } from './envelope.mjs';
35
+ import { WriteSentLog } from './sent-log.mjs';
36
+ import { parseWriteIntentToolName } from './write-intent.mjs';
37
+ import { createGovernedJiraProvider } from '../providers/contract/adapters/jira/governed-write.mjs';
38
+ import { createJiraTransport } from '../providers/contract/adapters/jira/transport.mjs';
39
+ import { createGovernedConfluenceProvider } from '../providers/contract/adapters/confluence/governed-write.mjs';
40
+ import { createConfluenceTransport } from '../providers/contract/adapters/confluence/transport.mjs';
41
+ import { createGovernedGithubProvider } from '../providers/contract/adapters/github/governed-write.mjs';
42
+ import githubAdapter from '../providers/contract/adapters/github/index.mjs';
43
+
44
+ /**
45
+ * Default adapter factories, one per known governed provider. Lazy per-call
46
+ * construction (not module-load-time) keeps a missing-credential error
47
+ * scoped to the one drain call that needed that provider, matching
48
+ * lib/mcp/tools/provider-write.mjs's resolution strategy.
49
+ */
50
+ const DEFAULT_ADAPTER_FACTORIES = {
51
+ jira: () => createGovernedJiraProvider({ jiraTransport: createJiraTransport() }),
52
+ confluence: () => createGovernedConfluenceProvider({ confluenceTransport: createConfluenceTransport() }),
53
+ github: () => createGovernedGithubProvider({ ghAdapter: githubAdapter }),
54
+ };
55
+
56
+ /**
57
+ * Execute exactly one approved queue record through the envelope. Throws if
58
+ * the record's state is not 'approved' — this is the structural gate: there
59
+ * is no code path in this module that reaches writeWithEnvelope() without
60
+ * first observing state === 'approved' on the queue record itself.
61
+ *
62
+ * @param {object} record - an ApprovalQueue record (state, toolCall, requestedBy, approvalId)
63
+ * @param {object} [opts]
64
+ * @param {Record<string, () => object>} [opts.adapterFactories] - override adapter resolution (tests)
65
+ * @param {WriteSentLog} [opts.sentLog]
66
+ * @param {string} [opts.rootDir]
67
+ * @returns {Promise<object>} the envelope result ({ status, envelope })
68
+ */
69
+ export async function executeApprovedWriteIntent(record, opts = {}) {
70
+ if (!record || record.state !== 'approved') {
71
+ throw new Error(
72
+ `executeApprovedWriteIntent: refusing to execute record in state "${record?.state ?? 'unknown'}" — only 'approved' records may reach the envelope`,
73
+ );
74
+ }
75
+
76
+ const parsed = parseWriteIntentToolName(record.toolCall?.tool);
77
+ if (!parsed) {
78
+ throw new Error(`executeApprovedWriteIntent: cannot resolve provider from tool name "${record.toolCall?.tool}"`);
79
+ }
80
+
81
+ const factories = opts.adapterFactories ?? DEFAULT_ADAPTER_FACTORIES;
82
+ const factory = factories[parsed.providerId];
83
+ if (!factory) {
84
+ throw new Error(`executeApprovedWriteIntent: no governed adapter registered for provider "${parsed.providerId}"`);
85
+ }
86
+ const adapter = factory();
87
+
88
+ const sentLog = opts.sentLog ?? new WriteSentLog({ persistPath: WriteSentLog.resolvePersistPath(opts.rootDir ?? process.cwd()) });
89
+
90
+ return writeWithEnvelope({
91
+ provider: adapter,
92
+ config: {},
93
+ payload: { type: parsed.writeKind, ...(record.toolCall?.args ?? {}) },
94
+ dryRun: false,
95
+ sentLog,
96
+ idempotencyKey: record.toolCall?.argsHash,
97
+ requestedBy: record.requestedBy ?? {},
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Drain every 'approved' record in an ApprovalQueue through the envelope,
103
+ * marking each executed record so a subsequent drain does not re-execute it
104
+ * (the envelope's sent-log dedup is a second, independent line of defense,
105
+ * not the only one — this in-memory executed-set makes the "exactly one
106
+ * adapter call" property observable per drain call even before sent-log
107
+ * persistence is consulted).
108
+ *
109
+ * Records in any other state ('awaiting_approval', 'denied', 'expired') are
110
+ * left untouched — this is read-only with respect to the queue's approval
111
+ * decision, it only ever appends outcomes for records already decided
112
+ * 'approved' by something outside this module.
113
+ *
114
+ * @param {import('../embed/approval-queue.mjs').ApprovalQueue} approvalQueue
115
+ * @param {object} [opts]
116
+ * @param {Record<string, () => object>} [opts.adapterFactories]
117
+ * @param {WriteSentLog} [opts.sentLog]
118
+ * @param {string} [opts.rootDir]
119
+ * @param {Set<string>} [opts.executedApprovalIds] - approvalIds already drained; mutated in place
120
+ * @returns {Promise<Array<{ approvalId: string, result: object|null, error: string|null }>>}
121
+ */
122
+ export async function drainApprovedWriteIntents(approvalQueue, opts = {}) {
123
+ const executed = opts.executedApprovalIds ?? new Set();
124
+ const outcomes = [];
125
+
126
+ const approvedRecords = approvalQueue.list('approved');
127
+ for (const record of approvedRecords) {
128
+ if (executed.has(record.approvalId)) continue;
129
+
130
+ try {
131
+ const result = await executeApprovedWriteIntent(record, opts);
132
+ executed.add(record.approvalId);
133
+ outcomes.push({ approvalId: record.approvalId, result, error: null });
134
+ } catch (err) {
135
+ outcomes.push({ approvalId: record.approvalId, result: null, error: err.message ?? String(err) });
136
+ }
137
+ }
138
+
139
+ return outcomes;
140
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * lib/writes/envelope.mjs — the single governed path for external writes.
3
+ *
4
+ * writeWithEnvelope() runs every external write through: idempotency key →
5
+ * sent-log dedup → dry-run render → policy/approval gate → rate-limited retry →
6
+ * sent-log append + audit. Specialists never call provider adapters directly;
7
+ * they recommend a write-intent and the control plane executes it here.
8
+ */
9
+
10
+ import crypto from 'node:crypto';
11
+ import { WriteSentLog } from './sent-log.mjs';
12
+ import { ApprovalQueue } from '../embed/approval-queue.mjs';
13
+
14
+ /**
15
+ * Execute a governed write through the envelope.
16
+ *
17
+ * Flow:
18
+ * 1. Generate idempotency key (if none provided) from provider + writeType + stable payload hash
19
+ * 2. Check sent-log: if same key already succeeded, return cached result
20
+ * 3. Dry-run mode: return rendered payload without executing
21
+ * 4. Policy gate: if broker provided, run policy check
22
+ * - If approval required, create durable approval record and return awaiting_approval
23
+ * 5. Execute provider.write() with retry
24
+ * 6. Record in sent-log
25
+ * 7. Append audit record
26
+ * 8. Return structured result with linkback
27
+ *
28
+ * @param {object} spec
29
+ * @param {object} spec.provider - Provider instance with write() method
30
+ * @param {object} spec.config - Provider configuration
31
+ * @param {object} spec.payload - Write payload (e.g. { type: 'issue', title, body, ... })
32
+ * @param {string} [spec.idempotencyKey] - Explicit idempotency key (auto-generated if absent)
33
+ * @param {boolean} [spec.dryRun] - If true, return rendered payload without executing
34
+ * @param {object} [spec.broker] - Broker instance for policy gating
35
+ * @param {ApprovalQueue} [spec.approvalQueue] - For durable approval
36
+ * @param {WriteSentLog} [spec.sentLog] - For idempotency tracking
37
+ * @param {object} [spec.requestedBy] - Actor identity { userId, serviceId, role }
38
+ * @param {object} [spec.traceInfo] - { traceId, spanId } for observability
39
+ * @param {number} [spec.maxRetries] - Max retry attempts on rate-limit (default 3)
40
+ * @returns {object} { status: 'sent'|'awaiting_approval'|'denied'|'error'|'cached', envelope: {...} }
41
+ */
42
+ export async function writeWithEnvelope(spec) {
43
+ const {
44
+ provider, config, payload,
45
+ idempotencyKey: explicitKey,
46
+ dryRun = false,
47
+ broker, approvalQueue, sentLog,
48
+ requestedBy = {},
49
+ traceInfo = {},
50
+ maxRetries = 3,
51
+ } = spec;
52
+
53
+ const payloadStable = JSON.stringify(payload, Object.keys(payload).sort());
54
+ const idempotencyKey = explicitKey || crypto
55
+ .createHash('sha256')
56
+ .update(`${provider.meta?.id || 'unknown'}:${payload.type || 'write'}:${payloadStable}`)
57
+ .digest('hex')
58
+ .slice(0, 24);
59
+
60
+ const writeType = payload.type || 'write';
61
+ const providerId = provider.meta?.id || spec.providerId || 'unknown';
62
+ const now = new Date().toISOString();
63
+
64
+ if (sentLog) {
65
+ const existing = sentLog.findByIdempotencyKey(idempotencyKey);
66
+ if (existing && existing.status === 'sent') {
67
+ return {
68
+ status: 'cached',
69
+ envelope: {
70
+ idempotencyKey,
71
+ sentAt: existing.sentAt,
72
+ provider: existing.provider || providerId,
73
+ writeType,
74
+ state: 'sent',
75
+ externalUrl: existing.externalUrl,
76
+ externalId: existing.externalId,
77
+ result: existing.result,
78
+ },
79
+ };
80
+ }
81
+ }
82
+
83
+ if (sentLog) {
84
+ sentLog.record({ idempotencyKey, writeType, provider: providerId, sentAt: now, status: 'pending' });
85
+ }
86
+
87
+ if (dryRun) {
88
+ return {
89
+ status: 'dry-run',
90
+ envelope: {
91
+ idempotencyKey,
92
+ sentAt: now,
93
+ provider: providerId,
94
+ writeType,
95
+ state: 'dry-run',
96
+ payload,
97
+ },
98
+ };
99
+ }
100
+
101
+ if (broker) {
102
+ try {
103
+ const policyResult = await broker.invoke({
104
+ role: requestedBy.role || 'member',
105
+ tool: `write:${providerId}:${writeType}`,
106
+ action: 'write',
107
+ toolArgs: payload,
108
+ risk: 'write',
109
+ requestedBy,
110
+ resumeToken: spec.resumeToken,
111
+ execute: async () => ({ ok: true, deferred: true }),
112
+ });
113
+
114
+ if (policyResult.status === 'awaiting_approval') {
115
+ if (sentLog) sentLog.record({
116
+ idempotencyKey, writeType, provider: providerId, sentAt: now,
117
+ status: 'awaiting_approval',
118
+ result: { approvalId: policyResult.approvalId },
119
+ });
120
+ return {
121
+ status: 'awaiting_approval',
122
+ envelope: {
123
+ idempotencyKey,
124
+ approvalId: policyResult.approvalId,
125
+ resumeToken: policyResult.resumeToken,
126
+ expiresAt: policyResult.expiresAt,
127
+ },
128
+ };
129
+ }
130
+
131
+ if (policyResult.status === 'denied') {
132
+ if (sentLog) sentLog.record({
133
+ idempotencyKey, writeType, provider: providerId, sentAt: now,
134
+ status: 'denied',
135
+ error: policyResult.reason || 'denied by policy',
136
+ });
137
+ return {
138
+ status: 'denied',
139
+ envelope: { idempotencyKey, reason: policyResult.reason },
140
+ };
141
+ }
142
+ } catch (err) {
143
+ if (err.name === 'PolicyDenied') {
144
+ return { status: 'denied', envelope: { idempotencyKey, reason: err.message } };
145
+ }
146
+ }
147
+ }
148
+
149
+ let lastError = null;
150
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
151
+ try {
152
+ const result = await provider.write(config, payload);
153
+ const externalUrl = result?.url || result?.externalUrl || null;
154
+ const externalId = result?.id || result?.key || result?.externalId || null;
155
+
156
+ if (sentLog) {
157
+ sentLog.record({
158
+ idempotencyKey, writeType, provider: providerId, sentAt: now,
159
+ completedAt: new Date().toISOString(),
160
+ status: 'sent',
161
+ externalUrl, externalId, result,
162
+ });
163
+ }
164
+
165
+ return {
166
+ status: 'sent',
167
+ envelope: {
168
+ idempotencyKey,
169
+ sentAt: now,
170
+ provider: providerId,
171
+ writeType,
172
+ state: 'sent',
173
+ externalUrl,
174
+ externalId,
175
+ result,
176
+ },
177
+ };
178
+ } catch (err) {
179
+ lastError = err;
180
+ const isRateLimit = err.message?.includes('rate') || err.status === 429;
181
+ if (!isRateLimit || attempt >= maxRetries) break;
182
+ await new Promise(r => setTimeout(r, Math.pow(2, attempt - 1) * 1000));
183
+ }
184
+ }
185
+
186
+ if (sentLog) {
187
+ sentLog.record({
188
+ idempotencyKey, writeType, provider: providerId, sentAt: now,
189
+ completedAt: new Date().toISOString(),
190
+ status: 'failed',
191
+ error: lastError?.message || String(lastError),
192
+ });
193
+ }
194
+
195
+ return {
196
+ status: 'error',
197
+ envelope: {
198
+ idempotencyKey,
199
+ sentAt: now,
200
+ provider: providerId,
201
+ writeType,
202
+ state: 'failed',
203
+ error: lastError?.message || String(lastError),
204
+ },
205
+ };
206
+ }