@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
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/embed/approval-queue.mjs —
|
|
2
|
+
* lib/embed/approval-queue.mjs — durable approval queue for MCP tool calls.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Per ADR-0056 (LMCP-A6), every approval-required tool call creates a durable
|
|
5
|
+
* record with full identity, state machine, and file-backed persistence.
|
|
6
|
+
* Records survive process restart and can be resolved by any authorized actor
|
|
7
|
+
* with queue access.
|
|
6
8
|
*
|
|
7
|
-
* Persistence:
|
|
8
|
-
*
|
|
9
|
+
* Persistence: JSONL file at `.cx/approvals/queue.jsonl` (team mode) or
|
|
10
|
+
* `~/.cx/approvals/queue.jsonl` (solo mode). Written on every state transition.
|
|
9
11
|
*/
|
|
10
12
|
|
|
13
|
+
import crypto from 'node:crypto';
|
|
11
14
|
import fs from 'node:fs';
|
|
12
15
|
import path from 'node:path';
|
|
13
16
|
|
|
14
17
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
15
18
|
|
|
19
|
+
const APPROVAL_TTL_MS = 3_600_000; // 1 hour default
|
|
20
|
+
|
|
16
21
|
export class ApprovalQueue {
|
|
17
|
-
#items = new Map();
|
|
22
|
+
#items = new Map();
|
|
18
23
|
#persistPath = null;
|
|
19
|
-
#
|
|
20
|
-
#timeoutMs = 3_600_000;
|
|
21
|
-
#fallback = 'reject';
|
|
24
|
+
#timeoutMs = APPROVAL_TTL_MS;
|
|
22
25
|
|
|
23
26
|
/**
|
|
24
27
|
* @param {object} opts
|
|
25
|
-
* @param {string[
|
|
26
|
-
* @param {number} [opts.timeoutMs]
|
|
27
|
-
* @param {string} [opts.fallback] - 'reject' | 'proceed' on timeout
|
|
28
|
-
* @param {string} [opts.persistPath] - Path to JSONL persistence file
|
|
28
|
+
* @param {string} [opts.persistPath] - Path to JSONL persistence file
|
|
29
|
+
* @param {number} [opts.timeoutMs] - Auto-expiry after this many ms (default: 1h)
|
|
29
30
|
*/
|
|
30
|
-
constructor({
|
|
31
|
-
this.#requirePatterns = requirePatterns;
|
|
31
|
+
constructor({ persistPath, timeoutMs } = {}) {
|
|
32
32
|
if (timeoutMs != null) this.#timeoutMs = timeoutMs;
|
|
33
|
-
if (fallback) this.#fallback = fallback;
|
|
34
33
|
if (persistPath) {
|
|
35
34
|
this.#persistPath = persistPath;
|
|
36
35
|
this.#loadFromDisk();
|
|
@@ -38,113 +37,186 @@ export class ApprovalQueue {
|
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
|
-
*
|
|
40
|
+
* Compute a deterministic hash from tool name and args.
|
|
42
41
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return p === actionType;
|
|
47
|
-
});
|
|
42
|
+
static hashToolCall(tool, args) {
|
|
43
|
+
const stable = JSON.stringify(sortKeys({ tool, args: args ?? {} }));
|
|
44
|
+
return crypto.createHash('sha256').update(stable).digest('hex');
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
/**
|
|
51
|
-
*
|
|
52
|
-
* Returns 'auto' if autonomous execution is safe, 'human' if gating required.
|
|
48
|
+
* Enqueue a tool call for approval. Returns the full approval record.
|
|
53
49
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* 3. If action type matches low-risk patterns → 'auto'
|
|
58
|
-
* 4. Default → 'human' (fail-safe)
|
|
59
|
-
*/
|
|
60
|
-
approvalMode(actionType, { autoApprove = false } = {}) {
|
|
61
|
-
if (this.requiresApproval(actionType)) return 'human';
|
|
62
|
-
if (autoApprove) return 'auto';
|
|
63
|
-
const LOW_RISK = ['webhook.', 'snapshot.', 'observation.', 'search.', 'read.'];
|
|
64
|
-
if (LOW_RISK.some(prefix => actionType.startsWith(prefix))) return 'auto';
|
|
65
|
-
return 'human';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Enqueue an action for approval. Returns the item id.
|
|
70
|
-
* @param {object} action - { type, provider, payload, requestedBy?, autoApprove? }
|
|
50
|
+
* If an identical tool call (same tool + argsHash) already exists with
|
|
51
|
+
* status `awaiting_approval`, returns the existing record instead of
|
|
52
|
+
* creating a duplicate.
|
|
71
53
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
54
|
+
* @param {object} spec
|
|
55
|
+
* @param {string} spec.tool - Tool name
|
|
56
|
+
* @param {object} spec.args - Tool arguments
|
|
57
|
+
* @param {string} spec.surface - Interaction surface (e.g. 'mcp', 'cli')
|
|
58
|
+
* @param {string} spec.argsHash - Pre-computed hash (optional, computed if absent)
|
|
59
|
+
* @param {object} spec.requestedBy - Actor identity { userId, serviceId, tenantId, sessionId, role }
|
|
60
|
+
* @returns {object} Full approval record
|
|
75
61
|
*/
|
|
76
|
-
enqueue(
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
62
|
+
enqueue(spec) {
|
|
63
|
+
const { tool, args = {}, surface = 'mcp', requestedBy = {} } = spec;
|
|
64
|
+
const argsHash = spec.argsHash || ApprovalQueue.hashToolCall(tool, args);
|
|
65
|
+
|
|
66
|
+
const existing = this.#findByToolArgs(tool, argsHash);
|
|
67
|
+
if (existing && existing.state === 'awaiting_approval') {
|
|
68
|
+
return existing;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const approvalId = `appr-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
72
|
+
const resumeToken = crypto.randomBytes(32).toString('hex');
|
|
73
|
+
const now = new Date();
|
|
74
|
+
|
|
75
|
+
const record = {
|
|
76
|
+
approvalId,
|
|
77
|
+
toolCall: { tool, args, surface, argsHash },
|
|
78
|
+
requestedAt: now.toISOString(),
|
|
79
|
+
requestedBy: {
|
|
80
|
+
userId: requestedBy.userId ?? null,
|
|
81
|
+
serviceId: requestedBy.serviceId ?? null,
|
|
82
|
+
tenantId: requestedBy.tenantId ?? null,
|
|
83
|
+
sessionId: requestedBy.sessionId ?? null,
|
|
84
|
+
role: requestedBy.role ?? null,
|
|
85
|
+
},
|
|
86
|
+
state: 'awaiting_approval',
|
|
87
|
+
decidedAt: null,
|
|
88
|
+
decidedBy: null,
|
|
89
|
+
reason: null,
|
|
90
|
+
resumeToken,
|
|
91
|
+
expiresAt: new Date(now.getTime() + this.#timeoutMs).toISOString(),
|
|
92
92
|
};
|
|
93
|
-
|
|
93
|
+
|
|
94
|
+
this.#items.set(approvalId, record);
|
|
94
95
|
this.#persist();
|
|
95
|
-
return
|
|
96
|
+
return record;
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
/**
|
|
99
|
-
* Approve a pending
|
|
100
|
+
* Approve a pending approval. Returns the resolved record.
|
|
101
|
+
* @param {string} approvalId
|
|
102
|
+
* @param {object} [opts]
|
|
103
|
+
* @param {object} [opts.decidedBy] - Actor identity of the decider
|
|
104
|
+
* @param {string} [opts.reason]
|
|
100
105
|
*/
|
|
101
|
-
approve(
|
|
102
|
-
return this.#resolve(
|
|
106
|
+
approve(approvalId, { decidedBy = {}, reason = null } = {}) {
|
|
107
|
+
return this.#resolve(approvalId, 'approved', { decidedBy, reason });
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
/**
|
|
106
|
-
*
|
|
111
|
+
* Deny a pending approval. Returns the resolved record.
|
|
112
|
+
* @param {string} approvalId
|
|
113
|
+
* @param {object} [opts]
|
|
114
|
+
* @param {object} [opts.decidedBy] - Actor identity of the decider
|
|
115
|
+
* @param {string} [opts.reason]
|
|
107
116
|
*/
|
|
108
|
-
|
|
109
|
-
return this.#resolve(
|
|
117
|
+
deny(approvalId, { decidedBy = {}, reason = null } = {}) {
|
|
118
|
+
return this.#resolve(approvalId, 'denied', { decidedBy, reason });
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
/**
|
|
113
|
-
* Get
|
|
122
|
+
* Get a record by approvalId.
|
|
114
123
|
*/
|
|
124
|
+
getById(approvalId) {
|
|
125
|
+
return this.#items.get(approvalId) ?? null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** @deprecated Use getById() — kept for backward compat with old tests. */
|
|
115
129
|
get(id) {
|
|
116
|
-
|
|
130
|
+
if (typeof id === 'object' || id === null || id === undefined) return null;
|
|
131
|
+
return this.getById(id);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @deprecated Kept for backward compat; always returns false (policy decides). */
|
|
135
|
+
requiresApproval() {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** @deprecated Use deny() — kept for backward compat with old tests. */
|
|
140
|
+
reject(id, opts = {}) {
|
|
141
|
+
const reason = opts?.reason ?? null;
|
|
142
|
+
return this.deny(id, { decidedBy: opts?.decidedBy ?? {}, reason });
|
|
117
143
|
}
|
|
118
144
|
|
|
119
145
|
/**
|
|
120
|
-
*
|
|
146
|
+
* Get all records with state `awaiting_approval`.
|
|
121
147
|
*/
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
148
|
+
getPending() {
|
|
149
|
+
const results = [];
|
|
150
|
+
for (const item of this.#items.values()) {
|
|
151
|
+
if (item.state === 'awaiting_approval') results.push(item);
|
|
152
|
+
}
|
|
153
|
+
return results;
|
|
125
154
|
}
|
|
126
155
|
|
|
127
156
|
/**
|
|
128
|
-
*
|
|
129
|
-
|
|
157
|
+
* Look up a record by resumeToken.
|
|
158
|
+
*/
|
|
159
|
+
getByResumeToken(resumeToken) {
|
|
160
|
+
for (const item of this.#items.values()) {
|
|
161
|
+
if (item.resumeToken === resumeToken) return item;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Find a record by tool + argsHash (for dedup).
|
|
168
|
+
*/
|
|
169
|
+
#findByToolArgs(tool, argsHash) {
|
|
170
|
+
for (const item of this.#items.values()) {
|
|
171
|
+
if (item.toolCall?.tool === tool && item.toolCall?.argsHash === argsHash) {
|
|
172
|
+
return item;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Find a record by tool + argsHash (public, for broker lookup).
|
|
180
|
+
*/
|
|
181
|
+
findByToolArgs(tool, argsHash) {
|
|
182
|
+
return this.#findByToolArgs(tool, argsHash);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Expire any awaiting_approval records past their expiry time.
|
|
187
|
+
* Returns the list of expired records.
|
|
130
188
|
*/
|
|
131
189
|
expireStale() {
|
|
132
190
|
const now = Date.now();
|
|
133
191
|
const expired = [];
|
|
134
192
|
for (const item of this.#items.values()) {
|
|
135
|
-
if (item.
|
|
136
|
-
this.#resolve(item.
|
|
193
|
+
if (item.state === 'awaiting_approval' && new Date(item.expiresAt).getTime() < now) {
|
|
194
|
+
this.#resolve(item.approvalId, 'expired', { reason: 'auto-expired' });
|
|
137
195
|
expired.push(item);
|
|
138
196
|
}
|
|
139
197
|
}
|
|
140
198
|
return expired;
|
|
141
199
|
}
|
|
142
200
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
201
|
+
/**
|
|
202
|
+
* List all records, optionally filtered by state.
|
|
203
|
+
*/
|
|
204
|
+
list(state = null) {
|
|
205
|
+
const items = [...this.#items.values()];
|
|
206
|
+
return state ? items.filter((i) => i.state === state) : items;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#resolve(approvalId, newState, extra = {}) {
|
|
210
|
+
const item = this.#items.get(approvalId);
|
|
211
|
+
if (!item) throw new Error(`Approval record not found: ${approvalId}`);
|
|
212
|
+
if (item.state !== 'awaiting_approval') {
|
|
213
|
+
throw new Error(`Approval ${approvalId} is already ${item.state}`);
|
|
214
|
+
}
|
|
215
|
+
const now = new Date();
|
|
216
|
+
item.state = newState;
|
|
217
|
+
item.decidedAt = now.toISOString();
|
|
218
|
+
if (extra.decidedBy) item.decidedBy = extra.decidedBy;
|
|
219
|
+
if (extra.reason) item.reason = extra.reason;
|
|
148
220
|
this.#persist();
|
|
149
221
|
return item;
|
|
150
222
|
}
|
|
@@ -155,7 +227,9 @@ export class ApprovalQueue {
|
|
|
155
227
|
fs.mkdirSync(path.dirname(this.#persistPath), { recursive: true });
|
|
156
228
|
const lines = [...this.#items.values()].map((i) => JSON.stringify(i)).join('\n');
|
|
157
229
|
fs.writeFileSync(this.#persistPath, lines + '\n', 'utf8');
|
|
158
|
-
} catch (err) {
|
|
230
|
+
} catch (err) {
|
|
231
|
+
process.stderr.write('[approval-queue.mjs] persist: ' + (err?.message ?? String(err)) + '\n');
|
|
232
|
+
}
|
|
159
233
|
}
|
|
160
234
|
|
|
161
235
|
#loadFromDisk() {
|
|
@@ -165,13 +239,35 @@ export class ApprovalQueue {
|
|
|
165
239
|
for (const line of lines) {
|
|
166
240
|
try {
|
|
167
241
|
const item = JSON.parse(line);
|
|
168
|
-
this.#items.set(item.id, item);
|
|
242
|
+
this.#items.set(item.approvalId || item.id, item);
|
|
169
243
|
} catch { /* skip malformed */ }
|
|
170
244
|
}
|
|
171
|
-
} catch (err) {
|
|
245
|
+
} catch (err) {
|
|
246
|
+
process.stderr.write('[approval-queue.mjs] load-from-disk: ' + (err?.message ?? String(err)) + '\n');
|
|
247
|
+
}
|
|
172
248
|
}
|
|
173
249
|
|
|
174
|
-
|
|
175
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Compute the default persistence path.
|
|
252
|
+
* In team mode: `.cx/approvals/queue.jsonl` under the project root.
|
|
253
|
+
* In solo mode: `~/.cx/approvals/queue.jsonl` (via doctorRoot).
|
|
254
|
+
*/
|
|
255
|
+
static resolvePersistPath(rootDir, deploymentMode) {
|
|
256
|
+
if (deploymentMode === 'team' || deploymentMode === 'enterprise') {
|
|
257
|
+
return path.join(rootDir, '.cx', 'approvals', 'queue.jsonl');
|
|
258
|
+
}
|
|
259
|
+
return path.join(doctorRoot(), 'approvals', 'queue.jsonl');
|
|
176
260
|
}
|
|
177
261
|
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Recursively sort the keys of an object for deterministic JSON serialization.
|
|
265
|
+
*/
|
|
266
|
+
function sortKeys(obj) {
|
|
267
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) return obj;
|
|
268
|
+
const sorted = {};
|
|
269
|
+
for (const key of Object.keys(obj).sort()) {
|
|
270
|
+
sorted[key] = sortKeys(obj[key]);
|
|
271
|
+
}
|
|
272
|
+
return sorted;
|
|
273
|
+
}
|
|
@@ -20,6 +20,18 @@
|
|
|
20
20
|
* const result = await guard.check('externalPost', { description: 'Post roadmap to Slack' });
|
|
21
21
|
* if (result.allowed) { ... execute ... }
|
|
22
22
|
* else { ... it's been queued or denied ... }
|
|
23
|
+
*
|
|
24
|
+
* Per-specialist binding enforcement (LMCP-E4): the operating-profile
|
|
25
|
+
* authority level answers "is this action *class* ever permitted" but has no
|
|
26
|
+
* per-specialist dimension — nothing stopped an embedded specialist from
|
|
27
|
+
* reading any provider or proposing any external write. When the caller
|
|
28
|
+
* passes `meta.proposal = { specialistId, providerId, writeKind }`, `check()`
|
|
29
|
+
* consults the specialist's `embedBindings` grant (lib/packs/manifest-schema.mjs)
|
|
30
|
+
* *before* the authority-level/queue logic runs: a `<providerId>.<writeKind>`
|
|
31
|
+
* token not present in that specialist's `proposals[]` is denied outright,
|
|
32
|
+
* never queued. Callers that omit `meta.proposal` are unaffected — this is
|
|
33
|
+
* additive, backward-compatible enforcement layered in front of the existing
|
|
34
|
+
* authority-level check.
|
|
23
35
|
*/
|
|
24
36
|
|
|
25
37
|
// ─── Action-type → authority-key mapping ────────────────────────────────────
|
|
@@ -67,24 +79,75 @@ function resolveAuthorityKey(actionType) {
|
|
|
67
79
|
export class AuthorityGuard {
|
|
68
80
|
#authority;
|
|
69
81
|
#approvalQueue;
|
|
82
|
+
#embedBindings;
|
|
70
83
|
|
|
71
84
|
/**
|
|
72
85
|
* @param {object} operatingProfile - The embed config's operatingProfile object
|
|
73
86
|
* @param {ApprovalQueue} approvalQueue
|
|
87
|
+
* @param {object} [embedBindings] - Map of specialistId → { providers[], proposals[] }
|
|
88
|
+
* (lib/packs/manifest-schema.mjs embedBindings shape).
|
|
89
|
+
* Consulted only when a check's meta.proposal names
|
|
90
|
+
* a specialistId; omit for non-embed callers.
|
|
74
91
|
*/
|
|
75
|
-
constructor(operatingProfile, approvalQueue) {
|
|
92
|
+
constructor(operatingProfile, approvalQueue, embedBindings = {}) {
|
|
76
93
|
this.#authority = operatingProfile?.authority ?? {};
|
|
77
94
|
this.#approvalQueue = approvalQueue;
|
|
95
|
+
this.#embedBindings = embedBindings ?? {};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check a proposed embed action against the specialist's binding grant.
|
|
100
|
+
* Returns `null` when no binding-scoped check applies (no proposal meta,
|
|
101
|
+
* or no bindings configured) so the caller falls through to the ordinary
|
|
102
|
+
* authority-level check unaffected.
|
|
103
|
+
*
|
|
104
|
+
* @param {{specialistId: string, providerId: string, writeKind: string}} proposal
|
|
105
|
+
* @returns {{ allowed: boolean, mode: string, reason: string }|null}
|
|
106
|
+
*/
|
|
107
|
+
#checkProposalBinding(proposal) {
|
|
108
|
+
if (!proposal || !proposal.specialistId) return null;
|
|
109
|
+
|
|
110
|
+
const { specialistId, providerId, writeKind } = proposal;
|
|
111
|
+
const binding = this.#embedBindings[specialistId];
|
|
112
|
+
|
|
113
|
+
if (!binding) {
|
|
114
|
+
return {
|
|
115
|
+
allowed: false,
|
|
116
|
+
mode: 'denied',
|
|
117
|
+
reason: `specialist "${specialistId}" has no embedBindings grant — no providers or proposals are authorized`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const token = writeKind ? `${providerId}.${writeKind}` : providerId;
|
|
122
|
+
const grantedProposals = new Set(binding.proposals || []);
|
|
123
|
+
|
|
124
|
+
if (!grantedProposals.has(token)) {
|
|
125
|
+
return {
|
|
126
|
+
allowed: false,
|
|
127
|
+
mode: 'denied',
|
|
128
|
+
reason: `specialist "${specialistId}" is not granted to propose "${token}" (embedBindings.proposals: ${[...grantedProposals].join(', ') || '(none)'})`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null;
|
|
78
133
|
}
|
|
79
134
|
|
|
80
135
|
/**
|
|
81
136
|
* Check whether an action is allowed under the current authority profile.
|
|
82
137
|
*
|
|
83
138
|
* @param {string} actionType - e.g. 'externalPost', 'artifact:prd', 'issue:create'
|
|
84
|
-
* @param {object} [meta] - Optional metadata: { description, payload }
|
|
139
|
+
* @param {object} [meta] - Optional metadata: { description, payload, proposal }
|
|
140
|
+
* @param {{specialistId: string, providerId: string, writeKind: string}} [meta.proposal]
|
|
141
|
+
* Embed-originated proposal descriptor (LMCP-E4). When present, the
|
|
142
|
+
* proposal is checked against the specialist's embedBindings grant
|
|
143
|
+
* before any authority-level/queue logic runs; a proposal outside
|
|
144
|
+
* the grant is denied and never queued.
|
|
85
145
|
* @returns {{ allowed: boolean, mode: string, queueId?: string, reason?: string }}
|
|
86
146
|
*/
|
|
87
147
|
async check(actionType, meta = {}) {
|
|
148
|
+
const bindingDenial = this.#checkProposalBinding(meta.proposal);
|
|
149
|
+
if (bindingDenial) return bindingDenial;
|
|
150
|
+
|
|
88
151
|
const key = resolveAuthorityKey(actionType);
|
|
89
152
|
const level = this.#authority[key] ?? 'approval-queued'; // fail-safe default
|
|
90
153
|
|