@geraldmaron/construct 1.4.2 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +57 -45
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +273 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +104 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -9,10 +9,28 @@
9
9
  * 3. Runtime handoff: a single artifact validated against the schema referenced
10
10
  * by a producer→consumer contract, with mustContain post-conditions.
11
11
  *
12
+ * A contract's `postconditions` array is classified per-entry via
13
+ * `postconditionType` ('executable' | 'advisory' — see construct-rf26.12).
14
+ * Four real enforcement mechanisms back the executable ones:
15
+ * - `contract.output.mustContain` / `mustContainOneOf` / `mustMatchEnum` —
16
+ * declarative field-presence and enum checks against the handoff artifact.
17
+ * - `contract.output.schema` — recursive required+enum validation against
18
+ * the referenced lib/contract-schemas/*.json (not just top-level required).
19
+ * - `contract.postconditions[].check` — structured checks against the
20
+ * on-disk artifact (see validateArtifactPostconditions), for markdown
21
+ * deliverables (sections, citations, tables).
22
+ * - lib/specialists/postconditions.mjs POSTCONDITIONS table — binary,
23
+ * producer-keyed packet invariants, referenced from a postcondition via
24
+ * `enforcedVia: 'binary-postcondition'`.
25
+ * Advisory entries are prose that no mechanism above can decide (subjective
26
+ * quality, cross-document consistency, sequencing/process guarantees) —
27
+ * documented, not silently "enforced."
28
+ *
12
29
  * Surfaces:
13
30
  * - scripts/sync-specialists.mjs invokes validateContractsFile at sync time.
14
31
  * - bin/construct lint:contracts invokes the same path in CI.
15
32
  * - workflowContractValidate (runtime) invokes validateHandoff per handoff.
33
+ * - lib/contracts/coverage.mjs reports executable-vs-advisory coverage.
16
34
  */
17
35
 
18
36
  import { existsSync, readFileSync } from 'node:fs';
@@ -238,11 +256,12 @@ export function validateHandoff(opts = {}) {
238
256
  }
239
257
  }
240
258
 
241
- // A handoff carries a producer's output, which is the consumer's input. The
242
- // input contract is what the consumer expects to receive; check mustContain
243
- // against that first. Output.schema validation only applies when the artifact
244
- // declares the matching `type` (e.g. the consumer is forwarding its own
245
- // produced artifact downstream).
259
+ // A handoff carries a producer's output, which is the consumer's input: the
260
+ // same physical packet, checked from both ends. `input.mustContain` is what
261
+ // the consumer expects to receive; `output.mustContain` / `mustContainOneOf` /
262
+ // `mustMatchEnum` is what the producer is contractually required to have put
263
+ // there. Both apply to the same `artifact` value — there is no separate
264
+ // "producer's copy" to check.
246
265
  const inputMustContain = contract.input?.mustContain || [];
247
266
  for (const field of inputMustContain) {
248
267
  if (!hasField(artifact, field)) {
@@ -250,6 +269,36 @@ export function validateHandoff(opts = {}) {
250
269
  }
251
270
  }
252
271
 
272
+ const outputSpec = contract.output || {};
273
+ for (const field of outputSpec.mustContain || []) {
274
+ if (!isPresentValue(artifact?.[field])) {
275
+ errors.push(`artifact missing required output field: ${field}`);
276
+ }
277
+ }
278
+ for (const group of outputSpec.mustContainOneOf || []) {
279
+ if (!Array.isArray(group) || group.length === 0) continue;
280
+ if (!group.some((field) => isPresentValue(artifact?.[field]))) {
281
+ errors.push(`artifact missing one of required output fields: ${group.join('|')}`);
282
+ }
283
+ }
284
+ if (outputSpec.mustMatchEnum && typeof outputSpec.mustMatchEnum === 'object') {
285
+ for (const [field, allowed] of Object.entries(outputSpec.mustMatchEnum)) {
286
+ if (!Array.isArray(allowed) || allowed.length === 0) continue;
287
+ const value = artifact?.[field];
288
+ if (value === undefined || value === null) continue;
289
+ if (!allowed.includes(value)) {
290
+ errors.push(`artifact field '${field}' value '${value}' not in allowed set: ${allowed.join(', ')}`);
291
+ }
292
+ }
293
+ }
294
+
295
+ // Output.schema validation only applies when the artifact declares the
296
+ // matching `type` — several contracts share one schema file (e.g.
297
+ // implementation.json), so `type` disambiguates which schema this
298
+ // particular packet claims to satisfy. Once matched, the check recurses
299
+ // into nested `required` and `enum` constraints (e.g. a `verificationChecklist`
300
+ // sub-object's required booleans, or a `findings[].severity` enum) rather
301
+ // than stopping at top-level required-field presence.
253
302
  const outputType = contract.output?.type;
254
303
  const schemaRef = contract.output?.schema;
255
304
  if (schemaRef && outputType && artifact && artifact.type === outputType) {
@@ -258,9 +307,7 @@ export function validateHandoff(opts = {}) {
258
307
  errors.push(`contract output.schema '${schemaRef}' does not exist on disk`);
259
308
  } else {
260
309
  const schema = readJson(schemaPath);
261
- for (const field of schema.required || []) {
262
- if (!hasField(artifact, field)) errors.push(`artifact missing schema-required field: ${field}`);
263
- }
310
+ collectSchemaShapeErrors(artifact, schema, 'artifact', errors);
264
311
  }
265
312
  }
266
313
 
@@ -304,6 +351,43 @@ function hasField(obj, field) {
304
351
  return Object.prototype.hasOwnProperty.call(obj, field) && obj[field] != null && obj[field] !== '';
305
352
  }
306
353
 
354
+ // Stricter than hasField: an empty array counts as "declared but empty", not
355
+ // present. Used for output.mustContain / mustContainOneOf, mirroring
356
+ // lib/specialist-contracts.mjs's validatePacket — a reviewer with
357
+ // findings: [] and no noIssuesFoundAt must not read as "findings present".
358
+
359
+ function isPresentValue(value) {
360
+ if (value === undefined || value === null) return false;
361
+ if (typeof value === 'string' && value.trim() === '') return false;
362
+ if (Array.isArray(value) && value.length === 0) return false;
363
+ return true;
364
+ }
365
+
366
+ /**
367
+ * Recursively validate `value` against a JSON-schema-shaped `schema`,
368
+ * collecting `required` and `enum` violations (both top-level and nested
369
+ * through `properties` and array `items`). Bounded to the constraint kinds
370
+ * contract output schemas actually use — not a general JSON Schema validator.
371
+ */
372
+ function collectSchemaShapeErrors(value, schema, pathLabel, errors) {
373
+ if (!schema || typeof schema !== 'object') return;
374
+ if (value === undefined || value === null) return;
375
+ for (const field of schema.required || []) {
376
+ if (!hasField(value, field)) errors.push(`${pathLabel} missing schema-required field: ${field}`);
377
+ }
378
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
379
+ errors.push(`${pathLabel} value '${value}' not in schema enum: ${schema.enum.join(', ')}`);
380
+ }
381
+ if (schema.properties && typeof value === 'object' && !Array.isArray(value)) {
382
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
383
+ if (key in value) collectSchemaShapeErrors(value[key], propSchema, `${pathLabel}.${key}`, errors);
384
+ }
385
+ }
386
+ if (schema.type === 'array' && schema.items && Array.isArray(value)) {
387
+ value.forEach((item, i) => collectSchemaShapeErrors(item, schema.items, `${pathLabel}[${i}]`, errors));
388
+ }
389
+ }
390
+
307
391
  /**
308
392
  * Validate team boundary enforcement on a contract.
309
393
  * Returns array of error strings (empty if validation passes).
@@ -471,21 +555,26 @@ function claimsHaveCitations(body) {
471
555
 
472
556
  /**
473
557
  * Run structured postcondition checks against a handoff's output artifact on
474
- * disk. Postconditions can be strings (descriptive, ignored here) or objects
475
- * with { id, check, field?, section? }. Returns an array of error strings.
558
+ * disk. Every postcondition is classified via `postconditionType`:
559
+ * `executable` entries carry a `check` this function runs; `advisory`
560
+ * entries are documented but deliberately not mechanically checked (see
561
+ * lib/decisions/registry.mjs's contract-decision classification). Bare
562
+ * strings are a legacy shape, tolerated but never executed. Returns an
563
+ * array of error strings.
476
564
  */
477
565
  export function validateArtifactPostconditions({ contract, artifactPath, cwd = process.cwd(), rootDir } = {}) {
478
566
  if (!contract?.postconditions?.length) return [];
567
+ const executableDeclared = contract.postconditions.filter((p) => typeof p === 'object' && p !== null && p.check);
479
568
  if (!artifactPath || !existsSync(artifactPath)) {
480
- const structured = contract.postconditions.filter((p) => typeof p === 'object');
481
- if (structured.length === 0) return [];
569
+ if (executableDeclared.length === 0) return [];
482
570
  return [`postcondition checks declared but artifact not found on disk: ${artifactPath}`];
483
571
  }
484
572
  const text = readFileSync(artifactPath, 'utf8');
485
573
  const { frontmatter, body } = parseArtifactFrontmatter(text);
486
574
  const errors = [];
487
575
  for (const pc of contract.postconditions) {
488
- if (typeof pc !== 'object') continue;
576
+ if (typeof pc !== 'object' || pc === null) continue;
577
+ if (pc.postconditionType === 'advisory' || !pc.check) continue;
489
578
  const tag = pc.id || pc.check;
490
579
  switch (pc.check) {
491
580
  case 'artifact-has-frontmatter-field': {
@@ -100,6 +100,49 @@ function violationMatchesTail(tail, contractId, direction, missing, packet) {
100
100
  && JSON.stringify(tailKeys) === JSON.stringify(keys);
101
101
  }
102
102
 
103
+ const BARE_GOAL_FIELD_LABELS = ['intent', 'workCategory', 'riskFlags', 'acceptanceCriteria'];
104
+
105
+ const RESEARCHER_PROBE_MISSING = [
106
+ 'artifact missing required field: question',
107
+ 'artifact missing required field: method',
108
+ 'artifact missing required field: sources',
109
+ 'artifact missing required field: findings',
110
+ 'artifact missing required field: confidence',
111
+ ];
112
+
113
+ const A11Y_PROBE_MISSING = [
114
+ 'artifact missing required field: findings',
115
+ 'artifact missing required field: wcagCriterion',
116
+ 'artifact missing required field: userImpact',
117
+ ];
118
+
119
+ function missingBareGoalConstructFields(missing) {
120
+ if (!Array.isArray(missing) || missing.length !== BARE_GOAL_FIELD_LABELS.length) return false;
121
+ return BARE_GOAL_FIELD_LABELS.every((field) =>
122
+ missing.some((entry) => String(entry).includes(field)),
123
+ );
124
+ }
125
+
126
+ function isBareGoalConstructViolation(row) {
127
+ if (row?.contractId !== 'construct-to-orchestrator') return false;
128
+ if (!missingBareGoalConstructFields(row.missing)) return false;
129
+ const keys = row.packet_keys ?? row.packetKeys;
130
+ return Array.isArray(keys) && keys.length === 1 && keys[0] === 'goal';
131
+ }
132
+
133
+ function isEmptyProbeViolation(row, contractId, expectedMissing) {
134
+ if (row?.contractId !== contractId) return false;
135
+ const keys = row.packet_keys ?? row.packetKeys;
136
+ if (!Array.isArray(keys) || keys.length !== 0) return false;
137
+ return JSON.stringify(row.missing) === JSON.stringify(expectedMissing);
138
+ }
139
+
140
+ export function isDevSessionContractNoise(row) {
141
+ return isBareGoalConstructViolation(row)
142
+ || isEmptyProbeViolation(row, 'researcher-to-architect', RESEARCHER_PROBE_MISSING)
143
+ || isEmptyProbeViolation(row, 'accessibility-to-qa', A11Y_PROBE_MISSING);
144
+ }
145
+
103
146
  function sha256(input) { return createHash('sha256').update(input).digest('hex'); }
104
147
 
105
148
  function readLastAgent() {
@@ -160,12 +203,12 @@ export function logViolation(contractId, direction, missing, packet, extra = {})
160
203
  * Rotated segments are intentionally not included — this is the doctor
161
204
  * surface, scoped to the active window.
162
205
  */
163
- export function recentViolations({ windowMs = 24 * 60 * 60 * 1000, repoRoot } = {}) {
206
+ export function recentViolations({ windowMs = 24 * 60 * 60 * 1000, repoRoot, excludeDevNoise = false, limit = Infinity, now = Date.now() } = {}) {
164
207
  const file = logFile(repoRoot);
165
208
  if (!existsSync(file)) return [];
166
209
  const supersedeCutoff = readViolationSupersedeCutoff(repoRoot);
167
210
  try {
168
- const windowCutoff = Date.now() - windowMs;
211
+ const windowCutoff = now - windowMs;
169
212
  return readFileSync(file, 'utf8')
170
213
  .trim()
171
214
  .split('\n')
@@ -175,8 +218,11 @@ export function recentViolations({ windowMs = 24 * 60 * 60 * 1000, repoRoot } =
175
218
  if (!r) return false;
176
219
  const ts = new Date(r.ts).getTime();
177
220
  if (supersedeCutoff != null && ts <= supersedeCutoff) return false;
178
- return ts >= windowCutoff;
179
- });
221
+ if (ts < windowCutoff) return false;
222
+ if (excludeDevNoise && isDevSessionContractNoise(r)) return false;
223
+ return true;
224
+ })
225
+ .slice(-limit);
180
226
  } catch { return []; }
181
227
  }
182
228
 
@@ -0,0 +1,69 @@
1
+ /**
2
+ * lib/db/migrate.mjs — Postgres migration runner.
3
+ *
4
+ * The runner only operates when a SQL client is explicitly configured. Migration
5
+ * files are repo-owned SQL, so sql.unsafe is limited to this static directory.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const DB_DIR = path.dirname(fileURLToPath(import.meta.url));
13
+ export const MIGRATIONS_DIR = path.join(DB_DIR, 'migrations');
14
+ export const MIGRATIONS_TABLE = 'construct_schema_migrations';
15
+
16
+ function migrationId(filePath) {
17
+ return path.basename(filePath).replace(/\.sql$/i, '');
18
+ }
19
+
20
+ export function listMigrationFiles({ migrationsDir = MIGRATIONS_DIR } = {}) {
21
+ if (!fs.existsSync(migrationsDir)) return [];
22
+ return fs.readdirSync(migrationsDir)
23
+ .filter((name) => /^\d+_.+\.sql$/i.test(name))
24
+ .sort()
25
+ .map((name) => path.join(migrationsDir, name));
26
+ }
27
+
28
+ export async function ensureMigrationsTable(sql) {
29
+ await sql`
30
+ CREATE TABLE IF NOT EXISTS construct_schema_migrations (
31
+ id TEXT PRIMARY KEY,
32
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
33
+ )
34
+ `;
35
+ }
36
+
37
+ export async function getAppliedMigrations(sql) {
38
+ await ensureMigrationsTable(sql);
39
+ const rows = await sql`SELECT id, applied_at FROM construct_schema_migrations ORDER BY id`;
40
+ return rows.map((row) => ({ id: row.id, appliedAt: row.applied_at }));
41
+ }
42
+
43
+ export async function getMigrationStatus(sql, opts = {}) {
44
+ const files = listMigrationFiles(opts);
45
+ const applied = new Set((await getAppliedMigrations(sql)).map((row) => row.id));
46
+ return files.map((file) => {
47
+ const id = migrationId(file);
48
+ return { id, file, applied: applied.has(id) };
49
+ });
50
+ }
51
+
52
+ export async function applyMigrations(sql, opts = {}) {
53
+ await ensureMigrationsTable(sql);
54
+ const applied = new Set((await getAppliedMigrations(sql)).map((row) => row.id));
55
+ const appliedNow = [];
56
+
57
+ for (const file of listMigrationFiles(opts)) {
58
+ const id = migrationId(file);
59
+ if (applied.has(id)) continue;
60
+ const body = fs.readFileSync(file, 'utf8');
61
+ await sql.begin(async (tx) => {
62
+ await tx.unsafe(body);
63
+ await tx`INSERT INTO construct_schema_migrations (id) VALUES (${id})`;
64
+ });
65
+ appliedNow.push(id);
66
+ }
67
+
68
+ return { applied: appliedNow, status: await getMigrationStatus(sql, opts) };
69
+ }
@@ -0,0 +1,9 @@
1
+ CREATE TABLE IF NOT EXISTS construct_orchestration_runs (
2
+ run_id TEXT,
3
+ project TEXT,
4
+ created_at TEXT,
5
+ status TEXT,
6
+ execution_mode TEXT,
7
+ payload JSONB,
8
+ PRIMARY KEY (run_id, project)
9
+ );
@@ -0,0 +1,50 @@
1
+ CREATE TABLE IF NOT EXISTS construct_queue_items (
2
+ project TEXT NOT NULL,
3
+ tenant_id TEXT NOT NULL DEFAULT 'local',
4
+ queue_name TEXT NOT NULL DEFAULT 'intake',
5
+ item_id TEXT NOT NULL,
6
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (
7
+ status IN ('pending', 'claimed', 'processed', 'skipped', 'awaiting_approval', 'dead_letter')
8
+ ),
9
+ payload JSONB NOT NULL,
10
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
11
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
12
+ available_at TIMESTAMPTZ NOT NULL DEFAULT now(),
13
+ attempt INTEGER NOT NULL DEFAULT 0,
14
+ max_attempts INTEGER NOT NULL DEFAULT 3,
15
+ claimed_by TEXT,
16
+ lease_expires_at TIMESTAMPTZ,
17
+ processed_at TIMESTAMPTZ,
18
+ processed_by TEXT,
19
+ skipped_at TIMESTAMPTZ,
20
+ skipped_by TEXT,
21
+ terminal_reason TEXT,
22
+ PRIMARY KEY (project, tenant_id, queue_name, item_id)
23
+ );
24
+
25
+ CREATE INDEX IF NOT EXISTS construct_queue_items_claim_idx
26
+ ON construct_queue_items (project, tenant_id, queue_name, status, available_at, created_at);
27
+
28
+ CREATE INDEX IF NOT EXISTS construct_queue_items_lease_idx
29
+ ON construct_queue_items (project, tenant_id, queue_name, lease_expires_at)
30
+ WHERE status = 'claimed';
31
+
32
+ CREATE TABLE IF NOT EXISTS construct_queue_claims (
33
+ claim_id TEXT PRIMARY KEY,
34
+ project TEXT NOT NULL,
35
+ tenant_id TEXT NOT NULL DEFAULT 'local',
36
+ queue_name TEXT NOT NULL DEFAULT 'intake',
37
+ item_id TEXT NOT NULL,
38
+ worker_id TEXT NOT NULL,
39
+ attempt INTEGER NOT NULL,
40
+ claimed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
41
+ lease_expires_at TIMESTAMPTZ NOT NULL,
42
+ released_at TIMESTAMPTZ,
43
+ release_reason TEXT,
44
+ FOREIGN KEY (project, tenant_id, queue_name, item_id)
45
+ REFERENCES construct_queue_items (project, tenant_id, queue_name, item_id)
46
+ ON DELETE CASCADE
47
+ );
48
+
49
+ CREATE INDEX IF NOT EXISTS construct_queue_claims_item_idx
50
+ ON construct_queue_claims (project, tenant_id, queue_name, item_id, claimed_at DESC);
@@ -0,0 +1,17 @@
1
+ CREATE TABLE IF NOT EXISTS construct_workers (
2
+ project TEXT NOT NULL,
3
+ tenant_id TEXT NOT NULL DEFAULT 'local',
4
+ worker_id TEXT NOT NULL,
5
+ host TEXT,
6
+ pid INTEGER,
7
+ capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
8
+ registered_at TIMESTAMPTZ NOT NULL DEFAULT now(),
9
+ heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT now(),
10
+ lease_ttl_seconds INTEGER NOT NULL DEFAULT 120,
11
+ status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'stale', 'stopped')),
12
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
13
+ PRIMARY KEY (project, tenant_id, worker_id)
14
+ );
15
+
16
+ CREATE INDEX IF NOT EXISTS construct_workers_heartbeat_idx
17
+ ON construct_workers (project, tenant_id, heartbeat_at);
@@ -0,0 +1,16 @@
1
+ CREATE TABLE IF NOT EXISTS construct_trace_events (
2
+ project TEXT NOT NULL,
3
+ tenant_id TEXT NOT NULL DEFAULT 'local',
4
+ trace_id TEXT NOT NULL,
5
+ span_id TEXT NOT NULL,
6
+ parent_span_id TEXT,
7
+ event_type TEXT NOT NULL,
8
+ role TEXT,
9
+ task_id TEXT,
10
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
11
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
12
+ PRIMARY KEY (project, tenant_id, trace_id, span_id)
13
+ );
14
+
15
+ CREATE INDEX IF NOT EXISTS construct_trace_events_trace_idx
16
+ ON construct_trace_events (project, tenant_id, trace_id, created_at);
@@ -0,0 +1,15 @@
1
+ CREATE TABLE IF NOT EXISTS construct_shared_memory (
2
+ id TEXT NOT NULL,
3
+ project TEXT NOT NULL,
4
+ tenant_id TEXT NOT NULL DEFAULT 'local',
5
+ category TEXT NOT NULL,
6
+ summary TEXT,
7
+ content TEXT,
8
+ tags JSONB NOT NULL DEFAULT '[]'::jsonb,
9
+ provenance JSONB NOT NULL,
10
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
11
+ PRIMARY KEY (project, tenant_id, id)
12
+ );
13
+
14
+ CREATE INDEX IF NOT EXISTS construct_shared_memory_project_idx
15
+ ON construct_shared_memory (project, tenant_id, created_at);
@@ -0,0 +1,5 @@
1
+ ALTER TABLE construct_orchestration_runs
2
+ ADD COLUMN IF NOT EXISTS tenant_id TEXT NOT NULL DEFAULT 'local';
3
+
4
+ CREATE INDEX IF NOT EXISTS construct_orchestration_runs_tenant_idx
5
+ ON construct_orchestration_runs (project, tenant_id, created_at);
@@ -14,11 +14,9 @@
14
14
  "architect-to-data-engineer",
15
15
  "architect-to-evaluator",
16
16
  "architect-to-legal-compliance",
17
- "construct-to-rd-lead",
18
17
  "product-manager-to-architect",
19
18
  "product-manager-to-data-analyst",
20
19
  "product-manager-to-ux-researcher",
21
- "qa-to-test-automation",
22
20
  "researcher-to-product-manager",
23
21
  "rule:common/comments",
24
22
  "rule:common/no-fabrication",
@@ -165,8 +165,9 @@ function contractDecisions({ repoRoot, bindings }) {
165
165
  const checks = [];
166
166
  let advisoryPostconditions = 0;
167
167
  for (const pc of c.postconditions || []) {
168
- if (typeof pc === 'string') advisoryPostconditions += 1;
169
- else if (pc && pc.check) checks.push(pc.id || pc.check);
168
+ if (typeof pc !== 'object' || pc === null) { advisoryPostconditions += 1; continue; }
169
+ if (pc.check) checks.push(pc.id || pc.check);
170
+ else if (pc.postconditionType === 'advisory' || !pc.postconditionType) advisoryPostconditions += 1;
170
171
  }
171
172
  const enforcingTests = bindings.get(c.id) || [];
172
173
  out.push({
@@ -25,7 +25,7 @@ export const PARITY_CONTRACT = {
25
25
  queue: {
26
26
  parityClass: 'parity',
27
27
  capability: 'Work intake queue (enqueue, list pending, mark processed)',
28
- rationale: 'Filesystem queue (solo) and Postgres queue (team) implement one interface via lib/intake/queue.mjs.',
28
+ rationale: 'One kind:queue contract via lib/intake/queue.mjs; filesystem is the solo default and Postgres is the team/enterprise default with explicit degraded git fallback.',
29
29
  evidence: 'tests/intake-queue-factory.test.mjs',
30
30
  },
31
31
  memory: {
@@ -7,13 +7,20 @@
7
7
  * (`getDeploymentMode(env, {cwd})`); the resource topology for each mode
8
8
  * is derived here, not stored.
9
9
  */
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
10
12
  import { loadProjectConfig } from './config/project-config.mjs';
13
+ import { resolveTenantContext, TenantResolutionError } from './tenant/context.mjs';
11
14
 
12
15
  export const DEPLOYMENT_MODES = ['solo', 'team', 'enterprise'];
13
16
  export const DEFAULT_DEPLOYMENT_MODE = 'solo';
14
17
 
15
18
  export const DEPLOYMENT_MODE_ENV_KEY = 'CONSTRUCT_DEPLOYMENT_MODE';
16
19
 
20
+ // The queue dimension names the default kind:'queue' provider, not a hardcoded
21
+ // backend. Solo keeps the zero-dependency local provider; team/enterprise now
22
+ // default to Postgres so distributed claims are row-locked and lease-backed.
23
+
17
24
  const RESOURCE_TOPOLOGY = {
18
25
  solo: {
19
26
  queue: 'filesystem',
@@ -45,8 +52,8 @@ const RESOURCE_TOPOLOGY = {
45
52
  };
46
53
 
47
54
  const MODE_DESCRIPTIONS = {
48
- solo: 'Individual usage. Filesystem queue, local repo state, optional Postgres/Docker/telemetry.',
49
- team: 'Shared usage. Postgres queue, shared memory, Docker worker pool, central telemetry, brokered MCP.',
55
+ solo: 'Individual usage. Filesystem intake queue, local repo state, optional Postgres/Docker/telemetry.',
56
+ team: 'Shared usage. Postgres intake queue, shared memory, Docker worker pool, central telemetry, brokered MCP.',
50
57
  enterprise: 'Hardened usage. Adds tenant isolation, RBAC/ABAC scaffolding, isolated worker containers, signed MCP allowlists, mandatory audit on top of team.',
51
58
  };
52
59
 
@@ -80,7 +87,76 @@ export function resolveResourceMode(mode = DEFAULT_DEPLOYMENT_MODE) {
80
87
  return { ...topology };
81
88
  }
82
89
 
90
+ // ADR-0057 (A7) IMPLEMENT-NOW: enterprise mode without a resolvable tenant id
91
+ // fails closed at startup rather than running unlabeled multi-tenant traffic.
92
+ // Solo/team resolve to the explicit default tenant 'local' and never throw
93
+ // here. Call once per process/runtime start (CLI entry, MCP server boot,
94
+ // orchestration runtime) — resolveTenantContext itself is cheap and pure, so
95
+ // re-validating per call is safe but redundant beyond the first.
96
+
97
+ export function validateTenantAtStartup(env = process.env, { cwd } = {}) {
98
+ const mode = getDeploymentMode(env, { cwd });
99
+ let config = null;
100
+ try {
101
+ ({ config } = loadProjectConfig(cwd, env));
102
+ } catch { /* loader is best-effort — resolveTenantContext still sees env */ }
103
+ return resolveTenantContext({ env, config, mode });
104
+ }
105
+
106
+ export { TenantResolutionError };
107
+
83
108
  export function describeResourceLine(mode = DEFAULT_DEPLOYMENT_MODE) {
84
109
  const r = resolveResourceMode(mode);
85
110
  return `queue:${r.queue} · memory:${r.memory} · workers:${r.workers} · telemetry:${r.telemetry}`;
86
111
  }
112
+
113
+ // Thrown when a team/enterprise mode requires a capability that is unavailable
114
+ // and the caller has not opted into degraded operation via CONSTRUCT_DEGRADED_OK.
115
+
116
+ export class DeploymentModeError extends Error {
117
+ constructor(message) {
118
+ super(message);
119
+ this.name = 'DeploymentModeError';
120
+ }
121
+ }
122
+
123
+ /**
124
+ * requireTeamCapabilityOrDegrade(subsystem, isAvailable, env, { cwd } = {})
125
+ *
126
+ * Enforces mode honesty: team/enterprise modes must not silently fall back to
127
+ * solo behavior when a required subsystem is unavailable.
128
+ *
129
+ * - If mode is solo: no-op (solo degrades gracefully by design).
130
+ * - If mode is team/enterprise AND isAvailable is true: no-op.
131
+ * - If mode is team/enterprise AND isAvailable is false:
132
+ * - If CONSTRUCT_DEGRADED_OK includes `subsystem` → logs a durable degradation
133
+ * record to `.cx/degradation.jsonl` (if cwd is available) and returns normally.
134
+ * - Otherwise → throws DeploymentModeError.
135
+ */
136
+ export function requireTeamCapabilityOrDegrade(subsystem, isAvailable, env = process.env, { cwd } = {}) {
137
+ const mode = getDeploymentMode(env, { cwd });
138
+ if (mode === 'solo') return; // solo is always best-effort
139
+ if (isAvailable) return;
140
+
141
+ const degradedOk = String(env?.CONSTRUCT_DEGRADED_OK || '').split(',').map((s) => s.trim()).filter(Boolean);
142
+ if (!degradedOk.includes(subsystem)) {
143
+ throw new DeploymentModeError(
144
+ `team mode requires ${subsystem}; set CONSTRUCT_DEGRADED_OK=${subsystem} to allow degraded operation`,
145
+ );
146
+ }
147
+
148
+ // Degraded operation is explicitly permitted — write a durable record and continue.
149
+ const record = JSON.stringify({
150
+ ts: new Date().toISOString(),
151
+ mode,
152
+ subsystem,
153
+ degradedOk: true,
154
+ });
155
+ if (cwd) {
156
+ try {
157
+ const cxDir = path.join(cwd, '.cx');
158
+ fs.mkdirSync(cxDir, { recursive: true });
159
+ fs.appendFileSync(path.join(cxDir, 'degradation.jsonl'), record + '\n', 'utf8');
160
+ } catch { /* degradation log is best-effort */ }
161
+ }
162
+ }
@@ -53,7 +53,16 @@ const MERMAID_INIT = `%%{init: {
53
53
  'look': 'handDrawn',
54
54
  'fontFamily': '${MERMAID_FONT}',
55
55
  'flowchart': {
56
- 'htmlLabels': false
56
+ 'htmlLabels': false,
57
+ 'nodeSpacing': 46,
58
+ 'rankSpacing': 56,
59
+ 'padding': 12
60
+ },
61
+ 'quadrantChart': {
62
+ 'chartWidth': 620,
63
+ 'chartHeight': 460,
64
+ 'pointLabelFontSize': 13,
65
+ 'pointRadius': 4
57
66
  },
58
67
  'themeVariables': {
59
68
  'fontFamily': '${MERMAID_FONT}',
package/lib/distill.mjs CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  UTF8_TEXT_EXTS,
18
18
  extractDocumentText as extractSharedDocumentText,
19
19
  } from './document-extract.mjs';
20
+ import { isMainModule } from './roots.mjs';
20
21
 
21
22
  const TEXT_EXTS = EXTRACTABLE_DOCUMENT_EXTS;
22
23
 
@@ -359,7 +360,9 @@ export async function distill(dir, { format = 'summary', query = '', mode = 'aut
359
360
  const result = await withGenAiSpan('chat', {
360
361
  [GenAiAttrs.SYSTEM]: 'anthropic',
361
362
  [GenAiAttrs.OPERATION_NAME]: 'chat',
362
- [GenAiAttrs.REQUEST_MODEL]: process.env.CONSTRUCT_MODEL || 'claude-3-5-sonnet',
363
+ // Telemetry label only — the real model is whatever the user's claude CLI
364
+ // resolves; never imply a pinned model id here.
365
+ [GenAiAttrs.REQUEST_MODEL]: process.env.CONSTRUCT_MODEL || 'claude-cli-default',
363
366
  'construct.operation': 'distill',
364
367
  'construct.prompt_chars': prompt.length,
365
368
  }, async (span) => {
@@ -412,7 +415,7 @@ export async function runDistillCli(argv = process.argv.slice(2)) {
412
415
  });
413
416
  }
414
417
 
415
- const isMain = import.meta.url === `file://${process.argv[1]}`;
418
+ const isMain = isMainModule(import.meta.url);
416
419
  if (isMain) {
417
420
  const { positional, flags } = parseArgs(process.argv.slice(2));
418
421
  const dir = positional[0];
@@ -23,6 +23,7 @@ import { statSync, readFileSync, existsSync } from "node:fs";
23
23
  import { spawnSync } from "node:child_process";
24
24
  import { hasIntakeReference } from "./intake/traceability.mjs";
25
25
  import { ensureCxDir } from "./project-init-shared.mjs";
26
+ import { isMainModule } from './roots.mjs';
26
27
 
27
28
  const args = process.argv.slice(2);
28
29
  const quickMode = args.includes("--quick");
@@ -463,7 +464,7 @@ function main() {
463
464
  process.exit(0);
464
465
  }
465
466
 
466
- if (import.meta.url === `file://${process.argv[1]}`) {
467
+ if (isMainModule(import.meta.url)) {
467
468
  main();
468
469
  }
469
470
 
@@ -85,6 +85,7 @@ export async function runCli(args) {
85
85
  import('./watchers/service-health.mjs'),
86
86
  import('./watchers/bd-watch.mjs'),
87
87
  import('./watchers/consistency.mjs'),
88
+ import('./watchers/orchestration-runs.mjs'),
88
89
  ]);
89
90
  for (const w of watchers) {
90
91
  const r = await w.tick();