@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.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- 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 +6 -29
- 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 +89 -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 +18 -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/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 +50 -9
- 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-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- 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 +57 -45
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- 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-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 +154 -1411
- 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-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- 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 +0 -8
- 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 +31 -7
- 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/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 +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- 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/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 +273 -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 +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- 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/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 +8 -3
- 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/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/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 +38 -19
- 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 +77 -5
- 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 +104 -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 +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- 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 +40 -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 +25 -30
- 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/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
|
@@ -16,10 +16,25 @@
|
|
|
16
16
|
* optional one. The returned store is always async-uniform so callers await it
|
|
17
17
|
* regardless of backend; the filesystem and sqlite backends resolve synchronously
|
|
18
18
|
* under the hood, so the default Mode-A run record is byte-for-byte unchanged.
|
|
19
|
+
*
|
|
20
|
+
* Tenant scoping (LMCP-H4, ADR-0057/A7 stage 1): the postgres backend is the
|
|
21
|
+
* one tier multiple tenants can share a physical table on, so resolveRunStore
|
|
22
|
+
* resolves the tenant context (resolveTenantContext) before constructing it
|
|
23
|
+
* and scopes every query by (project, tenant_id). Enterprise mode with no
|
|
24
|
+
* resolvable tenant id fails closed here — TenantResolutionError propagates
|
|
25
|
+
* uncaught rather than the resolver silently defaulting to a shared bucket.
|
|
26
|
+
* Filesystem/sqlite backends are already isolated per project directory and
|
|
27
|
+
* are not multi-tenant-shared, so they carry no tenant filter.
|
|
19
28
|
*/
|
|
20
29
|
|
|
21
30
|
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
22
31
|
import { createSqlClient } from '../storage/backend.mjs';
|
|
32
|
+
import { resolveTenantContext } from '../tenant/context.mjs';
|
|
33
|
+
import {
|
|
34
|
+
resolveStorageBackend,
|
|
35
|
+
UnregisteredStorageProviderError,
|
|
36
|
+
UnsupportedStorageProviderError,
|
|
37
|
+
} from '../storage/backend-registry.mjs';
|
|
23
38
|
import { saveRun as fsSaveRun, loadRun as fsLoadRun, listRuns as fsListRuns } from './run-store.mjs';
|
|
24
39
|
import { sqliteAvailable, createSqliteRunStore } from './run-store-sqlite.mjs';
|
|
25
40
|
import { PostgresRunStore } from './run-store-postgres.mjs';
|
|
@@ -46,8 +61,8 @@ function sqliteStore(cwd) {
|
|
|
46
61
|
};
|
|
47
62
|
}
|
|
48
63
|
|
|
49
|
-
function postgresStore({ sql, project }) {
|
|
50
|
-
const store = new PostgresRunStore({ sql, project });
|
|
64
|
+
function postgresStore({ sql, project, tenantId }) {
|
|
65
|
+
const store = new PostgresRunStore({ sql, project, tenantId });
|
|
51
66
|
let ensured = null;
|
|
52
67
|
const ready = () => {
|
|
53
68
|
if (!ensured) ensured = store.ensureSchema();
|
|
@@ -61,15 +76,32 @@ function postgresStore({ sql, project }) {
|
|
|
61
76
|
};
|
|
62
77
|
}
|
|
63
78
|
|
|
64
|
-
|
|
79
|
+
/**
|
|
80
|
+
* The same project key resolveRunStore uses to scope Postgres rows —
|
|
81
|
+
* exported so other team-store consumers (trace-store.mjs, shared-memory.mjs)
|
|
82
|
+
* key their own tables identically instead of re-deriving a divergent value.
|
|
83
|
+
*/
|
|
84
|
+
export function projectKey(config, cwd) {
|
|
65
85
|
return config?.deployment?.projectName || cwd || 'default';
|
|
66
86
|
}
|
|
67
87
|
|
|
68
88
|
function chooseBackend({ config, env, cwd }) {
|
|
69
89
|
const explicit = String(env?.[STORE_ENV_KEY] || config?.orchestration?.store || '').trim().toLowerCase();
|
|
70
|
-
if (
|
|
90
|
+
if (explicit) return resolveStorageBackend(explicit, { rootDir: cwd, env });
|
|
71
91
|
const mode = getDeploymentMode(env, { cwd });
|
|
72
|
-
return mode === 'solo' ? 'filesystem' : 'postgres';
|
|
92
|
+
return resolveStorageBackend(mode === 'solo' ? 'filesystem' : 'postgres', { rootDir: cwd, env });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function degradedFilesystem({ requested, reason, warning, provider = null, cwd }) {
|
|
96
|
+
return {
|
|
97
|
+
store: filesystemStore(cwd),
|
|
98
|
+
backend: 'filesystem',
|
|
99
|
+
requestedBackend: requested,
|
|
100
|
+
provider,
|
|
101
|
+
degraded: true,
|
|
102
|
+
degradedReason: reason,
|
|
103
|
+
warnings: [warning],
|
|
104
|
+
};
|
|
73
105
|
}
|
|
74
106
|
|
|
75
107
|
/**
|
|
@@ -83,20 +115,63 @@ function chooseBackend({ config, env, cwd }) {
|
|
|
83
115
|
*/
|
|
84
116
|
export function resolveRunStore({ config = {}, env = process.env, cwd = process.cwd() } = {}) {
|
|
85
117
|
const warnings = [];
|
|
86
|
-
|
|
118
|
+
let resolved;
|
|
119
|
+
try {
|
|
120
|
+
resolved = chooseBackend({ config, env, cwd });
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if (err instanceof UnregisteredStorageProviderError) {
|
|
123
|
+
return degradedFilesystem({
|
|
124
|
+
cwd,
|
|
125
|
+
requested: err.backend,
|
|
126
|
+
reason: 'storage-backend-unregistered',
|
|
127
|
+
warning: `orchestration.store "${err.backend}" has no registered kind:'storage' provider; falling back to filesystem.`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (err instanceof UnsupportedStorageProviderError) {
|
|
131
|
+
return degradedFilesystem({
|
|
132
|
+
cwd,
|
|
133
|
+
requested: err.backend,
|
|
134
|
+
reason: 'storage-backend-unsupported',
|
|
135
|
+
warning: err.message,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const requested = resolved.implementation;
|
|
87
142
|
|
|
88
143
|
if (requested === 'sqlite') {
|
|
89
|
-
if (sqliteAvailable()) return { store: sqliteStore(cwd), backend:
|
|
144
|
+
if (sqliteAvailable()) return { store: sqliteStore(cwd), backend: resolved.backend, provider: resolved.provider, warnings };
|
|
90
145
|
warnings.push('orchestration.store "sqlite" requires Node >=22.5 (node:sqlite unavailable); falling back to filesystem.');
|
|
91
|
-
return {
|
|
146
|
+
return {
|
|
147
|
+
store: filesystemStore(cwd),
|
|
148
|
+
backend: 'filesystem',
|
|
149
|
+
requestedBackend: resolved.backend,
|
|
150
|
+
provider: resolved.provider,
|
|
151
|
+
degraded: true,
|
|
152
|
+
degradedReason: 'sqlite-unavailable',
|
|
153
|
+
warnings,
|
|
154
|
+
};
|
|
92
155
|
}
|
|
93
156
|
|
|
94
157
|
if (requested === 'postgres') {
|
|
95
158
|
const sql = createSqlClient(env);
|
|
96
|
-
if (sql)
|
|
97
|
-
|
|
98
|
-
|
|
159
|
+
if (sql) {
|
|
160
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
161
|
+
const { tenantId } = resolveTenantContext({ env, config, mode });
|
|
162
|
+
return { store: postgresStore({ sql, project: projectKey(config, cwd), tenantId }), backend: resolved.backend, provider: resolved.provider, tenantId, warnings };
|
|
163
|
+
}
|
|
164
|
+
warnings.push(`orchestration.store "${resolved.backend}" requires a reachable DATABASE_URL; falling back to filesystem.`);
|
|
165
|
+
return {
|
|
166
|
+
store: filesystemStore(cwd),
|
|
167
|
+
backend: 'filesystem',
|
|
168
|
+
requestedBackend: resolved.backend,
|
|
169
|
+
provider: resolved.provider,
|
|
170
|
+
degraded: true,
|
|
171
|
+
degradedReason: 'postgres-unavailable',
|
|
172
|
+
warnings,
|
|
173
|
+
};
|
|
99
174
|
}
|
|
100
175
|
|
|
101
|
-
return { store: filesystemStore(cwd), backend:
|
|
176
|
+
return { store: filesystemStore(cwd), backend: resolved.backend, provider: resolved.provider, warnings };
|
|
102
177
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/orchestration/trace-store.mjs — team-shared trace event persistence (LMCP-G10).
|
|
3
|
+
*
|
|
4
|
+
* emitTraceEvent (lib/worker/trace.mjs) always writes the local, append-only
|
|
5
|
+
* `.cx/traces/<date>.jsonl` shard — that stays unchanged, on every deployment
|
|
6
|
+
* mode, with no credentials required. This module is the additional,
|
|
7
|
+
* postgres-backed channel a run's lifecycle/worker trace events feed so a
|
|
8
|
+
* team member on a different machine can read the SAME trace a worker on
|
|
9
|
+
* another machine emitted — the "run/task traces -> team store" half of the
|
|
10
|
+
* A1/A6 thread-vs-store boundary (see lib/storage/shared-memory.mjs for the
|
|
11
|
+
* long-term-memory half).
|
|
12
|
+
*
|
|
13
|
+
* Solo mode (no reachable Postgres) resolves to a `kind:'none'` store whose
|
|
14
|
+
* save/list are no-ops — the local JSONL trace already covers that case, and
|
|
15
|
+
* a team-shared read path with nothing durable behind it would be a silent
|
|
16
|
+
* lie rather than a real capability.
|
|
17
|
+
*
|
|
18
|
+
* Tenant scoping (LMCP-H4, ADR-0057/A7 stage 1): every row carries a
|
|
19
|
+
* `tenant_id` column and every query filters on it, matching the run store
|
|
20
|
+
* (run-store-postgres.mjs) and shared-memory store (shared-memory.mjs).
|
|
21
|
+
* resolveTraceStore resolves the tenant context up front and fails closed —
|
|
22
|
+
* TenantResolutionError propagates uncaught — when enterprise mode has no
|
|
23
|
+
* resolvable tenant id, rather than defaulting rows into a shared 'local'
|
|
24
|
+
* bucket. Stage 2 (physical per-tenant isolation, per-tenant encryption) is
|
|
25
|
+
* explicitly deferred and not advertised; this remains one shared table with
|
|
26
|
+
* row-level tenant scoping only.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { createSqlClient } from '../storage/backend.mjs';
|
|
30
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
31
|
+
import { resolveTenantContext } from '../tenant/context.mjs';
|
|
32
|
+
|
|
33
|
+
function noneStore() {
|
|
34
|
+
return {
|
|
35
|
+
kind: 'none',
|
|
36
|
+
saveTraceEvent: async () => ({ ok: false, reason: 'no-team-store-configured' }),
|
|
37
|
+
listTeamTraces: async () => [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function postgresStore(sql, defaultTenantId) {
|
|
42
|
+
let ensured = null;
|
|
43
|
+
const ready = () => {
|
|
44
|
+
if (!ensured) {
|
|
45
|
+
ensured = (async () => {
|
|
46
|
+
const { applyMigrations } = await import('../db/migrate.mjs');
|
|
47
|
+
await applyMigrations(sql);
|
|
48
|
+
})();
|
|
49
|
+
}
|
|
50
|
+
return ensured;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
kind: 'postgres',
|
|
55
|
+
tenantId: defaultTenantId,
|
|
56
|
+
async saveTraceEvent(event, { project, tenantId = defaultTenantId } = {}) {
|
|
57
|
+
if (!project) throw new Error('saveTraceEvent: project is required');
|
|
58
|
+
if (!event?.traceId || !event?.spanId) throw new Error('saveTraceEvent: event.traceId and event.spanId are required');
|
|
59
|
+
await ready();
|
|
60
|
+
await sql`
|
|
61
|
+
INSERT INTO construct_trace_events (
|
|
62
|
+
project, tenant_id, trace_id, span_id, parent_span_id, event_type, role, task_id, metadata, created_at
|
|
63
|
+
) VALUES (
|
|
64
|
+
${project}, ${tenantId}, ${event.traceId}, ${event.spanId}, ${event.parentSpanId || null},
|
|
65
|
+
${event.eventType}, ${event.role || null}, ${event.taskId || null},
|
|
66
|
+
${sql.json(event.metadata || {})}, ${event.createdAt || new Date().toISOString()}
|
|
67
|
+
)
|
|
68
|
+
ON CONFLICT (project, tenant_id, trace_id, span_id) DO NOTHING
|
|
69
|
+
`;
|
|
70
|
+
return { ok: true };
|
|
71
|
+
},
|
|
72
|
+
async listTeamTraces({ project, tenantId = defaultTenantId, traceId, limit = 100 } = {}) {
|
|
73
|
+
if (!project) throw new Error('listTeamTraces: project is required');
|
|
74
|
+
await ready();
|
|
75
|
+
const rows = traceId
|
|
76
|
+
? await sql`
|
|
77
|
+
SELECT trace_id, span_id, parent_span_id, event_type, role, task_id, metadata, created_at
|
|
78
|
+
FROM construct_trace_events
|
|
79
|
+
WHERE project = ${project} AND tenant_id = ${tenantId} AND trace_id = ${traceId}
|
|
80
|
+
ORDER BY created_at ASC
|
|
81
|
+
LIMIT ${limit}
|
|
82
|
+
`
|
|
83
|
+
: await sql`
|
|
84
|
+
SELECT trace_id, span_id, parent_span_id, event_type, role, task_id, metadata, created_at
|
|
85
|
+
FROM construct_trace_events
|
|
86
|
+
WHERE project = ${project} AND tenant_id = ${tenantId}
|
|
87
|
+
ORDER BY created_at DESC
|
|
88
|
+
LIMIT ${limit}
|
|
89
|
+
`;
|
|
90
|
+
return rows.map((row) => ({
|
|
91
|
+
traceId: row.trace_id,
|
|
92
|
+
spanId: row.span_id,
|
|
93
|
+
parentSpanId: row.parent_span_id,
|
|
94
|
+
eventType: row.event_type,
|
|
95
|
+
role: row.role,
|
|
96
|
+
taskId: row.task_id,
|
|
97
|
+
metadata: row.metadata,
|
|
98
|
+
createdAt: row.created_at,
|
|
99
|
+
}));
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* resolveTraceStore(opts)
|
|
106
|
+
*
|
|
107
|
+
* `sql` accepts an injected client (matching lib/team/health.mjs's
|
|
108
|
+
* convention) so a caller — or a test backed by an in-memory fake — can
|
|
109
|
+
* supply a shared substrate directly instead of resolving one from env.
|
|
110
|
+
* Enterprise mode with no resolvable tenant id throws TenantResolutionError
|
|
111
|
+
* before any query runs (fail-closed).
|
|
112
|
+
*
|
|
113
|
+
* @param {{ env?: object, cwd?: string, config?: object, sql?: object }} [opts]
|
|
114
|
+
* @returns {{ kind: string, saveTraceEvent: Function, listTeamTraces: Function }}
|
|
115
|
+
*/
|
|
116
|
+
export function resolveTraceStore({ env = process.env, cwd = process.cwd(), config = null, sql = null } = {}) {
|
|
117
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
118
|
+
if (mode === 'solo' && !sql) return noneStore();
|
|
119
|
+
|
|
120
|
+
const ownSql = sql || createSqlClient(env);
|
|
121
|
+
if (!ownSql) return noneStore();
|
|
122
|
+
|
|
123
|
+
const { tenantId } = resolveTenantContext({ env, config, mode });
|
|
124
|
+
return postgresStore(ownSql, tenantId);
|
|
125
|
+
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { getSpecialist } from '../registry/loader.mjs';
|
|
20
|
+
import { resolveNonNegativeSetting } from '../env-config.mjs';
|
|
20
21
|
|
|
21
22
|
const DEFAULT_MAX_USES = 5;
|
|
22
23
|
const DEFAULT_MAX_RESULTS = 5;
|
|
@@ -39,11 +40,11 @@ export function resolveWebCapability({ family, env = process.env } = {}) {
|
|
|
39
40
|
return { mode: 'governed' };
|
|
40
41
|
}
|
|
41
42
|
if (family === 'anthropic') {
|
|
42
|
-
const maxUses =
|
|
43
|
+
const maxUses = resolveNonNegativeSetting(env, 'CONSTRUCT_WORKER_WEB_MAX_USES', DEFAULT_MAX_USES);
|
|
43
44
|
return { mode: 'provider-native', providerTool: 'anthropic', toolType: 'web_search_20250305', maxUses };
|
|
44
45
|
}
|
|
45
46
|
if (family === 'openrouter') {
|
|
46
|
-
const maxResults =
|
|
47
|
+
const maxResults = resolveNonNegativeSetting(env, 'CONSTRUCT_WORKER_WEB_MAX_RESULTS', DEFAULT_MAX_RESULTS);
|
|
47
48
|
return {
|
|
48
49
|
mode: 'provider-native',
|
|
49
50
|
providerTool: 'openrouter',
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/orchestration/worker-runtime.mjs — team worker identity + heartbeat.
|
|
3
|
+
*
|
|
4
|
+
* Durable liveness sidecar for queue leases. The module does not start a worker
|
|
5
|
+
* pool; it records lease ownership so status/doctor can distinguish live and
|
|
6
|
+
* expired workers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import process from 'node:process';
|
|
11
|
+
|
|
12
|
+
import { applyMigrations } from '../db/migrate.mjs';
|
|
13
|
+
|
|
14
|
+
export const WORKER_ID_ENV_KEY = 'CONSTRUCT_WORKER_ID';
|
|
15
|
+
export const WORKER_TTL_ENV_KEY = 'CONSTRUCT_WORKER_TTL_SECONDS';
|
|
16
|
+
export const WORKER_DEFAULT_TTL_SECONDS = 120;
|
|
17
|
+
|
|
18
|
+
function defaultWorkerId(env = process.env) {
|
|
19
|
+
const explicit = env?.[WORKER_ID_ENV_KEY];
|
|
20
|
+
if (explicit && String(explicit).trim()) return String(explicit).trim();
|
|
21
|
+
return `${os.hostname()}:${process.pid}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeTtl(value) {
|
|
25
|
+
const n = Number(value);
|
|
26
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : WORKER_DEFAULT_TTL_SECONDS;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeCapabilities(capabilities = []) {
|
|
30
|
+
return Array.isArray(capabilities)
|
|
31
|
+
? [...new Set(capabilities.map((c) => String(c).trim()).filter(Boolean))].sort()
|
|
32
|
+
: [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class WorkerRegistry {
|
|
36
|
+
constructor({ sql, project, tenantId = 'local' } = {}) {
|
|
37
|
+
if (!sql) throw new Error('WorkerRegistry: sql client is required');
|
|
38
|
+
if (!project) throw new Error('WorkerRegistry: project is required');
|
|
39
|
+
this.sql = sql;
|
|
40
|
+
this.project = project;
|
|
41
|
+
this.tenantId = tenantId;
|
|
42
|
+
this._ensured = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async ensureSchema() {
|
|
46
|
+
if (!this._ensured) this._ensured = applyMigrations(this.sql);
|
|
47
|
+
return this._ensured;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async register({
|
|
51
|
+
workerId,
|
|
52
|
+
env = process.env,
|
|
53
|
+
host = os.hostname(),
|
|
54
|
+
pid = process.pid,
|
|
55
|
+
capabilities = [],
|
|
56
|
+
ttlSeconds = env?.[WORKER_TTL_ENV_KEY],
|
|
57
|
+
metadata = {},
|
|
58
|
+
} = {}) {
|
|
59
|
+
await this.ensureSchema();
|
|
60
|
+
const id = workerId || defaultWorkerId(env);
|
|
61
|
+
const ttl = normalizeTtl(ttlSeconds);
|
|
62
|
+
const caps = normalizeCapabilities(capabilities);
|
|
63
|
+
await this.sql`
|
|
64
|
+
INSERT INTO construct_workers (
|
|
65
|
+
project, tenant_id, worker_id, host, pid, capabilities,
|
|
66
|
+
registered_at, heartbeat_at, lease_ttl_seconds, status, metadata
|
|
67
|
+
) VALUES (
|
|
68
|
+
${this.project}, ${this.tenantId}, ${id}, ${host || null}, ${Number(pid) || null},
|
|
69
|
+
${this.sql.json(caps)}, now(), now(), ${ttl}, 'active', ${this.sql.json(metadata || {})}
|
|
70
|
+
)
|
|
71
|
+
ON CONFLICT (project, tenant_id, worker_id) DO UPDATE SET
|
|
72
|
+
host = EXCLUDED.host,
|
|
73
|
+
pid = EXCLUDED.pid,
|
|
74
|
+
capabilities = EXCLUDED.capabilities,
|
|
75
|
+
heartbeat_at = now(),
|
|
76
|
+
lease_ttl_seconds = EXCLUDED.lease_ttl_seconds,
|
|
77
|
+
status = 'active',
|
|
78
|
+
metadata = EXCLUDED.metadata
|
|
79
|
+
`;
|
|
80
|
+
return { workerId: id, project: this.project, tenantId: this.tenantId, host, pid, capabilities: caps, ttlSeconds: ttl };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async heartbeat(workerId, { ttlSeconds, renewQueue = null } = {}) {
|
|
84
|
+
if (!workerId) throw new Error('heartbeat: workerId is required');
|
|
85
|
+
await this.ensureSchema();
|
|
86
|
+
const ttl = normalizeTtl(ttlSeconds);
|
|
87
|
+
const rows = await this.sql`
|
|
88
|
+
UPDATE construct_workers
|
|
89
|
+
SET heartbeat_at = now(),
|
|
90
|
+
lease_ttl_seconds = ${ttl},
|
|
91
|
+
status = 'active'
|
|
92
|
+
WHERE project = ${this.project}
|
|
93
|
+
AND tenant_id = ${this.tenantId}
|
|
94
|
+
AND worker_id = ${workerId}
|
|
95
|
+
RETURNING worker_id, heartbeat_at, lease_ttl_seconds
|
|
96
|
+
`;
|
|
97
|
+
if (!rows[0]) return { workerId, renewed: false };
|
|
98
|
+
if (renewQueue && typeof renewQueue.heartbeat === 'function') {
|
|
99
|
+
await renewQueue.heartbeat(renewQueue.itemId, { workerId, leaseSeconds: ttl });
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
workerId,
|
|
103
|
+
renewed: true,
|
|
104
|
+
heartbeatAt: rows[0].heartbeat_at,
|
|
105
|
+
ttlSeconds: rows[0].lease_ttl_seconds,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async deregister(workerId) {
|
|
110
|
+
if (!workerId) throw new Error('deregister: workerId is required');
|
|
111
|
+
await this.ensureSchema();
|
|
112
|
+
const rows = await this.sql`
|
|
113
|
+
UPDATE construct_workers
|
|
114
|
+
SET status = 'stopped'
|
|
115
|
+
WHERE project = ${this.project}
|
|
116
|
+
AND tenant_id = ${this.tenantId}
|
|
117
|
+
AND worker_id = ${workerId}
|
|
118
|
+
RETURNING worker_id
|
|
119
|
+
`;
|
|
120
|
+
return { workerId, stopped: Boolean(rows[0]) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async list({ includeStale = true } = {}) {
|
|
124
|
+
await this.ensureSchema();
|
|
125
|
+
const rows = includeStale
|
|
126
|
+
? await this.sql`
|
|
127
|
+
SELECT worker_id, host, pid, capabilities, registered_at, heartbeat_at,
|
|
128
|
+
lease_ttl_seconds, status, metadata,
|
|
129
|
+
(heartbeat_at + (lease_ttl_seconds * interval '1 second') < now()) AS stale
|
|
130
|
+
FROM construct_workers
|
|
131
|
+
WHERE project = ${this.project} AND tenant_id = ${this.tenantId}
|
|
132
|
+
ORDER BY heartbeat_at DESC, worker_id ASC
|
|
133
|
+
`
|
|
134
|
+
: await this.sql`
|
|
135
|
+
SELECT worker_id, host, pid, capabilities, registered_at, heartbeat_at,
|
|
136
|
+
lease_ttl_seconds, status, metadata,
|
|
137
|
+
(heartbeat_at + (lease_ttl_seconds * interval '1 second') < now()) AS stale
|
|
138
|
+
FROM construct_workers
|
|
139
|
+
WHERE project = ${this.project}
|
|
140
|
+
AND tenant_id = ${this.tenantId}
|
|
141
|
+
AND status = 'active'
|
|
142
|
+
AND heartbeat_at + (lease_ttl_seconds * interval '1 second') >= now()
|
|
143
|
+
ORDER BY heartbeat_at DESC, worker_id ASC
|
|
144
|
+
`;
|
|
145
|
+
return rows.map((row) => ({
|
|
146
|
+
workerId: row.worker_id,
|
|
147
|
+
host: row.host,
|
|
148
|
+
pid: row.pid,
|
|
149
|
+
capabilities: row.capabilities || [],
|
|
150
|
+
registeredAt: row.registered_at,
|
|
151
|
+
heartbeatAt: row.heartbeat_at,
|
|
152
|
+
ttlSeconds: row.lease_ttl_seconds,
|
|
153
|
+
status: row.status === 'active' && row.stale ? 'stale' : row.status,
|
|
154
|
+
stale: Boolean(row.stale),
|
|
155
|
+
metadata: row.metadata || {},
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function createWorkerRegistry({ sql, project, tenantId } = {}) {
|
|
161
|
+
return new WorkerRegistry({ sql, project, tenantId });
|
|
162
|
+
}
|