@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
@@ -27,6 +27,21 @@
27
27
  * mistake a prepared task for executed specialist output. When the execution
28
28
  * contract resolves to prompt-only or host-direct, Construct owns no specialist
29
29
  * task sequence and the run records that explicitly.
30
+ *
31
+ * Tenant propagation (ADR-0057/A7, LMCP-H1): resolveTenantContext runs once
32
+ * per planRun/executeRun call and its tenantId rides the run record, every
33
+ * task, and every emitted trace/run event. Enterprise mode with no
34
+ * resolvable tenant throws TenantResolutionError — planning and resuming a
35
+ * run both fail closed rather than persisting an unlabeled run.
36
+ *
37
+ * Run-level executionState (LMCP-F4): executeRun aggregates every task's
38
+ * LMCP-F1 executionState (prepared|executed|degraded-executed|failed) into one
39
+ * `run.executionState` via aggregateExecutionState, so a caller (construct
40
+ * status, an MCP host) never has to walk run.tasks to learn whether a run only
41
+ * prepared work or actually executed it. Precedence: failed >
42
+ * degraded-executed > executed > prepared; a zero-task run (prompt-only,
43
+ * host-direct) aggregates to null rather than fabricating a state for work
44
+ * that never happened.
30
45
  */
31
46
 
32
47
  import { randomBytes } from 'node:crypto';
@@ -37,12 +52,16 @@ import { routeRequest } from '../orchestration-policy.mjs';
37
52
  import { resolveExecution } from '../embedded-contract/execution.mjs';
38
53
  import { detectHostCapabilities } from '../host-capabilities.mjs';
39
54
  import { loadProjectConfig } from '../config/project-config.mjs';
40
- import { CHAIN_OF_THOUGHT_MODES } from '../config/schema.mjs';
55
+ import { CHAIN_OF_THOUGHT_MODES, DEFAULT_PROJECT_CONFIG } from '../config/schema.mjs';
41
56
  import { newTraceId, newSpanId, emitTraceEvent } from '../worker/trace.mjs';
42
- import { resolveRunStore } from './store.mjs';
43
- import { runTaskViaProvider, INLINE, PROVIDER, WORKER_BACKEND_SET } from './worker.mjs';
57
+ import { resolveStateDir } from '../state-root.mjs';
58
+ import { resolveRunStore, projectKey } from './store.mjs';
59
+ import { resolveTraceStore } from './trace-store.mjs';
60
+ import { runTaskViaProvider, materializeTaskPrompt, INLINE, PROVIDER, HOST, WORKER_BACKEND_SET } from './worker.mjs';
44
61
  import { roleHoldsWebCapability } from './web-capability.mjs';
45
62
  import { emitRunEvent, isCancelRequested, clearCancel } from './events.mjs';
63
+ import { getDeploymentMode } from '../deployment-mode.mjs';
64
+ import { resolveTenantContext } from '../tenant/context.mjs';
46
65
 
47
66
  export const WORKER_BACKENDS = WORKER_BACKEND_SET;
48
67
  export const DEFAULT_WORKER_BACKEND = INLINE;
@@ -88,7 +107,7 @@ function pickExecution(data) {
88
107
  };
89
108
  }
90
109
 
91
- function buildTasks(route) {
110
+ function buildTasks(route, tenantId) {
92
111
  const reasons = route.dispatchReasons || {};
93
112
  const handoffByProducer = new Map();
94
113
  for (const edge of route.contractChain || []) {
@@ -99,6 +118,7 @@ function buildTasks(route) {
99
118
  id: `t${i + 1}`,
100
119
  seq: i,
101
120
  role,
121
+ tenantId,
102
122
  reason: reasons[role] || null,
103
123
  handoffContract: handoffByProducer.get(role.replace(/^cx-/, '')) || null,
104
124
  status: 'queued',
@@ -111,11 +131,26 @@ function buildTasks(route) {
111
131
  }));
112
132
  }
113
133
 
134
+ // Best-effort mirror of a local trace event into the team-shared trace store
135
+ // (LMCP-G10): a no-op in solo mode or when Postgres is unreachable
136
+ // (resolveTraceStore already encodes that fallback), and never allowed to
137
+ // fail the run itself — the local `.cx/traces` JSONL emitTraceEvent already
138
+ // wrote is the durable record either way.
139
+
140
+ function persistTeamTrace({ cwd, env, config, event, tenantId }) {
141
+ if (!event) return;
142
+ try {
143
+ const teamStore = resolveTraceStore({ env, cwd, config });
144
+ if (teamStore.kind !== 'postgres') return;
145
+ teamStore.saveTraceEvent(event, { project: projectKey(config, cwd), tenantId }).catch(() => {});
146
+ } catch { /* observability must not break the caller — see header comment */ }
147
+ }
148
+
114
149
  // The worker backend is a deployment concern: an explicit option wins, then the
115
150
  // run's recorded backend, then config (orchestration.workerBackend), then the
116
151
  // inline default. An unknown value falls back to inline rather than guessing.
117
152
 
118
- function resolveWorkerBackend({ explicit, run, config }) {
153
+ export function resolveWorkerBackend({ explicit, run, config }) {
119
154
  const candidate = explicit || run?.workerBackend || config?.orchestration?.workerBackend || DEFAULT_WORKER_BACKEND;
120
155
  return WORKER_BACKENDS.includes(candidate) ? candidate : DEFAULT_WORKER_BACKEND;
121
156
  }
@@ -130,14 +165,32 @@ function resolveChainOfThought({ explicit, run, config }) {
130
165
  return CHAIN_OF_THOUGHT.includes(candidate) ? candidate : DEFAULT_CHAIN_OF_THOUGHT;
131
166
  }
132
167
 
133
- function loadConfig(cwd, env) {
168
+ // loadProjectConfig reverts the whole file to defaults on any validation
169
+ // error (ADR-0021's fail-safe stance); loadConfig historically discarded the
170
+ // errors/source that reversion carries, so a typo (e.g. store: "sqllite")
171
+ // silently flipped workerBackend back to inline with no user-visible signal.
172
+ // configWarnings names the offending key/value and states defaults were
173
+ // applied, mirrored into planRun's warnings and construct doctor
174
+ // (construct-uccl.5).
175
+
176
+ function loadConfigWithWarnings(cwd, env) {
134
177
  try {
135
- return loadProjectConfig(cwd, env).config || {};
178
+ const loaded = loadProjectConfig(cwd, env);
179
+ const configWarnings = loaded.source === 'invalid'
180
+ ? loaded.errors.map(
181
+ (e) => `construct.config.json invalid (${e}) — reverted to defaults (workerBackend=${DEFAULT_PROJECT_CONFIG.orchestration.workerBackend}). Fix construct.config.json to keep your settings.`,
182
+ )
183
+ : [];
184
+ return { config: loaded.config || {}, configWarnings };
136
185
  } catch {
137
- return {};
186
+ return { config: {}, configWarnings: [] };
138
187
  }
139
188
  }
140
189
 
190
+ function loadConfig(cwd, env) {
191
+ return loadConfigWithWarnings(cwd, env).config;
192
+ }
193
+
141
194
  /**
142
195
  * Plan a run: resolve the execution contract, decompose into a specialist chain,
143
196
  * and persist a durable run record (status `planned`). Pure of model calls.
@@ -153,8 +206,17 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
153
206
  workerBackend: explicitWorkerBackend = null,
154
207
  } = request;
155
208
 
156
- const config = loadConfig(cwd, env);
157
- const { store, warnings: storeWarnings } = resolveRunStore({ config, env, cwd });
209
+ const { config, configWarnings } = loadConfigWithWarnings(cwd, env);
210
+ const { store, warnings: storeWarnings, degraded: storeDegraded, degradedReason: storeDegradedReason } = resolveRunStore({ config, env, cwd });
211
+ if (storeDegraded) {
212
+ process.stderr.write(`[construct] orchestration store degraded (${storeDegradedReason ?? 'unknown'}): ${storeWarnings.join('; ')}\n`);
213
+ }
214
+
215
+ // ADR-0057 (A7): tenant context is resolved once per run, here, and rides
216
+ // every run/task record below. Enterprise mode with no resolvable tenant
217
+ // throws TenantResolutionError — planRun fails closed rather than planning
218
+ // a run with a null tenant label.
219
+ const { tenantId } = resolveTenantContext({ env, config, mode: getDeploymentMode(env, { cwd }) });
158
220
 
159
221
  const execData = resolveExecution(
160
222
  { workflowType, requestedStrategy, useConstruct, host, hostModel, hostProvider },
@@ -172,11 +234,12 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
172
234
  // orchestrated execution; prompt-only and host-direct own none, and the run
173
235
  // records that rather than implying orchestration that did not happen.
174
236
  const orchestrates = execData.effectiveStrategy === 'orchestrated';
175
- const tasks = orchestrates ? buildTasks(route) : [];
237
+ const tasks = orchestrates ? buildTasks(route, tenantId) : [];
176
238
 
177
239
  const run = {
178
240
  runId,
179
241
  traceId,
242
+ tenantId,
180
243
  createdAt: now,
181
244
  updatedAt: now,
182
245
  request: { summary: truncate(text, 200), workflowType, requestedStrategy },
@@ -195,7 +258,7 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
195
258
  },
196
259
  tasks,
197
260
  status: 'planned',
198
- warnings: [...(execData.warnings || []), ...storeWarnings],
261
+ warnings: [...(execData.warnings || []), ...storeWarnings, ...configWarnings],
199
262
  semantics: RUNTIME_SEMANTICS,
200
263
  executionSemantics: execData.semantics,
201
264
  };
@@ -203,9 +266,9 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
203
266
  await store.saveRun(run);
204
267
  emitTraceEvent({
205
268
  rootDir: cwd, env, traceId, spanId: newSpanId(), eventType: 'task_graph.created',
206
- metadata: { runId, executionMode: run.execution.executionMode, specialists: run.plan.specialists, workerBackend: run.workerBackend },
269
+ metadata: { runId, tenantId, executionMode: run.execution.executionMode, specialists: run.plan.specialists, workerBackend: run.workerBackend },
207
270
  });
208
- emitRunEvent(runId, { type: 'planned', status: 'planned', executionMode: run.execution.executionMode, taskCount: tasks.length });
271
+ emitRunEvent(runId, { type: 'planned', status: 'planned', tenantId, executionMode: run.execution.executionMode, taskCount: tasks.length });
209
272
  return run;
210
273
  }
211
274
 
@@ -216,6 +279,11 @@ function prepareTaskInline(task) {
216
279
  task.executor = 'inline:prepared';
217
280
  task.status = 'prepared';
218
281
  task.finishedAt = new Date().toISOString();
282
+ // The inline backend never resolves a persona or calls a model (ADR-0020), so
283
+ // packId/promptVersion/model/provider/toolGrants stay unset — there is nothing
284
+ // real to report yet. executionState still carries the honest signal: this task
285
+ // was prepared, not executed (LMCP-F1/F4).
286
+ task.executionState = 'prepared';
219
287
  // A prepared web-capable task reached no web: mark it so a host never infers live
220
288
  // web access from a task that only planned (ADR-0050).
221
289
  if (roleHoldsWebCapability(task.role)) task.webCapability = 'prepare-only';
@@ -225,13 +293,13 @@ function prepareTaskInline(task) {
225
293
  // task is recorded (status `failed`, task.error) and does not abort the run, so
226
294
  // one specialist failure cannot lose the work of the others.
227
295
 
228
- async function executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought) {
296
+ async function executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought, cwd) {
229
297
  try {
230
298
  const result = await runTaskViaProvider({
231
299
  task, run,
232
300
  model: run.execution?.selectedModel,
233
301
  provider: run.execution?.selectedProvider,
234
- env, fetchImpl, chainOfThought,
302
+ env, fetchImpl, chainOfThought, cwd,
235
303
  });
236
304
  task.output = result.output;
237
305
  task.executor = `provider:${result.provider}:${result.model}`;
@@ -247,19 +315,196 @@ async function executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought)
247
315
  task.webSearchRequests = result.webSearchRequests || 0;
248
316
  }
249
317
 
318
+ // Evidence grounding (construct-5wkl AC#5): a citation the specialist wrote
319
+ // but that never appeared in its own governed webEvidence is unverified —
320
+ // the task still completes (real output, already paid for) but carries the
321
+ // warning rather than looking identical to a fully-grounded answer.
322
+ if (result.evidenceStatus) {
323
+ task.evidenceStatus = result.evidenceStatus;
324
+ task.unverifiedCitations = result.unverifiedCitations || [];
325
+ }
326
+
327
+ // Provider telemetry (construct-5wkl AC#3): redacted, machine-readable
328
+ // execution metadata — provider, model, finish_reason, usage, elapsedMs,
329
+ // retryCount, and reasoning request/return — so a debugging pass never has
330
+ // to re-run the task to learn why it behaved the way it did.
331
+ if (result.providerMeta) task.providerMeta = result.providerMeta;
332
+
333
+ // Persona resolution (LMCP-E2): personaAvailable rides on every task so a
334
+ // host never has to infer whether the specialist ran under its real prompt
335
+ // or the solo-mode fallback; degraded is set only on the fallback path.
336
+ task.personaAvailable = result.personaAvailable;
337
+ if (result.degraded) task.degraded = result.degraded;
338
+
339
+ // Execution provenance (LMCP-F1): which specialist/pack/prompt/model/provider
340
+ // actually ran and under which tool grants — the basis for audit and
341
+ // evaluation. Rides on every provider-executed task, not gated on
342
+ // chainOfThought, same as personaAvailable above.
343
+ task.specialistId = result.specialistId;
344
+ task.packId = result.packId;
345
+ task.promptVersion = result.promptVersion;
346
+ task.model = result.model;
347
+ task.provider = result.provider;
348
+ task.toolGrants = result.toolGrants || [];
349
+ task.executionState = result.executionState;
350
+
250
351
  // `surface` attaches reasoning to the task so every display surface renders
251
352
  // it; `telemetry_only` keeps it off the task (recorded only in the trace).
252
353
  if (chainOfThought === 'surface' && result.reasoning) task.reasoning = result.reasoning;
253
- return { ok: true, reasoning: result.reasoning || '', webCapability: result.webCapability || null };
354
+ return {
355
+ ok: true,
356
+ reasoning: result.reasoning || '',
357
+ webCapability: result.webCapability || null,
358
+ personaAvailable: result.personaAvailable,
359
+ personaDegraded: Boolean(result.degraded),
360
+ specialistId: result.specialistId,
361
+ packId: result.packId,
362
+ promptVersion: result.promptVersion,
363
+ model: result.model,
364
+ provider: result.provider,
365
+ toolGrants: result.toolGrants || [],
366
+ executionState: result.executionState,
367
+ };
254
368
  } catch (err) {
255
369
  task.executor = `provider:error`;
256
- task.error = { code: err.code || 'PROVIDER_EXECUTION_FAILED', message: err.message };
370
+ task.error = {
371
+ code: err.code || 'PROVIDER_EXECUTION_FAILED',
372
+ message: err.message,
373
+ ...(err.remediation ? { remediation: err.remediation } : {}),
374
+ ...(Number.isFinite(err.retryCount) ? { retryCount: err.retryCount } : {}),
375
+ };
257
376
  task.status = 'failed';
258
377
  task.finishedAt = new Date().toISOString();
259
- return { ok: false, reasoning: '' };
378
+ // A refused/failed provider call never resolved a real specialist/pack/prompt to
379
+ // report — executionState is the one honest signal available here (LMCP-F1/F4).
380
+ task.executionState = 'failed';
381
+ // A content-shaped failure (empty/reasoning-only/content-filtered/malformed)
382
+ // still reached a real HTTP response, so its redacted provider metadata
383
+ // (construct-5wkl AC#3) is worth keeping even though the task failed.
384
+ if (err.providerMeta) task.providerMeta = err.providerMeta;
385
+ return { ok: false, reasoning: '', executionState: 'failed' };
260
386
  }
261
387
  }
262
388
 
389
+ // The host backend materializes one task's prompt (same materializeTaskPrompt the
390
+ // provider executor uses) and stops — no model call runs here. The calling host
391
+ // (the MCP client that requested the run) executes the prompt in its own session
392
+ // and reports the result back through submitHostTaskResult. A materialization
393
+ // failure (e.g. a team/enterprise persona hard-fail) is recorded as a task
394
+ // failure, same posture as a provider call that throws before ever reaching a
395
+ // model — one specialist's prompt failing to resolve does not lose the others.
396
+
397
+ function prepareTaskForHost(task, run, cwd, env) {
398
+ try {
399
+ const prompt = materializeTaskPrompt({ task, run, cwd, env });
400
+ task.hostPrompt = { system: prompt.system, user: prompt.user };
401
+ task.specialistId = prompt.specialistId;
402
+ task.packId = prompt.packId;
403
+ task.promptVersion = prompt.promptVersion;
404
+ task.toolGrants = prompt.toolGrants;
405
+ task.personaAvailable = prompt.personaAvailable;
406
+ if (prompt.degraded) task.degraded = prompt.degraded;
407
+ task.executor = 'host:awaiting';
408
+ task.status = 'awaiting-host';
409
+ task.finishedAt = null;
410
+ task.executionState = 'awaiting-host';
411
+ return { ok: true };
412
+ } catch (err) {
413
+ task.executor = 'host:error';
414
+ task.error = {
415
+ code: err.code || 'HOST_MATERIALIZE_FAILED',
416
+ message: err.message,
417
+ ...(err.remediation ? { remediation: err.remediation } : {}),
418
+ };
419
+ task.status = 'failed';
420
+ task.finishedAt = new Date().toISOString();
421
+ task.executionState = 'failed';
422
+ return { ok: false };
423
+ }
424
+ }
425
+
426
+ // Run-level executionState aggregates every task's LMCP-F1 executionState
427
+ // (prepared|executed|degraded-executed|failed|awaiting-host) into one honest
428
+ // signal for the whole run (LMCP-F4). A run with no tasks (prompt-only,
429
+ // host-direct) owns no specialist sequence, so it aggregates to null rather
430
+ // than fabricating a state for work that never happened. `failed` wins over
431
+ // everything — a run that failed even one task must never report an
432
+ // aggregate that reads as clean. `degraded-executed` beats `executed` next,
433
+ // so a solo-mode persona fallback is never hidden behind a bare 'executed'
434
+ // when at least one real execution also happened. `awaiting-host` beats
435
+ // `prepared` — a host-backend task materialized its prompt but has not
436
+ // prepared-and-stopped the way the inline backend does, so it is a distinct,
437
+ // more specific "still pending" signal. All-`prepared` is the most specific
438
+ // "nothing ran" signal and only applies when every task shares it.
439
+
440
+ const EXECUTION_STATE_PRECEDENCE = ['failed', 'degraded-executed', 'executed', 'awaiting-host', 'prepared'];
441
+
442
+ function aggregateExecutionState(tasks) {
443
+ if (!Array.isArray(tasks) || tasks.length === 0) return null;
444
+ const present = new Set(tasks.map((t) => t.executionState).filter(Boolean));
445
+ for (const state of EXECUTION_STATE_PRECEDENCE) {
446
+ if (present.has(state)) return state;
447
+ }
448
+ // No task carries a recognized executionState (pre-F1 legacy run) — nothing
449
+ // honest to report, so null rather than guessing.
450
+ return null;
451
+ }
452
+
453
+ // The one honest terminal-status computation, shared by executeRun (the
454
+ // synchronous provider/inline/host-materialization path) and
455
+ // submitHostTaskResult (the asynchronous host-pickup completion path) — a run
456
+ // must resolve to the same completed/completed-with-failures/degraded/
457
+ // completed-prepare-only taxonomy regardless of which of those two callers
458
+ // closes it out, so this is written once and reused, not reimplemented.
459
+
460
+ async function finalizeRun(run, { cwd, env, config, store, cancelled = false, anyFailed = false, webUnavailable = false, personaDegraded = false } = {}) {
461
+ // LMCP-F4: aggregate every task's executionState into one run-level signal
462
+ // before the terminal status is computed, so a reader never has to walk
463
+ // run.tasks to learn whether the run prepared, executed, degraded-executed,
464
+ // awaited a host, or failed.
465
+ run.executionState = aggregateExecutionState(run.tasks);
466
+
467
+ let terminalStatus;
468
+ // degraded can come from the execution contract (no model resolved), a web
469
+ // capability gap, or a solo-mode persona fallback; any source means the run
470
+ // must not surface as a bare success.
471
+ const isDegraded = run.degraded || run.execution?.degraded || webUnavailable || personaDegraded;
472
+ if (isDegraded) {
473
+ run.degraded = true;
474
+ run.degradationReason = run.degradationReason || run.execution?.degradationReason
475
+ || (webUnavailable ? 'capability-unavailable' : personaDegraded ? 'persona-fallback' : 'no-model-resolved');
476
+ }
477
+ // Prepare-only is the more specific "no specialist executed" signal, so an
478
+ // all-prepared run reports that even if also degraded; otherwise any degraded run
479
+ // — zero-task or executed-with-a-gap — is 'degraded', never bare 'completed'.
480
+ if (cancelled) {
481
+ terminalStatus = 'cancelled';
482
+ } else if (anyFailed) {
483
+ terminalStatus = 'completed-with-failures';
484
+ } else if (run.tasks.length > 0 && run.tasks.every((t) => t.status === 'prepared')) {
485
+ terminalStatus = 'completed-prepare-only';
486
+ } else if (isDegraded) {
487
+ terminalStatus = 'degraded';
488
+ } else {
489
+ terminalStatus = 'completed';
490
+ }
491
+ run.status = terminalStatus;
492
+ run.updatedAt = new Date().toISOString();
493
+ await store.saveRun(run);
494
+ clearCancel(run.runId);
495
+ // terminalStatus already encodes prepared-vs-executed (completed-prepare-only)
496
+ // and degraded-vs-clean, but its precedence order (cancelled > failed >
497
+ // prepare-only > degraded) can collapse a run that is BOTH e.g.
498
+ // completed-with-failures AND degraded into one string. executionState
499
+ // (LMCP-F4) and the degraded boolean ride alongside it so a trace/SSE
500
+ // consumer never has to re-read the run store to recover the signal
501
+ // terminalStatus's precedence dropped.
502
+ const completedTrace = emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'lifecycle.completed', metadata: { runId: run.runId, tenantId: run.tenantId, status: run.status, executionState: run.executionState, degraded: Boolean(run.degraded), tasks: run.tasks.length } });
503
+ persistTeamTrace({ cwd, env, config, event: completedTrace, tenantId: run.tenantId });
504
+ emitRunEvent(run.runId, { type: 'completed', status: run.status, executionState: run.executionState, degraded: Boolean(run.degraded), tenantId: run.tenantId });
505
+ return run;
506
+ }
507
+
263
508
  /**
264
509
  * Execute a planned run through the chosen worker backend, persisting after each
265
510
  * task transition so the run is resumable and observable.
@@ -279,7 +524,14 @@ export async function executeRun(cwd, runId, { env = process.env, workerBackend
279
524
  throw err;
280
525
  }
281
526
 
282
- const runFilePath = join(cwd, '.cx', 'runtime', 'orchestration', 'runs', `${runId}.json`);
527
+ const runFilePath = join(resolveStateDir(cwd, 'runtime', 'orchestration', 'runs'), `${runId}.json`);
528
+
529
+ // A run persisted before this bead carries no tenantId; resolve it now
530
+ // rather than leave the field permanently absent on resumed runs. Enterprise
531
+ // mode fails closed here exactly as it would have at planRun time.
532
+ if (run.tenantId === undefined) {
533
+ run.tenantId = resolveTenantContext({ env, config, mode: getDeploymentMode(env, { cwd }) }).tenantId;
534
+ }
283
535
 
284
536
  try {
285
537
  const backend = resolveWorkerBackend({ explicit: workerBackend, run, config });
@@ -294,49 +546,80 @@ export async function executeRun(cwd, runId, { env = process.env, workerBackend
294
546
  let anyFailed = false;
295
547
  let cancelled = false;
296
548
  let webUnavailable = false;
549
+ let personaDegraded = false;
297
550
  for (const task of run.tasks) {
298
551
  if (isCancelRequested(runId)) { cancelled = true; break; }
299
552
  task.status = 'running';
300
553
  task.startedAt = new Date().toISOString();
301
- emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.started', role: task.role, taskId: task.id, metadata: { runId, workerBackend: backend } });
302
- emitRunEvent(runId, { type: 'task', taskId: task.id, role: task.role, status: 'running' });
554
+ emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.started', role: task.role, taskId: task.id, metadata: { runId, tenantId: run.tenantId, workerBackend: backend } });
555
+ emitRunEvent(runId, { type: 'task', taskId: task.id, role: task.role, status: 'running', tenantId: run.tenantId });
303
556
 
304
557
  let taskReasoning = '';
305
558
  if (backend === PROVIDER) {
306
- const res = await executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought);
559
+ const res = await executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought, cwd);
307
560
  if (!res.ok) anyFailed = true;
308
561
  if (res.webCapability === 'unavailable') webUnavailable = true;
562
+ if (res.personaDegraded) personaDegraded = true;
309
563
  taskReasoning = res.reasoning;
564
+ } else if (backend === HOST) {
565
+ const res = prepareTaskForHost(task, run, cwd, env);
566
+ if (!res.ok) anyFailed = true;
310
567
  } else {
311
568
  prepareTaskInline(task);
312
569
  }
313
570
 
314
571
  // telemetry_only records reasoning to the trace without ever surfacing it to
315
572
  // a display; surface keeps it off the trace (it rides on task.reasoning).
316
- const completedMeta = { runId, status: task.status };
573
+ // personaAvailable/degraded ride the trace unconditionally (not gated on
574
+ // chainOfThought) so a persona-degraded run is always discoverable from
575
+ // trace history alone, independent of reasoning-disclosure mode.
576
+ const completedMeta = { runId, tenantId: run.tenantId, status: task.status };
317
577
  if (chainOfThought === 'telemetry_only' && taskReasoning) {
318
578
  completedMeta.reasoning = taskReasoning;
319
579
  completedMeta.reasoningChars = taskReasoning.length;
320
580
  }
581
+ if (task.personaAvailable === false) {
582
+ completedMeta.personaAvailable = false;
583
+ completedMeta.degraded = task.degraded || 'persona-fallback';
584
+ }
585
+ // Execution provenance (LMCP-F1): specialist/pack/prompt/model/provider/
586
+ // tool-grants/executionState ride the trace unconditionally whenever the
587
+ // task carries them (inline prepare-only tasks carry only executionState;
588
+ // provider tasks, success or failure, carry the full set) — same
589
+ // always-on pattern as personaAvailable above, so a reader never has to
590
+ // reconstruct provenance from a separate source.
591
+ if (task.executionState !== undefined) completedMeta.executionState = task.executionState;
592
+ if (task.specialistId !== undefined) completedMeta.specialistId = task.specialistId;
593
+ if (task.packId !== undefined) completedMeta.packId = task.packId;
594
+ if (task.promptVersion !== undefined) completedMeta.promptVersion = task.promptVersion;
595
+ if (task.model !== undefined) completedMeta.model = task.model;
596
+ if (task.provider !== undefined) completedMeta.provider = task.provider;
597
+ if (task.toolGrants !== undefined) completedMeta.toolGrants = task.toolGrants;
321
598
  emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.completed', role: task.role, taskId: task.id, metadata: completedMeta });
322
599
  emitRunEvent(runId, { type: 'task', taskId: task.id, role: task.role, status: task.status, executor: task.executor, ...(task.reasoning ? { reasoning: task.reasoning } : {}), ...(task.error ? { error: task.error } : {}) });
323
600
  run.updatedAt = new Date().toISOString();
324
601
  await store.saveRun(run);
325
602
  }
326
603
 
327
- run.status = cancelled ? 'cancelled' : (anyFailed ? 'completed-with-failures' : 'completed');
328
- // A web-capable task that reached no web path degrades the run so a host surfaces the
329
- // capability gap rather than treating an insufficient-evidence answer as complete.
330
- if (webUnavailable) {
331
- run.degraded = true;
332
- run.degradationReason = 'capability-unavailable';
604
+ // The host backend never executes synchronously it materializes prompts
605
+ // and stands the run at 'awaiting-host' until the calling host submits every
606
+ // task's result via submitHostTaskResult, which finalizes through the same
607
+ // finalizeRun below. The only case that does NOT stand awaiting is when no
608
+ // task actually reached 'awaiting-host' (materialization failed for every
609
+ // task, or the route planned zero tasks) — there is nothing pending pickup,
610
+ // so the run must resolve to a real terminal state rather than stand
611
+ // forever with nothing to submit.
612
+ if (backend === HOST && run.tasks.some((t) => t.status === 'awaiting-host')) {
613
+ run.executionState = aggregateExecutionState(run.tasks);
614
+ run.status = 'awaiting-host';
615
+ run.updatedAt = new Date().toISOString();
616
+ await store.saveRun(run);
617
+ clearCancel(runId);
618
+ emitRunEvent(runId, { type: 'awaiting-host', status: 'awaiting-host', executionState: run.executionState, tenantId: run.tenantId, taskCount: run.tasks.length });
619
+ return run;
333
620
  }
334
- run.updatedAt = new Date().toISOString();
335
- await store.saveRun(run);
336
- clearCancel(runId);
337
- emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'lifecycle.completed', metadata: { runId, status: run.status, tasks: run.tasks.length } });
338
- emitRunEvent(runId, { type: 'completed', status: run.status });
339
- return run;
621
+
622
+ return finalizeRun(run, { cwd, env, config, store, cancelled, anyFailed, webUnavailable, personaDegraded });
340
623
  } catch (err) {
341
624
  run.status = 'error';
342
625
  run.error = { code: err.code || 'RUN_EXECUTE_FAILED', message: err.message };
@@ -352,6 +635,99 @@ export async function executeRun(cwd, runId, { env = process.env, workerBackend
352
635
  }
353
636
  }
354
637
 
638
+ // Terminal run statuses a host result can never be submitted against — a run
639
+ // that already finalized has nothing left awaiting pickup, so a late submit is
640
+ // rejected rather than silently mutating a closed record.
641
+
642
+ const TERMINAL_RUN_STATUSES = new Set(['completed', 'completed-with-failures', 'completed-prepare-only', 'degraded', 'cancelled', 'error']);
643
+
644
+ function hostResultError(code, message) {
645
+ const err = new Error(message);
646
+ err.code = code;
647
+ return err;
648
+ }
649
+
650
+ /**
651
+ * Record the calling host's execution of one `awaiting-host` task, submitted
652
+ * by the host after it ran the materialized prompt (task.hostPrompt) in its own
653
+ * model session. The result is host-reported, not independently verified —
654
+ * every field this writes is tagged `provenanceSource: 'host-reported'` so it
655
+ * can never be mistaken for a construct-verified provider execution
656
+ * (task.executor also differs: `host:<hostRole>` vs `provider:<provider>:<model>`).
657
+ * When every task on the run is terminal, the run is finalized through the
658
+ * SAME honest terminal-status logic executeRun uses (finalizeRun) — completed
659
+ * vs completed-with-failures is decided once, not reimplemented here.
660
+ *
661
+ * Persistence is read-modify-write via store.saveRun, the same single-writer
662
+ * posture executeRun already has; concurrent submissions racing the same run
663
+ * are not safety-guarded here (team-mode row-locking is a deferred follow-up,
664
+ * see the ADR).
665
+ *
666
+ * @param {string} cwd
667
+ * @param {string} runId
668
+ * @param {string} taskId
669
+ * @param {{output:string, model?:string, provider?:string, reasoning?:string}} result
670
+ * @param {object} [opts] { env }
671
+ * @returns {Promise<{run:object, nextTask:object|null}>}
672
+ */
673
+ export async function submitHostTaskResult(cwd, runId, taskId, result = {}, { env = process.env } = {}) {
674
+ const config = loadConfig(cwd, env);
675
+ const { store } = resolveRunStore({ config, env, cwd });
676
+ const run = await store.loadRun(runId);
677
+ if (!run) throw hostResultError('RUN_NOT_FOUND', `Orchestration run not found: ${runId}`);
678
+
679
+ const task = (run.tasks || []).find((t) => t.id === taskId);
680
+ if (!task) throw hostResultError('TASK_NOT_FOUND', `Task ${taskId} not found on run ${runId}.`);
681
+
682
+ if (TERMINAL_RUN_STATUSES.has(run.status)) {
683
+ throw hostResultError('RUN_ALREADY_TERMINAL', `Run ${runId} is already terminal (${run.status}); no task result can be submitted.`);
684
+ }
685
+ if (task.status !== 'awaiting-host') {
686
+ throw hostResultError('TASK_NOT_AWAITING_HOST', `Task ${taskId} is not awaiting host execution (status: ${task.status}).`);
687
+ }
688
+ const { output, model = null, provider = null, reasoning = null } = result;
689
+ if (typeof output !== 'string' || !output.trim()) {
690
+ throw hostResultError('HOST_RESULT_EMPTY_OUTPUT', `Host task result for ${taskId} requires non-empty output.`);
691
+ }
692
+
693
+ task.output = output;
694
+ task.executor = `host:${run.hostRole || 'unknown'}`;
695
+ task.status = 'done';
696
+ task.finishedAt = new Date().toISOString();
697
+ task.executionState = 'executed';
698
+ // Host-reported, never independently verified — the distinguishing marker a
699
+ // reader needs so this never looks like a construct-verified provider
700
+ // execution (which carries no provenanceSource field at all).
701
+ task.provenanceSource = 'host-reported';
702
+ if (model) task.model = model;
703
+ if (provider) task.provider = provider;
704
+ if (reasoning) task.reasoning = reasoning;
705
+ delete task.hostPrompt;
706
+
707
+ emitTraceEvent({
708
+ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.completed', role: task.role, taskId: task.id,
709
+ metadata: {
710
+ runId, tenantId: run.tenantId, status: task.status, executionState: task.executionState,
711
+ specialistId: task.specialistId, packId: task.packId, promptVersion: task.promptVersion,
712
+ model: task.model, provider: task.provider, toolGrants: task.toolGrants, provenanceSource: task.provenanceSource,
713
+ },
714
+ });
715
+ emitRunEvent(runId, { type: 'task', taskId: task.id, role: task.role, status: task.status, executor: task.executor, ...(task.reasoning ? { reasoning: task.reasoning } : {}) });
716
+
717
+ run.updatedAt = new Date().toISOString();
718
+
719
+ const stillAwaiting = run.tasks.some((t) => t.status === 'awaiting-host');
720
+ if (stillAwaiting) {
721
+ await store.saveRun(run);
722
+ const nextTask = run.tasks.find((t) => t.status === 'awaiting-host');
723
+ return { run, nextTask };
724
+ }
725
+
726
+ const anyFailed = run.tasks.some((t) => t.status === 'failed');
727
+ const finalized = await finalizeRun(run, { cwd, env, config, store, anyFailed });
728
+ return { run: finalized, nextTask: null };
729
+ }
730
+
355
731
  /**
356
732
  * Plan and execute in one call (the common `orchestrate run` path).
357
733
  */
@@ -372,6 +748,15 @@ export async function runOrchestration(request = {}, { env = process.env, cwd =
372
748
  */
373
749
  export async function startRun(request = {}, { env = process.env, cwd = process.cwd(), workerBackend = null, fetchImpl } = {}) {
374
750
  const planned = await planRun(request, { env, cwd });
751
+
752
+ // Fire-and-forget: no claim, heartbeat, or retry — process death silently kills the run.
753
+ // Solo mode only; durable team/enterprise worker execution is a separate bead. runMode
754
+ // labels the record as in-process so orchestration_status never conflates it with a durable worker.
755
+
756
+ planned.runMode = 'in-process';
757
+ const { store } = resolveRunStore({ config: loadConfig(cwd, env), env, cwd });
758
+ await store.saveRun(planned);
759
+
375
760
  Promise.resolve()
376
761
  .then(() => executeRun(cwd, planned.runId, { env, workerBackend, fetchImpl }))
377
762
  .catch((err) => {
@@ -388,7 +773,11 @@ export function hostAdapterMetadata(run) {
388
773
  return {
389
774
  runId: run.runId,
390
775
  traceId: run.traceId,
776
+ tenantId: run.tenantId ?? null,
391
777
  status: run.status,
778
+ // LMCP-F4: the run-level aggregate of every task's executionState, or null
779
+ // for a zero-task run (prompt-only/host-direct) or a pre-F4 legacy record.
780
+ executionState: run.executionState ?? aggregateExecutionState(run.tasks) ?? null,
392
781
  requestedStrategy: e.requestedStrategy,
393
782
  effectiveStrategy: e.effectiveStrategy,
394
783
  executionMode: e.executionMode,
@@ -396,11 +785,29 @@ export function hostAdapterMetadata(run) {
396
785
  workerBackend: run.workerBackend,
397
786
  chainOfThought: run.chainOfThought ?? null,
398
787
  hostRole: run.hostRole,
399
- degraded: e.degraded,
400
- degradationReason: e.degradationReason,
788
+ degraded: run.degraded ?? e.degraded ?? false,
789
+ degradationReason: run.degradationReason ?? e.degradationReason ?? null,
401
790
  selectedProvider: e.selectedProvider,
402
791
  selectedModel: e.selectedModel,
403
- tasks: (run.tasks || []).map((t) => ({ id: t.id, role: t.role, status: t.status, executor: t.executor, reasoning: t.reasoning ?? null, output: t.output ?? null, error: t.error ?? null })),
792
+ // ?? null / ?? [] on every LMCP-F1 field: a pre-F1 run record carries none of
793
+ // these keys, so a typed absence is returned rather than `undefined`.
794
+ tasks: (run.tasks || []).map((t) => ({
795
+ id: t.id, role: t.role, status: t.status, executor: t.executor, tenantId: t.tenantId ?? null,
796
+ reasoning: t.reasoning ?? null, output: t.output ?? null, error: t.error ?? null,
797
+ specialistId: t.specialistId ?? null,
798
+ packId: t.packId ?? null,
799
+ promptVersion: t.promptVersion ?? null,
800
+ model: t.model ?? null,
801
+ provider: t.provider ?? null,
802
+ toolGrants: t.toolGrants ?? [],
803
+ executionState: t.executionState ?? null,
804
+ personaAvailable: t.personaAvailable ?? null,
805
+ // Present only on a host-executed task — absence (not a bare `false`)
806
+ // is what keeps a provider-verified task from ever reading the same as
807
+ // a host-reported one.
808
+ ...(t.provenanceSource ? { provenanceSource: t.provenanceSource } : {}),
809
+ ...(t.hostPrompt ? { hostPrompt: t.hostPrompt } : {}),
810
+ })),
404
811
  warnings: run.warnings || [],
405
812
  semantics: run.semantics,
406
813
  executionSemantics: run.executionSemantics,