@geraldmaron/construct 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -27,19 +27,41 @@
|
|
|
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';
|
|
48
|
+
import { writeFileSync } from 'node:fs';
|
|
49
|
+
import { join } from 'node:path';
|
|
33
50
|
|
|
34
51
|
import { routeRequest } from '../orchestration-policy.mjs';
|
|
35
52
|
import { resolveExecution } from '../embedded-contract/execution.mjs';
|
|
36
53
|
import { detectHostCapabilities } from '../host-capabilities.mjs';
|
|
37
54
|
import { loadProjectConfig } from '../config/project-config.mjs';
|
|
38
|
-
import { CHAIN_OF_THOUGHT_MODES } from '../config/schema.mjs';
|
|
55
|
+
import { CHAIN_OF_THOUGHT_MODES, DEFAULT_PROJECT_CONFIG } from '../config/schema.mjs';
|
|
39
56
|
import { newTraceId, newSpanId, emitTraceEvent } from '../worker/trace.mjs';
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
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';
|
|
61
|
+
import { roleHoldsWebCapability } from './web-capability.mjs';
|
|
42
62
|
import { emitRunEvent, isCancelRequested, clearCancel } from './events.mjs';
|
|
63
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
64
|
+
import { resolveTenantContext } from '../tenant/context.mjs';
|
|
43
65
|
|
|
44
66
|
export const WORKER_BACKENDS = WORKER_BACKEND_SET;
|
|
45
67
|
export const DEFAULT_WORKER_BACKEND = INLINE;
|
|
@@ -85,7 +107,7 @@ function pickExecution(data) {
|
|
|
85
107
|
};
|
|
86
108
|
}
|
|
87
109
|
|
|
88
|
-
function buildTasks(route) {
|
|
110
|
+
function buildTasks(route, tenantId) {
|
|
89
111
|
const reasons = route.dispatchReasons || {};
|
|
90
112
|
const handoffByProducer = new Map();
|
|
91
113
|
for (const edge of route.contractChain || []) {
|
|
@@ -96,6 +118,7 @@ function buildTasks(route) {
|
|
|
96
118
|
id: `t${i + 1}`,
|
|
97
119
|
seq: i,
|
|
98
120
|
role,
|
|
121
|
+
tenantId,
|
|
99
122
|
reason: reasons[role] || null,
|
|
100
123
|
handoffContract: handoffByProducer.get(role.replace(/^cx-/, '')) || null,
|
|
101
124
|
status: 'queued',
|
|
@@ -108,11 +131,26 @@ function buildTasks(route) {
|
|
|
108
131
|
}));
|
|
109
132
|
}
|
|
110
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
|
+
|
|
111
149
|
// The worker backend is a deployment concern: an explicit option wins, then the
|
|
112
150
|
// run's recorded backend, then config (orchestration.workerBackend), then the
|
|
113
151
|
// inline default. An unknown value falls back to inline rather than guessing.
|
|
114
152
|
|
|
115
|
-
function resolveWorkerBackend({ explicit, run, config }) {
|
|
153
|
+
export function resolveWorkerBackend({ explicit, run, config }) {
|
|
116
154
|
const candidate = explicit || run?.workerBackend || config?.orchestration?.workerBackend || DEFAULT_WORKER_BACKEND;
|
|
117
155
|
return WORKER_BACKENDS.includes(candidate) ? candidate : DEFAULT_WORKER_BACKEND;
|
|
118
156
|
}
|
|
@@ -127,14 +165,32 @@ function resolveChainOfThought({ explicit, run, config }) {
|
|
|
127
165
|
return CHAIN_OF_THOUGHT.includes(candidate) ? candidate : DEFAULT_CHAIN_OF_THOUGHT;
|
|
128
166
|
}
|
|
129
167
|
|
|
130
|
-
|
|
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) {
|
|
131
177
|
try {
|
|
132
|
-
|
|
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 };
|
|
133
185
|
} catch {
|
|
134
|
-
return {};
|
|
186
|
+
return { config: {}, configWarnings: [] };
|
|
135
187
|
}
|
|
136
188
|
}
|
|
137
189
|
|
|
190
|
+
function loadConfig(cwd, env) {
|
|
191
|
+
return loadConfigWithWarnings(cwd, env).config;
|
|
192
|
+
}
|
|
193
|
+
|
|
138
194
|
/**
|
|
139
195
|
* Plan a run: resolve the execution contract, decompose into a specialist chain,
|
|
140
196
|
* and persist a durable run record (status `planned`). Pure of model calls.
|
|
@@ -150,8 +206,17 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
|
|
|
150
206
|
workerBackend: explicitWorkerBackend = null,
|
|
151
207
|
} = request;
|
|
152
208
|
|
|
153
|
-
const config =
|
|
154
|
-
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 }) });
|
|
155
220
|
|
|
156
221
|
const execData = resolveExecution(
|
|
157
222
|
{ workflowType, requestedStrategy, useConstruct, host, hostModel, hostProvider },
|
|
@@ -169,11 +234,12 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
|
|
|
169
234
|
// orchestrated execution; prompt-only and host-direct own none, and the run
|
|
170
235
|
// records that rather than implying orchestration that did not happen.
|
|
171
236
|
const orchestrates = execData.effectiveStrategy === 'orchestrated';
|
|
172
|
-
const tasks = orchestrates ? buildTasks(route) : [];
|
|
237
|
+
const tasks = orchestrates ? buildTasks(route, tenantId) : [];
|
|
173
238
|
|
|
174
239
|
const run = {
|
|
175
240
|
runId,
|
|
176
241
|
traceId,
|
|
242
|
+
tenantId,
|
|
177
243
|
createdAt: now,
|
|
178
244
|
updatedAt: now,
|
|
179
245
|
request: { summary: truncate(text, 200), workflowType, requestedStrategy },
|
|
@@ -192,7 +258,7 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
|
|
|
192
258
|
},
|
|
193
259
|
tasks,
|
|
194
260
|
status: 'planned',
|
|
195
|
-
warnings: [...(execData.warnings || []), ...storeWarnings],
|
|
261
|
+
warnings: [...(execData.warnings || []), ...storeWarnings, ...configWarnings],
|
|
196
262
|
semantics: RUNTIME_SEMANTICS,
|
|
197
263
|
executionSemantics: execData.semantics,
|
|
198
264
|
};
|
|
@@ -200,9 +266,9 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
|
|
|
200
266
|
await store.saveRun(run);
|
|
201
267
|
emitTraceEvent({
|
|
202
268
|
rootDir: cwd, env, traceId, spanId: newSpanId(), eventType: 'task_graph.created',
|
|
203
|
-
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 },
|
|
204
270
|
});
|
|
205
|
-
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 });
|
|
206
272
|
return run;
|
|
207
273
|
}
|
|
208
274
|
|
|
@@ -213,38 +279,232 @@ function prepareTaskInline(task) {
|
|
|
213
279
|
task.executor = 'inline:prepared';
|
|
214
280
|
task.status = 'prepared';
|
|
215
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';
|
|
287
|
+
// A prepared web-capable task reached no web: mark it so a host never infers live
|
|
288
|
+
// web access from a task that only planned (ADR-0050).
|
|
289
|
+
if (roleHoldsWebCapability(task.role)) task.webCapability = 'prepare-only';
|
|
216
290
|
}
|
|
217
291
|
|
|
218
292
|
// The provider backend executes one task against the configured model. A failed
|
|
219
293
|
// task is recorded (status `failed`, task.error) and does not abort the run, so
|
|
220
294
|
// one specialist failure cannot lose the work of the others.
|
|
221
295
|
|
|
222
|
-
async function executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought) {
|
|
296
|
+
async function executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought, cwd) {
|
|
223
297
|
try {
|
|
224
298
|
const result = await runTaskViaProvider({
|
|
225
299
|
task, run,
|
|
226
300
|
model: run.execution?.selectedModel,
|
|
227
301
|
provider: run.execution?.selectedProvider,
|
|
228
|
-
env, fetchImpl, chainOfThought,
|
|
302
|
+
env, fetchImpl, chainOfThought, cwd,
|
|
229
303
|
});
|
|
230
304
|
task.output = result.output;
|
|
231
305
|
task.executor = `provider:${result.provider}:${result.model}`;
|
|
232
306
|
task.status = 'done';
|
|
233
307
|
task.finishedAt = new Date().toISOString();
|
|
234
308
|
|
|
309
|
+
// Web-capable execution records which grant mode reached (or did not reach) the web
|
|
310
|
+
// and the F08-governed evidence gathered, so a host never has to infer it (ADR-0050).
|
|
311
|
+
if (result.webCapability) {
|
|
312
|
+
task.webCapability = result.webCapability;
|
|
313
|
+
task.webEvidence = result.webEvidence || [];
|
|
314
|
+
task.webCalls = result.webCalls || 0;
|
|
315
|
+
task.webSearchRequests = result.webSearchRequests || 0;
|
|
316
|
+
}
|
|
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
|
+
|
|
235
351
|
// `surface` attaches reasoning to the task so every display surface renders
|
|
236
352
|
// it; `telemetry_only` keeps it off the task (recorded only in the trace).
|
|
237
353
|
if (chainOfThought === 'surface' && result.reasoning) task.reasoning = result.reasoning;
|
|
238
|
-
return {
|
|
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
|
+
};
|
|
239
368
|
} catch (err) {
|
|
240
369
|
task.executor = `provider:error`;
|
|
241
|
-
task.error = {
|
|
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
|
+
};
|
|
376
|
+
task.status = 'failed';
|
|
377
|
+
task.finishedAt = new Date().toISOString();
|
|
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' };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
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
|
+
};
|
|
242
419
|
task.status = 'failed';
|
|
243
420
|
task.finishedAt = new Date().toISOString();
|
|
244
|
-
|
|
421
|
+
task.executionState = 'failed';
|
|
422
|
+
return { ok: false };
|
|
245
423
|
}
|
|
246
424
|
}
|
|
247
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
|
+
|
|
248
508
|
/**
|
|
249
509
|
* Execute a planned run through the chosen worker backend, persisting after each
|
|
250
510
|
* task transition so the run is resumable and observable.
|
|
@@ -264,53 +524,208 @@ export async function executeRun(cwd, runId, { env = process.env, workerBackend
|
|
|
264
524
|
throw err;
|
|
265
525
|
}
|
|
266
526
|
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
run
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
run.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
}
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
const backend = resolveWorkerBackend({ explicit: workerBackend, run, config });
|
|
538
|
+
const chainOfThought = resolveChainOfThought({ run, config });
|
|
539
|
+
run.workerBackend = backend;
|
|
540
|
+
run.chainOfThought = chainOfThought;
|
|
541
|
+
run.status = 'running';
|
|
542
|
+
run.updatedAt = new Date().toISOString();
|
|
543
|
+
await store.saveRun(run);
|
|
544
|
+
emitRunEvent(runId, { type: 'running', status: 'running', workerBackend: backend });
|
|
545
|
+
|
|
546
|
+
let anyFailed = false;
|
|
547
|
+
let cancelled = false;
|
|
548
|
+
let webUnavailable = false;
|
|
549
|
+
let personaDegraded = false;
|
|
550
|
+
for (const task of run.tasks) {
|
|
551
|
+
if (isCancelRequested(runId)) { cancelled = true; break; }
|
|
552
|
+
task.status = 'running';
|
|
553
|
+
task.startedAt = new Date().toISOString();
|
|
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 });
|
|
556
|
+
|
|
557
|
+
let taskReasoning = '';
|
|
558
|
+
if (backend === PROVIDER) {
|
|
559
|
+
const res = await executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought, cwd);
|
|
560
|
+
if (!res.ok) anyFailed = true;
|
|
561
|
+
if (res.webCapability === 'unavailable') webUnavailable = true;
|
|
562
|
+
if (res.personaDegraded) personaDegraded = true;
|
|
563
|
+
taskReasoning = res.reasoning;
|
|
564
|
+
} else if (backend === HOST) {
|
|
565
|
+
const res = prepareTaskForHost(task, run, cwd, env);
|
|
566
|
+
if (!res.ok) anyFailed = true;
|
|
567
|
+
} else {
|
|
568
|
+
prepareTaskInline(task);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// telemetry_only records reasoning to the trace without ever surfacing it to
|
|
572
|
+
// a display; surface keeps it off the trace (it rides on task.reasoning).
|
|
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 };
|
|
577
|
+
if (chainOfThought === 'telemetry_only' && taskReasoning) {
|
|
578
|
+
completedMeta.reasoning = taskReasoning;
|
|
579
|
+
completedMeta.reasoningChars = taskReasoning.length;
|
|
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;
|
|
598
|
+
emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.completed', role: task.role, taskId: task.id, metadata: completedMeta });
|
|
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 } : {}) });
|
|
600
|
+
run.updatedAt = new Date().toISOString();
|
|
601
|
+
await store.saveRun(run);
|
|
292
602
|
}
|
|
293
603
|
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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;
|
|
300
620
|
}
|
|
301
|
-
|
|
302
|
-
|
|
621
|
+
|
|
622
|
+
return finalizeRun(run, { cwd, env, config, store, cancelled, anyFailed, webUnavailable, personaDegraded });
|
|
623
|
+
} catch (err) {
|
|
624
|
+
run.status = 'error';
|
|
625
|
+
run.error = { code: err.code || 'RUN_EXECUTE_FAILED', message: err.message };
|
|
303
626
|
run.updatedAt = new Date().toISOString();
|
|
304
|
-
|
|
627
|
+
// Direct (non-atomic) write to the existing run file: the file was created by
|
|
628
|
+
// planRun so the inode exists; overwriting an existing file succeeds even on a
|
|
629
|
+
// read-only directory (only creating new files requires write permission on the
|
|
630
|
+
// directory itself). This persists the terminal failure durably so
|
|
631
|
+
// getRun/getRuns surfaces the error status to doctor and orchestration_status.
|
|
632
|
+
try { writeFileSync(runFilePath, `${JSON.stringify(run, null, 2)}\n`); } catch { /* best-effort */ }
|
|
633
|
+
emitRunEvent(runId, { type: 'error', status: 'error', error: run.error });
|
|
634
|
+
throw err;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
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.`);
|
|
305
691
|
}
|
|
306
692
|
|
|
307
|
-
|
|
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
|
+
|
|
308
717
|
run.updatedAt = new Date().toISOString();
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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 };
|
|
314
729
|
}
|
|
315
730
|
|
|
316
731
|
/**
|
|
@@ -333,6 +748,15 @@ export async function runOrchestration(request = {}, { env = process.env, cwd =
|
|
|
333
748
|
*/
|
|
334
749
|
export async function startRun(request = {}, { env = process.env, cwd = process.cwd(), workerBackend = null, fetchImpl } = {}) {
|
|
335
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
|
+
|
|
336
760
|
Promise.resolve()
|
|
337
761
|
.then(() => executeRun(cwd, planned.runId, { env, workerBackend, fetchImpl }))
|
|
338
762
|
.catch((err) => {
|
|
@@ -349,7 +773,11 @@ export function hostAdapterMetadata(run) {
|
|
|
349
773
|
return {
|
|
350
774
|
runId: run.runId,
|
|
351
775
|
traceId: run.traceId,
|
|
776
|
+
tenantId: run.tenantId ?? null,
|
|
352
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,
|
|
353
781
|
requestedStrategy: e.requestedStrategy,
|
|
354
782
|
effectiveStrategy: e.effectiveStrategy,
|
|
355
783
|
executionMode: e.executionMode,
|
|
@@ -357,11 +785,29 @@ export function hostAdapterMetadata(run) {
|
|
|
357
785
|
workerBackend: run.workerBackend,
|
|
358
786
|
chainOfThought: run.chainOfThought ?? null,
|
|
359
787
|
hostRole: run.hostRole,
|
|
360
|
-
degraded: e.degraded,
|
|
361
|
-
degradationReason: e.degradationReason,
|
|
788
|
+
degraded: run.degraded ?? e.degraded ?? false,
|
|
789
|
+
degradationReason: run.degradationReason ?? e.degradationReason ?? null,
|
|
362
790
|
selectedProvider: e.selectedProvider,
|
|
363
791
|
selectedModel: e.selectedModel,
|
|
364
|
-
|
|
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
|
+
})),
|
|
365
811
|
warnings: run.warnings || [],
|
|
366
812
|
semantics: run.semantics,
|
|
367
813
|
executionSemantics: run.executionSemantics,
|