@geraldmaron/construct 1.1.1 → 1.2.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/README.md +29 -19
- package/bin/construct +311 -81
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +112 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +1 -1
- package/lib/auto-docs.mjs +87 -18
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +245 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +129 -15
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/schema.mjs +36 -5
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/document-export.mjs +360 -36
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/worker.mjs +2 -2
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +4 -5
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +27 -97
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +84 -9
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +373 -92
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/playwright-demo.mjs +252 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +228 -7
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +28 -14
- package/personas/construct.md +2 -2
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +41 -28
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +7 -2
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +480 -0
- package/specialists/artifact-manifest.schema.json +53 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models/execution-capability-profile.mjs — the single resolved capability
|
|
3
|
+
* record (construct-6zga.1.8).
|
|
4
|
+
*
|
|
5
|
+
* Consolidates the four formerly-scattered capability producers into one
|
|
6
|
+
* serializable, versioned record that chat (resolveCapabilityTier) and specialist
|
|
7
|
+
* prompt composition (resolveModelOperatingProfile via lib/prompt-composer.js) both
|
|
8
|
+
* consume. It is keyed by provider, requested/resolved model, adapter protocol, and
|
|
9
|
+
* observation time. Every capability declares an evidence source
|
|
10
|
+
* (provider_metadata | live_probe | operator_override | compatibility_fallback |
|
|
11
|
+
* unknown) and a confidence, so a reader can tell measured truth from a name/size
|
|
12
|
+
* guess. Unknowns compile to conservative defaults and set a degraded flag for
|
|
13
|
+
* telemetry.
|
|
14
|
+
*
|
|
15
|
+
* Provenance, not behavior change: this bead unifies the source of truth and
|
|
16
|
+
* exposes measured evidence (provider metadata, the Ollama agentic-coherence
|
|
17
|
+
* probe). The derived capabilityTier / operatingProfile still equal the existing
|
|
18
|
+
* name/size heuristics (tagged compatibility_fallback) so live behavior is
|
|
19
|
+
* preserved; construct-6zga.1.2 (capability-adaptive policy) is where measured
|
|
20
|
+
* evidence will supersede the heuristic. Compatibility heuristics stay isolated in
|
|
21
|
+
* model-router's resolveCapabilityTier / resolveModelOperatingProfile and are
|
|
22
|
+
* documented there as the temporary fallback.
|
|
23
|
+
*
|
|
24
|
+
* Reference shape: schemas/execution-capability-profile.schema.json.
|
|
25
|
+
*/
|
|
26
|
+
import { readFileSync } from 'node:fs';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
import { homedir } from 'node:os';
|
|
29
|
+
|
|
30
|
+
import { resolveProviderCapabilitiesSync } from '../provider-capabilities.js';
|
|
31
|
+
import { getModelVerdict } from '../ollama/capability-store.mjs';
|
|
32
|
+
import { isLocalModel } from '../mcp/tool-budget.mjs';
|
|
33
|
+
import { transportForProviderGroup, classifyCapabilityClass } from './behavior-matrix.mjs';
|
|
34
|
+
import { resolveCapabilityTier, resolveModelOperatingProfile } from '../model-router.mjs';
|
|
35
|
+
|
|
36
|
+
export const EXECUTION_PROFILE_SCHEMA_VERSION = 1;
|
|
37
|
+
|
|
38
|
+
export const PROFILE_EVIDENCE_SOURCES = Object.freeze([
|
|
39
|
+
'provider_metadata',
|
|
40
|
+
'live_probe',
|
|
41
|
+
'operator_override',
|
|
42
|
+
'compatibility_fallback',
|
|
43
|
+
'unknown',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const CONFIDENCE_LEVELS = Object.freeze(['high', 'medium', 'low', 'none']);
|
|
47
|
+
|
|
48
|
+
const OVERRIDES_FILENAME = 'capability-overrides.json';
|
|
49
|
+
|
|
50
|
+
function field(value, source, confidence) {
|
|
51
|
+
return Object.freeze({ value, source, confidence });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function providerGroupForModel(model) {
|
|
55
|
+
const seg = String(model || '').split('/')[0];
|
|
56
|
+
return seg || 'unknown';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Adapter protocol mirrors the dispatch in apps/chat/engine/ai-sdk-agent.mjs:
|
|
60
|
+
// Anthropic uses its native messages API, OpenAI and Copilot use chat completions,
|
|
61
|
+
// and every other provider goes through the OpenAI-compatible shim.
|
|
62
|
+
|
|
63
|
+
function adapterProtocolForGroup(group) {
|
|
64
|
+
if (group === 'anthropic') return 'anthropic-messages';
|
|
65
|
+
if (group === 'openai' || group === 'github-copilot') return 'openai-chat-completions';
|
|
66
|
+
return 'openai-compatible';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Mirrors resolveAdapterKey in lib/provider-capabilities.js: a model that maps to a
|
|
70
|
+
// vendor adapter carries that vendor's declared metadata; anything else falls to the
|
|
71
|
+
// generic default, which is a guess, not provider metadata.
|
|
72
|
+
|
|
73
|
+
function hasVendorAdapter(modelId) {
|
|
74
|
+
const id = String(modelId || '').toLowerCase();
|
|
75
|
+
if (/^anthropic\//.test(id) || /^openrouter\/anthropic\//.test(id)) return true;
|
|
76
|
+
if (/^google\//.test(id) || /^openrouter\/google\//.test(id)) return true;
|
|
77
|
+
if (/^openai\//.test(id) || /^openrouter\/openai\//.test(id) || /^github-copilot\//.test(id)) return true;
|
|
78
|
+
if (/^deepseek\//.test(id) || /^openrouter\/deepseek\//.test(id)) return true;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function loadOverridesFile(homeDir) {
|
|
83
|
+
try {
|
|
84
|
+
const raw = JSON.parse(readFileSync(join(homeDir, '.cx', OVERRIDES_FILENAME), 'utf8'));
|
|
85
|
+
if (raw && typeof raw === 'object' && raw.models && typeof raw.models === 'object') return raw.models;
|
|
86
|
+
} catch { /* absent or malformed — no overrides */ }
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The Ollama agentic-coherence store is keyed by the native model id (no ollama/
|
|
91
|
+
// prefix), the same transform bin/construct uses before isKnownCollapsed.
|
|
92
|
+
|
|
93
|
+
function localVerdict(model) {
|
|
94
|
+
if (!isLocalModel(model)) return null;
|
|
95
|
+
const native = String(model).replace(/^ollama\//, '');
|
|
96
|
+
return getModelVerdict(native);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the execution capability profile for a model. Synchronous and
|
|
101
|
+
* side-effect free: it reads cached/declared evidence only — live probing is the
|
|
102
|
+
* opt-in job of lib/models/behavior-matrix.mjs, never this resolver.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveExecutionCapabilityProfile({
|
|
105
|
+
model,
|
|
106
|
+
resolvedModel = null,
|
|
107
|
+
envValues = {},
|
|
108
|
+
overrides = null,
|
|
109
|
+
homeDir = homedir(),
|
|
110
|
+
now = () => new Date().toISOString(),
|
|
111
|
+
} = {}) {
|
|
112
|
+
const requestedModel = model || null;
|
|
113
|
+
const group = providerGroupForModel(model);
|
|
114
|
+
const transport = transportForProviderGroup(group);
|
|
115
|
+
const overrideMap = (overrides ?? loadOverridesFile(homeDir))[requestedModel] || {};
|
|
116
|
+
|
|
117
|
+
const vendor = hasVendorAdapter(model);
|
|
118
|
+
const providerCaps = resolveProviderCapabilitiesSync(model);
|
|
119
|
+
const capsSource = vendor ? 'provider_metadata' : 'compatibility_fallback';
|
|
120
|
+
const capsConfidence = vendor ? 'medium' : 'low';
|
|
121
|
+
|
|
122
|
+
const verdict = localVerdict(model);
|
|
123
|
+
const coherent = verdict ? verdict.coherent === true : null;
|
|
124
|
+
|
|
125
|
+
const measured = {
|
|
126
|
+
toolsKnown: verdict ? verdict.calledTool === true : false,
|
|
127
|
+
tools: verdict ? verdict.calledTool === true : false,
|
|
128
|
+
coherent,
|
|
129
|
+
};
|
|
130
|
+
const capabilityClass = classifyCapabilityClass({ transport, measured });
|
|
131
|
+
|
|
132
|
+
const tierFallback = resolveCapabilityTier({ model });
|
|
133
|
+
const operatingProfileFallback = resolveModelOperatingProfile({ envValues, selectedModel: model });
|
|
134
|
+
|
|
135
|
+
const apply = (key, fallbackField) => {
|
|
136
|
+
if (Object.prototype.hasOwnProperty.call(overrideMap, key)) {
|
|
137
|
+
return field(overrideMap[key], 'operator_override', 'high');
|
|
138
|
+
}
|
|
139
|
+
return fallbackField;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const capabilities = {
|
|
143
|
+
contextWindow: apply('contextWindow', field(providerCaps.maxContextWindow ?? null, capsSource, capsConfidence)),
|
|
144
|
+
structuredOutput: apply('structuredOutput', field(providerCaps.structuredOutput === true, capsSource, capsConfidence)),
|
|
145
|
+
cacheControl: apply('cacheControl', field(providerCaps.cacheControl === true, capsSource, capsConfidence)),
|
|
146
|
+
tokenRatio: apply('tokenRatio', field(providerCaps.tokenRatio ?? null, capsSource, capsConfidence)),
|
|
147
|
+
agenticCoherence: apply('agenticCoherence', verdict
|
|
148
|
+
? field(verdict.coherent ? 'COHERENT' : 'COLLAPSED', 'live_probe', 'medium')
|
|
149
|
+
: field(null, 'unknown', 'none')),
|
|
150
|
+
capabilityTier: apply('capabilityTier', field(tierFallback, 'compatibility_fallback', 'low')),
|
|
151
|
+
operatingProfileId: apply('operatingProfileId', field(operatingProfileFallback.id, 'compatibility_fallback', 'low')),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const evidenceSources = [...new Set(Object.values(capabilities).map((f) => f.source))];
|
|
155
|
+
const hasMeasured = evidenceSources.some((s) => s === 'provider_metadata' || s === 'live_probe' || s === 'operator_override');
|
|
156
|
+
const degraded = capabilityClass === 'unknown' || !hasMeasured;
|
|
157
|
+
|
|
158
|
+
return Object.freeze({
|
|
159
|
+
schemaVersion: EXECUTION_PROFILE_SCHEMA_VERSION,
|
|
160
|
+
key: Object.freeze({
|
|
161
|
+
provider: group,
|
|
162
|
+
requestedModel,
|
|
163
|
+
resolvedModel: resolvedModel || requestedModel,
|
|
164
|
+
adapterProtocol: adapterProtocolForGroup(group),
|
|
165
|
+
observedAt: now(),
|
|
166
|
+
}),
|
|
167
|
+
capabilityClass,
|
|
168
|
+
transport,
|
|
169
|
+
capabilities: Object.freeze(capabilities),
|
|
170
|
+
degraded,
|
|
171
|
+
evidenceSources: Object.freeze(evidenceSources),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function capabilityTierFromProfile(profile) {
|
|
176
|
+
return profile?.capabilities?.capabilityTier?.value ?? 'full';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function operatingProfileIdFromProfile(profile) {
|
|
180
|
+
return profile?.capabilities?.operatingProfileId?.value ?? 'balanced';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function validateExecutionCapabilityProfile(profile) {
|
|
184
|
+
const errors = [];
|
|
185
|
+
if (!profile || typeof profile !== 'object') return { valid: false, errors: ['profile is not an object'] };
|
|
186
|
+
if (profile.schemaVersion !== EXECUTION_PROFILE_SCHEMA_VERSION) errors.push(`schemaVersion must be ${EXECUTION_PROFILE_SCHEMA_VERSION}`);
|
|
187
|
+
if (!profile.key || typeof profile.key !== 'object') errors.push('key missing');
|
|
188
|
+
if (typeof profile.degraded !== 'boolean') errors.push('degraded must be boolean');
|
|
189
|
+
if (!Array.isArray(profile.evidenceSources)) errors.push('evidenceSources must be an array');
|
|
190
|
+
for (const [name, f] of Object.entries(profile.capabilities || {})) {
|
|
191
|
+
if (!f || typeof f !== 'object') { errors.push(`capabilities.${name} not a field`); continue; }
|
|
192
|
+
if (!PROFILE_EVIDENCE_SOURCES.includes(f.source)) errors.push(`capabilities.${name}.source invalid: ${f.source}`);
|
|
193
|
+
if (!CONFIDENCE_LEVELS.includes(f.confidence)) errors.push(`capabilities.${name}.confidence invalid: ${f.confidence}`);
|
|
194
|
+
}
|
|
195
|
+
return { valid: errors.length === 0, errors };
|
|
196
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models/execution-policy.mjs — the immutable per-turn execution policy
|
|
3
|
+
* compiler (construct-6zga.1.2).
|
|
4
|
+
*
|
|
5
|
+
* Compiles one frozen, traceable policy from an ExecutionCapabilityProfile
|
|
6
|
+
* (construct-6zga.1.8) plus the turn's intent, task risk, and evidence
|
|
7
|
+
* requirement. The policy is the single record the owned loop reads for prompt
|
|
8
|
+
* section budgets, tool-schema and tool-group limits, tool-iteration caps, the
|
|
9
|
+
* evidence-first requirement, output budget, visible-thinking policy, the
|
|
10
|
+
* continuation/compaction trigger, and caching eligibility.
|
|
11
|
+
*
|
|
12
|
+
* Capability-driven, never name-driven: every control branches on the profile's
|
|
13
|
+
* measured capability class, transport, and capability values plus the
|
|
14
|
+
* normalized turn inputs — never on a provider or model-name string
|
|
15
|
+
* (construct-6zga.1.2 AC4). A missing, unknown, or degraded profile collapses to
|
|
16
|
+
* the conservative envelope and emits degraded-mode telemetry (AC3). The record
|
|
17
|
+
* is frozen and JSON-serializable, and carries the profile key and turn inputs it
|
|
18
|
+
* was derived from so a reader can re-verify it (AC1). Reference shape:
|
|
19
|
+
* schemas/execution-policy.schema.json.
|
|
20
|
+
*/
|
|
21
|
+
import { MODEL_OPERATING_PROFILES } from '../model-router.mjs';
|
|
22
|
+
import { capabilityTierFromProfile, operatingProfileIdFromProfile } from './execution-capability-profile.mjs';
|
|
23
|
+
|
|
24
|
+
export const EXECUTION_POLICY_SCHEMA_VERSION = 1;
|
|
25
|
+
|
|
26
|
+
export const POLICY_INTENTS = Object.freeze([
|
|
27
|
+
'repository-summary',
|
|
28
|
+
'code-change',
|
|
29
|
+
'research',
|
|
30
|
+
'tool-failure',
|
|
31
|
+
'general',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
export const POLICY_RISK_LEVELS = Object.freeze(['low', 'medium', 'high']);
|
|
35
|
+
|
|
36
|
+
export const POLICY_EVIDENCE_REQUIREMENTS = Object.freeze(['none', 'preferred', 'required']);
|
|
37
|
+
|
|
38
|
+
export const POLICY_TOOL_GROUPS = Object.freeze(['read', 'search', 'edit', 'shell', 'construct', 'heavy-mcp']);
|
|
39
|
+
|
|
40
|
+
export const POLICY_THINKING_MODES = Object.freeze(['hidden', 'summary', 'full']);
|
|
41
|
+
|
|
42
|
+
const KNOWN_CAPABILITY_CLASSES = Object.freeze(new Set([
|
|
43
|
+
'hosted-direct',
|
|
44
|
+
'hosted-routed',
|
|
45
|
+
'local-capable',
|
|
46
|
+
'local-constrained',
|
|
47
|
+
'unknown',
|
|
48
|
+
]));
|
|
49
|
+
|
|
50
|
+
// The capability class fixes the base execution envelope: how many tool schemas
|
|
51
|
+
// a model holds without overload, how many tool iterations it sustains
|
|
52
|
+
// coherently, how much output to budget, and whether visible reasoning helps or
|
|
53
|
+
// (for small local models that echo it back) hurts. Hosted-direct is the most
|
|
54
|
+
// capable; unknown is the most conservative. Turn intent and risk adjust these.
|
|
55
|
+
|
|
56
|
+
const CLASS_ENVELOPE = Object.freeze({
|
|
57
|
+
'hosted-direct': Object.freeze({ toolTier: 'rich', maxToolIterations: 16, outputTokenBudget: 8000, visibleThinking: 'summary' }),
|
|
58
|
+
'hosted-routed': Object.freeze({ toolTier: 'rich', maxToolIterations: 14, outputTokenBudget: 6000, visibleThinking: 'summary' }),
|
|
59
|
+
'local-capable': Object.freeze({ toolTier: 'standard', maxToolIterations: 10, outputTokenBudget: 4000, visibleThinking: 'hidden' }),
|
|
60
|
+
'local-constrained': Object.freeze({ toolTier: 'minimal', maxToolIterations: 6, outputTokenBudget: 2000, visibleThinking: 'hidden' }),
|
|
61
|
+
unknown: Object.freeze({ toolTier: 'minimal', maxToolIterations: 4, outputTokenBudget: 1500, visibleThinking: 'hidden' }),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const TOOL_TIER_SCHEMA_CAP = Object.freeze({ rich: 32, standard: 16, minimal: 8 });
|
|
65
|
+
|
|
66
|
+
const TOOL_TIER_GROUPS = Object.freeze({
|
|
67
|
+
rich: Object.freeze(['read', 'search', 'edit', 'shell', 'construct', 'heavy-mcp']),
|
|
68
|
+
standard: Object.freeze(['read', 'search', 'edit', 'construct']),
|
|
69
|
+
minimal: Object.freeze(['read', 'search', 'construct']),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const INTENT_EVIDENCE_FIRST = Object.freeze(new Set(['repository-summary', 'research', 'code-change']));
|
|
73
|
+
const INTENT_CITATIONS_REQUIRED = Object.freeze(new Set(['repository-summary', 'research']));
|
|
74
|
+
|
|
75
|
+
function clampInt(value, min, max) {
|
|
76
|
+
const n = Number.isFinite(value) ? Math.floor(value) : min;
|
|
77
|
+
return Math.max(min, Math.min(max, n));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function oneOf(allowed, value, fallback) {
|
|
81
|
+
return allowed.includes(value) ? value : fallback;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The chat turn overlay (lib/chat/transparency.mjs) speaks the orchestration
|
|
85
|
+
// vocabulary (INTENT_CLASSES, risk flags, externalResearch). These helpers map it
|
|
86
|
+
// onto the compiler's stable input enums so the compiler stays decoupled from
|
|
87
|
+
// routing changes. A turn that follows a failed tool result is the recovery case
|
|
88
|
+
// the loop signals with toolFailure.
|
|
89
|
+
|
|
90
|
+
export function normalizePolicyIntent({ intent = null, assumptionsBlocked = false, toolFailure = false } = {}) {
|
|
91
|
+
if (toolFailure === true) return 'tool-failure';
|
|
92
|
+
const i = String(intent || '').toLowerCase();
|
|
93
|
+
if (assumptionsBlocked === true && (i === '' || i === 'research' || i === 'investigation')) return 'repository-summary';
|
|
94
|
+
if (i === 'investigation') return 'repository-summary';
|
|
95
|
+
if (i === 'implementation' || i === 'fix') return 'code-change';
|
|
96
|
+
if (i === 'research' || i === 'evaluation') return 'research';
|
|
97
|
+
return 'general';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function normalizePolicyRisk(riskFlags = {}) {
|
|
101
|
+
const f = riskFlags && typeof riskFlags === 'object' ? riskFlags : {};
|
|
102
|
+
if (f.security === true || f.dataIntegrity === true) return 'high';
|
|
103
|
+
if (f.architecture === true || f.ai === true) return 'medium';
|
|
104
|
+
return 'low';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function normalizeEvidenceRequirement({ externalResearch = null, assumptionsBlocked = false } = {}) {
|
|
108
|
+
if (externalResearch?.required === true || assumptionsBlocked === true) return 'required';
|
|
109
|
+
return 'none';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Compile the immutable per-turn execution policy. Branches only on the profile's
|
|
114
|
+
* capability class, transport, and capability values plus the normalized intent,
|
|
115
|
+
* risk, and evidence requirement — never on a model or provider name (AC4).
|
|
116
|
+
*/
|
|
117
|
+
export function compileExecutionPolicy({
|
|
118
|
+
profile = null,
|
|
119
|
+
intent = 'general',
|
|
120
|
+
risk = 'low',
|
|
121
|
+
evidenceRequirement = 'none',
|
|
122
|
+
} = {}) {
|
|
123
|
+
const safeProfile = profile && typeof profile === 'object' ? profile : null;
|
|
124
|
+
const capabilityClass = KNOWN_CAPABILITY_CLASSES.has(safeProfile?.capabilityClass)
|
|
125
|
+
? safeProfile.capabilityClass
|
|
126
|
+
: 'unknown';
|
|
127
|
+
const transport = typeof safeProfile?.transport === 'string' ? safeProfile.transport : 'unknown';
|
|
128
|
+
|
|
129
|
+
const normIntent = oneOf(POLICY_INTENTS, intent, 'general');
|
|
130
|
+
const normRisk = oneOf(POLICY_RISK_LEVELS, risk, 'low');
|
|
131
|
+
const normEvidence = oneOf(POLICY_EVIDENCE_REQUIREMENTS, evidenceRequirement, 'none');
|
|
132
|
+
|
|
133
|
+
const degradeReasons = [];
|
|
134
|
+
if (!safeProfile) degradeReasons.push('profile-missing');
|
|
135
|
+
if (capabilityClass === 'unknown') degradeReasons.push('capability-class-unknown');
|
|
136
|
+
if (safeProfile?.degraded === true) degradeReasons.push('profile-degraded');
|
|
137
|
+
const degraded = degradeReasons.length > 0;
|
|
138
|
+
|
|
139
|
+
const envelope = CLASS_ENVELOPE[capabilityClass];
|
|
140
|
+
|
|
141
|
+
// A degraded or unmeasured record must not unlock rich tool budgets, so it
|
|
142
|
+
// collapses to the minimal tier regardless of its nominal class.
|
|
143
|
+
|
|
144
|
+
const effectiveToolTier = degraded ? 'minimal' : envelope.toolTier;
|
|
145
|
+
|
|
146
|
+
let maxToolSchemas = TOOL_TIER_SCHEMA_CAP[effectiveToolTier];
|
|
147
|
+
let maxToolIterations = envelope.maxToolIterations;
|
|
148
|
+
if (normIntent === 'code-change') maxToolIterations += 4;
|
|
149
|
+
else if (normIntent === 'research') maxToolIterations += 2;
|
|
150
|
+
else if (normIntent === 'tool-failure') {
|
|
151
|
+
maxToolIterations = clampInt(maxToolIterations / 2, 2, maxToolIterations);
|
|
152
|
+
maxToolSchemas = Math.min(maxToolSchemas, 6);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const groups = new Set(TOOL_TIER_GROUPS[effectiveToolTier]);
|
|
156
|
+
if (normIntent === 'repository-summary' || normIntent === 'research') {
|
|
157
|
+
groups.delete('edit');
|
|
158
|
+
groups.delete('shell');
|
|
159
|
+
}
|
|
160
|
+
if (normIntent === 'tool-failure') groups.delete('heavy-mcp');
|
|
161
|
+
const allowedToolGroups = POLICY_TOOL_GROUPS.filter((g) => groups.has(g));
|
|
162
|
+
|
|
163
|
+
const evidenceFirst = degraded
|
|
164
|
+
|| normRisk === 'high'
|
|
165
|
+
|| normEvidence !== 'none'
|
|
166
|
+
|| INTENT_EVIDENCE_FIRST.has(normIntent);
|
|
167
|
+
const citationsRequired = normEvidence === 'required'
|
|
168
|
+
|| normRisk === 'high'
|
|
169
|
+
|| INTENT_CITATIONS_REQUIRED.has(normIntent);
|
|
170
|
+
|
|
171
|
+
let visibleThinking = degraded ? 'hidden' : envelope.visibleThinking;
|
|
172
|
+
if (!degraded && normRisk === 'high' && visibleThinking === 'hidden') visibleThinking = 'summary';
|
|
173
|
+
|
|
174
|
+
const cacheCapable = safeProfile?.capabilities?.cacheControl?.value === true;
|
|
175
|
+
const cachingEligible = cacheCapable && !degraded;
|
|
176
|
+
|
|
177
|
+
const contextWindow = Number(safeProfile?.capabilities?.contextWindow?.value);
|
|
178
|
+
const hasContextWindow = Number.isFinite(contextWindow) && contextWindow > 0;
|
|
179
|
+
const compactionTriggerRatio = degraded ? 0.5 : 0.75;
|
|
180
|
+
const compactionTriggerTokens = hasContextWindow ? Math.floor(contextWindow * compactionTriggerRatio) : null;
|
|
181
|
+
|
|
182
|
+
const resolvedOperatingProfileId = operatingProfileIdFromProfile(safeProfile);
|
|
183
|
+
const operatingProfileId = MODEL_OPERATING_PROFILES[resolvedOperatingProfileId] ? resolvedOperatingProfileId : 'balanced';
|
|
184
|
+
const op = MODEL_OPERATING_PROFILES[operatingProfileId];
|
|
185
|
+
|
|
186
|
+
const policy = {
|
|
187
|
+
schemaVersion: EXECUTION_POLICY_SCHEMA_VERSION,
|
|
188
|
+
source: Object.freeze({
|
|
189
|
+
profileSchemaVersion: safeProfile?.schemaVersion ?? null,
|
|
190
|
+
profileKey: safeProfile?.key ? Object.freeze({ ...safeProfile.key }) : null,
|
|
191
|
+
capabilityClass,
|
|
192
|
+
transport,
|
|
193
|
+
intent: normIntent,
|
|
194
|
+
risk: normRisk,
|
|
195
|
+
evidenceRequirement: normEvidence,
|
|
196
|
+
evidenceSources: Object.freeze([...(Array.isArray(safeProfile?.evidenceSources) ? safeProfile.evidenceSources : [])]),
|
|
197
|
+
}),
|
|
198
|
+
prompt: Object.freeze({
|
|
199
|
+
systemPromptTier: capabilityTierFromProfile(safeProfile),
|
|
200
|
+
operatingProfileId,
|
|
201
|
+
sectionBudgets: Object.freeze({
|
|
202
|
+
maxPromptTokens: op.maxPromptTokens,
|
|
203
|
+
learnedPatternsTokens: op.learnedPatternsTokens,
|
|
204
|
+
taskPacketTokens: op.taskPacketTokens,
|
|
205
|
+
contextDigestTokens: op.contextDigestTokens,
|
|
206
|
+
hostConstraintsTokens: op.hostConstraintsTokens,
|
|
207
|
+
roleFlavorTokens: op.roleFlavorTokens,
|
|
208
|
+
}),
|
|
209
|
+
retrievalFirst: op.retrievalFirst === true,
|
|
210
|
+
preferCompressedGuidance: op.preferCompressedRoleGuidance === true,
|
|
211
|
+
}),
|
|
212
|
+
tools: Object.freeze({
|
|
213
|
+
maxToolSchemas,
|
|
214
|
+
allowedToolGroups: Object.freeze(allowedToolGroups),
|
|
215
|
+
maxToolIterations,
|
|
216
|
+
}),
|
|
217
|
+
evidence: Object.freeze({
|
|
218
|
+
evidenceFirst,
|
|
219
|
+
citationsRequired,
|
|
220
|
+
}),
|
|
221
|
+
output: Object.freeze({
|
|
222
|
+
outputTokenBudget: envelope.outputTokenBudget,
|
|
223
|
+
visibleThinking,
|
|
224
|
+
}),
|
|
225
|
+
continuation: Object.freeze({
|
|
226
|
+
compactionTriggerRatio,
|
|
227
|
+
compactionTriggerTokens,
|
|
228
|
+
}),
|
|
229
|
+
caching: Object.freeze({
|
|
230
|
+
eligible: cachingEligible,
|
|
231
|
+
scope: cachingEligible ? 'stable-prefix' : 'none',
|
|
232
|
+
}),
|
|
233
|
+
degradedMode: degraded,
|
|
234
|
+
telemetry: Object.freeze({
|
|
235
|
+
degraded,
|
|
236
|
+
reasons: Object.freeze(degradeReasons),
|
|
237
|
+
}),
|
|
238
|
+
};
|
|
239
|
+
return Object.freeze(policy);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Compile a policy directly from a chat turn overlay (lib/chat/transparency.mjs),
|
|
244
|
+
* normalizing its routing vocabulary into the compiler's input enums first.
|
|
245
|
+
*/
|
|
246
|
+
export function compilePolicyFromOverlay({ profile = null, overlay = null, toolFailure = false } = {}) {
|
|
247
|
+
const intent = normalizePolicyIntent({
|
|
248
|
+
intent: overlay?.intent,
|
|
249
|
+
assumptionsBlocked: overlay?.assumptionsBlocked === true,
|
|
250
|
+
toolFailure,
|
|
251
|
+
});
|
|
252
|
+
const risk = normalizePolicyRisk(overlay?.riskFlags);
|
|
253
|
+
const evidenceRequirement = normalizeEvidenceRequirement({
|
|
254
|
+
externalResearch: overlay?.externalResearch,
|
|
255
|
+
assumptionsBlocked: overlay?.assumptionsBlocked === true,
|
|
256
|
+
});
|
|
257
|
+
return compileExecutionPolicy({ profile, intent, risk, evidenceRequirement });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Hand-rolled validator (no ajv — Construct stays dependency-free at startup).
|
|
262
|
+
* Returns { valid, errors } against schemas/execution-policy.schema.json.
|
|
263
|
+
*/
|
|
264
|
+
export function validateExecutionPolicy(policy) {
|
|
265
|
+
const errors = [];
|
|
266
|
+
if (!policy || typeof policy !== 'object') return { valid: false, errors: ['policy is not an object'] };
|
|
267
|
+
if (policy.schemaVersion !== EXECUTION_POLICY_SCHEMA_VERSION) errors.push(`schemaVersion must be ${EXECUTION_POLICY_SCHEMA_VERSION}`);
|
|
268
|
+
|
|
269
|
+
const s = policy.source;
|
|
270
|
+
if (!s || typeof s !== 'object') errors.push('source missing');
|
|
271
|
+
else {
|
|
272
|
+
if (!KNOWN_CAPABILITY_CLASSES.has(s.capabilityClass)) errors.push(`source.capabilityClass invalid: ${s.capabilityClass}`);
|
|
273
|
+
if (!POLICY_INTENTS.includes(s.intent)) errors.push(`source.intent invalid: ${s.intent}`);
|
|
274
|
+
if (!POLICY_RISK_LEVELS.includes(s.risk)) errors.push(`source.risk invalid: ${s.risk}`);
|
|
275
|
+
if (!POLICY_EVIDENCE_REQUIREMENTS.includes(s.evidenceRequirement)) errors.push(`source.evidenceRequirement invalid: ${s.evidenceRequirement}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const t = policy.tools;
|
|
279
|
+
if (!t || typeof t !== 'object') errors.push('tools missing');
|
|
280
|
+
else {
|
|
281
|
+
if (!Number.isInteger(t.maxToolSchemas) || t.maxToolSchemas < 0) errors.push('tools.maxToolSchemas invalid');
|
|
282
|
+
if (!Number.isInteger(t.maxToolIterations) || t.maxToolIterations < 1) errors.push('tools.maxToolIterations invalid');
|
|
283
|
+
if (!Array.isArray(t.allowedToolGroups) || t.allowedToolGroups.some((g) => !POLICY_TOOL_GROUPS.includes(g))) errors.push('tools.allowedToolGroups invalid');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const o = policy.output;
|
|
287
|
+
if (!o || typeof o !== 'object') errors.push('output missing');
|
|
288
|
+
else {
|
|
289
|
+
if (!Number.isInteger(o.outputTokenBudget) || o.outputTokenBudget < 1) errors.push('output.outputTokenBudget invalid');
|
|
290
|
+
if (!POLICY_THINKING_MODES.includes(o.visibleThinking)) errors.push(`output.visibleThinking invalid: ${o.visibleThinking}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const e = policy.evidence;
|
|
294
|
+
if (!e || typeof e !== 'object') errors.push('evidence missing');
|
|
295
|
+
else {
|
|
296
|
+
if (typeof e.evidenceFirst !== 'boolean') errors.push('evidence.evidenceFirst must be boolean');
|
|
297
|
+
if (typeof e.citationsRequired !== 'boolean') errors.push('evidence.citationsRequired must be boolean');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!policy.caching || typeof policy.caching !== 'object') errors.push('caching missing');
|
|
301
|
+
else if (typeof policy.caching.eligible !== 'boolean') errors.push('caching.eligible must be boolean');
|
|
302
|
+
|
|
303
|
+
if (typeof policy.degradedMode !== 'boolean') errors.push('degradedMode must be boolean');
|
|
304
|
+
if (!policy.telemetry || typeof policy.telemetry !== 'object' || typeof policy.telemetry.degraded !== 'boolean') errors.push('telemetry.degraded must be boolean');
|
|
305
|
+
|
|
306
|
+
return { valid: errors.length === 0, errors };
|
|
307
|
+
}
|