@geraldmaron/construct 1.4.2 → 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 (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 +55 -38
  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 +261 -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 +103 -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
@@ -13,19 +13,60 @@
13
13
  */
14
14
 
15
15
  import { runOrchestration, startRun, getRun, getRuns } from '../../orchestration/runtime.mjs';
16
+ import { HOST } from '../../orchestration/worker.mjs';
17
+ import { loadProjectConfig } from '../../config/project-config.mjs';
16
18
  import { governWebResults } from './web-search-governance.mjs';
19
+ import { resolveNonNegativeSetting } from '../../env-config.mjs';
17
20
 
18
- const TERMINAL = ['completed', 'completed-with-failures', 'cancelled', 'error'];
21
+ // Host loop instructions echoed on an awaiting-host run so the calling agent
22
+ // never has to guess the protocol: execute each materialized prompt as that
23
+ // specialist, submit the result, and follow next_task until it is null.
24
+ // Worded to be un-misreadable as a failure: a VS Code host once read "did not
25
+ // execute" as prepare-only-and-stuck, retried with worker_backend "provider",
26
+ // and died on missing API credits — so the text must lead with success and
27
+ // explicitly forbid that fallback.
28
+
29
+ const HOST_INSTRUCTIONS =
30
+ 'ACTION REQUIRED — this run succeeded and is now waiting on YOU, the calling agent. The host worker '
31
+ + 'backend means Construct materialized each specialist prompt and you execute them in your own '
32
+ + 'session (no API credits are spent). This is the normal path, not a failure: do NOT re-run with '
33
+ + 'worker_backend "provider" (real API spend, needs provider credits) and do not report the run as '
34
+ + 'incomplete. For each task in `tasks`, execute its `system`/`user` prompt yourself as that '
35
+ + 'specialist role, in order, then submit the result by calling orchestration_task_result with '
36
+ + '{ run_id, task_id, output }. Its response carries next_task (the next prompt to execute) or null '
37
+ + 'once the run is terminal — keep submitting until next_task is null, then re-read the run via '
38
+ + 'orchestration_status if you need the final consolidated output.';
39
+
40
+ // Every state a run can rest in. `degraded` and `completed-prepare-only` are
41
+ // terminal too — the poll loop must stop on them or a finished degraded/prepare-only
42
+ // remote run reads as "still executing" until the overall deadline elapses.
43
+
44
+ const TERMINAL = ['completed', 'completed-with-failures', 'completed-prepare-only', 'degraded', 'cancelled', 'error'];
45
+
46
+ export function shapeRun(run) {
47
+ const hasTasks = Array.isArray(run.tasks) && run.tasks.length > 0;
48
+ const allPrepared = hasTasks && run.tasks.every((t) => t.status === 'prepared');
49
+ const degraded = run.degraded ?? run.execution?.degraded ?? false;
50
+
51
+ // Terminal-status taxonomy (construct-fbxv.1): a degraded run never surfaces as a
52
+ // bare 'completed'. All-prepared is the more specific "no specialist executed"
53
+ // signal, so it wins; otherwise any degraded run — whether it prepared zero tasks
54
+ // or executed with a capability gap — reports 'degraded'. Legacy runs persisted as
55
+ // bare 'completed' before this taxonomy are re-derived the same way.
56
+ const shapedStatus = allPrepared
57
+ ? 'completed-prepare-only'
58
+ : degraded && run.status === 'completed'
59
+ ? 'degraded'
60
+ : run.status;
19
61
 
20
- function shapeRun(run) {
21
- const allPrepared = Array.isArray(run.tasks) && run.tasks.length > 0 && run.tasks.every((t) => t.status === 'prepared');
22
62
  return {
23
63
  runId: run.runId,
24
- status: allPrepared ? 'completed-prepare-only' : run.status,
64
+ status: shapedStatus,
25
65
  prepareOnly: allPrepared,
66
+ runMode: run.runMode ?? null,
26
67
  semantics: run.semantics ?? null,
27
68
  executionMode: run.execution?.executionMode,
28
- degraded: run.degraded ?? run.execution?.degraded ?? false,
69
+ degraded,
29
70
  degradationReason: run.degradationReason ?? run.execution?.degradationReason ?? null,
30
71
  intent: run.plan?.intent ?? null,
31
72
  track: run.plan?.track ?? null,
@@ -35,10 +76,15 @@ function shapeRun(run) {
35
76
  tasks: (run.tasks || []).map((t) => ({
36
77
  id: t.id, role: t.role, status: t.status, executor: t.executor,
37
78
  output: t.output ?? null, reasoning: t.reasoning ?? null, error: t.error ?? null,
38
- // ADR-0050: which web grant reached the web, the F08-governed evidence, and search count.
39
79
  webCapability: t.webCapability ?? null,
40
80
  webEvidence: t.webEvidence ?? null,
41
81
  webSearchRequests: t.webSearchRequests ?? 0,
82
+ // Host-backend fields only: the materialized prompt an awaiting-host task
83
+ // carries (nulled out once submitHostTaskResult records a real result),
84
+ // and provenanceSource — present only on a host-reported result, so it can
85
+ // never be confused with a construct-verified provider execution.
86
+ ...(t.hostPrompt ? { system: t.hostPrompt.system, user: t.hostPrompt.user } : {}),
87
+ ...(t.provenanceSource ? { provenanceSource: t.provenanceSource } : {}),
42
88
  })),
43
89
  };
44
90
  }
@@ -75,6 +121,27 @@ function toRequest(args) {
75
121
  };
76
122
  }
77
123
 
124
+ // Only MCP dispatch reaches orchestrationRun() — an in-process CLI call goes
125
+ // through runOrchestration/planRun directly (see bin/construct's `orchestrate
126
+ // run`), never through this function — so the host-execution default belongs
127
+ // exactly here: when the caller gave no explicit worker_backend AND the
128
+ // project's own construct.config.json declares none, an MCP-originated run
129
+ // defaults to the host executing specialists in its own session rather than
130
+ // inline prepare-only or a provider API spend the caller may have no credits
131
+ // for. The RAW (pre-default-merge) project config is the check that matters —
132
+ // loadProjectConfig always merges DEFAULT_PROJECT_CONFIG in, so the merged
133
+ // config's workerBackend is never actually absent; only the raw file reveals
134
+ // whether the user configured one.
135
+
136
+ function resolveMcpDefaultBackend(explicitBackend, { cwd, env }) {
137
+ if (explicitBackend) return explicitBackend;
138
+ try {
139
+ const { raw } = loadProjectConfig(cwd, env);
140
+ if (raw?.orchestration?.workerBackend) return undefined;
141
+ } catch { /* no resolvable config — fall through to the host default */ }
142
+ return HOST;
143
+ }
144
+
78
145
  // A remote orchestration service is opt-in: only when CONSTRUCT_ORCHESTRATION_URL
79
146
  // is set (team / enterprise). The token comes from the Construct env, not from
80
147
  // the dashboard server, so the in-process default path carries no dashboard coupling.
@@ -94,6 +161,17 @@ function unreachable(base, detail) {
94
161
  };
95
162
  }
96
163
 
164
+ // A bounded fetch timing out is not the same failure as a genuinely unreachable host
165
+ // (ORCH-004 AC3): the error text must say timeout and name the bound so a healthy but
166
+ // slow service is never misreported as network-down.
167
+
168
+ function remoteFetchFailed(base, err) {
169
+ if (err?.name === 'RemoteFetchTimeoutError') {
170
+ return { error: `${err.message}. Raise CONSTRUCT_ORCHESTRATION_TIMEOUT_MS if the remote service is healthy but slow.`, service: base, failFast: true };
171
+ }
172
+ return unreachable(base, err.message);
173
+ }
174
+
97
175
  export async function orchestrationRun(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
98
176
  const { request, worker_backend, wait = true, timeout_ms = 120000 } = args;
99
177
  if (!request || typeof request !== 'string') {
@@ -102,15 +180,17 @@ export async function orchestrationRun(args = {}, { env = process.env, cwd = pro
102
180
 
103
181
  const reqObj = toRequest(args);
104
182
  const remote = remoteService(env);
105
- if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl });
183
+ if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl, env });
106
184
 
107
- const opts = { env, cwd, workerBackend: worker_backend, fetchImpl };
185
+ const workerBackend = resolveMcpDefaultBackend(worker_backend, { cwd, env });
186
+ const opts = { env, cwd, workerBackend, fetchImpl };
108
187
  try {
109
188
  if (!wait) {
110
189
  const planned = await startRun(reqObj, opts);
111
190
  return { runId: planned.runId, status: planned.status, executionMode: planned.execution?.executionMode, poll: 'orchestration_status with run_id' };
112
191
  }
113
- return shapeRun(await runOrchestration(reqObj, opts));
192
+ const shaped = shapeRun(await runOrchestration(reqObj, opts));
193
+ return shaped.status === 'awaiting-host' ? { ...shaped, hostInstructions: HOST_INSTRUCTIONS } : shaped;
114
194
  } catch (err) {
115
195
  return { error: `Orchestration failed: ${err.message}`, failFast: true };
116
196
  }
@@ -119,13 +199,16 @@ export async function orchestrationRun(args = {}, { env = process.env, cwd = pro
119
199
  export async function orchestrationStatus(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
120
200
  const { run_id, limit = 20 } = args;
121
201
  const remote = remoteService(env);
122
- if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl });
202
+ if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl, env });
123
203
  try {
124
204
  if (run_id) {
125
205
  const run = await getRun(cwd, run_id, { env });
126
- return run || { error: `No run found with id ${run_id}.` };
206
+ if (!run) return { error: `No run found with id ${run_id}.` };
207
+ const shaped = shapeRun(run);
208
+ return shaped.status === 'awaiting-host' ? { ...shaped, hostInstructions: HOST_INSTRUCTIONS } : shaped;
127
209
  }
128
- return await getRuns(cwd, { env, limit });
210
+ const runs = await getRuns(cwd, { env, limit });
211
+ return runs.map(shapeRun);
129
212
  } catch (err) {
130
213
  return { error: `Failed to read orchestration runs: ${err.message}` };
131
214
  }
@@ -135,18 +218,58 @@ export async function orchestrationStatus(args = {}, { env = process.env, cwd =
135
218
  // is set; the wire shape matches the standalone orchestration service's
136
219
  // /api/orchestration/runs contract (ADR-0022).
137
220
 
138
- // Per-request AbortSignal so a single hung fetch cannot stall runViaService
139
- // past a deterministic window even before the poll loop's overall deadline check.
221
+ // Reachability fail-fast belongs to the deadline check in the poll loop, not the
222
+ // per-request budget: a remote LLM-backed run needs a generous per-request window
223
+ // (ORCH-004; construct-o6t8.2). Default is 30s, well above the industry reachability-probe
224
+ // mistake this replaces, and short of the 600s ceiling the OpenAI/Anthropic SDKs use for
225
+ // long completions — an orchestration run/status round trip is shorter-lived than that.
226
+ // Env-overridable via the shared non-negative resolver so an explicit 0 is honored
227
+ // (near-instant abort) rather than silently treated as unset, and garbage/negative
228
+ // values fall back to the default instead of producing a NaN AbortSignal delay.
229
+
230
+ export const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
231
+
232
+ function requestTimeoutMs(env) {
233
+ return resolveNonNegativeSetting(env, 'CONSTRUCT_ORCHESTRATION_TIMEOUT_MS', DEFAULT_REQUEST_TIMEOUT_MS);
234
+ }
235
+
236
+ // Per-request bound on every remote fetch (ORCH-004; construct-o6t8.2 / construct-neq9.6):
237
+ // a timer aborts the request at perRequestMs and races it against a timeout rejection, so
238
+ // a hung request settles the call even when the underlying fetch ignores its own signal.
239
+ // The timer is cleared the instant either side of the race wins, so a healthy fast response
240
+ // leaves no scheduled timer alive to fire and resolve a promise outside its own async scope
241
+ // — the leak that tripped node:test's "resolution still pending after the loop resolved". The
242
+ // rejection carries the numbered RemoteFetchTimeoutError so a bounded timeout stays
243
+ // distinguishable from a genuinely unreachable host, never the signal's own DOMException
244
+ // message ("aborted due to timeout" with no number).
245
+
246
+ class RemoteFetchTimeoutError extends Error {
247
+ constructor(perRequestMs) {
248
+ super(`Remote orchestration request timed out after ${perRequestMs}ms`);
249
+ this.name = 'RemoteFetchTimeoutError';
250
+ this.timeoutMs = perRequestMs;
251
+ }
252
+ }
140
253
 
141
- function timedRemoteFetch(fetchImpl, url, opts, perRequestMs) {
142
- const signal = AbortSignal.timeout(perRequestMs);
143
- return Promise.race([
144
- fetchImpl(url, { ...opts, signal }),
145
- new Promise((_, reject) => signal.addEventListener('abort', () => reject(signal.reason ?? new Error(`remote orchestration fetch timed out after ${perRequestMs}ms`)), { once: true })),
146
- ]);
254
+ async function timedRemoteFetch(fetchImpl, url, opts, perRequestMs) {
255
+ const controller = new AbortController();
256
+ let clearTimer;
257
+ const timeout = new Promise((_, reject) => {
258
+ const timer = setTimeout(() => {
259
+ const err = new RemoteFetchTimeoutError(perRequestMs);
260
+ controller.abort(err);
261
+ reject(err);
262
+ }, perRequestMs);
263
+ clearTimer = () => clearTimeout(timer);
264
+ });
265
+ try {
266
+ return await Promise.race([fetchImpl(url, { ...opts, signal: controller.signal }), timeout]);
267
+ } finally {
268
+ clearTimer();
269
+ }
147
270
  }
148
271
 
149
- async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend, fetchImpl, perRequestTimeoutMs = 200 }) {
272
+ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend, fetchImpl, env = process.env, perRequestTimeoutMs = requestTimeoutMs(env) } = {}) {
150
273
  const { base, token } = remote;
151
274
  const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
152
275
  const body = JSON.stringify({ ...reqObj, workerBackend });
@@ -160,7 +283,7 @@ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend,
160
283
  if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
161
284
  started = envelope.data;
162
285
  } catch (err) {
163
- return unreachable(base, err.message);
286
+ return remoteFetchFailed(base, err);
164
287
  }
165
288
 
166
289
  if (!wait) {
@@ -180,22 +303,39 @@ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend,
180
303
  if (!res.ok) return { error: `Remote service error while polling (HTTP ${res.status}): ${envelope.error || 'unknown'}`, runId: started.runId, service: base };
181
304
  run = envelope.data;
182
305
  } catch (err) {
306
+ if (err?.name === 'RemoteFetchTimeoutError') {
307
+ return { error: `${err.message} while polling run ${started.runId}. Raise CONSTRUCT_ORCHESTRATION_TIMEOUT_MS if the remote service is healthy but slow.`, runId: started.runId, service: base, failFast: true };
308
+ }
183
309
  return { error: `Lost connection to the remote service while polling run ${started.runId}: ${err.message}`, runId: started.runId, service: base, failFast: true };
184
310
  }
185
311
  }
186
312
  return { ...shapeRun(governRemoteWebEvidence(run)), service: base };
187
313
  }
188
314
 
189
- async function statusViaService(remote, { run_id, limit }, { fetchImpl }) {
315
+ // The status poll GET carries the same AbortSignal bound as the run POST (ORCH-004),
316
+ // so a service that accepts the connection but never replies cannot hang this call
317
+ // indefinitely. `timeoutMs` is overridable directly for tests; production callers
318
+ // resolve it from env the same way runViaService does.
319
+
320
+ export async function statusViaService(remote, { run_id, limit } = {}, { fetchImpl, env = process.env, timeoutMs = requestTimeoutMs(env) } = {}) {
190
321
  const { base, token } = remote;
191
322
  const headers = token ? { Authorization: `Bearer ${token}` } : {};
192
323
  const path = run_id ? `/api/orchestration/runs/${encodeURIComponent(run_id)}` : `/api/orchestration/runs?limit=${encodeURIComponent(limit)}`;
193
324
  try {
194
- const res = await fetchImpl(`${base}${path}`, { headers });
325
+ const res = await timedRemoteFetch(fetchImpl, `${base}${path}`, { headers }, timeoutMs);
195
326
  const envelope = await res.json();
196
327
  if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
328
+ // The remote service is out-of-repo and cannot be trusted to have governed its
329
+ // own web evidence (ADR-0050); re-run every run through the F08 grader on the
330
+ // status path exactly as runViaService does, so a citation can never arrive
331
+ // pre-marked trusted. shapeRun then applies the honest terminal-status taxonomy.
332
+ if (Array.isArray(envelope.data)) {
333
+ return envelope.data.map((r) => shapeRun(governRemoteWebEvidence(r)));
334
+ } else if (envelope.data) {
335
+ return shapeRun(governRemoteWebEvidence(envelope.data));
336
+ }
197
337
  return envelope.data;
198
338
  } catch (err) {
199
- return unreachable(base, err.message);
339
+ return remoteFetchFailed(base, err);
200
340
  }
201
341
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * lib/mcp/tools/orchestration-task-result.tool.mjs — self-registered MCP tool
3
+ * (LMCP-B5, lib/mcp/tool-registry.mjs) that ingests one host-executed
4
+ * specialist task result for a run planned with worker_backend=host.
5
+ *
6
+ * orchestration_run materializes each specialist's prompt (system + user turn)
7
+ * and stands the run at 'awaiting-host' without ever calling a model itself —
8
+ * the calling host executes each prompt in its own session (the model/
9
+ * subscription it is already running under) and reports the result back here.
10
+ * submitHostTaskResult (lib/orchestration/runtime.mjs) is the single place that
11
+ * validates and records the result and finalizes the run once every task is
12
+ * terminal; this file only shapes that call as an MCP tool and echoes back the
13
+ * next materialized prompt so a host can drive the whole run in a simple loop:
14
+ * call orchestration_run once, then call this tool per task until next_task
15
+ * is null.
16
+ */
17
+
18
+ import { submitHostTaskResult } from '../../orchestration/runtime.mjs';
19
+ import { shapeRun } from './orchestration-run.mjs';
20
+
21
+ export const TOOL_DEFS = [
22
+ {
23
+ name: 'orchestration_task_result',
24
+ description:
25
+ 'Submit one host-executed specialist task result for an orchestration run planned with '
26
+ + 'worker_backend=host (the MCP default). orchestration_run returns each task\'s materialized '
27
+ + 'prompt (system/user) without executing it; execute that prompt yourself as the named '
28
+ + 'specialist role, then call this tool with the run_id, task_id, and your output. The '
29
+ + 'response carries next_task (the next awaiting prompt) or null once the run is terminal — '
30
+ + 'loop until null. model/provider/reasoning are optional, self-reported fields (never '
31
+ + 'independently verified), recorded with provenanceSource "host-reported".',
32
+ inputSchema: {
33
+ type: 'object',
34
+ required: ['run_id', 'task_id', 'output'],
35
+ properties: {
36
+ run_id: { type: 'string', description: 'The run id returned by orchestration_run.' },
37
+ task_id: { type: 'string', description: 'The task id this result answers (e.g. "t1"), from orchestration_run\'s task list or a prior call\'s next_task.' },
38
+ output: { type: 'string', description: 'The specialist output you produced for this task. Must be non-empty.' },
39
+ model: { type: 'string', description: 'Optional: the model you used to execute this task (self-reported).' },
40
+ provider: { type: 'string', description: 'Optional: the provider/vendor family you used (self-reported).' },
41
+ reasoning: { type: 'string', description: 'Optional: your reasoning/thinking for this task, if you want it disclosed.' },
42
+ },
43
+ },
44
+ outputSchema: { type: 'object' },
45
+ safety: { class: 'write', filesystem: 'write', network: 'none', process: 'none' },
46
+ },
47
+ ];
48
+
49
+ /**
50
+ * @param {object} args { run_id, task_id, output, model?, provider?, reasoning? }
51
+ * @param {object} [opts] { env, cwd } — defaulted so this behaves identically whether called
52
+ * directly (tests, CLI) or dispatched by the MCP server (which does not inject opts today,
53
+ * matching orchestration_run/orchestration_status's own convention).
54
+ */
55
+ export async function orchestrationTaskResult(args = {}, { env = process.env, cwd = process.cwd() } = {}) {
56
+ const { run_id, task_id, output, model, provider, reasoning } = args;
57
+ if (!run_id || typeof run_id !== 'string') return { error: 'Missing "run_id" — the run id from orchestration_run.' };
58
+ if (!task_id || typeof task_id !== 'string') return { error: 'Missing "task_id".' };
59
+
60
+ try {
61
+ const { run, nextTask } = await submitHostTaskResult(cwd, run_id, task_id, { output, model, provider, reasoning }, { env });
62
+ const shaped = shapeRun(run);
63
+ return {
64
+ accepted: true,
65
+ run_status: shaped.status,
66
+ next_task: nextTask
67
+ ? { task_id: nextTask.id, role: nextTask.role, system: nextTask.hostPrompt?.system ?? null, user: nextTask.hostPrompt?.user ?? null }
68
+ : null,
69
+ };
70
+ } catch (err) {
71
+ return { accepted: false, error: err.message, code: err.code || 'HOST_RESULT_REJECTED' };
72
+ }
73
+ }
74
+
75
+ export const TOOL_HANDLERS = {
76
+ orchestration_task_result: orchestrationTaskResult,
77
+ };
@@ -0,0 +1,187 @@
1
+ /**
2
+ * lib/mcp/tools/provider-write.mjs — provider_write MCP tool (LMCP-I7).
3
+ *
4
+ * The only MCP-reachable path from a host agent (Claude Code, OpenCode) to an
5
+ * external write. Classified destructive in lib/mcp/tool-safety.mjs, so
6
+ * server.mjs's dispatch-time checkDestructiveGate() call already refuses the
7
+ * execute path without a valid out-of-band approval_token before this module
8
+ * ever runs — this file does not re-implement that check, it is downstream
9
+ * of it.
10
+ *
11
+ * Two modes, selected by `dry_run` (default true):
12
+ * - dry_run=true: resolves the governed-write adapter for `provider` and
13
+ * asks it to render the payload it would submit (adapter.renderDryRun),
14
+ * which runs field/shape validation only. Never calls
15
+ * lib/writes/envelope.mjs and never reaches adapter.write() — no network
16
+ * call, no side effect, regardless of token presence.
17
+ * - dry_run=false (execute): the destructive gate has already run in
18
+ * server.mjs by the time dispatchToolByName reaches this module, so
19
+ * arriving here at all means a token was consumed. Dispatches through
20
+ * writeWithEnvelope() (LMCP-J2) — idempotency key, sent-log dedup,
21
+ * policy/approval gate, retry, audit — which is the only caller of
22
+ * adapter.write(). This module never calls adapter.write() directly.
23
+ *
24
+ * E4 respect: when args.specialist_id is present (an embedded-specialist
25
+ * caller), the proposed provider+writeKind is checked against that
26
+ * specialist's embedBindings grant via AuthorityGuard before either mode
27
+ * proceeds. A specialist outside its grant is denied here, before the
28
+ * adapter is even resolved — independent of and in addition to the
29
+ * destructive-gate token check server.mjs already performed.
30
+ */
31
+
32
+ import { writeWithEnvelope } from '../../writes/envelope.mjs';
33
+ import { WriteSentLog } from '../../writes/sent-log.mjs';
34
+ import { AuthorityGuard } from '../../embed/authority-guard.mjs';
35
+ import { mergedEmbedBindings } from '../../embed/capability-jobs.mjs';
36
+ import { createGovernedJiraProvider } from '../../providers/contract/adapters/jira/governed-write.mjs';
37
+ import { createJiraTransport } from '../../providers/contract/adapters/jira/transport.mjs';
38
+ import { createGovernedConfluenceProvider } from '../../providers/contract/adapters/confluence/governed-write.mjs';
39
+ import { createConfluenceTransport } from '../../providers/contract/adapters/confluence/transport.mjs';
40
+ import { createGovernedGithubProvider } from '../../providers/contract/adapters/github/governed-write.mjs';
41
+ import githubAdapter from '../../providers/contract/adapters/github/index.mjs';
42
+
43
+ // Real transports read credentials from process.env at construction time
44
+ // (JIRA_URL/JIRA_EMAIL/JIRA_TOKEN, CONFLUENCE_URL/CONFLUENCE_EMAIL/
45
+ // CONFLUENCE_TOKEN); github's transport is the gh CLI, already
46
+ // credential-free at construction. Building the transport lazily per call
47
+ // (rather than once at module load) keeps a missing-credential AuthError
48
+ // scoped to the one call that needed it.
49
+
50
+ const ADAPTER_FACTORIES = {
51
+ jira: () => createGovernedJiraProvider({ jiraTransport: createJiraTransport() }),
52
+ confluence: () => createGovernedConfluenceProvider({ confluenceTransport: createConfluenceTransport() }),
53
+ github: () => createGovernedGithubProvider({ ghAdapter: githubAdapter }),
54
+ };
55
+
56
+ /**
57
+ * Resolve the governed-write adapter for a provider name. Isolated as its
58
+ * own function so tests can override resolution via the `deps.resolveAdapter`
59
+ * injection point without touching this module's real-transport wiring.
60
+ *
61
+ * @param {string} provider
62
+ * @returns {{ meta: object, write: Function, search?: Function, renderDryRun?: Function }}
63
+ */
64
+ function resolveAdapter(provider) {
65
+ const factory = ADAPTER_FACTORIES[provider];
66
+ if (!factory) {
67
+ const known = Object.keys(ADAPTER_FACTORIES).join(', ');
68
+ throw new Error(`provider_write: unknown provider "${provider}" (known: ${known})`);
69
+ }
70
+ return factory();
71
+ }
72
+
73
+ /**
74
+ * Check an embedded-specialist caller's proposal against its embedBindings
75
+ * grant. Returns null (no opinion) when args carries no specialist_id, so
76
+ * non-embed callers are unaffected — additive enforcement layered in front
77
+ * of the destructive gate, mirroring lib/embed/capability-jobs.mjs's
78
+ * checkProposalAuthority for the daemon path.
79
+ *
80
+ * @param {object} args
81
+ * @param {{ rootDir?: string, env?: object, embedBindings?: object }} opts
82
+ * @returns {Promise<{ allowed: boolean, reason?: string }|null>}
83
+ */
84
+ async function checkE4Binding(args, { rootDir, env = process.env, embedBindings } = {}) {
85
+ if (!args.specialist_id) return null;
86
+
87
+ const bindings = embedBindings ?? mergedEmbedBindings({ rootDir, env });
88
+ const guard = new AuthorityGuard({ authority: {} }, null, bindings);
89
+ const result = await guard.check('externalPost', {
90
+ proposal: {
91
+ specialistId: args.specialist_id,
92
+ providerId: args.provider,
93
+ writeKind: args.item?.type,
94
+ },
95
+ });
96
+
97
+ // Only a binding-scoped denial (mode: 'denied' with no queueId) short-circuits
98
+ // provider_write; a queued/autonomous authority-level outcome is not this
99
+ // tool's concern — the destructive gate + J2 envelope already own approval.
100
+ if (result.mode === 'denied' && !result.allowed) {
101
+ return { allowed: false, reason: result.reason };
102
+ }
103
+ return null;
104
+ }
105
+
106
+ /**
107
+ * provider_write tool implementation.
108
+ *
109
+ * @param {object} args
110
+ * @param {string} args.provider - 'jira' | 'confluence' | 'github'
111
+ * @param {object} args.item - write payload, shape depends on provider (see governed-write.mjs per adapter)
112
+ * @param {boolean} [args.dry_run=true] - render-only when true; never touches adapter.write()
113
+ * @param {string} [args.specialist_id] - embedded-specialist caller id (LMCP-E4 binding check)
114
+ * @param {string} [args.idempotency_key] - explicit idempotency key forwarded to the J2 envelope
115
+ * @param {object} [deps] - injection points for tests
116
+ * @param {Function} [deps.resolveAdapter] - override adapter resolution
117
+ * @param {WriteSentLog} [deps.sentLog] - override sent-log instance
118
+ * @param {object} [deps.embedBindings] - override merged embedBindings map
119
+ * @param {string} [deps.rootDir]
120
+ * @param {object} [deps.env]
121
+ * @returns {Promise<object>}
122
+ */
123
+ export async function providerWrite(args = {}, deps = {}) {
124
+ const provider = args.provider;
125
+ const item = args.item;
126
+ const dryRun = args.dry_run !== false;
127
+
128
+ if (!provider) return { error: 'provider_write: args.provider is required' };
129
+ if (!item || typeof item !== 'object') return { error: 'provider_write: args.item is required' };
130
+
131
+ const bindingDenial = await checkE4Binding(args, {
132
+ rootDir: deps.rootDir,
133
+ env: deps.env,
134
+ embedBindings: deps.embedBindings,
135
+ });
136
+ if (bindingDenial) {
137
+ return { status: 'denied', provider, dryRun, reason: bindingDenial.reason };
138
+ }
139
+
140
+ const resolve = deps.resolveAdapter ?? resolveAdapter;
141
+ let adapter;
142
+ try {
143
+ adapter = resolve(provider);
144
+ } catch (err) {
145
+ return { error: err.message ?? String(err) };
146
+ }
147
+
148
+ if (dryRun) {
149
+ if (typeof adapter.renderDryRun !== 'function') {
150
+ return {
151
+ status: 'dry-run',
152
+ provider,
153
+ dryRun: true,
154
+ diff: null,
155
+ note: `provider "${provider}" adapter has no renderDryRun; payload echoed verbatim, no validation performed.`,
156
+ payload: item,
157
+ };
158
+ }
159
+ try {
160
+ const diff = adapter.renderDryRun(item);
161
+ return { status: 'dry-run', provider, dryRun: true, diff };
162
+ } catch (err) {
163
+ return { status: 'dry-run-invalid', provider, dryRun: true, error: err.message ?? String(err) };
164
+ }
165
+ }
166
+
167
+ const sentLog = deps.sentLog ?? new WriteSentLog({ persistPath: WriteSentLog.resolvePersistPath(deps.rootDir ?? process.cwd()) });
168
+
169
+ const envelopeResult = await writeWithEnvelope({
170
+ provider: adapter,
171
+ config: {},
172
+ payload: item,
173
+ dryRun: false,
174
+ sentLog,
175
+ idempotencyKey: args.idempotency_key,
176
+ requestedBy: args.specialist_id ? { role: `cx-${args.specialist_id}` } : {},
177
+ });
178
+
179
+ return {
180
+ status: envelopeResult.status,
181
+ provider,
182
+ dryRun: false,
183
+ envelope: envelopeResult.envelope,
184
+ };
185
+ }
186
+
187
+ export default providerWrite;
@@ -201,9 +201,6 @@ export function scopeCreate(args = {}) {
201
201
  // Mutating, gated, destructive: archive a curated scope.
202
202
 
203
203
  export function scopeArchive(args = {}) {
204
- if (args.confirm !== true) {
205
- return { error: 'scope_archive requires confirm=true (destructive: moves files into archive/scopes/)' };
206
- }
207
204
  if (!args.id || !args.reason || String(args.reason).trim().length < 8) {
208
205
  return { error: 'scope_archive requires id:string and reason:string (>=8 chars)' };
209
206
  }
@@ -253,7 +253,7 @@ export async function orchestrationPolicy(args) {
253
253
  const { emitTraceEvent, newTraceId } = await import('../../worker/trace.mjs');
254
254
  const cwd = process.cwd();
255
255
  const queue = createIntakeQueue(cwd, process.env);
256
- const intake = queue.read(intakeId);
256
+ const intake = await queue.read(intakeId);
257
257
  if (!intake) {
258
258
  intakeError = `intake packet not found: ${intakeId}`;
259
259
  } else if (!intake.triage) {
@@ -8,8 +8,6 @@
8
8
  import { resolve } from 'node:path';
9
9
  import { deleteIngestedArtifacts, getStorageStatus, inferProjectName, purgeExpiredData, resetStorage } from '../../storage/admin.mjs';
10
10
  import { syncFileStateToSql } from '../../storage/sync.mjs';
11
- import { consumeApprovalToken } from '../destructive-approval.mjs';
12
-
13
11
  export async function storageStatus(args) {
14
12
  const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
15
13
  const project = args.project ? String(args.project) : inferProjectName(cwd);
@@ -36,9 +34,6 @@ export async function storageReset(args) {
36
34
  if (args.confirm !== true) {
37
35
  return { error: 'storage_reset requires confirm=true' };
38
36
  }
39
- if (!consumeApprovalToken('storage_reset', args.approval_token)) {
40
- return { error: 'storage_reset requires a valid out-of-band approval token; confirm=true alone is not authorization' };
41
- }
42
37
  return resetStorage(cwd, {
43
38
  env: process.env,
44
39
  project,
@@ -53,9 +48,6 @@ export async function deleteIngestedArtifactsTool(args) {
53
48
  if (args.confirm !== true) {
54
49
  return { error: 'delete_ingested_artifacts requires confirm=true' };
55
50
  }
56
- if (!consumeApprovalToken('delete_ingested_artifacts', args.approval_token)) {
57
- return { error: 'delete_ingested_artifacts requires a valid out-of-band approval token; confirm=true alone is not authorization' };
58
- }
59
51
  const files = Array.isArray(args.files)
60
52
  ? args.files.map((value) => String(value))
61
53
  : [];