@aiwg/cli 2026.10.1 → 2026.10.4
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 +2 -2
- package/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3183 -24
- package/bin/aiwg.mjs +135 -47
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +78 -12
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +218 -5
- package/dist/src/cli/handlers/diagnose.js +13 -5
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +248 -30
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +169 -10
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +28 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +25 -11
- package/dist/src/cli/handlers/script-runner.js +19 -8
- package/dist/src/cli/handlers/session.js +406 -62
- package/dist/src/cli/handlers/sessions.js +243 -76
- package/dist/src/cli/handlers/setup-manifest.js +474 -79
- package/dist/src/cli/handlers/setup.js +180 -39
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +60 -3
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +307 -55
- package/dist/src/cli/handlers/utilities.js +125 -26
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +349 -35
- package/dist/src/config/cli.js +256 -23
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +38 -11
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +14 -7
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +25 -6
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +15 -14
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +942 -0
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +64 -18
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +15 -3
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +95 -5
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +366 -56
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +620 -85
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +17 -27
- package/dist/src/mcp/helpers.mjs +109 -4
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +248 -131
- package/dist/src/mcp/registry.mjs +169 -100
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +127 -82
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +22 -3
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +50 -2
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +647 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +76 -14
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3183 -24
- package/dist/src/models/model-discovery.js +931 -67
- package/dist/src/models/provider-policy.js +719 -11
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +261 -0
- package/dist/src/models/worker-direct-readiness.js +550 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +144 -0
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +986 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +430 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +53 -23
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +47 -12
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +558 -0
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +49 -0
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +43 -9
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +434 -24
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
- package/dist/src/sessions/adapters/muse.js +200 -104
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +30 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +25 -5
- package/dist/src/sessions/batch-import.js +72 -51
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +75 -2
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +84 -46
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +199 -63
- package/dist/src/sessions/workspace-discovery.js +315 -88
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +32 -2
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
- package/tools/_resolve-impl.mjs +39 -2
- package/tools/agents/deploy-agents.mjs +64 -23
- package/tools/agents/providers/base.mjs +153 -166
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +62 -49
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +360 -0
- package/tools/providers/omp-transport.mjs +194 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +2916 -0
|
@@ -3,14 +3,24 @@
|
|
|
3
3
|
* @implements #1802
|
|
4
4
|
* @implements #1805
|
|
5
5
|
*/
|
|
6
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
7
|
+
import addFormats from 'ajv-formats';
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { dirname, resolve } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
6
11
|
import { z } from 'zod';
|
|
7
12
|
import { createRequire } from 'node:module';
|
|
13
|
+
import { compileWorkerVersionPattern, workerVersionRangeMismatch } from './worker-installation.mjs';
|
|
14
|
+
import { normalizeProviderDefinitionId } from '../providers/provider-definitions.js';
|
|
15
|
+
import { qualificationDigest } from './qualification-digest.mjs';
|
|
8
16
|
const require = createRequire(import.meta.url);
|
|
9
17
|
function requireModelResource(filename) {
|
|
10
18
|
const candidates = [
|
|
11
19
|
`./${filename}`,
|
|
12
20
|
`../../agentic/code/providers/${filename}`,
|
|
13
21
|
`../../../agentic/code/providers/${filename}`,
|
|
22
|
+
`../../schemas/models/${filename}`,
|
|
23
|
+
`../../../schemas/models/${filename}`,
|
|
14
24
|
];
|
|
15
25
|
for (const candidate of candidates) {
|
|
16
26
|
try {
|
|
@@ -32,7 +42,29 @@ const ProviderSchema = z.enum([
|
|
|
32
42
|
const OutcomeSchema = z.enum([
|
|
33
43
|
'native', 'compiled', 'inherited', 'global-only', 'informational', 'unsupported',
|
|
34
44
|
]);
|
|
45
|
+
// Use the bundled owner for nested controls, including forward-compatible optional
|
|
46
|
+
// health records. Zod must validate and preserve them, never silently strip them.
|
|
47
|
+
const capabilitySchema = requireModelResource('provider-model-capabilities.v1.schema.json');
|
|
48
|
+
const capabilityAjv = addFormats(new Ajv2020({ strict: false, allErrors: true }));
|
|
49
|
+
capabilityAjv.addSchema(capabilitySchema);
|
|
50
|
+
const validateAdapter = capabilityAjv.compile({ $ref: `${capabilitySchema.$id}#/$defs/workerAdapter` });
|
|
51
|
+
export const WorkerAdapterSchema = z.custom().superRefine((value, context) => {
|
|
52
|
+
if (!validateAdapter(value)) {
|
|
53
|
+
context.addIssue({ code: 'custom', message: `Invalid worker adapter: ${capabilityAjv.errorsText(validateAdapter.errors)}` });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const invocation = value.invocation;
|
|
58
|
+
if (invocation.kind === 'process' && invocation.versionPattern !== undefined)
|
|
59
|
+
compileWorkerVersionPattern(invocation.versionPattern);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
context.addIssue({ code: 'custom', path: ['invocation', 'versionPattern'],
|
|
63
|
+
message: `Invalid worker adapter: ${error instanceof Error ? error.message : String(error)}` });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
35
66
|
const CapabilityEntrySchema = z.object({
|
|
67
|
+
sessionEnvAllowlist: z.array(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/)).optional(),
|
|
36
68
|
agent: OutcomeSchema,
|
|
37
69
|
skill: OutcomeSchema,
|
|
38
70
|
globalChild: OutcomeSchema,
|
|
@@ -45,12 +77,29 @@ const CapabilityEntrySchema = z.object({
|
|
|
45
77
|
verification: z.string().min(1),
|
|
46
78
|
sourceUrl: z.string().url(),
|
|
47
79
|
verifiedAt: z.string().date(),
|
|
48
|
-
|
|
80
|
+
workerAdapter: WorkerAdapterSchema.optional(),
|
|
81
|
+
// Descriptive health coverage is not an installed executable registration.
|
|
82
|
+
workerHealthAdapter: WorkerAdapterSchema.optional(),
|
|
83
|
+
workerHealthCoverage: z.object({
|
|
84
|
+
scope: z.literal('out'), coverage: z.literal('unobserved'), handshake: z.literal('unobserved'),
|
|
85
|
+
evidenceGrades: z.array(z.enum(['H', 'R', 'D'])).max(3),
|
|
86
|
+
qualificationOwner: z.string().regex(/^B-OG-[0-9]{2}$/),
|
|
87
|
+
provenance: z.array(z.string().min(1).max(4096)).min(1).max(16), reason: z.string().min(1).max(4096),
|
|
88
|
+
}).strict().optional(),
|
|
89
|
+
}).strict();
|
|
49
90
|
const CapabilityRegistrySchema = z.object({
|
|
91
|
+
$schema: z.enum([
|
|
92
|
+
'../../../schemas/models/provider-model-capabilities.v1.schema.json',
|
|
93
|
+
'https://aiwg.io/schemas/models/provider-model-capabilities.v1.schema.json',
|
|
94
|
+
]).optional(),
|
|
50
95
|
version: z.literal('1.0.0'),
|
|
51
96
|
verifiedAt: z.string().date(),
|
|
52
97
|
providers: z.record(ProviderSchema, CapabilityEntrySchema),
|
|
53
|
-
})
|
|
98
|
+
workerAdapters: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerAdapterSchema)
|
|
99
|
+
.refine(value => Object.keys(value).length <= 128, 'At most 128 worker adapters').optional(),
|
|
100
|
+
workerHealthAdapters: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerAdapterSchema)
|
|
101
|
+
.refine(value => Object.keys(value).length <= 128, 'At most 128 worker health descriptors').optional(),
|
|
102
|
+
}).strict();
|
|
54
103
|
const CatalogModelSchema = z.object({
|
|
55
104
|
id: z.string().min(1),
|
|
56
105
|
status: z.enum(['active', 'deprecated', 'unverified']),
|
|
@@ -66,22 +115,228 @@ const CatalogProviderSchema = z.object({
|
|
|
66
115
|
sourceUrl: z.string().url(),
|
|
67
116
|
verifiedAt: z.string().date(),
|
|
68
117
|
});
|
|
118
|
+
let catalogValidators;
|
|
119
|
+
function getCatalogValidators() {
|
|
120
|
+
if (!catalogValidators) {
|
|
121
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
122
|
+
const file = [resolve(here, '../../schemas/models/model-catalog.v1.schema.json'), resolve(here, '../../../schemas/models/model-catalog.v1.schema.json')].find(existsSync);
|
|
123
|
+
if (!file)
|
|
124
|
+
throw new Error('Model catalog schema is unavailable');
|
|
125
|
+
const schema = JSON.parse(readFileSync(file, 'utf8'));
|
|
126
|
+
const ajv = addFormats(new Ajv2020({ strict: false, allErrors: true }));
|
|
127
|
+
ajv.addSchema(schema);
|
|
128
|
+
catalogValidators = { catalog: ajv.getSchema(schema.$id), observation: ajv.compile({ $ref: `${schema.$id}#/$defs/workerObservation` }), discovery: ajv.compile({ $ref: `${schema.$id}#/properties/discovery` }) };
|
|
129
|
+
}
|
|
130
|
+
return catalogValidators;
|
|
131
|
+
}
|
|
132
|
+
export function validateWorkerModelObservation(input) {
|
|
133
|
+
const validate = getCatalogValidators().observation;
|
|
134
|
+
return validate(input) ? [] : (validate.errors ?? []).map(error => `${error.instancePath}: ${error.message}`);
|
|
135
|
+
}
|
|
136
|
+
const WorkerObservationSchema = z.custom(input => validateWorkerModelObservation(input).length === 0);
|
|
137
|
+
const DiscoveredWorkerModelSchema = z.object({
|
|
138
|
+
id: z.string().min(1), apiModelId: z.string().optional(), llmProvider: z.string().optional(),
|
|
139
|
+
hidden: z.boolean().optional(), reasoningEfforts: z.array(z.string()).optional(), worker: WorkerObservationSchema.optional(),
|
|
140
|
+
}).passthrough().superRefine((model, context) => {
|
|
141
|
+
if (!model.worker)
|
|
142
|
+
return;
|
|
143
|
+
if (model.hidden !== undefined && model.worker.visibility !== (model.hidden ? 'hidden' : 'listed'))
|
|
144
|
+
context.addIssue({ code: 'custom', path: ['worker', 'visibility'], message: 'Conflicting visibility evidence' });
|
|
145
|
+
const efforts = model.reasoningEfforts ?? [];
|
|
146
|
+
const observed = model.worker.efforts.map(e => e.id);
|
|
147
|
+
if (efforts.length !== observed.length || new Set(observed).size !== observed.length || efforts.some(e => !observed.includes(e)))
|
|
148
|
+
context.addIssue({ code: 'custom', path: ['worker', 'efforts'], message: 'Conflicting effort evidence' });
|
|
149
|
+
});
|
|
150
|
+
const ProviderDiscoverySchema = z.object({
|
|
151
|
+
provider: z.string(), source: z.enum(['native', 'cache', 'remote', 'static']), observedAt: z.string().datetime({ offset: true }),
|
|
152
|
+
runtimeVersion: z.string().optional(), accountScope: z.enum(['local-account', 'local-runtime', 'public-feed', 'static']),
|
|
153
|
+
accountBinding: z.string().min(1).optional(), models: z.array(DiscoveredWorkerModelSchema),
|
|
154
|
+
}).passthrough();
|
|
155
|
+
const DiscoverySchema = z.object({
|
|
156
|
+
source: z.enum(['native', 'cache', 'remote', 'static']), upstreamSource: z.enum(['native', 'remote', 'static']).optional(),
|
|
157
|
+
fetchedAt: z.string().datetime({ offset: true }), remoteUrl: z.string().url().optional(), remoteError: z.string().optional(),
|
|
158
|
+
inventorySignature: z.string().optional(), providers: z.record(z.string(), ProviderDiscoverySchema),
|
|
159
|
+
instances: z.record(z.string(), ProviderDiscoverySchema).optional(),
|
|
160
|
+
}).strict();
|
|
161
|
+
/** Optional enrichment is checked without tightening legacy catalog rows. */
|
|
162
|
+
export function validateWorkerCatalogDiscovery(input) {
|
|
163
|
+
if (input === undefined)
|
|
164
|
+
return [];
|
|
165
|
+
const validate = getCatalogValidators().discovery;
|
|
166
|
+
if (!validate(input))
|
|
167
|
+
return (validate.errors ?? []).map(error => `${error.instancePath}: ${error.message}`);
|
|
168
|
+
const parsed = DiscoverySchema.safeParse(input);
|
|
169
|
+
return parsed.success ? [] : parsed.error.issues.map(issue => `${issue.path.join('/')}: ${issue.message}`);
|
|
170
|
+
}
|
|
69
171
|
const CatalogSchema = z.object({
|
|
70
172
|
version: z.literal('1.0.0'),
|
|
173
|
+
$schema: z.literal('../../../schemas/models/model-catalog.v1.schema.json').optional(),
|
|
174
|
+
discovery: DiscoverySchema.optional(),
|
|
71
175
|
refreshedAt: z.string().date(),
|
|
72
176
|
staleAfterDays: z.number().int().positive(),
|
|
73
177
|
providers: z.record(ProviderSchema, CatalogProviderSchema),
|
|
74
|
-
});
|
|
178
|
+
}).strict();
|
|
75
179
|
const PolicySchema = z.object({
|
|
76
180
|
role: z.enum(['reasoning', 'coding', 'efficiency']),
|
|
77
181
|
tier: z.enum(['economy', 'standard', 'premium']),
|
|
78
182
|
effort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
|
|
79
183
|
override: z.string().min(1).optional(),
|
|
80
184
|
}).strict();
|
|
185
|
+
const uniqueStrings = z.array(z.string().min(1)).refine(values => new Set(values).size === values.length, 'Duplicate entries');
|
|
186
|
+
const WorkerDataPolicySchema = z.object({
|
|
187
|
+
allowedProcessing: z.array(z.enum(['local', 'remote'])).refine(values => new Set(values).size === values.length).optional(),
|
|
188
|
+
allowProductImprovement: z.boolean().optional(),
|
|
189
|
+
allowUnknown: z.boolean().optional(),
|
|
190
|
+
maxRetentionDays: z.number().int().nonnegative().optional(),
|
|
191
|
+
}).strict();
|
|
192
|
+
const WorkerRoleSchema = z.object({
|
|
193
|
+
groups: uniqueStrings.refine(values => values.length > 0),
|
|
194
|
+
tier: z.enum(['economy', 'standard', 'premium', 'max-quality']),
|
|
195
|
+
initialEffort: z.string().min(1).nullable(),
|
|
196
|
+
effortLadder: uniqueStrings,
|
|
197
|
+
escalateAfter: z.number().int().positive().optional(),
|
|
198
|
+
maxEscalations: z.number().int().nonnegative().optional(),
|
|
199
|
+
deescalateAfter: z.number().int().positive().optional(),
|
|
200
|
+
allowNotApplicableEffort: z.boolean().optional(),
|
|
201
|
+
}).strict();
|
|
202
|
+
export const WorkerSelectionPolicySchema = z.object({
|
|
203
|
+
roleGuidance: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerRoleSchema).optional(),
|
|
204
|
+
guards: z.object({
|
|
205
|
+
deniedEfforts: uniqueStrings.optional(),
|
|
206
|
+
deniedModels: uniqueStrings.optional(),
|
|
207
|
+
denySelfDelegation: z.boolean().optional(),
|
|
208
|
+
requireExactModel: z.boolean().optional(),
|
|
209
|
+
allowedVisibility: z.array(z.enum(['listed', 'hidden'])).refine(v => new Set(v).size === v.length).optional(),
|
|
210
|
+
allowedLifecycle: z.array(z.enum(['active', 'deprecated'])).refine(v => new Set(v).size === v.length).optional(),
|
|
211
|
+
minimumIdentityEvidence: z.enum(['runtime-configured', 'backend-attested']).optional(),
|
|
212
|
+
}).strict().optional(),
|
|
213
|
+
dataPolicy: WorkerDataPolicySchema.optional(),
|
|
214
|
+
dataClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerDataPolicySchema).optional(),
|
|
215
|
+
freshness: z.object({
|
|
216
|
+
warnHours: z.number().int().positive().optional(),
|
|
217
|
+
blockHours: z.number().int().positive().optional(),
|
|
218
|
+
policyDays: z.number().int().positive().optional(),
|
|
219
|
+
}).strict().optional(),
|
|
220
|
+
taskClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), z.string().min(1)).optional(),
|
|
221
|
+
}).strict().superRefine((policy, context) => {
|
|
222
|
+
for (const [role, guidance] of Object.entries(policy.roleGuidance ?? {})) {
|
|
223
|
+
if (!['efficiency', 'coding', 'reasoning'].includes(role))
|
|
224
|
+
context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Unknown worker role' });
|
|
225
|
+
if (guidance.effortLadder.includes('none') || (guidance.initialEffort === 'none' ? guidance.effortLadder.length !== 0 : guidance.initialEffort !== null && !guidance.effortLadder.includes(guidance.initialEffort))) {
|
|
226
|
+
context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Initial effort must belong to the ladder; real none requires an empty ladder' });
|
|
227
|
+
}
|
|
228
|
+
if (guidance.initialEffort === null && (guidance.allowNotApplicableEffort !== true || guidance.effortLadder.length !== 0)) {
|
|
229
|
+
context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Null effort requires an empty ladder and allowNotApplicableEffort=true' });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
for (const key of policy.guards?.deniedModels ?? []) {
|
|
233
|
+
if (!isCanonicalWorkerModelKey(key))
|
|
234
|
+
context.addIssue({ code: 'custom', path: ['guards', 'deniedModels'], message: 'Noncanonical inference model key' });
|
|
235
|
+
}
|
|
236
|
+
if (policy.freshness?.warnHours !== undefined && policy.freshness.blockHours !== undefined && policy.freshness.warnHours > policy.freshness.blockHours) {
|
|
237
|
+
context.addIssue({ code: 'custom', path: ['freshness'], message: 'warnHours must not exceed blockHours' });
|
|
238
|
+
}
|
|
239
|
+
for (const role of Object.values(policy.taskClasses ?? {})) {
|
|
240
|
+
if (!['coding', 'reasoning', 'efficiency'].includes(role))
|
|
241
|
+
context.addIssue({ code: 'custom', path: ['taskClasses'], message: 'Unknown worker role' });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
/** Canonical RFC3986 identity; installation and account never enter the key. */
|
|
245
|
+
export function workerModelKey(inferenceProvider, apiModelId) {
|
|
246
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(inferenceProvider) || !apiModelId)
|
|
247
|
+
throw new Error('OG_CONFIG_REFERENCE: invalid inference model identity');
|
|
248
|
+
return `${inferenceProvider}/${encodeURIComponent(apiModelId).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`;
|
|
249
|
+
}
|
|
250
|
+
export function isCanonicalWorkerModelKey(key) {
|
|
251
|
+
const slash = key.indexOf('/');
|
|
252
|
+
try {
|
|
253
|
+
return slash > 0 && workerModelKey(key.slice(0, slash), decodeURIComponent(key.slice(slash + 1))) === key;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
export const DEFAULT_WORKER_SELECTION = {
|
|
260
|
+
guards: { deniedEfforts: ['ultra'], deniedModels: [], denySelfDelegation: true, requireExactModel: true, allowedVisibility: ['listed'], allowedLifecycle: ['active'], minimumIdentityEvidence: 'runtime-configured' },
|
|
261
|
+
dataPolicy: { allowedProcessing: ['local', 'remote'], allowProductImprovement: false, allowUnknown: false },
|
|
262
|
+
freshness: { warnHours: 24, blockHours: 168, policyDays: 30 },
|
|
263
|
+
taskClasses: { inventory: 'efficiency', implementation: 'coding', architecture: 'reasoning', review: 'reasoning' },
|
|
264
|
+
};
|
|
265
|
+
/** Merge leaves, preserving pre-merge explicit origins and attempted relaxations. */
|
|
266
|
+
export function mergeWorkerSelection(layers) {
|
|
267
|
+
const policy = {};
|
|
268
|
+
const origins = {};
|
|
269
|
+
const diagnostics = [];
|
|
270
|
+
const visit = (target, input, parts, layer) => {
|
|
271
|
+
for (const [key, value] of Object.entries(input)) {
|
|
272
|
+
const path = [...parts, key];
|
|
273
|
+
const pointer = `/defaults/workerSelection/${path.map(p => p.replace(/~/g, '~0').replace(/\//g, '~1')).join('/')}`;
|
|
274
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
275
|
+
target[key] ??= {};
|
|
276
|
+
visit(target[key], value, path, layer);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const origin = { source: layer.source, authority: layer.authority, digest: layer.digest, pointer, contribution: structuredClone(value) };
|
|
280
|
+
const prior = origins[pointer] ?? [];
|
|
281
|
+
const explicit = prior.filter(o => o.authority !== 'builtin');
|
|
282
|
+
let effective = structuredClone(value);
|
|
283
|
+
let relaxation = false;
|
|
284
|
+
if (layer.authority !== 'builtin' && explicit.length > 0) {
|
|
285
|
+
const previous = target[key];
|
|
286
|
+
if (['deniedEfforts', 'deniedModels'].includes(key))
|
|
287
|
+
effective = [...new Set([...previous, ...value])];
|
|
288
|
+
else if (['allowedVisibility', 'allowedLifecycle', 'allowedProcessing'].includes(key)) {
|
|
289
|
+
relaxation = value.some((v) => !previous.includes(v));
|
|
290
|
+
effective = previous.filter((v) => value.includes(v));
|
|
291
|
+
}
|
|
292
|
+
else if (['denySelfDelegation', 'requireExactModel'].includes(key)) {
|
|
293
|
+
relaxation = previous === true && value === false;
|
|
294
|
+
effective = previous || value;
|
|
295
|
+
}
|
|
296
|
+
else if (['allowUnknown', 'allowProductImprovement'].includes(key)) {
|
|
297
|
+
relaxation = previous === false && value === true;
|
|
298
|
+
effective = previous && value;
|
|
299
|
+
}
|
|
300
|
+
else if (['maxRetentionDays', 'warnHours', 'blockHours', 'policyDays'].includes(key)) {
|
|
301
|
+
relaxation = value > previous;
|
|
302
|
+
effective = Math.min(previous, value);
|
|
303
|
+
}
|
|
304
|
+
else if (key === 'minimumIdentityEvidence') {
|
|
305
|
+
relaxation = previous === 'backend-attested' && value !== previous;
|
|
306
|
+
effective = previous === 'backend-attested' ? previous : value;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (relaxation)
|
|
310
|
+
diagnostics.push({ code: 'OG_POLICY_RELAXATION', pointer, message: 'Explicit restriction cannot relax an inherited restriction', origins: [...explicit, origin] });
|
|
311
|
+
target[key] = effective;
|
|
312
|
+
origins[pointer] = [...prior, origin];
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
for (const layer of layers)
|
|
316
|
+
if (layer.policy)
|
|
317
|
+
visit(policy, layer.policy, [], layer);
|
|
318
|
+
const validation = WorkerSelectionPolicySchema.safeParse(policy);
|
|
319
|
+
if (!validation.success)
|
|
320
|
+
for (const issue of validation.error.issues)
|
|
321
|
+
diagnostics.push({ code: 'OG_CONFIG_REFERENCE', pointer: `/defaults/workerSelection/${issue.path.join('/')}`, message: issue.message });
|
|
322
|
+
// Data classes are applied as further restrictions to the base path.
|
|
323
|
+
for (const dataClass of Object.values(policy.dataClasses ?? {})) {
|
|
324
|
+
const base = policy.dataPolicy ?? {};
|
|
325
|
+
if (dataClass.allowedProcessing)
|
|
326
|
+
dataClass.allowedProcessing = dataClass.allowedProcessing.filter(p => !base.allowedProcessing || base.allowedProcessing.includes(p));
|
|
327
|
+
for (const key of ['allowUnknown', 'allowProductImprovement'])
|
|
328
|
+
if (base[key] === false)
|
|
329
|
+
dataClass[key] = false;
|
|
330
|
+
if (base.maxRetentionDays !== undefined)
|
|
331
|
+
dataClass.maxRetentionDays = Math.min(dataClass.maxRetentionDays ?? base.maxRetentionDays, base.maxRetentionDays);
|
|
332
|
+
}
|
|
333
|
+
return { policy, origins, diagnostics };
|
|
334
|
+
}
|
|
81
335
|
const UserProjectModelConfigSchema = z.object({
|
|
82
336
|
defaults: z.object({
|
|
83
337
|
tier: z.enum(['economy', 'standard', 'premium', 'max-quality']).optional(),
|
|
84
338
|
provider: ProviderSchema.optional(),
|
|
339
|
+
workerSelection: WorkerSelectionPolicySchema.optional(),
|
|
85
340
|
}).strict().optional(),
|
|
86
341
|
agentOverrides: z.record(z.string(), z.object({
|
|
87
342
|
'model-tier': z.enum(['economy', 'standard', 'premium', 'max-quality']).optional(),
|
|
@@ -103,18 +358,316 @@ let registryCache = null;
|
|
|
103
358
|
let catalogCache = null;
|
|
104
359
|
export function loadProviderModelCapabilities() {
|
|
105
360
|
if (!registryCache) {
|
|
106
|
-
registryCache =
|
|
107
|
-
const expected = new Set(ProviderSchema.options);
|
|
108
|
-
const actual = new Set(Object.keys(registryCache.providers));
|
|
109
|
-
if (actual.size !== expected.size || [...expected].some(id => !actual.has(id))) {
|
|
110
|
-
throw new Error('Provider model capability registry must cover all registered providers');
|
|
111
|
-
}
|
|
361
|
+
registryCache = parseProviderModelCapabilities(capabilityData);
|
|
112
362
|
}
|
|
113
363
|
return registryCache;
|
|
114
364
|
}
|
|
365
|
+
export function parseProviderModelCapabilities(input) {
|
|
366
|
+
const registry = CapabilityRegistrySchema.parse(input);
|
|
367
|
+
const expected = new Set(ProviderSchema.options);
|
|
368
|
+
const actual = new Set(Object.keys(registry.providers));
|
|
369
|
+
if (actual.size !== expected.size || [...expected].some(id => !actual.has(id))) {
|
|
370
|
+
throw new Error('Provider model capability registry must cover all registered providers');
|
|
371
|
+
}
|
|
372
|
+
const ids = new Set();
|
|
373
|
+
for (const adapter of [...Object.values(registry.providers).flatMap(p => p.workerAdapter ? [p.workerAdapter] : []),
|
|
374
|
+
...Object.values(registry.workerAdapters ?? {})]) {
|
|
375
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(adapter.id) || ids.has(adapter.id))
|
|
376
|
+
throw new Error(`Duplicate or invalid worker adapter ID: ${adapter.id}`);
|
|
377
|
+
ids.add(adapter.id);
|
|
378
|
+
}
|
|
379
|
+
return registry;
|
|
380
|
+
}
|
|
381
|
+
/** Exact, case-sensitive references; deployment and inference provider identities differ. */
|
|
382
|
+
export function resolveWorkerAdapter(registry, reference) {
|
|
383
|
+
const match = /^(providers|workerAdapters):([a-z][a-z0-9-]{0,62})$/.exec(reference);
|
|
384
|
+
if (!match)
|
|
385
|
+
throw new Error(`Invalid worker adapter reference: ${reference}`);
|
|
386
|
+
const [, namespace, id] = match;
|
|
387
|
+
const adapter = namespace === 'providers'
|
|
388
|
+
? registry.providers[id]?.workerAdapter
|
|
389
|
+
: registry.workerAdapters?.[id];
|
|
390
|
+
if (!adapter)
|
|
391
|
+
throw new Error(`Unknown worker adapter reference: ${reference}`);
|
|
392
|
+
return WorkerAdapterSchema.parse(adapter);
|
|
393
|
+
}
|
|
394
|
+
/** Pure direct/shared mapping: no admission store, Mission or activation marker. */
|
|
395
|
+
export function resolveWorkerInstallation(registry, installation) {
|
|
396
|
+
const adapter = resolveWorkerAdapter(registry, installation.adapter);
|
|
397
|
+
if (adapter.invocation.kind === 'process') {
|
|
398
|
+
if (installation.endpoint !== undefined)
|
|
399
|
+
throw new Error('Process adapter forbids endpoint');
|
|
400
|
+
const executable = installation.executable ?? adapter.invocation.executable;
|
|
401
|
+
if (!executable.trim() || executable.includes('\0'))
|
|
402
|
+
throw new Error('Process adapter requires executable');
|
|
403
|
+
return { adapter, executable };
|
|
404
|
+
}
|
|
405
|
+
if (installation.executable !== undefined)
|
|
406
|
+
throw new Error('HTTP adapter forbids executable');
|
|
407
|
+
if (!installation.endpoint)
|
|
408
|
+
throw new Error('HTTP adapter requires endpoint');
|
|
409
|
+
const endpoint = new URL(installation.endpoint);
|
|
410
|
+
if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.hash) {
|
|
411
|
+
throw new Error('Invalid HTTP adapter endpoint');
|
|
412
|
+
}
|
|
413
|
+
return { adapter, endpoint: installation.endpoint };
|
|
414
|
+
}
|
|
415
|
+
/** Typed policy failures let public surfaces retain the cause without exposing internal details. */
|
|
416
|
+
export class WorkerPolicyError extends Error {
|
|
417
|
+
code;
|
|
418
|
+
constructor(code, message) {
|
|
419
|
+
super(message);
|
|
420
|
+
this.code = code;
|
|
421
|
+
this.name = 'WorkerPolicyError';
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Dispatch-time safety checks belong here, alongside transport qualification.
|
|
426
|
+
* Like route validation, rendering requires observation evidence; a default
|
|
427
|
+
* observation may supply it when no model observation is available.
|
|
428
|
+
*/
|
|
429
|
+
export function assertWorkerSelectionAllowed(selected, context) {
|
|
430
|
+
const policy = context.policy ?? DEFAULT_WORKER_SELECTION;
|
|
431
|
+
if (isWorkerEffortDenied(selected.effort.value, policy)) {
|
|
432
|
+
throw new WorkerPolicyError('OG_EFFORT_DENIED', `Worker invocation denied effort: ${selected.effort.value}`);
|
|
433
|
+
}
|
|
434
|
+
if (isContributorWorkerModel(selected.model)
|
|
435
|
+
|| isWorkerModelDenied(workerModelKey(selected.inferenceProvider, selected.model), policy)) {
|
|
436
|
+
throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', `Worker invocation denied model: ${selected.model}`);
|
|
437
|
+
}
|
|
438
|
+
const { data, unknownDataClass } = workerRouteDataPolicy(policy, context.dataClass);
|
|
439
|
+
if (unknownDataClass)
|
|
440
|
+
throw new WorkerPolicyError('OG_DATA_POLICY', 'Worker invocation has denied data-use: unknown data class');
|
|
441
|
+
const observation = context.modelObservation ?? context.defaultObservation;
|
|
442
|
+
if (!observation)
|
|
443
|
+
throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', 'Worker invocation requires model or default observation evidence');
|
|
444
|
+
const guidance = context.role ? policy.roleGuidance?.[context.role] : undefined;
|
|
445
|
+
if (isWorkerNullEffortDenied(selected.effort.value, observation, guidance)) {
|
|
446
|
+
throw new WorkerPolicyError('OG_EFFORT_DENIED', 'Worker invocation denied effort: null effort requires qualified absence of an effort control');
|
|
447
|
+
}
|
|
448
|
+
for (const [kind, observation] of [
|
|
449
|
+
['model', context.modelObservation], ['default', context.defaultObservation],
|
|
450
|
+
]) {
|
|
451
|
+
if (!observation)
|
|
452
|
+
continue;
|
|
453
|
+
if (observation.lifecycle === 'retired'
|
|
454
|
+
|| (observation.retiredAt != null && Date.parse(observation.retiredAt) <= context.now)
|
|
455
|
+
|| (observation.sunsetAt != null && Date.parse(observation.sunsetAt) <= context.now)) {
|
|
456
|
+
throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', `Worker invocation ${kind} observation is retired`);
|
|
457
|
+
}
|
|
458
|
+
const blockMs = (policy.freshness?.blockHours ?? DEFAULT_WORKER_SELECTION.freshness.blockHours) * 3_600_000;
|
|
459
|
+
const observedAt = Date.parse(observation.observedAt);
|
|
460
|
+
if (!Number.isFinite(context.now) || !Number.isFinite(observedAt)
|
|
461
|
+
|| !Number.isFinite(blockMs) || blockMs <= 0
|
|
462
|
+
|| (observation.verifiedAt !== undefined && !Number.isFinite(Date.parse(observation.verifiedAt)))
|
|
463
|
+
|| isWorkerEvidenceStale(observation, policy, context.now)) {
|
|
464
|
+
throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', `Worker invocation ${kind} observation is stale or invalid`);
|
|
465
|
+
}
|
|
466
|
+
if (hasSelectionRelevantConflicts(observation)) {
|
|
467
|
+
throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', `Worker invocation ${kind} observation has conflicting selection-relevant evidence`);
|
|
468
|
+
}
|
|
469
|
+
if (observation.dataUse && isWorkerDataUseDenied({
|
|
470
|
+
processing: observation.dataUse.processing ?? 'unknown',
|
|
471
|
+
productImprovement: observation.dataUse.productImprovement ?? null,
|
|
472
|
+
retentionDays: observation.dataUse.retentionDays ?? null,
|
|
473
|
+
}, data)) {
|
|
474
|
+
throw new WorkerPolicyError('OG_DATA_POLICY', `Worker invocation ${kind} observation has denied data-use`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const WORKER_CONTROLS = ['model', 'effort', 'sandbox', 'approval', 'workdir',
|
|
479
|
+
'finalOutput', 'stdin', 'structuredOutput', 'stepCap', 'trust', 'children', 'fallback'];
|
|
480
|
+
/** Caller controls only add restrictions. Absent resolved authority, require all controls. */
|
|
481
|
+
export function requiredWorkerControls(context) {
|
|
482
|
+
if (context.requiredControls !== undefined && (!Array.isArray(context.requiredControls)
|
|
483
|
+
|| context.requiredControls.some(name => typeof name !== 'string' || !name))) {
|
|
484
|
+
throw new Error('Worker required controls must be an array of control names');
|
|
485
|
+
}
|
|
486
|
+
const controls = new Set(WORKER_CONTROLS);
|
|
487
|
+
if (context.policy && context.permissions) {
|
|
488
|
+
if (context.effort === null)
|
|
489
|
+
controls.delete('effort');
|
|
490
|
+
// Step limits are observed unless explicitly required by the trusted route.
|
|
491
|
+
controls.delete('stepCap');
|
|
492
|
+
if (context.policy.guards?.denySelfDelegation === false)
|
|
493
|
+
controls.delete('children');
|
|
494
|
+
}
|
|
495
|
+
for (const name of context.requiredControls ?? [])
|
|
496
|
+
controls.add(name);
|
|
497
|
+
return [...controls];
|
|
498
|
+
}
|
|
499
|
+
/** The same sandbox requirement governs inspection, resolution and rendering. */
|
|
500
|
+
export function requiresWorkerExternalBoundary(adapter, selection) {
|
|
501
|
+
if (hasOperatorWorkerNativeRenderer(adapter) && selection.permissions?.sandbox !== 'external')
|
|
502
|
+
return false;
|
|
503
|
+
const providers = [adapter.id, adapter.provider].map(normalizeProviderDefinitionId);
|
|
504
|
+
return requiredWorkerControls(selection).includes('sandbox')
|
|
505
|
+
&& (providers.some(provider => provider && ['claude', 'muse', 'hermes'].includes(provider))
|
|
506
|
+
|| adapter.invocationProfiles?.[selection.profile]?.controls.sandbox?.support === 'external'
|
|
507
|
+
|| selection.permissions?.sandbox === 'external');
|
|
508
|
+
}
|
|
509
|
+
/** Explicit local operator approval for the direct CLI baseline (decision 17).
|
|
510
|
+
* This is not backend attestation or a live negative-fixture result. */
|
|
511
|
+
export const WORKER_OPERATOR_APPROVAL = 'aiwg doctor --probe-workers --qualify';
|
|
512
|
+
export function isOperatorQualifiedWorkerAdapter(adapter) {
|
|
513
|
+
return adapter.qualification.status === 'qualified'
|
|
514
|
+
&& adapter.qualification.evidenceRef === WORKER_OPERATOR_APPROVAL;
|
|
515
|
+
}
|
|
516
|
+
/** Only this bundled renderer has an independently checked native direct baseline. */
|
|
517
|
+
export function hasOperatorWorkerNativeRenderer(adapter) {
|
|
518
|
+
return isOperatorQualifiedWorkerAdapter(adapter) && adapter.provider === 'codex'
|
|
519
|
+
&& adapter.invocation.kind === 'process';
|
|
520
|
+
}
|
|
521
|
+
/** Check the actual argv before granting the direct renderer exception. */
|
|
522
|
+
export function assertOperatorWorkerNativeArgv(adapter, argv, selected, permissions) {
|
|
523
|
+
if (!hasOperatorWorkerNativeRenderer(adapter))
|
|
524
|
+
return;
|
|
525
|
+
const args = argv.slice(1);
|
|
526
|
+
const requirePair = (flag, value) => {
|
|
527
|
+
if (args.filter(token => token === flag).length !== 1 || args[args.indexOf(flag) + 1] !== value) {
|
|
528
|
+
throw new Error(`Requested native worker control is absent or ambiguous: ${flag}`);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
const sandbox = permissions?.sandbox ?? 'workspace-write';
|
|
532
|
+
if (!['read-only', 'workspace-write'].includes(sandbox))
|
|
533
|
+
throw new Error('Requested sandbox has no verified native renderer');
|
|
534
|
+
requirePair('-m', selected.model);
|
|
535
|
+
requirePair('-s', sandbox);
|
|
536
|
+
for (const [name, value] of [['model_reasoning_effort', selected.effort.value], ['approval_policy', permissions?.approval ?? 'never']]) {
|
|
537
|
+
const overrides = args.flatMap((token, index) => token === '-c' ? [args[index + 1]] : [])
|
|
538
|
+
.filter(token => token?.split('=')[0] === name);
|
|
539
|
+
if (value === null || overrides.length !== 1 || overrides[0] !== `${name}=${JSON.stringify(value)}`) {
|
|
540
|
+
throw new Error(`Requested native worker control is absent or ambiguous: ${name}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Permit only the bounded native command grammar. Alternate/combined flags,
|
|
544
|
+
// profiles and additional config overrides must not undo the checked pairs.
|
|
545
|
+
if (args[0] !== '--no-daemon' || args[1] !== 'exec' || !args.includes('--ignore-user-config')) {
|
|
546
|
+
throw new Error('Native worker controls could be overridden');
|
|
547
|
+
}
|
|
548
|
+
for (let index = 2; index < args.length; index++) {
|
|
549
|
+
const flag = args[index];
|
|
550
|
+
if (['--ignore-user-config', '--skip-git-repo-check', '--json'].includes(flag))
|
|
551
|
+
continue;
|
|
552
|
+
if (flag === '-' && index === args.length - 1)
|
|
553
|
+
continue;
|
|
554
|
+
if (!['-m', '-s', '-c', '-C', '-o'].includes(flag) || index + 1 >= args.length) {
|
|
555
|
+
throw new Error('Native worker controls could be overridden');
|
|
556
|
+
}
|
|
557
|
+
const value = args[++index];
|
|
558
|
+
if (flag === '-c' && !['model_reasoning_effort', 'approval_policy'].includes(value.split('=')[0])) {
|
|
559
|
+
throw new Error('Native worker controls could be overridden');
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
/** Inspect declared controls before installation evidence can be produced. This
|
|
564
|
+
* does not authorize invocation; consumers must use qualifyWorkerInvocationProfile. */
|
|
565
|
+
export function inspectWorkerInvocationProfile(adapter, selection) {
|
|
566
|
+
WorkerAdapterSchema.parse(adapter);
|
|
567
|
+
const profile = adapter.invocationProfiles?.[selection.profile];
|
|
568
|
+
if (adapter.qualification.status !== 'qualified' || !profile || profile.transport !== selection.transport) {
|
|
569
|
+
throw new Error('Worker transport/profile is unqualified');
|
|
570
|
+
}
|
|
571
|
+
// Shared range predicate (worker-installation.mjs): read-only surfaces
|
|
572
|
+
// (worker-direct-readiness.ts) reuse the same helper instead of
|
|
573
|
+
// re-deriving this inspector's range logic.
|
|
574
|
+
if (workerVersionRangeMismatch(selection.cliVersion, [adapter.supportedVersions, profile.supportedVersions]) !== null) {
|
|
575
|
+
throw new Error('Worker CLI version is outside qualified profile');
|
|
576
|
+
}
|
|
577
|
+
// The direct CLI baseline trusts the installed driver's bounded renderer.
|
|
578
|
+
// Explicit extra controls still require the existing qualified mappings.
|
|
579
|
+
if (hasOperatorWorkerNativeRenderer(adapter) && profile.transport === 'shell') {
|
|
580
|
+
if (selection.permissions?.sandbox && !['read-only', 'workspace-write'].includes(selection.permissions.sandbox)) {
|
|
581
|
+
throw new Error('Requested sandbox has no verified native renderer');
|
|
582
|
+
}
|
|
583
|
+
if (selection.permissions?.sandbox === 'external' && (!selection.boundary
|
|
584
|
+
|| !['os', 'executor'].includes(selection.boundary.kind) || !selection.boundary.controls.includes('sandbox'))) {
|
|
585
|
+
throw new Error('Requested external sandbox requires an enforcing boundary');
|
|
586
|
+
}
|
|
587
|
+
for (const name of selection.requiredControls ?? []) {
|
|
588
|
+
if (!['native', 'external'].includes(profile.controls[name]?.support)) {
|
|
589
|
+
throw new Error(`Required worker control is unqualified: ${name}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return profile;
|
|
593
|
+
}
|
|
594
|
+
const requiredControls = requiredWorkerControls(selection);
|
|
595
|
+
if (requiresWorkerExternalBoundary(adapter, selection)
|
|
596
|
+
&& (!selection.boundary || !['os', 'executor'].includes(selection.boundary.kind)
|
|
597
|
+
|| !selection.boundary.identity || !/^sha256:[a-f0-9]{64}$/.test(selection.boundary.digest)
|
|
598
|
+
|| !selection.boundary.controls?.includes('sandbox'))) {
|
|
599
|
+
throw new Error('Required worker control sandbox needs a qualified external enforcing boundary');
|
|
600
|
+
}
|
|
601
|
+
for (const name of requiredControls) {
|
|
602
|
+
if ((name === 'children' && !adapter.capabilities.nestedAgentsControllable)
|
|
603
|
+
|| (name === 'fallback' && !adapter.capabilities.nativeFallbackControllable)) {
|
|
604
|
+
throw new Error(`Required worker control is unqualified: ${name}`);
|
|
605
|
+
}
|
|
606
|
+
const control = profile.controls[name];
|
|
607
|
+
if (!control || !['native', 'external'].includes(control.support) || !control.mapping || !control.evidence) {
|
|
608
|
+
throw new Error(`Required worker control is unqualified: ${name}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (selection.transport === 'mcp' && (!selection.mcp || !profile.mcp
|
|
612
|
+
|| Object.keys(profile.mcp).some(key => profile.mcp[key]
|
|
613
|
+
!== selection.mcp[key]))) {
|
|
614
|
+
throw new Error('MCP server/tool/schema/session qualification changed');
|
|
615
|
+
}
|
|
616
|
+
return profile;
|
|
617
|
+
}
|
|
618
|
+
/** Qualification is transport/version scoped; declaration or boundary claims
|
|
619
|
+
* alone cannot qualify an external sandbox. The invocation owner rechecks the
|
|
620
|
+
* installation scope, including this boundary, against the recorded digest. */
|
|
621
|
+
export function qualifyWorkerInvocationProfile(adapter, selection) {
|
|
622
|
+
const profile = inspectWorkerInvocationProfile(adapter, selection);
|
|
623
|
+
if (isOperatorQualifiedWorkerAdapter(adapter) && selection.policy?.guards?.denySelfDelegation !== false
|
|
624
|
+
&& !adapter.capabilities.nestedAgentsControllable) {
|
|
625
|
+
throw new Error('Required worker control is unqualified: children');
|
|
626
|
+
}
|
|
627
|
+
if (requiresWorkerExternalBoundary(adapter, selection) && !profile.installationQualification) {
|
|
628
|
+
throw new Error('Required worker control sandbox needs matching installation qualification for its external boundary');
|
|
629
|
+
}
|
|
630
|
+
return profile;
|
|
631
|
+
}
|
|
632
|
+
/** Health timing evidence belongs to the exact execution profile and tuple.
|
|
633
|
+
* Fixture qualification does not become authenticated/live qualification. */
|
|
634
|
+
export function workerHealthScopeDigest(profile, scope) {
|
|
635
|
+
const { installationQualification: _installation, health, ...executionProfile } = profile;
|
|
636
|
+
const { qualification: _qualification, ...signals } = health ?? {};
|
|
637
|
+
return qualificationDigest({ profile: executionProfile, health: signals,
|
|
638
|
+
profileRef: scope.profileRef, cliVersion: scope.cliVersion, accountScope: scope.accountScope,
|
|
639
|
+
modelKey: workerModelKey(scope.inferenceProvider, scope.model), effort: scope.effort });
|
|
640
|
+
}
|
|
641
|
+
/** Reused by observation and invocation owners before using qualified health.
|
|
642
|
+
* Older profiles have no qualification record and retain degraded observation. */
|
|
643
|
+
export function assertWorkerHealthQualification(profile, scope) {
|
|
644
|
+
const evidence = profile.health?.qualification;
|
|
645
|
+
if (!evidence)
|
|
646
|
+
return;
|
|
647
|
+
if (evidence.status === 'unqualified') {
|
|
648
|
+
if (profile.health?.coverage === 'full' || profile.health?.handshake === 'semantic') {
|
|
649
|
+
throw new Error('Unqualified worker health cannot assert full or semantic coverage');
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const modelKey = workerModelKey(scope.inferenceProvider, scope.model);
|
|
654
|
+
if (evidence.cliVersion !== scope.cliVersion || evidence.accountScope !== scope.accountScope
|
|
655
|
+
|| evidence.profileRef !== scope.profileRef || evidence.modelKey !== modelKey || evidence.effort !== scope.effort
|
|
656
|
+
|| !profile.health?.qualifiedAt || !Number.isFinite(Date.parse(profile.health.qualifiedAt))
|
|
657
|
+
|| evidence.scopeDigest !== workerHealthScopeDigest(profile, scope)
|
|
658
|
+
|| (evidence.status === 'live-qualified' && evidence.authorizedLive !== true)) {
|
|
659
|
+
throw new Error('Worker health qualification scope changed or lacks live authorization');
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
export function parseProviderModelCatalog(input) {
|
|
663
|
+
const validate = getCatalogValidators().catalog;
|
|
664
|
+
if (!validate(input))
|
|
665
|
+
throw new Error(`MODEL_POLICY_INVALID: ${validate.errors?.map(e => `${e.instancePath} ${e.message}`).join('; ')}`);
|
|
666
|
+
return CatalogSchema.parse(input);
|
|
667
|
+
}
|
|
115
668
|
export function loadProviderModelCatalog() {
|
|
116
669
|
if (!catalogCache)
|
|
117
|
-
catalogCache =
|
|
670
|
+
catalogCache = parseProviderModelCatalog(catalogData);
|
|
118
671
|
return catalogCache;
|
|
119
672
|
}
|
|
120
673
|
export function validateCanonicalModelPolicy(input) {
|
|
@@ -138,7 +691,8 @@ export function validateUserProjectModelConfig(input) {
|
|
|
138
691
|
return {
|
|
139
692
|
valid: false,
|
|
140
693
|
diagnostics: parsed.error.issues.map(issue => ({
|
|
141
|
-
code: 'MODEL_POLICY_INVALID',
|
|
694
|
+
code: issue.path.includes('workerSelection') ? 'OG_CONFIG_REFERENCE' : 'MODEL_POLICY_INVALID',
|
|
695
|
+
pointer: `/${issue.path.map(p => String(p).replace(/~/g, '~0').replace(/\//g, '~1')).join('/')}`,
|
|
142
696
|
severity: 'error',
|
|
143
697
|
message: `${issue.path.join('.') || 'config'}: ${issue.message}`,
|
|
144
698
|
})),
|
|
@@ -274,4 +828,158 @@ export function resetModelPolicyCachesForTests() {
|
|
|
274
828
|
registryCache = null;
|
|
275
829
|
catalogCache = null;
|
|
276
830
|
}
|
|
831
|
+
// Dispatch rechecks consume the effective policy from resolution. Keep these
|
|
832
|
+
// predicates shared with route validation so defaults and restrictions agree.
|
|
833
|
+
function isContributorWorkerModel(model) {
|
|
834
|
+
return /(?:^|[-_:/.])contributor(?:$|[-_:/.])/i.test(model);
|
|
835
|
+
}
|
|
836
|
+
function isWorkerModelDenied(modelKey, policy) {
|
|
837
|
+
return (policy.guards?.deniedModels ?? DEFAULT_WORKER_SELECTION.guards.deniedModels).includes(modelKey);
|
|
838
|
+
}
|
|
839
|
+
function isWorkerEffortDenied(effort, policy) {
|
|
840
|
+
return effort !== null && (policy.guards?.deniedEfforts ?? DEFAULT_WORKER_SELECTION.guards.deniedEfforts).includes(effort);
|
|
841
|
+
}
|
|
842
|
+
function isWorkerNullEffortDenied(effort, observation, guidance) {
|
|
843
|
+
return effort === null && (observation.effortSupport !== 'not-applicable' || guidance?.allowNotApplicableEffort !== true);
|
|
844
|
+
}
|
|
845
|
+
function workerRouteDataPolicy(policy, dataClass) {
|
|
846
|
+
const data = { ...DEFAULT_WORKER_SELECTION.dataPolicy, ...policy.dataPolicy };
|
|
847
|
+
const narrowed = dataClass ? policy.dataClasses?.[dataClass] : undefined;
|
|
848
|
+
if (narrowed) {
|
|
849
|
+
if (narrowed.allowedProcessing)
|
|
850
|
+
data.allowedProcessing = data.allowedProcessing?.filter(p => narrowed.allowedProcessing.includes(p));
|
|
851
|
+
if (narrowed.allowUnknown === false)
|
|
852
|
+
data.allowUnknown = false;
|
|
853
|
+
if (narrowed.allowProductImprovement === false)
|
|
854
|
+
data.allowProductImprovement = false;
|
|
855
|
+
if (narrowed.maxRetentionDays !== undefined)
|
|
856
|
+
data.maxRetentionDays = Math.min(data.maxRetentionDays ?? Infinity, narrowed.maxRetentionDays);
|
|
857
|
+
}
|
|
858
|
+
return { data, unknownDataClass: Boolean(dataClass && !narrowed) };
|
|
859
|
+
}
|
|
860
|
+
function isWorkerDataUseDenied(facts, data) {
|
|
861
|
+
return (facts.processing === 'unknown' && data.allowUnknown !== true)
|
|
862
|
+
|| (facts.processing !== 'unknown' && !data.allowedProcessing?.includes(facts.processing))
|
|
863
|
+
|| (facts.productImprovement === null && data.allowUnknown !== true)
|
|
864
|
+
|| (facts.productImprovement === true && data.allowProductImprovement !== true)
|
|
865
|
+
|| (data.maxRetentionDays !== undefined && (facts.retentionDays === null ? data.allowUnknown !== true : facts.retentionDays > data.maxRetentionDays));
|
|
866
|
+
}
|
|
867
|
+
function isWorkerEvidenceStale(observation, policy, now) {
|
|
868
|
+
const observed = Date.parse(observation.observedAt);
|
|
869
|
+
const verified = observation.verifiedAt === undefined ? undefined : Date.parse(observation.verifiedAt);
|
|
870
|
+
const blockHours = policy.freshness?.blockHours ?? DEFAULT_WORKER_SELECTION.freshness.blockHours;
|
|
871
|
+
const policyDays = policy.freshness?.policyDays ?? DEFAULT_WORKER_SELECTION.freshness.policyDays;
|
|
872
|
+
return observed > now || now - observed > blockHours * 3600000
|
|
873
|
+
|| (verified !== undefined && (verified > now || now - verified > policyDays * 86400000));
|
|
874
|
+
}
|
|
875
|
+
/** Optional catalog facts do not veto otherwise qualified selection. */
|
|
876
|
+
export function hasSelectionRelevantConflicts(observation) {
|
|
877
|
+
const relevant = /^\/(?:lifecycle|availability|visibility|hidden|efforts|effortSupport|reasoningEfforts|sunsetAt|retiredAt|replacementModelId|supersededBy|dataUse|id|apiModelId|llmProvider|cliId|family|runtimeVersion|accountScope|endpointFingerprint|identityEvidence|contextTokens|maxOutputTokens|qualification)(?:\/|$)/;
|
|
878
|
+
return observation.conflicts?.some(conflict => relevant.test(conflict.field)) ?? false;
|
|
879
|
+
}
|
|
880
|
+
/** Validate a resolved route without launching or creating managed state. */
|
|
881
|
+
export function validateWorkerPolicyRoute(input) {
|
|
882
|
+
const diagnostics = [];
|
|
883
|
+
const deny = (code, pointer, message) => { diagnostics.push({ code, pointer, message }); };
|
|
884
|
+
const shapeErrors = validateWorkerModelObservation(input.observation);
|
|
885
|
+
if (shapeErrors.length)
|
|
886
|
+
return [{ code: 'OG_ADAPTER_UNQUALIFIED', pointer: '/worker', message: shapeErrors.join('; ') }];
|
|
887
|
+
const observation = input.observation;
|
|
888
|
+
if (hasSelectionRelevantConflicts(observation))
|
|
889
|
+
deny('OG_MODEL_UNAPPROVED', '/worker/conflicts', 'Conflicting selection-relevant model evidence blocks the route');
|
|
890
|
+
else if (observation.conflicts?.length)
|
|
891
|
+
diagnostics.push({
|
|
892
|
+
code: 'OG_MODEL_UNAPPROVED', pointer: '/worker/conflicts', blocking: false,
|
|
893
|
+
message: `Conflicting optional model evidence remains unknown: ${observation.conflicts.map(conflict => conflict.field).join(', ')}`,
|
|
894
|
+
});
|
|
895
|
+
const guards = input.policy.guards ?? DEFAULT_WORKER_SELECTION.guards;
|
|
896
|
+
const guidance = input.policy.roleGuidance?.[input.role];
|
|
897
|
+
if (observation.qualification.status !== 'qualified' || !observation.qualification.roles.includes(input.role))
|
|
898
|
+
deny('OG_ADAPTER_UNQUALIFIED', '/worker/qualification', 'Role lacks qualified evidence. Run aiwg doctor --probe-workers --qualify.');
|
|
899
|
+
if (!guidance)
|
|
900
|
+
deny('OG_CONFIG_REFERENCE', `/defaults/workerSelection/roleGuidance/${input.role}`, 'Role guidance is unavailable');
|
|
901
|
+
const substitution = input.substitution?.model === input.selectedModel
|
|
902
|
+
&& input.substitution.effort === input.effort && input.substitution.rationale.trim();
|
|
903
|
+
if (input.wrapperPin && input.wrapperPin !== input.selectedModel && !substitution)
|
|
904
|
+
deny('OG_PIN_MISMATCH', '/agentOverrides', 'Wrapper exact pin differs from selected model');
|
|
905
|
+
if (guards.requireExactModel !== false && input.selectedModel !== input.apiModelId)
|
|
906
|
+
deny('OG_PIN_MISMATCH', '/worker/id', 'Selected model is not the exact observed API model');
|
|
907
|
+
let modelKey;
|
|
908
|
+
try {
|
|
909
|
+
modelKey = workerModelKey(input.inferenceProvider, input.apiModelId);
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
deny('OG_CONFIG_REFERENCE', '/worker/id', 'Invalid inference model identity');
|
|
913
|
+
}
|
|
914
|
+
// The dispatch guard checks the selected model; when requireExactModel is
|
|
915
|
+
// false that can differ from apiModelId, so deny on either identity.
|
|
916
|
+
let selectedKey;
|
|
917
|
+
try {
|
|
918
|
+
selectedKey = workerModelKey(input.inferenceProvider, input.selectedModel);
|
|
919
|
+
}
|
|
920
|
+
catch { /* reported via apiModelId */ }
|
|
921
|
+
if (isContributorWorkerModel(input.apiModelId) || isContributorWorkerModel(input.selectedModel)
|
|
922
|
+
|| (modelKey && isWorkerModelDenied(modelKey, input.policy))
|
|
923
|
+
|| (selectedKey && isWorkerModelDenied(selectedKey, input.policy)))
|
|
924
|
+
deny('OG_MODEL_UNAPPROVED', '/defaults/workerSelection/guards/deniedModels', 'Inference model is denied');
|
|
925
|
+
if (input.identityEvidence === 'unknown' || input.identityEvidence === 'mismatched' || (guards.minimumIdentityEvidence === 'backend-attested' && input.identityEvidence !== 'backend-attested'))
|
|
926
|
+
deny('OG_ADAPTER_UNQUALIFIED', '/defaults/workerSelection/guards/minimumIdentityEvidence', 'Identity evidence does not meet the required order');
|
|
927
|
+
if (!(guards.allowedVisibility ?? ['listed']).includes(observation.visibility) || !(guards.allowedLifecycle ?? ['active']).includes(observation.lifecycle) || observation.availability !== 'accessible')
|
|
928
|
+
deny('OG_MODEL_UNAPPROVED', '/worker', 'Visibility, lifecycle or availability is ineligible');
|
|
929
|
+
if (input.effort === null) {
|
|
930
|
+
if (isWorkerNullEffortDenied(input.effort, observation, guidance))
|
|
931
|
+
deny('OG_EFFORT_DENIED', '/worker/effortSupport', 'Null effort requires qualified absence of an effort control');
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
const effort = observation.efforts.find(e => e.id === input.effort);
|
|
935
|
+
if (observation.effortSupport !== 'explicit' || !effort || !(input.effort === 'none' ? guidance?.initialEffort === 'none' && guidance.effortLadder.length === 0 : guidance?.effortLadder.includes(input.effort)) || isWorkerEffortDenied(input.effort, input.policy) || (guards.denySelfDelegation !== false && effort.selfDelegates !== false))
|
|
936
|
+
deny('OG_EFFORT_DENIED', '/worker/efforts', 'Unsupported, denied or self-delegating effort. Check catalog effort evidence and models.json defaults.workerSelection.roleGuidance, guards.deniedEfforts and guards.denySelfDelegation.');
|
|
937
|
+
}
|
|
938
|
+
const { data, unknownDataClass } = workerRouteDataPolicy(input.policy, input.dataClass);
|
|
939
|
+
if (unknownDataClass)
|
|
940
|
+
deny('OG_DATA_POLICY', '/defaults/workerSelection/dataClasses', 'Unknown data class');
|
|
941
|
+
if (isWorkerDataUseDenied(observation.dataUse, data)) {
|
|
942
|
+
const facts = observation.dataUse;
|
|
943
|
+
const keys = [
|
|
944
|
+
...((facts.processing === 'unknown' || facts.productImprovement === null
|
|
945
|
+
|| data.maxRetentionDays !== undefined && facts.retentionDays === null) && data.allowUnknown !== true ? ['allowUnknown'] : []),
|
|
946
|
+
...(facts.processing !== 'unknown' && !data.allowedProcessing?.includes(facts.processing) ? ['allowedProcessing'] : []),
|
|
947
|
+
...(facts.productImprovement === true && data.allowProductImprovement !== true ? ['allowProductImprovement'] : []),
|
|
948
|
+
...(data.maxRetentionDays !== undefined && facts.retentionDays !== null && facts.retentionDays > data.maxRetentionDays ? ['maxRetentionDays'] : []),
|
|
949
|
+
];
|
|
950
|
+
deny('OG_DATA_POLICY', '/worker/dataUse', `Execution path data facts do not satisfy policy. Review models.json ${keys.map(key => `defaults.workerSelection.dataPolicy.${key}`).join(', ')}${input.dataClass ? ` and defaults.workerSelection.dataClasses.${input.dataClass}` : ''}; unknown facts remain unknown.`);
|
|
951
|
+
}
|
|
952
|
+
const now = (input.now ?? new Date()).getTime();
|
|
953
|
+
if (observation.retiredAt != null && Date.parse(observation.retiredAt) <= now
|
|
954
|
+
|| observation.sunsetAt && Date.parse(observation.sunsetAt) <= now) {
|
|
955
|
+
deny('OG_MODEL_UNAPPROVED', '/worker/lifecycle', 'Model retirement or sunset is effective');
|
|
956
|
+
}
|
|
957
|
+
if (isWorkerEvidenceStale(observation, input.policy, now))
|
|
958
|
+
deny('OG_ADAPTER_UNQUALIFIED', '/worker/observedAt', 'Model evidence is stale or from the future');
|
|
959
|
+
else if (now - Date.parse(observation.observedAt) > (input.policy.freshness?.warnHours ?? DEFAULT_WORKER_SELECTION.freshness.warnHours) * 3600000) {
|
|
960
|
+
diagnostics.push({ code: 'OG_ADAPTER_UNQUALIFIED', pointer: '/worker/observedAt', blocking: false, message: 'Model observation exceeds the freshness warning threshold' });
|
|
961
|
+
}
|
|
962
|
+
if (input.context && !workerContextHeadroom(input.context, observation).fits) {
|
|
963
|
+
const missing = [observation.contextTokens === null ? 'contextTokens' : ''].filter(Boolean);
|
|
964
|
+
deny('OG_MODEL_UNAPPROVED', '/worker/contextTokens', missing.length
|
|
965
|
+
? `Bounded context and output cannot be checked: catalog does not report a qualified ${missing.join(', ')} limit. Refresh discovery with reported limits; operator approval does not invent limits.`
|
|
966
|
+
: 'Bounded context and output do not fit qualified limits; reduce the request context or reserved output.');
|
|
967
|
+
}
|
|
968
|
+
if (input.context && observation.contextTokens !== null && observation.maxOutputTokens === null) {
|
|
969
|
+
diagnostics.push({ code: 'OG_MODEL_UNAPPROVED', pointer: '/worker/maxOutputTokens', blocking: false,
|
|
970
|
+
message: 'Output limit is unreported; only the context window was checked.' });
|
|
971
|
+
}
|
|
972
|
+
return diagnostics;
|
|
973
|
+
}
|
|
974
|
+
export function workerContextHeadroom(estimate, observation) {
|
|
975
|
+
const safetyMargin = Math.max(estimate.safetyMargin ?? 0, Math.ceil((observation.contextTokens ?? 0) * 0.1));
|
|
976
|
+
const terms = [estimate.briefTokens, estimate.inputTokens, estimate.toolOutputAllowance, estimate.reservedOutput, safetyMargin];
|
|
977
|
+
const total = terms.reduce((sum, value) => sum + value, 0);
|
|
978
|
+
const fits = Boolean(estimate.estimator.trim()) && terms.every(value => Number.isSafeInteger(value) && value >= 0)
|
|
979
|
+
&& (estimate.safetyMargin === undefined || Number.isSafeInteger(estimate.safetyMargin) && estimate.safetyMargin >= 0)
|
|
980
|
+
&& Number.isSafeInteger(total) && observation.contextTokens !== null
|
|
981
|
+
&& total <= observation.contextTokens && (observation.maxOutputTokens === null || estimate.reservedOutput <= observation.maxOutputTokens);
|
|
982
|
+
return { ...estimate, safetyMargin, total, qualifiedContextTokens: observation.contextTokens,
|
|
983
|
+
qualifiedMaxOutputTokens: observation.maxOutputTokens, fits };
|
|
984
|
+
}
|
|
277
985
|
//# sourceMappingURL=provider-policy.js.map
|