@aiwg/cli 2026.10.1 → 2026.10.3

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.
Files changed (166) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +270 -0
  2. package/agentic/code/providers/model-capabilities.v1.json +3091 -24
  3. package/bin/aiwg.mjs +86 -21
  4. package/dist/src/a2a/codecs.js +9 -0
  5. package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
  6. package/dist/src/artifacts/repair.js +9 -4
  7. package/dist/src/channel/manager.mjs +59 -10
  8. package/dist/src/cli/child-process-result.js +40 -0
  9. package/dist/src/cli/env.js +38 -16
  10. package/dist/src/cli/flag-parsing.js +236 -0
  11. package/dist/src/cli/handlers/decision.js +210 -4
  12. package/dist/src/cli/handlers/diagnose.js +9 -2
  13. package/dist/src/cli/handlers/feedback.js +14 -5
  14. package/dist/src/cli/handlers/init.js +245 -30
  15. package/dist/src/cli/handlers/mc.js +23 -0
  16. package/dist/src/cli/handlers/models.js +69 -6
  17. package/dist/src/cli/handlers/ralph.js +3 -0
  18. package/dist/src/cli/handlers/refresh.js +16 -0
  19. package/dist/src/cli/handlers/regenerate.js +14 -0
  20. package/dist/src/cli/handlers/runtime-info.js +24 -9
  21. package/dist/src/cli/handlers/script-runner.js +16 -6
  22. package/dist/src/cli/handlers/session.js +396 -62
  23. package/dist/src/cli/handlers/sessions.js +146 -40
  24. package/dist/src/cli/handlers/setup-manifest.js +439 -39
  25. package/dist/src/cli/handlers/setup.js +139 -39
  26. package/dist/src/cli/handlers/subcommands.js +3 -1
  27. package/dist/src/cli/handlers/team.js +18 -8
  28. package/dist/src/cli/handlers/use.js +13 -0
  29. package/dist/src/cli/handlers/utilities.js +106 -23
  30. package/dist/src/cli/prompt-utils.js +72 -0
  31. package/dist/src/cli/provider-resolution.js +11 -0
  32. package/dist/src/config/aiwg-config.js +268 -4
  33. package/dist/src/config/cli.js +146 -19
  34. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  35. package/dist/src/config/project-artifacts-health.mjs +14 -3
  36. package/dist/src/config/user-config.js +36 -10
  37. package/dist/src/decision/adapters/jev.js +75 -27
  38. package/dist/src/decision/driver.js +3 -1
  39. package/dist/src/decision/entry.js +38 -6
  40. package/dist/src/decision/heldout/collector.js +10 -1
  41. package/dist/src/decision/index.js +1 -0
  42. package/dist/src/decision/interop.js +60 -8
  43. package/dist/src/decision/ledger.js +839 -0
  44. package/dist/src/decision/validate.js +6 -3
  45. package/dist/src/effects/dispatch-verifier.js +252 -0
  46. package/dist/src/effects/index.js +1 -0
  47. package/dist/src/features/installer.js +2 -2
  48. package/dist/src/flow/graph-metadata.js +5 -0
  49. package/dist/src/gates/providers/loader.js +3 -1
  50. package/dist/src/installation/manager-command.mjs +1 -1
  51. package/dist/src/installation/manager.mjs +17 -2
  52. package/dist/src/lint/runner.js +2 -2
  53. package/dist/src/mcp/adapters/codex-runtime.js +360 -56
  54. package/dist/src/mcp/cli.mjs +587 -77
  55. package/dist/src/mcp/config-file.mjs +279 -0
  56. package/dist/src/mcp/config-layers.mjs +348 -0
  57. package/dist/src/mcp/credentials.mjs +484 -0
  58. package/dist/src/mcp/grok-build-config.mjs +16 -26
  59. package/dist/src/mcp/helpers.mjs +106 -2
  60. package/dist/src/mcp/omp-config.mjs +50 -29
  61. package/dist/src/mcp/profiles.js +97 -23
  62. package/dist/src/mcp/profiles.mjs +99 -27
  63. package/dist/src/mcp/registry.js +264 -57
  64. package/dist/src/mcp/registry.mjs +198 -45
  65. package/dist/src/mcp/server.mjs +403 -20
  66. package/dist/src/mcp/toml-string.mjs +9 -0
  67. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  68. package/dist/src/mcp/tool-filters.mjs +357 -0
  69. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  70. package/dist/src/mcp/tools/decision.mjs +21 -3
  71. package/dist/src/mcp/tools/orchestration.mjs +38 -0
  72. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  73. package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
  74. package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
  75. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  76. package/dist/src/memory/project-registry.js +2 -2
  77. package/dist/src/mission-protocol/codecs.js +639 -1
  78. package/dist/src/mission-protocol/types.js +110 -0
  79. package/dist/src/mission-protocol/worker-status.js +2 -0
  80. package/dist/src/models/config-loader.js +70 -13
  81. package/dist/src/models/grok-build-models.js +93 -41
  82. package/dist/src/models/model-capabilities.v1.json +3091 -24
  83. package/dist/src/models/model-discovery.js +862 -63
  84. package/dist/src/models/provider-policy.js +625 -11
  85. package/dist/src/models/qualification-digest.mjs +17 -0
  86. package/dist/src/models/resolver.js +259 -0
  87. package/dist/src/models/worker-direct-readiness.js +529 -0
  88. package/dist/src/models/worker-dispatch-offer.js +169 -0
  89. package/dist/src/models/worker-health-readiness.js +131 -0
  90. package/dist/src/models/worker-installation.mjs +879 -0
  91. package/dist/src/models/wrapper-route.js +18 -0
  92. package/dist/src/ops/registry.js +9 -14
  93. package/dist/src/plugins/standalone-packager.js +4 -3
  94. package/dist/src/process/bounded-child-process.mjs +387 -0
  95. package/dist/src/process/cli-command.mjs +75 -0
  96. package/dist/src/providers/capability-matrix.js +8 -0
  97. package/dist/src/providers/capability-matrix.yaml +270 -0
  98. package/dist/src/providers/grok-build-headless.js +43 -21
  99. package/dist/src/providers/provider-definitions.js +9 -1
  100. package/dist/src/providers/provider-definitions.mjs +9 -2
  101. package/dist/src/serve/executor-registry.js +16 -24
  102. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  103. package/dist/src/serve/mission-conductor.js +9 -0
  104. package/dist/src/serve/shared-host-scheduler.js +2 -0
  105. package/dist/src/serve/worker-invocation.js +538 -0
  106. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  107. package/dist/src/sessions/adapter-registry.js +45 -0
  108. package/dist/src/sessions/adapters/generic.js +37 -3
  109. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  110. package/dist/src/sessions/adapters/grokbot.js +423 -24
  111. package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
  112. package/dist/src/sessions/adapters/muse.js +68 -29
  113. package/dist/src/sessions/adapters/pi.js +1 -0
  114. package/dist/src/sessions/batch-contracts.js +13 -1
  115. package/dist/src/sessions/batch-import.js +23 -42
  116. package/dist/src/sessions/candidates.js +47 -11
  117. package/dist/src/sessions/contracts.js +35 -0
  118. package/dist/src/sessions/coverage-preflight.js +134 -0
  119. package/dist/src/sessions/discovery.js +3 -0
  120. package/dist/src/sessions/grok-build-cli.js +67 -5
  121. package/dist/src/sessions/importer.js +24 -8
  122. package/dist/src/sessions/index.js +2 -0
  123. package/dist/src/sessions/repository.js +54 -13
  124. package/dist/src/sessions/workspace-discovery.js +99 -37
  125. package/dist/src/skills/adapters/clawhub.js +10 -8
  126. package/dist/src/skills/adapters/openclaw.js +10 -7
  127. package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
  128. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  129. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  130. package/dist/src/smiths/mcpsmith/example.js +1 -1
  131. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
  132. package/dist/src/storage/backends/logseq.js +68 -7
  133. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  134. package/dist/src/testing/generators/test-code-generator.js +57 -30
  135. package/dist/src/uhp/client.js +11 -1
  136. package/dist/src/uhp/mission.js +5 -0
  137. package/dist/src/update/checker.mjs +5 -5
  138. package/dist/src/update/notifier.mjs +54 -2
  139. package/dist/src/writing/fidelity.js +18 -1
  140. package/package.json +18 -1
  141. package/schemas/mission-v1.schema.json +2429 -0
  142. package/schemas/models/model-catalog.v1.schema.json +301 -0
  143. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  144. package/schemas/models/model-policy.v1.schema.json +14 -0
  145. package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
  146. package/tools/_resolve-impl.mjs +39 -0
  147. package/tools/agents/deploy-agents.mjs +15 -2
  148. package/tools/agents/providers/base.mjs +49 -32
  149. package/tools/agents/providers/grokbot.mjs +11 -12
  150. package/tools/agents/providers/hermes.mjs +6 -3
  151. package/tools/agents/providers/omp.mjs +5 -2
  152. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  153. package/tools/providers/deepseek-harness-transport.mjs +358 -0
  154. package/tools/providers/omp-transport.mjs +192 -0
  155. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  156. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  157. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  158. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  159. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  160. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  161. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  162. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  163. package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
  164. package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
  165. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  166. package/vscode-extension/schemas/aiwg.config.v1.json +1953 -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,6 +42,27 @@ 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({
36
67
  agent: OutcomeSchema,
37
68
  skill: OutcomeSchema,
@@ -45,12 +76,29 @@ const CapabilityEntrySchema = z.object({
45
76
  verification: z.string().min(1),
46
77
  sourceUrl: z.string().url(),
47
78
  verifiedAt: z.string().date(),
48
- });
79
+ workerAdapter: WorkerAdapterSchema.optional(),
80
+ // Descriptive health coverage is not an installed executable registration.
81
+ workerHealthAdapter: WorkerAdapterSchema.optional(),
82
+ workerHealthCoverage: z.object({
83
+ scope: z.literal('out'), coverage: z.literal('unobserved'), handshake: z.literal('unobserved'),
84
+ evidenceGrades: z.array(z.enum(['H', 'R', 'D'])).max(3),
85
+ qualificationOwner: z.string().regex(/^B-OG-[0-9]{2}$/),
86
+ provenance: z.array(z.string().min(1).max(4096)).min(1).max(16), reason: z.string().min(1).max(4096),
87
+ }).strict().optional(),
88
+ }).strict();
49
89
  const CapabilityRegistrySchema = z.object({
90
+ $schema: z.enum([
91
+ '../../../schemas/models/provider-model-capabilities.v1.schema.json',
92
+ 'https://aiwg.io/schemas/models/provider-model-capabilities.v1.schema.json',
93
+ ]).optional(),
50
94
  version: z.literal('1.0.0'),
51
95
  verifiedAt: z.string().date(),
52
96
  providers: z.record(ProviderSchema, CapabilityEntrySchema),
53
- });
97
+ workerAdapters: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerAdapterSchema)
98
+ .refine(value => Object.keys(value).length <= 128, 'At most 128 worker adapters').optional(),
99
+ workerHealthAdapters: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerAdapterSchema)
100
+ .refine(value => Object.keys(value).length <= 128, 'At most 128 worker health descriptors').optional(),
101
+ }).strict();
54
102
  const CatalogModelSchema = z.object({
55
103
  id: z.string().min(1),
56
104
  status: z.enum(['active', 'deprecated', 'unverified']),
@@ -66,22 +114,228 @@ const CatalogProviderSchema = z.object({
66
114
  sourceUrl: z.string().url(),
67
115
  verifiedAt: z.string().date(),
68
116
  });
117
+ let catalogValidators;
118
+ function getCatalogValidators() {
119
+ if (!catalogValidators) {
120
+ const here = dirname(fileURLToPath(import.meta.url));
121
+ const file = [resolve(here, '../../schemas/models/model-catalog.v1.schema.json'), resolve(here, '../../../schemas/models/model-catalog.v1.schema.json')].find(existsSync);
122
+ if (!file)
123
+ throw new Error('Model catalog schema is unavailable');
124
+ const schema = JSON.parse(readFileSync(file, 'utf8'));
125
+ const ajv = addFormats(new Ajv2020({ strict: false, allErrors: true }));
126
+ ajv.addSchema(schema);
127
+ catalogValidators = { catalog: ajv.getSchema(schema.$id), observation: ajv.compile({ $ref: `${schema.$id}#/$defs/workerObservation` }), discovery: ajv.compile({ $ref: `${schema.$id}#/properties/discovery` }) };
128
+ }
129
+ return catalogValidators;
130
+ }
131
+ export function validateWorkerModelObservation(input) {
132
+ const validate = getCatalogValidators().observation;
133
+ return validate(input) ? [] : (validate.errors ?? []).map(error => `${error.instancePath}: ${error.message}`);
134
+ }
135
+ const WorkerObservationSchema = z.custom(input => validateWorkerModelObservation(input).length === 0);
136
+ const DiscoveredWorkerModelSchema = z.object({
137
+ id: z.string().min(1), apiModelId: z.string().optional(), llmProvider: z.string().optional(),
138
+ hidden: z.boolean().optional(), reasoningEfforts: z.array(z.string()).optional(), worker: WorkerObservationSchema.optional(),
139
+ }).passthrough().superRefine((model, context) => {
140
+ if (!model.worker)
141
+ return;
142
+ if (model.hidden !== undefined && model.worker.visibility !== (model.hidden ? 'hidden' : 'listed'))
143
+ context.addIssue({ code: 'custom', path: ['worker', 'visibility'], message: 'Conflicting visibility evidence' });
144
+ const efforts = model.reasoningEfforts ?? [];
145
+ const observed = model.worker.efforts.map(e => e.id);
146
+ if (efforts.length !== observed.length || new Set(observed).size !== observed.length || efforts.some(e => !observed.includes(e)))
147
+ context.addIssue({ code: 'custom', path: ['worker', 'efforts'], message: 'Conflicting effort evidence' });
148
+ });
149
+ const ProviderDiscoverySchema = z.object({
150
+ provider: z.string(), source: z.enum(['native', 'cache', 'remote', 'static']), observedAt: z.string().datetime({ offset: true }),
151
+ runtimeVersion: z.string().optional(), accountScope: z.enum(['local-account', 'local-runtime', 'public-feed', 'static']),
152
+ accountBinding: z.string().min(1).optional(), models: z.array(DiscoveredWorkerModelSchema),
153
+ }).passthrough();
154
+ const DiscoverySchema = z.object({
155
+ source: z.enum(['native', 'cache', 'remote', 'static']), upstreamSource: z.enum(['native', 'remote', 'static']).optional(),
156
+ fetchedAt: z.string().datetime({ offset: true }), remoteUrl: z.string().url().optional(), remoteError: z.string().optional(),
157
+ inventorySignature: z.string().optional(), providers: z.record(z.string(), ProviderDiscoverySchema),
158
+ instances: z.record(z.string(), ProviderDiscoverySchema).optional(),
159
+ }).strict();
160
+ /** Optional enrichment is checked without tightening legacy catalog rows. */
161
+ export function validateWorkerCatalogDiscovery(input) {
162
+ if (input === undefined)
163
+ return [];
164
+ const validate = getCatalogValidators().discovery;
165
+ if (!validate(input))
166
+ return (validate.errors ?? []).map(error => `${error.instancePath}: ${error.message}`);
167
+ const parsed = DiscoverySchema.safeParse(input);
168
+ return parsed.success ? [] : parsed.error.issues.map(issue => `${issue.path.join('/')}: ${issue.message}`);
169
+ }
69
170
  const CatalogSchema = z.object({
70
171
  version: z.literal('1.0.0'),
172
+ $schema: z.literal('../../../schemas/models/model-catalog.v1.schema.json').optional(),
173
+ discovery: DiscoverySchema.optional(),
71
174
  refreshedAt: z.string().date(),
72
175
  staleAfterDays: z.number().int().positive(),
73
176
  providers: z.record(ProviderSchema, CatalogProviderSchema),
74
- });
177
+ }).strict();
75
178
  const PolicySchema = z.object({
76
179
  role: z.enum(['reasoning', 'coding', 'efficiency']),
77
180
  tier: z.enum(['economy', 'standard', 'premium']),
78
181
  effort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
79
182
  override: z.string().min(1).optional(),
80
183
  }).strict();
184
+ const uniqueStrings = z.array(z.string().min(1)).refine(values => new Set(values).size === values.length, 'Duplicate entries');
185
+ const WorkerDataPolicySchema = z.object({
186
+ allowedProcessing: z.array(z.enum(['local', 'remote'])).refine(values => new Set(values).size === values.length).optional(),
187
+ allowProductImprovement: z.boolean().optional(),
188
+ allowUnknown: z.boolean().optional(),
189
+ maxRetentionDays: z.number().int().nonnegative().optional(),
190
+ }).strict();
191
+ const WorkerRoleSchema = z.object({
192
+ groups: uniqueStrings.refine(values => values.length > 0),
193
+ tier: z.enum(['economy', 'standard', 'premium', 'max-quality']),
194
+ initialEffort: z.string().min(1).nullable(),
195
+ effortLadder: uniqueStrings,
196
+ escalateAfter: z.number().int().positive().optional(),
197
+ maxEscalations: z.number().int().nonnegative().optional(),
198
+ deescalateAfter: z.number().int().positive().optional(),
199
+ allowNotApplicableEffort: z.boolean().optional(),
200
+ }).strict();
201
+ export const WorkerSelectionPolicySchema = z.object({
202
+ roleGuidance: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerRoleSchema).optional(),
203
+ guards: z.object({
204
+ deniedEfforts: uniqueStrings.optional(),
205
+ deniedModels: uniqueStrings.optional(),
206
+ denySelfDelegation: z.boolean().optional(),
207
+ requireExactModel: z.boolean().optional(),
208
+ allowedVisibility: z.array(z.enum(['listed', 'hidden'])).refine(v => new Set(v).size === v.length).optional(),
209
+ allowedLifecycle: z.array(z.enum(['active', 'deprecated'])).refine(v => new Set(v).size === v.length).optional(),
210
+ minimumIdentityEvidence: z.enum(['runtime-configured', 'backend-attested']).optional(),
211
+ }).strict().optional(),
212
+ dataPolicy: WorkerDataPolicySchema.optional(),
213
+ dataClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerDataPolicySchema).optional(),
214
+ freshness: z.object({
215
+ warnHours: z.number().int().positive().optional(),
216
+ blockHours: z.number().int().positive().optional(),
217
+ policyDays: z.number().int().positive().optional(),
218
+ }).strict().optional(),
219
+ taskClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), z.string().min(1)).optional(),
220
+ }).strict().superRefine((policy, context) => {
221
+ for (const [role, guidance] of Object.entries(policy.roleGuidance ?? {})) {
222
+ if (!['efficiency', 'coding', 'reasoning'].includes(role))
223
+ context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Unknown worker role' });
224
+ if (guidance.effortLadder.includes('none') || (guidance.initialEffort === 'none' ? guidance.effortLadder.length !== 0 : guidance.initialEffort !== null && !guidance.effortLadder.includes(guidance.initialEffort))) {
225
+ context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Initial effort must belong to the ladder; real none requires an empty ladder' });
226
+ }
227
+ if (guidance.initialEffort === null && (guidance.allowNotApplicableEffort !== true || guidance.effortLadder.length !== 0)) {
228
+ context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Null effort requires an empty ladder and allowNotApplicableEffort=true' });
229
+ }
230
+ }
231
+ for (const key of policy.guards?.deniedModels ?? []) {
232
+ if (!isCanonicalWorkerModelKey(key))
233
+ context.addIssue({ code: 'custom', path: ['guards', 'deniedModels'], message: 'Noncanonical inference model key' });
234
+ }
235
+ if (policy.freshness?.warnHours !== undefined && policy.freshness.blockHours !== undefined && policy.freshness.warnHours > policy.freshness.blockHours) {
236
+ context.addIssue({ code: 'custom', path: ['freshness'], message: 'warnHours must not exceed blockHours' });
237
+ }
238
+ for (const role of Object.values(policy.taskClasses ?? {})) {
239
+ if (!['coding', 'reasoning', 'efficiency'].includes(role))
240
+ context.addIssue({ code: 'custom', path: ['taskClasses'], message: 'Unknown worker role' });
241
+ }
242
+ });
243
+ /** Canonical RFC3986 identity; installation and account never enter the key. */
244
+ export function workerModelKey(inferenceProvider, apiModelId) {
245
+ if (!/^[a-z][a-z0-9-]{0,62}$/.test(inferenceProvider) || !apiModelId)
246
+ throw new Error('OG_CONFIG_REFERENCE: invalid inference model identity');
247
+ return `${inferenceProvider}/${encodeURIComponent(apiModelId).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`;
248
+ }
249
+ export function isCanonicalWorkerModelKey(key) {
250
+ const slash = key.indexOf('/');
251
+ try {
252
+ return slash > 0 && workerModelKey(key.slice(0, slash), decodeURIComponent(key.slice(slash + 1))) === key;
253
+ }
254
+ catch {
255
+ return false;
256
+ }
257
+ }
258
+ export const DEFAULT_WORKER_SELECTION = {
259
+ guards: { deniedEfforts: ['ultra'], deniedModels: [], denySelfDelegation: true, requireExactModel: true, allowedVisibility: ['listed'], allowedLifecycle: ['active'], minimumIdentityEvidence: 'runtime-configured' },
260
+ dataPolicy: { allowedProcessing: ['local', 'remote'], allowProductImprovement: false, allowUnknown: false },
261
+ freshness: { warnHours: 24, blockHours: 168, policyDays: 30 },
262
+ taskClasses: { inventory: 'efficiency', implementation: 'coding', architecture: 'reasoning', review: 'reasoning' },
263
+ };
264
+ /** Merge leaves, preserving pre-merge explicit origins and attempted relaxations. */
265
+ export function mergeWorkerSelection(layers) {
266
+ const policy = {};
267
+ const origins = {};
268
+ const diagnostics = [];
269
+ const visit = (target, input, parts, layer) => {
270
+ for (const [key, value] of Object.entries(input)) {
271
+ const path = [...parts, key];
272
+ const pointer = `/defaults/workerSelection/${path.map(p => p.replace(/~/g, '~0').replace(/\//g, '~1')).join('/')}`;
273
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
274
+ target[key] ??= {};
275
+ visit(target[key], value, path, layer);
276
+ continue;
277
+ }
278
+ const origin = { source: layer.source, authority: layer.authority, digest: layer.digest, pointer, contribution: structuredClone(value) };
279
+ const prior = origins[pointer] ?? [];
280
+ const explicit = prior.filter(o => o.authority !== 'builtin');
281
+ let effective = structuredClone(value);
282
+ let relaxation = false;
283
+ if (layer.authority !== 'builtin' && explicit.length > 0) {
284
+ const previous = target[key];
285
+ if (['deniedEfforts', 'deniedModels'].includes(key))
286
+ effective = [...new Set([...previous, ...value])];
287
+ else if (['allowedVisibility', 'allowedLifecycle', 'allowedProcessing'].includes(key)) {
288
+ relaxation = value.some((v) => !previous.includes(v));
289
+ effective = previous.filter((v) => value.includes(v));
290
+ }
291
+ else if (['denySelfDelegation', 'requireExactModel'].includes(key)) {
292
+ relaxation = previous === true && value === false;
293
+ effective = previous || value;
294
+ }
295
+ else if (['allowUnknown', 'allowProductImprovement'].includes(key)) {
296
+ relaxation = previous === false && value === true;
297
+ effective = previous && value;
298
+ }
299
+ else if (['maxRetentionDays', 'warnHours', 'blockHours', 'policyDays'].includes(key)) {
300
+ relaxation = value > previous;
301
+ effective = Math.min(previous, value);
302
+ }
303
+ else if (key === 'minimumIdentityEvidence') {
304
+ relaxation = previous === 'backend-attested' && value !== previous;
305
+ effective = previous === 'backend-attested' ? previous : value;
306
+ }
307
+ }
308
+ if (relaxation)
309
+ diagnostics.push({ code: 'OG_POLICY_RELAXATION', pointer, message: 'Explicit restriction cannot relax an inherited restriction', origins: [...explicit, origin] });
310
+ target[key] = effective;
311
+ origins[pointer] = [...prior, origin];
312
+ }
313
+ };
314
+ for (const layer of layers)
315
+ if (layer.policy)
316
+ visit(policy, layer.policy, [], layer);
317
+ const validation = WorkerSelectionPolicySchema.safeParse(policy);
318
+ if (!validation.success)
319
+ for (const issue of validation.error.issues)
320
+ diagnostics.push({ code: 'OG_CONFIG_REFERENCE', pointer: `/defaults/workerSelection/${issue.path.join('/')}`, message: issue.message });
321
+ // Data classes are applied as further restrictions to the base path.
322
+ for (const dataClass of Object.values(policy.dataClasses ?? {})) {
323
+ const base = policy.dataPolicy ?? {};
324
+ if (dataClass.allowedProcessing)
325
+ dataClass.allowedProcessing = dataClass.allowedProcessing.filter(p => !base.allowedProcessing || base.allowedProcessing.includes(p));
326
+ for (const key of ['allowUnknown', 'allowProductImprovement'])
327
+ if (base[key] === false)
328
+ dataClass[key] = false;
329
+ if (base.maxRetentionDays !== undefined)
330
+ dataClass.maxRetentionDays = Math.min(dataClass.maxRetentionDays ?? base.maxRetentionDays, base.maxRetentionDays);
331
+ }
332
+ return { policy, origins, diagnostics };
333
+ }
81
334
  const UserProjectModelConfigSchema = z.object({
82
335
  defaults: z.object({
83
336
  tier: z.enum(['economy', 'standard', 'premium', 'max-quality']).optional(),
84
337
  provider: ProviderSchema.optional(),
338
+ workerSelection: WorkerSelectionPolicySchema.optional(),
85
339
  }).strict().optional(),
86
340
  agentOverrides: z.record(z.string(), z.object({
87
341
  'model-tier': z.enum(['economy', 'standard', 'premium', 'max-quality']).optional(),
@@ -103,18 +357,239 @@ let registryCache = null;
103
357
  let catalogCache = null;
104
358
  export function loadProviderModelCapabilities() {
105
359
  if (!registryCache) {
106
- registryCache = CapabilityRegistrySchema.parse(capabilityData);
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
- }
360
+ registryCache = parseProviderModelCapabilities(capabilityData);
112
361
  }
113
362
  return registryCache;
114
363
  }
364
+ export function parseProviderModelCapabilities(input) {
365
+ const registry = CapabilityRegistrySchema.parse(input);
366
+ const expected = new Set(ProviderSchema.options);
367
+ const actual = new Set(Object.keys(registry.providers));
368
+ if (actual.size !== expected.size || [...expected].some(id => !actual.has(id))) {
369
+ throw new Error('Provider model capability registry must cover all registered providers');
370
+ }
371
+ const ids = new Set();
372
+ for (const adapter of [...Object.values(registry.providers).flatMap(p => p.workerAdapter ? [p.workerAdapter] : []),
373
+ ...Object.values(registry.workerAdapters ?? {})]) {
374
+ if (!/^[a-z][a-z0-9-]{0,62}$/.test(adapter.id) || ids.has(adapter.id))
375
+ throw new Error(`Duplicate or invalid worker adapter ID: ${adapter.id}`);
376
+ ids.add(adapter.id);
377
+ }
378
+ return registry;
379
+ }
380
+ /** Exact, case-sensitive references; deployment and inference provider identities differ. */
381
+ export function resolveWorkerAdapter(registry, reference) {
382
+ const match = /^(providers|workerAdapters):([a-z][a-z0-9-]{0,62})$/.exec(reference);
383
+ if (!match)
384
+ throw new Error(`Invalid worker adapter reference: ${reference}`);
385
+ const [, namespace, id] = match;
386
+ const adapter = namespace === 'providers'
387
+ ? registry.providers[id]?.workerAdapter
388
+ : registry.workerAdapters?.[id];
389
+ if (!adapter)
390
+ throw new Error(`Unknown worker adapter reference: ${reference}`);
391
+ return WorkerAdapterSchema.parse(adapter);
392
+ }
393
+ /** Pure direct/shared mapping: no admission store, Mission or activation marker. */
394
+ export function resolveWorkerInstallation(registry, installation) {
395
+ const adapter = resolveWorkerAdapter(registry, installation.adapter);
396
+ if (adapter.invocation.kind === 'process') {
397
+ if (installation.endpoint !== undefined)
398
+ throw new Error('Process adapter forbids endpoint');
399
+ const executable = installation.executable ?? adapter.invocation.executable;
400
+ if (!executable.trim() || executable.includes('\0'))
401
+ throw new Error('Process adapter requires executable');
402
+ return { adapter, executable };
403
+ }
404
+ if (installation.executable !== undefined)
405
+ throw new Error('HTTP adapter forbids executable');
406
+ if (!installation.endpoint)
407
+ throw new Error('HTTP adapter requires endpoint');
408
+ const endpoint = new URL(installation.endpoint);
409
+ if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.hash) {
410
+ throw new Error('Invalid HTTP adapter endpoint');
411
+ }
412
+ return { adapter, endpoint: installation.endpoint };
413
+ }
414
+ /** Typed policy failures let public surfaces retain the cause without exposing internal details. */
415
+ export class WorkerPolicyError extends Error {
416
+ code;
417
+ constructor(code, message) {
418
+ super(message);
419
+ this.code = code;
420
+ this.name = 'WorkerPolicyError';
421
+ }
422
+ }
423
+ /**
424
+ * Dispatch-time safety checks belong here, alongside transport qualification.
425
+ * Like route validation, rendering requires observation evidence; a default
426
+ * observation may supply it when no model observation is available.
427
+ */
428
+ export function assertWorkerSelectionAllowed(selected, context) {
429
+ const policy = context.policy ?? DEFAULT_WORKER_SELECTION;
430
+ if (isWorkerEffortDenied(selected.effort.value, policy)) {
431
+ throw new WorkerPolicyError('OG_EFFORT_DENIED', `Worker invocation denied effort: ${selected.effort.value}`);
432
+ }
433
+ if (isContributorWorkerModel(selected.model)
434
+ || isWorkerModelDenied(workerModelKey(selected.inferenceProvider, selected.model), policy)) {
435
+ throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', `Worker invocation denied model: ${selected.model}`);
436
+ }
437
+ const { data, unknownDataClass } = workerRouteDataPolicy(policy, context.dataClass);
438
+ if (unknownDataClass)
439
+ throw new WorkerPolicyError('OG_DATA_POLICY', 'Worker invocation has denied data-use: unknown data class');
440
+ const observation = context.modelObservation ?? context.defaultObservation;
441
+ if (!observation)
442
+ throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', 'Worker invocation requires model or default observation evidence');
443
+ const guidance = context.role ? policy.roleGuidance?.[context.role] : undefined;
444
+ if (isWorkerNullEffortDenied(selected.effort.value, observation, guidance)) {
445
+ throw new WorkerPolicyError('OG_EFFORT_DENIED', 'Worker invocation denied effort: null effort requires qualified absence of an effort control');
446
+ }
447
+ for (const [kind, observation] of [
448
+ ['model', context.modelObservation], ['default', context.defaultObservation],
449
+ ]) {
450
+ if (!observation)
451
+ continue;
452
+ if (observation.lifecycle === 'retired'
453
+ || (observation.retiredAt != null && Date.parse(observation.retiredAt) <= context.now)
454
+ || (observation.sunsetAt != null && Date.parse(observation.sunsetAt) <= context.now)) {
455
+ throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', `Worker invocation ${kind} observation is retired`);
456
+ }
457
+ const blockMs = (policy.freshness?.blockHours ?? DEFAULT_WORKER_SELECTION.freshness.blockHours) * 3_600_000;
458
+ const observedAt = Date.parse(observation.observedAt);
459
+ if (!Number.isFinite(context.now) || !Number.isFinite(observedAt)
460
+ || !Number.isFinite(blockMs) || blockMs <= 0
461
+ || (observation.verifiedAt !== undefined && !Number.isFinite(Date.parse(observation.verifiedAt)))
462
+ || isWorkerEvidenceStale(observation, policy, context.now)) {
463
+ throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', `Worker invocation ${kind} observation is stale or invalid`);
464
+ }
465
+ if (hasSelectionRelevantConflicts(observation)) {
466
+ throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', `Worker invocation ${kind} observation has conflicting selection-relevant evidence`);
467
+ }
468
+ if (observation.dataUse && isWorkerDataUseDenied({
469
+ processing: observation.dataUse.processing ?? 'unknown',
470
+ productImprovement: observation.dataUse.productImprovement ?? null,
471
+ retentionDays: observation.dataUse.retentionDays ?? null,
472
+ }, data)) {
473
+ throw new WorkerPolicyError('OG_DATA_POLICY', `Worker invocation ${kind} observation has denied data-use`);
474
+ }
475
+ }
476
+ }
477
+ const WORKER_CONTROLS = ['model', 'effort', 'sandbox', 'approval', 'workdir',
478
+ 'finalOutput', 'stdin', 'structuredOutput', 'stepCap', 'trust', 'children', 'fallback'];
479
+ /** Caller controls only add restrictions. Absent resolved authority, require all controls. */
480
+ export function requiredWorkerControls(context) {
481
+ if (context.requiredControls !== undefined && (!Array.isArray(context.requiredControls)
482
+ || context.requiredControls.some(name => typeof name !== 'string' || !name))) {
483
+ throw new Error('Worker required controls must be an array of control names');
484
+ }
485
+ const controls = new Set(WORKER_CONTROLS);
486
+ if (context.policy && context.permissions) {
487
+ if (context.effort === null)
488
+ controls.delete('effort');
489
+ // Step limits are observed unless explicitly required by the trusted route.
490
+ controls.delete('stepCap');
491
+ if (context.policy.guards?.denySelfDelegation === false)
492
+ controls.delete('children');
493
+ }
494
+ for (const name of context.requiredControls ?? [])
495
+ controls.add(name);
496
+ return [...controls];
497
+ }
498
+ /** The same sandbox requirement governs inspection, resolution and rendering. */
499
+ export function requiresWorkerExternalBoundary(adapter, selection) {
500
+ const providers = [adapter.id, adapter.provider].map(normalizeProviderDefinitionId);
501
+ return requiredWorkerControls(selection).includes('sandbox')
502
+ && (providers.some(provider => provider && ['claude', 'muse', 'hermes'].includes(provider))
503
+ || adapter.invocationProfiles?.[selection.profile]?.controls.sandbox?.support === 'external'
504
+ || selection.permissions?.sandbox === 'external');
505
+ }
506
+ /** Inspect declared controls before installation evidence can be produced. This
507
+ * does not authorize invocation; consumers must use qualifyWorkerInvocationProfile. */
508
+ export function inspectWorkerInvocationProfile(adapter, selection) {
509
+ WorkerAdapterSchema.parse(adapter);
510
+ const profile = adapter.invocationProfiles?.[selection.profile];
511
+ if (adapter.qualification.status !== 'qualified' || !profile || profile.transport !== selection.transport) {
512
+ throw new Error('Worker transport/profile is unqualified');
513
+ }
514
+ // Shared range predicate (worker-installation.mjs): read-only surfaces
515
+ // (worker-direct-readiness.ts) reuse the same helper instead of
516
+ // re-deriving this inspector's range logic.
517
+ if (workerVersionRangeMismatch(selection.cliVersion, [adapter.supportedVersions, profile.supportedVersions]) !== null) {
518
+ throw new Error('Worker CLI version is outside qualified profile');
519
+ }
520
+ const requiredControls = requiredWorkerControls(selection);
521
+ if (requiresWorkerExternalBoundary(adapter, selection)
522
+ && (!selection.boundary || !['os', 'executor'].includes(selection.boundary.kind)
523
+ || !selection.boundary.identity || !/^sha256:[a-f0-9]{64}$/.test(selection.boundary.digest)
524
+ || !selection.boundary.controls?.includes('sandbox'))) {
525
+ throw new Error('Required worker control sandbox needs a qualified external enforcing boundary');
526
+ }
527
+ for (const name of requiredControls) {
528
+ if ((name === 'children' && !adapter.capabilities.nestedAgentsControllable)
529
+ || (name === 'fallback' && !adapter.capabilities.nativeFallbackControllable)) {
530
+ throw new Error(`Required worker control is unqualified: ${name}`);
531
+ }
532
+ const control = profile.controls[name];
533
+ if (!control || !['native', 'external'].includes(control.support) || !control.mapping || !control.evidence) {
534
+ throw new Error(`Required worker control is unqualified: ${name}`);
535
+ }
536
+ }
537
+ if (selection.transport === 'mcp' && (!selection.mcp || !profile.mcp
538
+ || Object.keys(profile.mcp).some(key => profile.mcp[key]
539
+ !== selection.mcp[key]))) {
540
+ throw new Error('MCP server/tool/schema/session qualification changed');
541
+ }
542
+ return profile;
543
+ }
544
+ /** Qualification is transport/version scoped; declaration or boundary claims
545
+ * alone cannot qualify an external sandbox. The invocation owner rechecks the
546
+ * installation scope, including this boundary, against the recorded digest. */
547
+ export function qualifyWorkerInvocationProfile(adapter, selection) {
548
+ const profile = inspectWorkerInvocationProfile(adapter, selection);
549
+ if (requiresWorkerExternalBoundary(adapter, selection) && !profile.installationQualification) {
550
+ throw new Error('Required worker control sandbox needs matching installation qualification for its external boundary');
551
+ }
552
+ return profile;
553
+ }
554
+ /** Health timing evidence belongs to the exact execution profile and tuple.
555
+ * Fixture qualification does not become authenticated/live qualification. */
556
+ export function workerHealthScopeDigest(profile, scope) {
557
+ const { installationQualification: _installation, health, ...executionProfile } = profile;
558
+ const { qualification: _qualification, ...signals } = health ?? {};
559
+ return qualificationDigest({ profile: executionProfile, health: signals,
560
+ profileRef: scope.profileRef, cliVersion: scope.cliVersion, accountScope: scope.accountScope,
561
+ modelKey: workerModelKey(scope.inferenceProvider, scope.model), effort: scope.effort });
562
+ }
563
+ /** Reused by observation and invocation owners before using qualified health.
564
+ * Older profiles have no qualification record and retain degraded observation. */
565
+ export function assertWorkerHealthQualification(profile, scope) {
566
+ const evidence = profile.health?.qualification;
567
+ if (!evidence)
568
+ return;
569
+ if (evidence.status === 'unqualified') {
570
+ if (profile.health?.coverage === 'full' || profile.health?.handshake === 'semantic') {
571
+ throw new Error('Unqualified worker health cannot assert full or semantic coverage');
572
+ }
573
+ return;
574
+ }
575
+ const modelKey = workerModelKey(scope.inferenceProvider, scope.model);
576
+ if (evidence.cliVersion !== scope.cliVersion || evidence.accountScope !== scope.accountScope
577
+ || evidence.profileRef !== scope.profileRef || evidence.modelKey !== modelKey || evidence.effort !== scope.effort
578
+ || !profile.health?.qualifiedAt || !Number.isFinite(Date.parse(profile.health.qualifiedAt))
579
+ || evidence.scopeDigest !== workerHealthScopeDigest(profile, scope)
580
+ || (evidence.status === 'live-qualified' && evidence.authorizedLive !== true)) {
581
+ throw new Error('Worker health qualification scope changed or lacks live authorization');
582
+ }
583
+ }
584
+ export function parseProviderModelCatalog(input) {
585
+ const validate = getCatalogValidators().catalog;
586
+ if (!validate(input))
587
+ throw new Error(`MODEL_POLICY_INVALID: ${validate.errors?.map(e => `${e.instancePath} ${e.message}`).join('; ')}`);
588
+ return CatalogSchema.parse(input);
589
+ }
115
590
  export function loadProviderModelCatalog() {
116
591
  if (!catalogCache)
117
- catalogCache = CatalogSchema.parse(catalogData);
592
+ catalogCache = parseProviderModelCatalog(catalogData);
118
593
  return catalogCache;
119
594
  }
120
595
  export function validateCanonicalModelPolicy(input) {
@@ -138,7 +613,8 @@ export function validateUserProjectModelConfig(input) {
138
613
  return {
139
614
  valid: false,
140
615
  diagnostics: parsed.error.issues.map(issue => ({
141
- code: 'MODEL_POLICY_INVALID',
616
+ code: issue.path.includes('workerSelection') ? 'OG_CONFIG_REFERENCE' : 'MODEL_POLICY_INVALID',
617
+ pointer: `/${issue.path.map(p => String(p).replace(/~/g, '~0').replace(/\//g, '~1')).join('/')}`,
142
618
  severity: 'error',
143
619
  message: `${issue.path.join('.') || 'config'}: ${issue.message}`,
144
620
  })),
@@ -274,4 +750,142 @@ export function resetModelPolicyCachesForTests() {
274
750
  registryCache = null;
275
751
  catalogCache = null;
276
752
  }
753
+ // Dispatch rechecks consume the effective policy from resolution. Keep these
754
+ // predicates shared with route validation so defaults and restrictions agree.
755
+ function isContributorWorkerModel(model) {
756
+ return /(?:^|[-_:/.])contributor(?:$|[-_:/.])/i.test(model);
757
+ }
758
+ function isWorkerModelDenied(modelKey, policy) {
759
+ return (policy.guards?.deniedModels ?? DEFAULT_WORKER_SELECTION.guards.deniedModels).includes(modelKey);
760
+ }
761
+ function isWorkerEffortDenied(effort, policy) {
762
+ return effort !== null && (policy.guards?.deniedEfforts ?? DEFAULT_WORKER_SELECTION.guards.deniedEfforts).includes(effort);
763
+ }
764
+ function isWorkerNullEffortDenied(effort, observation, guidance) {
765
+ return effort === null && (observation.effortSupport !== 'not-applicable' || guidance?.allowNotApplicableEffort !== true);
766
+ }
767
+ function workerRouteDataPolicy(policy, dataClass) {
768
+ const data = { ...DEFAULT_WORKER_SELECTION.dataPolicy, ...policy.dataPolicy };
769
+ const narrowed = dataClass ? policy.dataClasses?.[dataClass] : undefined;
770
+ if (narrowed) {
771
+ if (narrowed.allowedProcessing)
772
+ data.allowedProcessing = data.allowedProcessing?.filter(p => narrowed.allowedProcessing.includes(p));
773
+ if (narrowed.allowUnknown === false)
774
+ data.allowUnknown = false;
775
+ if (narrowed.allowProductImprovement === false)
776
+ data.allowProductImprovement = false;
777
+ if (narrowed.maxRetentionDays !== undefined)
778
+ data.maxRetentionDays = Math.min(data.maxRetentionDays ?? Infinity, narrowed.maxRetentionDays);
779
+ }
780
+ return { data, unknownDataClass: Boolean(dataClass && !narrowed) };
781
+ }
782
+ function isWorkerDataUseDenied(facts, data) {
783
+ return (facts.processing === 'unknown' && data.allowUnknown !== true)
784
+ || (facts.processing !== 'unknown' && !data.allowedProcessing?.includes(facts.processing))
785
+ || (facts.productImprovement === null && data.allowUnknown !== true)
786
+ || (facts.productImprovement === true && data.allowProductImprovement !== true)
787
+ || (data.maxRetentionDays !== undefined && (facts.retentionDays === null ? data.allowUnknown !== true : facts.retentionDays > data.maxRetentionDays));
788
+ }
789
+ function isWorkerEvidenceStale(observation, policy, now) {
790
+ const observed = Date.parse(observation.observedAt);
791
+ const verified = observation.verifiedAt === undefined ? undefined : Date.parse(observation.verifiedAt);
792
+ const blockHours = policy.freshness?.blockHours ?? DEFAULT_WORKER_SELECTION.freshness.blockHours;
793
+ const policyDays = policy.freshness?.policyDays ?? DEFAULT_WORKER_SELECTION.freshness.policyDays;
794
+ return observed > now || now - observed > blockHours * 3600000
795
+ || (verified !== undefined && (verified > now || now - verified > policyDays * 86400000));
796
+ }
797
+ /** Optional catalog facts do not veto otherwise qualified selection. */
798
+ export function hasSelectionRelevantConflicts(observation) {
799
+ 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)(?:\/|$)/;
800
+ return observation.conflicts?.some(conflict => relevant.test(conflict.field)) ?? false;
801
+ }
802
+ /** Validate a resolved route without launching or creating managed state. */
803
+ export function validateWorkerPolicyRoute(input) {
804
+ const diagnostics = [];
805
+ const deny = (code, pointer, message) => { diagnostics.push({ code, pointer, message }); };
806
+ const shapeErrors = validateWorkerModelObservation(input.observation);
807
+ if (shapeErrors.length)
808
+ return [{ code: 'OG_ADAPTER_UNQUALIFIED', pointer: '/worker', message: shapeErrors.join('; ') }];
809
+ const observation = input.observation;
810
+ if (hasSelectionRelevantConflicts(observation))
811
+ deny('OG_MODEL_UNAPPROVED', '/worker/conflicts', 'Conflicting selection-relevant model evidence blocks the route');
812
+ else if (observation.conflicts?.length)
813
+ diagnostics.push({
814
+ code: 'OG_MODEL_UNAPPROVED', pointer: '/worker/conflicts', blocking: false,
815
+ message: `Conflicting optional model evidence remains unknown: ${observation.conflicts.map(conflict => conflict.field).join(', ')}`,
816
+ });
817
+ const guards = input.policy.guards ?? DEFAULT_WORKER_SELECTION.guards;
818
+ const guidance = input.policy.roleGuidance?.[input.role];
819
+ if (observation.qualification.status !== 'qualified' || !observation.qualification.roles.includes(input.role))
820
+ deny('OG_ADAPTER_UNQUALIFIED', '/worker/qualification', 'Role lacks qualified evidence');
821
+ if (!guidance)
822
+ deny('OG_CONFIG_REFERENCE', `/defaults/workerSelection/roleGuidance/${input.role}`, 'Role guidance is unavailable');
823
+ const substitution = input.substitution?.model === input.selectedModel
824
+ && input.substitution.effort === input.effort && input.substitution.rationale.trim();
825
+ if (input.wrapperPin && input.wrapperPin !== input.selectedModel && !substitution)
826
+ deny('OG_PIN_MISMATCH', '/agentOverrides', 'Wrapper exact pin differs from selected model');
827
+ if (guards.requireExactModel !== false && input.selectedModel !== input.apiModelId)
828
+ deny('OG_PIN_MISMATCH', '/worker/id', 'Selected model is not the exact observed API model');
829
+ let modelKey;
830
+ try {
831
+ modelKey = workerModelKey(input.inferenceProvider, input.apiModelId);
832
+ }
833
+ catch {
834
+ deny('OG_CONFIG_REFERENCE', '/worker/id', 'Invalid inference model identity');
835
+ }
836
+ // The dispatch guard checks the selected model; when requireExactModel is
837
+ // false that can differ from apiModelId, so deny on either identity.
838
+ let selectedKey;
839
+ try {
840
+ selectedKey = workerModelKey(input.inferenceProvider, input.selectedModel);
841
+ }
842
+ catch { /* reported via apiModelId */ }
843
+ if (isContributorWorkerModel(input.apiModelId) || isContributorWorkerModel(input.selectedModel)
844
+ || (modelKey && isWorkerModelDenied(modelKey, input.policy))
845
+ || (selectedKey && isWorkerModelDenied(selectedKey, input.policy)))
846
+ deny('OG_MODEL_UNAPPROVED', '/defaults/workerSelection/guards/deniedModels', 'Inference model is denied');
847
+ if (input.identityEvidence === 'unknown' || input.identityEvidence === 'mismatched' || (guards.minimumIdentityEvidence === 'backend-attested' && input.identityEvidence !== 'backend-attested'))
848
+ deny('OG_ADAPTER_UNQUALIFIED', '/defaults/workerSelection/guards/minimumIdentityEvidence', 'Identity evidence does not meet the required order');
849
+ if (!(guards.allowedVisibility ?? ['listed']).includes(observation.visibility) || !(guards.allowedLifecycle ?? ['active']).includes(observation.lifecycle) || observation.availability !== 'accessible')
850
+ deny('OG_MODEL_UNAPPROVED', '/worker', 'Visibility, lifecycle or availability is ineligible');
851
+ if (input.effort === null) {
852
+ if (isWorkerNullEffortDenied(input.effort, observation, guidance))
853
+ deny('OG_EFFORT_DENIED', '/worker/effortSupport', 'Null effort requires qualified absence of an effort control');
854
+ }
855
+ else {
856
+ const effort = observation.efforts.find(e => e.id === input.effort);
857
+ 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))
858
+ deny('OG_EFFORT_DENIED', '/worker/efforts', 'Unsupported, denied or self-delegating effort');
859
+ }
860
+ const { data, unknownDataClass } = workerRouteDataPolicy(input.policy, input.dataClass);
861
+ if (unknownDataClass)
862
+ deny('OG_DATA_POLICY', '/defaults/workerSelection/dataClasses', 'Unknown data class');
863
+ if (isWorkerDataUseDenied(observation.dataUse, data))
864
+ deny('OG_DATA_POLICY', '/worker/dataUse', 'Execution path data facts do not satisfy policy');
865
+ const now = (input.now ?? new Date()).getTime();
866
+ if (observation.retiredAt != null && Date.parse(observation.retiredAt) <= now
867
+ || observation.sunsetAt && Date.parse(observation.sunsetAt) <= now) {
868
+ deny('OG_MODEL_UNAPPROVED', '/worker/lifecycle', 'Model retirement or sunset is effective');
869
+ }
870
+ if (isWorkerEvidenceStale(observation, input.policy, now))
871
+ deny('OG_ADAPTER_UNQUALIFIED', '/worker/observedAt', 'Model evidence is stale or from the future');
872
+ else if (now - Date.parse(observation.observedAt) > (input.policy.freshness?.warnHours ?? DEFAULT_WORKER_SELECTION.freshness.warnHours) * 3600000) {
873
+ diagnostics.push({ code: 'OG_ADAPTER_UNQUALIFIED', pointer: '/worker/observedAt', blocking: false, message: 'Model observation exceeds the freshness warning threshold' });
874
+ }
875
+ if (input.context && !workerContextHeadroom(input.context, observation).fits) {
876
+ deny('OG_MODEL_UNAPPROVED', '/worker/contextTokens', 'Bounded context and output do not fit qualified limits');
877
+ }
878
+ return diagnostics;
879
+ }
880
+ export function workerContextHeadroom(estimate, observation) {
881
+ const safetyMargin = Math.max(estimate.safetyMargin ?? 0, Math.ceil((observation.contextTokens ?? 0) * 0.1));
882
+ const terms = [estimate.briefTokens, estimate.inputTokens, estimate.toolOutputAllowance, estimate.reservedOutput, safetyMargin];
883
+ const total = terms.reduce((sum, value) => sum + value, 0);
884
+ const fits = Boolean(estimate.estimator.trim()) && terms.every(value => Number.isSafeInteger(value) && value >= 0)
885
+ && (estimate.safetyMargin === undefined || Number.isSafeInteger(estimate.safetyMargin) && estimate.safetyMargin >= 0)
886
+ && Number.isSafeInteger(total) && observation.contextTokens !== null && observation.maxOutputTokens !== null
887
+ && total <= observation.contextTokens && estimate.reservedOutput <= observation.maxOutputTokens;
888
+ return { ...estimate, safetyMargin, total, qualifiedContextTokens: observation.contextTokens,
889
+ qualifiedMaxOutputTokens: observation.maxOutputTokens, fits };
890
+ }
277
891
  //# sourceMappingURL=provider-policy.js.map