@aiwg/cli 2026.10.5 → 2026.10.7

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 (263) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +7 -3
  6. package/dist/src/a2a/agent-card.js +11 -0
  7. package/dist/src/api/index.d.ts +1 -0
  8. package/dist/src/api/index.js +1 -0
  9. package/dist/src/artifacts/browser-export.js +1 -0
  10. package/dist/src/artifacts/citation-parser.js +8 -1
  11. package/dist/src/artifacts/cli.js +57 -6
  12. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  13. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  14. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
  15. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
  16. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
  17. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
  18. package/dist/src/artifacts/fortemi-shard-export.js +284 -0
  19. package/dist/src/artifacts/graph-query.js +18 -8
  20. package/dist/src/artifacts/index-adapters.js +339 -0
  21. package/dist/src/artifacts/index-builder.js +143 -14
  22. package/dist/src/artifacts/index-files.js +15 -4
  23. package/dist/src/artifacts/index-preparation.js +2 -0
  24. package/dist/src/artifacts/index-reader.js +44 -0
  25. package/dist/src/artifacts/index-refresh.js +114 -55
  26. package/dist/src/artifacts/index-status.js +20 -5
  27. package/dist/src/artifacts/memory-maintenance.js +503 -0
  28. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  29. package/dist/src/artifacts/query-engine.js +263 -108
  30. package/dist/src/artifacts/relations-builder.js +2 -2
  31. package/dist/src/artifacts/source-graph.js +2 -2
  32. package/dist/src/artifacts/types.js +9 -1
  33. package/dist/src/auth/client.js +5 -1
  34. package/dist/src/auth/credential-store.js +5 -1
  35. package/dist/src/auth/sensitive-input.js +151 -0
  36. package/dist/src/channel/manager.mjs +3 -0
  37. package/dist/src/cli/handlers/agentcard.js +47 -25
  38. package/dist/src/cli/handlers/auth.js +22 -2
  39. package/dist/src/cli/handlers/decision.js +22 -3
  40. package/dist/src/cli/handlers/effect.js +4 -84
  41. package/dist/src/cli/handlers/guided-setup.js +470 -0
  42. package/dist/src/cli/handlers/help.js +2 -2
  43. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  44. package/dist/src/cli/handlers/index.js +4 -2
  45. package/dist/src/cli/handlers/init.js +41 -6
  46. package/dist/src/cli/handlers/install.js +6 -0
  47. package/dist/src/cli/handlers/local-executor.js +5 -3
  48. package/dist/src/cli/handlers/marketplace.js +15 -4
  49. package/dist/src/cli/handlers/mc.js +509 -53
  50. package/dist/src/cli/handlers/models.js +32 -2
  51. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  52. package/dist/src/cli/handlers/rules.js +1 -1
  53. package/dist/src/cli/handlers/script-runner.js +73 -2
  54. package/dist/src/cli/handlers/serve.js +5 -3
  55. package/dist/src/cli/handlers/sessions.js +1 -0
  56. package/dist/src/cli/handlers/setup.js +432 -1
  57. package/dist/src/cli/handlers/skill-lint.js +1 -1
  58. package/dist/src/cli/handlers/subcommands.js +118 -8
  59. package/dist/src/cli/handlers/use.js +335 -107
  60. package/dist/src/cli/handlers/workspace.js +3 -1
  61. package/dist/src/cli/prompt-utils.js +81 -0
  62. package/dist/src/cli/router.js +16 -1
  63. package/dist/src/config/aiwg-config.js +42 -0
  64. package/dist/src/config/cli.js +7 -3
  65. package/dist/src/config/decision-config.js +14 -1
  66. package/dist/src/config/gitignore.js +4 -0
  67. package/dist/src/config/native-sandbox-warnings.js +25 -0
  68. package/dist/src/config/workspace.js +2 -1
  69. package/dist/src/contributors/validation.js +6 -5
  70. package/dist/src/decision/adapters/jev.js +20 -31
  71. package/dist/src/decision/admission.js +7 -3
  72. package/dist/src/decision/ask.js +6 -1
  73. package/dist/src/decision/benchmark.js +482 -0
  74. package/dist/src/decision/ledger.js +22 -3
  75. package/dist/src/decision/pricing.js +147 -0
  76. package/dist/src/decision/qualification/gate-evidence.js +23 -18
  77. package/dist/src/decision/qualification/load-staged.js +576 -0
  78. package/dist/src/decision/qualification/load.js +109 -33
  79. package/dist/src/decision/qualification/runner.js +12 -5
  80. package/dist/src/decision/review/service.js +6 -4
  81. package/dist/src/decision/review/validate.js +23 -2
  82. package/dist/src/decision/savings.js +8 -3
  83. package/dist/src/discovery/index.js +492 -0
  84. package/dist/src/discovery/interview.js +238 -0
  85. package/dist/src/discovery/plan-apply.js +567 -0
  86. package/dist/src/discovery/plan-store.js +171 -0
  87. package/dist/src/discovery/plan-types.js +2 -0
  88. package/dist/src/discovery/plan-validation.js +139 -0
  89. package/dist/src/discovery/types.js +2 -0
  90. package/dist/src/effects/host.js +91 -0
  91. package/dist/src/extensions/commands/definitions.js +49 -9
  92. package/dist/src/extensions/manifest.js +71 -44
  93. package/dist/src/extensions/project-local-discovery.js +1 -1
  94. package/dist/src/extensions/project-local-gitignore.js +2 -0
  95. package/dist/src/extensions/project-quickref.js +18 -17
  96. package/dist/src/extensions/validation.js +32 -29
  97. package/dist/src/housekeeper/daemon-config.js +148 -0
  98. package/dist/src/housekeeper/ipc.js +84 -0
  99. package/dist/src/housekeeper/lifecycle.js +148 -0
  100. package/dist/src/housekeeper/platform.js +35 -0
  101. package/dist/src/housekeeper/status.js +70 -0
  102. package/dist/src/housekeeper/units.js +114 -0
  103. package/dist/src/issues/cli.js +16 -5
  104. package/dist/src/issues/live.js +44 -12
  105. package/dist/src/jobs/flow.js +22 -21
  106. package/dist/src/lint/rule-activation.js +20 -5
  107. package/dist/src/lint/runner.js +1 -1
  108. package/dist/src/marketplace/exchange.js +18 -6
  109. package/dist/src/marketplace/provenance.js +3 -2
  110. package/dist/src/mcp/adapters/codex-runtime.js +46 -76
  111. package/dist/src/mcp/cli.mjs +22 -96
  112. package/dist/src/mcp/config-file.mjs +6 -2
  113. package/dist/src/mcp/config-layers.mjs +23 -7
  114. package/dist/src/mcp/grok-build-config.mjs +2 -1
  115. package/dist/src/mcp/muse-native-config.mjs +2 -1
  116. package/dist/src/mcp/profiles.js +18 -31
  117. package/dist/src/mcp/profiles.mjs +18 -32
  118. package/dist/src/mcp/registry.js +7 -27
  119. package/dist/src/mcp/registry.mjs +7 -28
  120. package/dist/src/mcp/server.mjs +34 -1
  121. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  122. package/dist/src/mcp/tools/command-run.mjs +3 -2
  123. package/dist/src/mcp/tools/decision.mjs +49 -48
  124. package/dist/src/mcp/tools/discovery.mjs +46 -21
  125. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  126. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  127. package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
  128. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  129. package/dist/src/memory/audit.js +719 -0
  130. package/dist/src/memory/canonical-context.js +392 -13
  131. package/dist/src/memory/canonical-maintenance.js +95 -0
  132. package/dist/src/memory/cli-result.js +22 -0
  133. package/dist/src/memory/cli.js +176 -0
  134. package/dist/src/memory/context-pack.js +57 -6
  135. package/dist/src/memory/cost.js +512 -0
  136. package/dist/src/memory/firewall-classification.d.mts +7 -0
  137. package/dist/src/memory/firewall-classification.mjs +32 -0
  138. package/dist/src/memory/index.js +8 -0
  139. package/dist/src/memory/line-maintenance.js +68 -0
  140. package/dist/src/memory/maintenance-cli.js +286 -0
  141. package/dist/src/memory/maintenance-contract.js +136 -0
  142. package/dist/src/memory/maintenance-index.js +18 -0
  143. package/dist/src/memory/maintenance-owner.js +2 -0
  144. package/dist/src/memory/maintenance-record.js +138 -0
  145. package/dist/src/memory/maintenance.js +254 -0
  146. package/dist/src/memory/project-registry.js +2 -1
  147. package/dist/src/memory/provider-memory-adapter.js +154 -0
  148. package/dist/src/memory/recommend-cli.js +83 -0
  149. package/dist/src/memory/recommend.js +269 -0
  150. package/dist/src/memory/scheduled.js +81 -0
  151. package/dist/src/memory/taxonomy.js +44 -0
  152. package/dist/src/mission-protocol/codecs.js +19 -9
  153. package/dist/src/mission-protocol/worker-status.js +91 -1
  154. package/dist/src/models/config-loader.js +11 -1
  155. package/dist/src/models/model-capabilities.v1.json +74 -50
  156. package/dist/src/models/provider-policy.js +125 -62
  157. package/dist/src/models/resolver.js +45 -8
  158. package/dist/src/models/worker-installation.d.mts +8 -1
  159. package/dist/src/models/worker-installation.mjs +231 -38
  160. package/dist/src/models/wrapper-route.js +1 -1
  161. package/dist/src/output-modes/registry.js +8 -7
  162. package/dist/src/packages/adapters/git.js +99 -23
  163. package/dist/src/packages/adapters/gitea.js +8 -27
  164. package/dist/src/packages/adapters/github.js +7 -25
  165. package/dist/src/packages/registry.js +4 -4
  166. package/dist/src/plugin/metadata-validator.js +1 -1
  167. package/dist/src/process/cli-command.d.mts +5 -0
  168. package/dist/src/process/cli-command.mjs +42 -0
  169. package/dist/src/providers/deploy-version.d.mts +1 -0
  170. package/dist/src/providers/deploy-version.mjs +17 -0
  171. package/dist/src/providers/provider-definitions.js +19 -16
  172. package/dist/src/providers/skill-ownership.d.mts +11 -2
  173. package/dist/src/providers/skill-ownership.mjs +35 -17
  174. package/dist/src/rules/activation.d.mts +11 -2
  175. package/dist/src/rules/activation.mjs +82 -6
  176. package/dist/src/rules/inventory.js +1 -1
  177. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  178. package/dist/src/schema/zod-validation.d.mts +6 -0
  179. package/dist/src/schema/zod-validation.mjs +50 -0
  180. package/dist/src/serve/executor-registry.js +297 -20
  181. package/dist/src/serve/executor-token.d.mts +1 -0
  182. package/dist/src/serve/executor-token.mjs +8 -0
  183. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  184. package/dist/src/serve/worker-invocation.js +124 -15
  185. package/dist/src/sessions/adapters/claude.js +19 -18
  186. package/dist/src/sessions/adapters/codex.js +10 -9
  187. package/dist/src/sessions/adapters/copilot.js +14 -13
  188. package/dist/src/sessions/adapters/cursor.js +19 -18
  189. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  190. package/dist/src/sessions/adapters/factory.js +11 -10
  191. package/dist/src/sessions/adapters/generic.js +32 -23
  192. package/dist/src/sessions/adapters/grokbot.js +35 -35
  193. package/dist/src/sessions/adapters/hermes.js +20 -19
  194. package/dist/src/sessions/adapters/muse.js +50 -49
  195. package/dist/src/sessions/adapters/omp.js +5 -4
  196. package/dist/src/sessions/adapters/openclaw.js +30 -29
  197. package/dist/src/sessions/adapters/opencode.js +26 -26
  198. package/dist/src/sessions/adapters/openhuman.js +24 -23
  199. package/dist/src/sessions/adapters/pi.js +5 -4
  200. package/dist/src/sessions/adapters/windsurf.js +8 -8
  201. package/dist/src/sessions/analytics.js +10 -9
  202. package/dist/src/sessions/candidates.js +7 -6
  203. package/dist/src/sessions/contracts.js +63 -53
  204. package/dist/src/sessions/output-registration.js +11 -10
  205. package/dist/src/sessions/workspace-discovery.js +3 -4
  206. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  207. package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
  208. package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
  209. package/dist/src/smiths/context-pipeline/finalization.js +3 -2
  210. package/dist/src/smiths/context-pipeline/generator.js +7 -19
  211. package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
  212. package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
  213. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  214. package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
  215. package/dist/src/tracker/project-inputs.js +2 -0
  216. package/dist/src/update/checker.d.mts +4 -0
  217. package/dist/src/update/checker.mjs +14 -0
  218. package/dist/src/update/notifier.mjs +2 -4
  219. package/dist/src/writing/exemplar-selection.js +6 -6
  220. package/dist/src/writing/voice-evaluation.js +24 -23
  221. package/dist/src/writing/writer-migration.js +14 -13
  222. package/dist/src/writing/writer-profile.js +19 -18
  223. package/dist/src/writing/writing-brief.js +18 -17
  224. package/dist/src/writing/writing-receipt.js +47 -46
  225. package/package.json +5 -2
  226. package/schemas/decision/DecisionReview.schema.json +78 -1
  227. package/schemas/mission-v1.schema.json +3 -0
  228. package/tools/agents/deploy-agents.mjs +67 -39
  229. package/tools/agents/providers/antigravity.mjs +6 -1
  230. package/tools/agents/providers/base.mjs +24 -14
  231. package/tools/agents/providers/claude.mjs +6 -3
  232. package/tools/agents/providers/codex.mjs +54 -57
  233. package/tools/agents/providers/context-write.mjs +28 -0
  234. package/tools/agents/providers/copilot.mjs +13 -4
  235. package/tools/agents/providers/cursor.mjs +1 -1
  236. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  237. package/tools/agents/providers/factory.mjs +7 -1
  238. package/tools/agents/providers/grok-build.mjs +18 -2
  239. package/tools/agents/providers/grokbot.mjs +1 -1
  240. package/tools/agents/providers/hermes.mjs +12 -15
  241. package/tools/agents/providers/muse-hooks.mjs +21 -14
  242. package/tools/agents/providers/muse.mjs +7 -2
  243. package/tools/agents/providers/omp.mjs +31 -5
  244. package/tools/agents/providers/opencode.mjs +14 -5
  245. package/tools/agents/providers/openhuman.mjs +1 -1
  246. package/tools/agents/providers/pi.mjs +1 -1
  247. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  248. package/tools/agents/providers/rule-delivery.mjs +289 -80
  249. package/tools/agents/providers/warp.mjs +4 -1
  250. package/tools/agents/providers/windsurf.mjs +13 -3
  251. package/tools/cli/workspace-status.mjs +5 -0
  252. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  253. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  254. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
  255. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
  256. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  257. package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
  258. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  259. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  260. package/tools/ralph-external/process-monitor.mjs +49 -8
  261. package/tools/ralph-external/session-launcher.mjs +405 -35
  262. package/tools/skills/deploy-skills-codex.mjs +11 -7
  263. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -9,6 +9,7 @@ import { existsSync, readFileSync } from 'node:fs';
9
9
  import { dirname, resolve } from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import { z } from 'zod';
12
+ import { integer, dateTime, url, number } from '../schema/zod-validation.mjs';
12
13
  import { createRequire } from 'node:module';
13
14
  import { compileWorkerVersionPattern, workerVersionRangeMismatch } from './worker-installation.mjs';
14
15
  import { normalizeProviderDefinitionId } from '../providers/provider-definitions.js';
@@ -63,7 +64,7 @@ export const WorkerAdapterSchema = z.custom().superRefine((value, context) => {
63
64
  message: `Invalid worker adapter: ${error instanceof Error ? error.message : String(error)}` });
64
65
  }
65
66
  });
66
- const CapabilityEntrySchema = z.object({
67
+ const CapabilityEntrySchema = z.strictObject({
67
68
  sessionEnvAllowlist: z.array(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/)).optional(),
68
69
  agent: OutcomeSchema,
69
70
  skill: OutcomeSchema,
@@ -75,31 +76,31 @@ const CapabilityEntrySchema = z.object({
75
76
  configTarget: z.string().min(1),
76
77
  artifactFormat: z.string().min(1),
77
78
  verification: z.string().min(1),
78
- sourceUrl: z.string().url(),
79
+ sourceUrl: url(),
79
80
  verifiedAt: z.string().date(),
80
81
  workerAdapter: WorkerAdapterSchema.optional(),
81
82
  // Descriptive health coverage is not an installed executable registration.
82
83
  workerHealthAdapter: WorkerAdapterSchema.optional(),
83
- workerHealthCoverage: z.object({
84
+ workerHealthCoverage: z.strictObject({
84
85
  scope: z.literal('out'), coverage: z.literal('unobserved'), handshake: z.literal('unobserved'),
85
86
  evidenceGrades: z.array(z.enum(['H', 'R', 'D'])).max(3),
86
87
  qualificationOwner: z.string().regex(/^B-OG-[0-9]{2}$/),
87
88
  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();
90
- const CapabilityRegistrySchema = z.object({
89
+ }).optional(),
90
+ });
91
+ const CapabilityRegistrySchema = z.strictObject({
91
92
  $schema: z.enum([
92
93
  '../../../schemas/models/provider-model-capabilities.v1.schema.json',
93
94
  'https://aiwg.io/schemas/models/provider-model-capabilities.v1.schema.json',
94
95
  ]).optional(),
95
96
  version: z.literal('1.0.0'),
96
97
  verifiedAt: z.string().date(),
97
- providers: z.record(ProviderSchema, CapabilityEntrySchema),
98
+ providers: z.partialRecord(ProviderSchema, CapabilityEntrySchema),
98
99
  workerAdapters: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerAdapterSchema)
99
100
  .refine(value => Object.keys(value).length <= 128, 'At most 128 worker adapters').optional(),
100
101
  workerHealthAdapters: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerAdapterSchema)
101
102
  .refine(value => Object.keys(value).length <= 128, 'At most 128 worker health descriptors').optional(),
102
- }).strict();
103
+ });
103
104
  const CatalogModelSchema = z.object({
104
105
  id: z.string().min(1),
105
106
  status: z.enum(['active', 'deprecated', 'unverified']),
@@ -112,7 +113,7 @@ const CatalogProviderSchema = z.object({
112
113
  coding: CatalogModelSchema,
113
114
  efficiency: CatalogModelSchema,
114
115
  }),
115
- sourceUrl: z.string().url(),
116
+ sourceUrl: url(),
116
117
  verifiedAt: z.string().date(),
117
118
  });
118
119
  let catalogValidators;
@@ -134,10 +135,10 @@ export function validateWorkerModelObservation(input) {
134
135
  return validate(input) ? [] : (validate.errors ?? []).map(error => `${error.instancePath}: ${error.message}`);
135
136
  }
136
137
  const WorkerObservationSchema = z.custom(input => validateWorkerModelObservation(input).length === 0);
137
- const DiscoveredWorkerModelSchema = z.object({
138
+ const DiscoveredWorkerModelSchema = z.looseObject({
138
139
  id: z.string().min(1), apiModelId: z.string().optional(), llmProvider: z.string().optional(),
139
140
  hidden: z.boolean().optional(), reasoningEfforts: z.array(z.string()).optional(), worker: WorkerObservationSchema.optional(),
140
- }).passthrough().superRefine((model, context) => {
141
+ }).superRefine((model, context) => {
141
142
  if (!model.worker)
142
143
  return;
143
144
  if (model.hidden !== undefined && model.worker.visibility !== (model.hidden ? 'hidden' : 'listed'))
@@ -147,17 +148,17 @@ const DiscoveredWorkerModelSchema = z.object({
147
148
  if (efforts.length !== observed.length || new Set(observed).size !== observed.length || efforts.some(e => !observed.includes(e)))
148
149
  context.addIssue({ code: 'custom', path: ['worker', 'efforts'], message: 'Conflicting effort evidence' });
149
150
  });
150
- const ProviderDiscoverySchema = z.object({
151
- provider: z.string(), source: z.enum(['native', 'cache', 'remote', 'static']), observedAt: z.string().datetime({ offset: true }),
151
+ const ProviderDiscoverySchema = z.looseObject({
152
+ provider: z.string(), source: z.enum(['native', 'cache', 'remote', 'static']), observedAt: dateTime({ offset: true }),
152
153
  runtimeVersion: z.string().optional(), accountScope: z.enum(['local-account', 'local-runtime', 'public-feed', 'static']),
153
154
  accountBinding: z.string().min(1).optional(), models: z.array(DiscoveredWorkerModelSchema),
154
- }).passthrough();
155
- const DiscoverySchema = z.object({
155
+ });
156
+ const DiscoverySchema = z.strictObject({
156
157
  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
+ fetchedAt: dateTime({ offset: true }), remoteUrl: url().optional(), remoteError: z.string().optional(),
158
159
  inventorySignature: z.string().optional(), providers: z.record(z.string(), ProviderDiscoverySchema),
159
160
  instances: z.record(z.string(), ProviderDiscoverySchema).optional(),
160
- }).strict();
161
+ });
161
162
  /** Optional enrichment is checked without tightening legacy catalog rows. */
162
163
  export function validateWorkerCatalogDiscovery(input) {
163
164
  if (input === undefined)
@@ -168,40 +169,40 @@ export function validateWorkerCatalogDiscovery(input) {
168
169
  const parsed = DiscoverySchema.safeParse(input);
169
170
  return parsed.success ? [] : parsed.error.issues.map(issue => `${issue.path.join('/')}: ${issue.message}`);
170
171
  }
171
- const CatalogSchema = z.object({
172
+ const CatalogSchema = z.strictObject({
172
173
  version: z.literal('1.0.0'),
173
174
  $schema: z.literal('../../../schemas/models/model-catalog.v1.schema.json').optional(),
174
175
  discovery: DiscoverySchema.optional(),
175
176
  refreshedAt: z.string().date(),
176
- staleAfterDays: z.number().int().positive(),
177
- providers: z.record(ProviderSchema, CatalogProviderSchema),
178
- }).strict();
179
- const PolicySchema = z.object({
177
+ staleAfterDays: integer().positive(),
178
+ providers: z.partialRecord(ProviderSchema, CatalogProviderSchema),
179
+ });
180
+ const PolicySchema = z.strictObject({
180
181
  role: z.enum(['reasoning', 'coding', 'efficiency']),
181
182
  tier: z.enum(['economy', 'standard', 'premium']),
182
183
  effort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
183
184
  override: z.string().min(1).optional(),
184
- }).strict();
185
+ });
185
186
  const uniqueStrings = z.array(z.string().min(1)).refine(values => new Set(values).size === values.length, 'Duplicate entries');
186
- const WorkerDataPolicySchema = z.object({
187
+ const WorkerDataPolicySchema = z.strictObject({
187
188
  allowedProcessing: z.array(z.enum(['local', 'remote'])).refine(values => new Set(values).size === values.length).optional(),
188
189
  allowProductImprovement: z.boolean().optional(),
189
190
  allowUnknown: z.boolean().optional(),
190
- maxRetentionDays: z.number().int().nonnegative().optional(),
191
- }).strict();
192
- const WorkerRoleSchema = z.object({
191
+ maxRetentionDays: integer().nonnegative().optional(),
192
+ });
193
+ const WorkerRoleSchema = z.strictObject({
193
194
  groups: uniqueStrings.refine(values => values.length > 0),
194
195
  tier: z.enum(['economy', 'standard', 'premium', 'max-quality']),
195
196
  initialEffort: z.string().min(1).nullable(),
196
197
  effortLadder: uniqueStrings,
197
- escalateAfter: z.number().int().positive().optional(),
198
- maxEscalations: z.number().int().nonnegative().optional(),
199
- deescalateAfter: z.number().int().positive().optional(),
198
+ escalateAfter: integer().positive().optional(),
199
+ maxEscalations: integer().nonnegative().optional(),
200
+ deescalateAfter: integer().positive().optional(),
200
201
  allowNotApplicableEffort: z.boolean().optional(),
201
- }).strict();
202
- export const WorkerSelectionPolicySchema = z.object({
202
+ });
203
+ export const WorkerSelectionPolicySchema = z.strictObject({
203
204
  roleGuidance: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerRoleSchema).optional(),
204
- guards: z.object({
205
+ guards: z.strictObject({
205
206
  deniedEfforts: uniqueStrings.optional(),
206
207
  deniedModels: uniqueStrings.optional(),
207
208
  denySelfDelegation: z.boolean().optional(),
@@ -209,16 +210,16 @@ export const WorkerSelectionPolicySchema = z.object({
209
210
  allowedVisibility: z.array(z.enum(['listed', 'hidden'])).refine(v => new Set(v).size === v.length).optional(),
210
211
  allowedLifecycle: z.array(z.enum(['active', 'deprecated'])).refine(v => new Set(v).size === v.length).optional(),
211
212
  minimumIdentityEvidence: z.enum(['runtime-configured', 'backend-attested']).optional(),
212
- }).strict().optional(),
213
+ }).optional(),
213
214
  dataPolicy: WorkerDataPolicySchema.optional(),
214
215
  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(),
216
+ freshness: z.strictObject({
217
+ warnHours: integer().positive().optional(),
218
+ blockHours: integer().positive().optional(),
219
+ policyDays: integer().positive().optional(),
220
+ }).optional(),
220
221
  taskClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), z.string().min(1)).optional(),
221
- }).strict().superRefine((policy, context) => {
222
+ }).superRefine((policy, context) => {
222
223
  for (const [role, guidance] of Object.entries(policy.roleGuidance ?? {})) {
223
224
  if (!['efficiency', 'coding', 'reasoning'].includes(role))
224
225
  context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Unknown worker role' });
@@ -332,28 +333,28 @@ export function mergeWorkerSelection(layers) {
332
333
  }
333
334
  return { policy, origins, diagnostics };
334
335
  }
335
- const UserProjectModelConfigSchema = z.object({
336
- defaults: z.object({
336
+ const UserProjectModelConfigSchema = z.looseObject({
337
+ defaults: z.strictObject({
337
338
  tier: z.enum(['economy', 'standard', 'premium', 'max-quality']).optional(),
338
339
  provider: ProviderSchema.optional(),
339
340
  workerSelection: WorkerSelectionPolicySchema.optional(),
340
- }).strict().optional(),
341
- agentOverrides: z.record(z.string(), z.object({
341
+ }).optional(),
342
+ agentOverrides: z.record(z.string(), z.strictObject({
342
343
  'model-tier': z.enum(['economy', 'standard', 'premium', 'max-quality']).optional(),
343
344
  'model-override': z.string().min(1).optional(),
344
345
  rationale: z.string().min(1).optional(),
345
- }).strict()).optional(),
346
- customTiers: z.record(z.string(), z.object({
346
+ })).optional(),
347
+ customTiers: z.record(z.string(), z.strictObject({
347
348
  description: z.string().min(1),
348
- costMultiplier: z.number().nonnegative(),
349
- roleMapping: z.object({
349
+ costMultiplier: number().check(z.gte(0)).meta({ type: 'number', minimum: 0 }),
350
+ roleMapping: z.strictObject({
350
351
  reasoning: z.enum(['reasoning', 'coding', 'efficiency']),
351
352
  coding: z.enum(['reasoning', 'coding', 'efficiency']),
352
353
  efficiency: z.enum(['reasoning', 'coding', 'efficiency']),
353
- }).strict(),
354
- }).strict()).optional(),
354
+ }),
355
+ })).optional(),
355
356
  rationale: z.string().optional(),
356
- }).passthrough();
357
+ });
357
358
  let registryCache = null;
358
359
  let catalogCache = null;
359
360
  export function loadProviderModelCapabilities() {
@@ -415,12 +416,29 @@ export function resolveWorkerInstallation(registry, installation) {
415
416
  /** Typed policy failures let public surfaces retain the cause without exposing internal details. */
416
417
  export class WorkerPolicyError extends Error {
417
418
  code;
418
- constructor(code, message) {
419
+ details;
420
+ constructor(code, message, details) {
419
421
  super(message);
420
422
  this.code = code;
423
+ this.details = details;
421
424
  this.name = 'WorkerPolicyError';
422
425
  }
423
426
  }
427
+ export class WorkerRoleEffortError extends WorkerPolicyError {
428
+ pointer;
429
+ constructor(role) {
430
+ const pointer = `/defaults/workerSelection/roleGuidance/${role.replace(/~/g, '~0').replace(/\//g, '~1')}/initialEffort`;
431
+ super('OG_CONFIG_REFERENCE', `Missing role guidance or explicit initial effort for role ${role}; configure models.json ${pointer}.`);
432
+ this.pointer = pointer;
433
+ }
434
+ }
435
+ /** Validate policy input before entering the sanitized adapter boundary. Null
436
+ * remains valid for models whose effort is explicitly not applicable. */
437
+ export function assertWorkerRoleEffort(policy, role, requiresExplicit = false) {
438
+ const effort = policy?.roleGuidance?.[role]?.initialEffort;
439
+ if (effort === undefined || requiresExplicit && (effort === null || !effort.trim()))
440
+ throw new WorkerRoleEffortError(role);
441
+ }
424
442
  /**
425
443
  * Dispatch-time safety checks belong here, alongside transport qualification.
426
444
  * Like route validation, rendering requires observation evidence; a default
@@ -498,7 +516,8 @@ export function requiredWorkerControls(context) {
498
516
  }
499
517
  /** The same sandbox requirement governs inspection, resolution and rendering. */
500
518
  export function requiresWorkerExternalBoundary(adapter, selection) {
501
- if (hasOperatorWorkerNativeRenderer(adapter) && selection.permissions?.sandbox !== 'external')
519
+ if (hasOperatorWorkerNativeRenderer(adapter)
520
+ && (selection.permissions?.sandbox !== 'external' || selection.transport === 'shell'))
502
521
  return false;
503
522
  const providers = [adapter.id, adapter.provider].map(normalizeProviderDefinitionId);
504
523
  return requiredWorkerControls(selection).includes('sandbox')
@@ -513,11 +532,23 @@ export function isOperatorQualifiedWorkerAdapter(adapter) {
513
532
  return adapter.qualification.status === 'qualified'
514
533
  && adapter.qualification.evidenceRef === WORKER_OPERATOR_APPROVAL;
515
534
  }
516
- /** Only this bundled renderer has an independently checked native direct baseline. */
535
+ /** Bundled renderers with an independently checked native direct baseline. */
517
536
  export function hasOperatorWorkerNativeRenderer(adapter) {
518
- return isOperatorQualifiedWorkerAdapter(adapter) && adapter.provider === 'codex'
537
+ return isOperatorQualifiedWorkerAdapter(adapter) && ['codex', 'muse'].includes(adapter.provider)
519
538
  && adapter.invocation.kind === 'process';
520
539
  }
540
+ /** The process-group launch boundary requires the independently verified native renderer. */
541
+ export function assertWorkerLaunchMode(adapter, permissions = {}) {
542
+ const mode = permissions.launchMode ?? 'systemd-unit';
543
+ if (!['systemd-unit', 'native-sandbox'].includes(mode)) {
544
+ throw new WorkerPolicyError('OG_CONFIG_REFERENCE', 'Unknown worker launch mode');
545
+ }
546
+ if (mode === 'native-sandbox' && (!hasOperatorWorkerNativeRenderer(adapter)
547
+ || !['read-only', 'workspace-write'].includes(permissions.sandbox ?? '')
548
+ || (permissions.transport ?? 'shell') !== 'shell')) {
549
+ throw new WorkerPolicyError('OG_POLICY_RELAXATION', 'native-sandbox requires a verified native read-only or workspace-write shell renderer');
550
+ }
551
+ }
521
552
  /** Check the actual argv before granting the direct renderer exception. */
522
553
  export function assertOperatorWorkerNativeArgv(adapter, argv, selected, permissions) {
523
554
  if (!hasOperatorWorkerNativeRenderer(adapter))
@@ -529,10 +560,46 @@ export function assertOperatorWorkerNativeArgv(adapter, argv, selected, permissi
529
560
  }
530
561
  };
531
562
  const sandbox = permissions?.sandbox ?? 'workspace-write';
532
- if (!['read-only', 'workspace-write'].includes(sandbox))
563
+ if (!['read-only', 'workspace-write', 'external'].includes(sandbox))
533
564
  throw new Error('Requested sandbox has no verified native renderer');
565
+ if (adapter.provider === 'muse') {
566
+ requirePair('--provider', 'meta');
567
+ requirePair('--model', selected.model);
568
+ if (selected.effort.value === null)
569
+ throw new Error('Muse requires explicit effort');
570
+ requirePair('--reasoning-effort', selected.effort.value);
571
+ if ((permissions?.approval ?? 'never') !== 'never')
572
+ throw new Error('Muse direct worker approval requires never');
573
+ requirePair('--approval-mode', 'never');
574
+ requirePair('--approval-judge', 'off');
575
+ if (args.includes('--disable-sandbox') !== (sandbox === 'external')) {
576
+ throw new Error('Requested native worker sandbox control does not match');
577
+ }
578
+ if (sandbox === 'read-only' && (!args.includes('--disable-write') || !args.includes('--disable-shell'))) {
579
+ throw new Error('Requested native worker read-only control is absent');
580
+ }
581
+ if (args[0] !== 'exec')
582
+ throw new Error('Native worker controls could be overridden');
583
+ const seen = new Set();
584
+ for (let index = 1; index < args.length; index++) {
585
+ const flag = args[index];
586
+ if (seen.has(flag))
587
+ throw new Error('Native worker controls could be overridden');
588
+ seen.add(flag);
589
+ if (['--json', '--trust-workspace', '--disable-write', '--disable-shell', '--disable-sandbox'].includes(flag))
590
+ continue;
591
+ if (!['--provider', '--model', '--reasoning-effort', '--workspace', '--approval-mode', '--approval-judge',
592
+ '--max-model-steps', '--output-schema', '--prompt-file'].includes(flag) || index + 1 >= args.length) {
593
+ throw new Error('Native worker controls could be overridden');
594
+ }
595
+ index++;
596
+ }
597
+ return;
598
+ }
534
599
  requirePair('-m', selected.model);
535
- requirePair('-s', sandbox);
600
+ requirePair('-s', sandbox === 'external' ? 'danger-full-access' : sandbox);
601
+ if ((permissions?.approval ?? 'never') !== 'never')
602
+ throw new Error('Codex direct worker approval requires never');
536
603
  for (const [name, value] of [['model_reasoning_effort', selected.effort.value], ['approval_policy', permissions?.approval ?? 'never']]) {
537
604
  const overrides = args.flatMap((token, index) => token === '-c' ? [args[index + 1]] : [])
538
605
  .filter(token => token?.split('=')[0] === name);
@@ -577,13 +644,9 @@ export function inspectWorkerInvocationProfile(adapter, selection) {
577
644
  // The direct CLI baseline trusts the installed driver's bounded renderer.
578
645
  // Explicit extra controls still require the existing qualified mappings.
579
646
  if (hasOperatorWorkerNativeRenderer(adapter) && profile.transport === 'shell') {
580
- if (selection.permissions?.sandbox && !['read-only', 'workspace-write'].includes(selection.permissions.sandbox)) {
647
+ if (selection.permissions?.sandbox && !['read-only', 'workspace-write', 'external'].includes(selection.permissions.sandbox)) {
581
648
  throw new Error('Requested sandbox has no verified native renderer');
582
649
  }
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
650
  for (const name of selection.requiredControls ?? []) {
588
651
  if (!['native', 'external'].includes(profile.controls[name]?.support)) {
589
652
  throw new Error(`Required worker control is unqualified: ${name}`);
@@ -8,7 +8,7 @@
8
8
  import { ConfigLoader } from './config-loader.js';
9
9
  import { WORKER_DISPATCH_ENABLE_COMMAND, WORKER_DISPATCH_READINESS_COMMAND } from './worker-dispatch-offer.js';
10
10
  import { resolveWorkerGroupInstallation, resolveParallelism, validateWorkerGroupQualifications, validateWorkerGroupReferences, validateWorkerBudgetEnforcement, workerActivationDigests, workerConfigDigest, } from '../config/aiwg-config.js';
11
- import { qualifyWorkerInvocationProfile, validateWorkerPolicyRoute, validateWorkerModelObservation, hasOperatorWorkerNativeRenderer, hasSelectionRelevantConflicts, workerContextHeadroom, workerModelKey, isCanonicalWorkerModelKey, WorkerSelectionPolicySchema, validateWorkerCatalogDiscovery, } from './provider-policy.js';
11
+ import { qualifyWorkerInvocationProfile, validateWorkerPolicyRoute, validateWorkerModelObservation, assertWorkerLaunchMode, hasOperatorWorkerNativeRenderer, hasSelectionRelevantConflicts, workerContextHeadroom, workerModelKey, isCanonicalWorkerModelKey, WorkerSelectionPolicySchema, validateWorkerCatalogDiscovery, } from './provider-policy.js';
12
12
  import { MODEL_WRAPPERS } from './provider-models.js';
13
13
  import { routeModelTier } from './router.js';
14
14
  /**
@@ -257,6 +257,14 @@ function findWorkerModelRow(rows, model) {
257
257
  const matches = rows?.filter(row => row.id === model || row.apiModelId === model) ?? [];
258
258
  return matches.length === 1 ? matches[0] : undefined;
259
259
  }
260
+ function workerCatalogDigest(catalog) {
261
+ if (!catalog.discovery)
262
+ return workerConfigDigest(catalog);
263
+ // Offline loads synthesize fetchedAt. Keep catalog facts and persisted
264
+ // observation/qualification times pinned, independent of the load clock.
265
+ const { fetchedAt, ...discovery } = catalog.discovery;
266
+ return workerConfigDigest({ ...catalog, discovery });
267
+ }
260
268
  /** Pure S1 resolution. No probing, launch, activation, admission, or writes.
261
269
  * Recheck against freshly loaded snapshots before preparing an invocation. */
262
270
  export function resolveWorkerPlan(snapshot, request) {
@@ -266,7 +274,7 @@ export function resolveWorkerPlan(snapshot, request) {
266
274
  ...snapshot.modelDigests,
267
275
  configDigest: workerActivationDigests(snapshot.config).configDigest,
268
276
  resolvedModelsDigest: workerConfigDigest(snapshot.models),
269
- catalogDigest: workerConfigDigest(snapshot.catalog),
277
+ catalogDigest: workerCatalogDigest(snapshot.catalog),
270
278
  profileDigest: workerConfigDigest([snapshot.registry, snapshot.installations]),
271
279
  constraintsDigest: workerConfigDigest(snapshot.constraints),
272
280
  };
@@ -305,7 +313,6 @@ export function resolveWorkerPlan(snapshot, request) {
305
313
  if (!activation || ['configDigest', 'projectModelsDigest', 'userModelsDigest', 'builtinModelsDigest'].some(key => activation[key] !== digests[key])) {
306
314
  block('OG_CONFIG_REFERENCE', '/parallelism/activation', 'Managed activation is missing or stale');
307
315
  }
308
- block('OG_ADAPTER_UNQUALIFIED', '/parallelism/worker_mode', 'Managed activation/admission runtime is deferred to S2+');
309
316
  }
310
317
  if (!policy)
311
318
  block('OG_CONFIG_REFERENCE', '/defaults/workerSelection', 'Current merged worker policy is required');
@@ -344,9 +351,12 @@ export function resolveWorkerPlan(snapshot, request) {
344
351
  ? observed.qualification.roles.filter(role => policy.roleGuidance?.[role]?.groups.includes(groupId)) : [];
345
352
  }
346
353
  }
347
- plan.diagnostics.push(...validateWorkerGroupQualifications(parallelism, qualifiedRoles).map(diagnostic => ({
354
+ // Qualification gaps are group-scoped: an unqualified group must not block a
355
+ // request that never considers it. Gaps are applied once the candidates are known.
356
+ const qualificationGaps = validateWorkerGroupQualifications(parallelism, qualifiedRoles).map(diagnostic => ({
348
357
  ...diagnostic, message: `${diagnostic.message}. Run aiwg doctor --probe-workers --qualify.`,
349
- })));
358
+ }));
359
+ const gapGroup = (diagnostic) => diagnostic.pointer.split('/')[3] ?? '';
350
360
  const taskRole = request.taskClass ? policy.taskClasses?.[request.taskClass] : undefined;
351
361
  const role = request.role ?? taskRole;
352
362
  plan.role = role ?? null;
@@ -365,10 +375,25 @@ export function resolveWorkerPlan(snapshot, request) {
365
375
  || hasSelectionRelevantConflicts(request.reviewOf) || !request.reviewOf.family || request.reviewOf.family === 'unknown')) {
366
376
  block('OG_MODEL_UNAPPROVED', '/request/reviewOf', 'Required review needs unambiguous observed source lineage');
367
377
  }
378
+ const candidateGroups = request.group ? [request.group] : guidance?.groups ?? [];
379
+ const unqualifiedGroups = new Set(qualificationGaps.map(gapGroup));
380
+ const qualifiedGroups = candidateGroups.filter(groupId => !unqualifiedGroups.has(groupId));
381
+ if (!qualifiedGroups.length) {
382
+ plan.diagnostics.push(...qualificationGaps.filter(gap => candidateGroups.includes(gapGroup(gap))));
383
+ }
384
+ else {
385
+ // Unpinned requests skip an unqualified guidance group visibly instead of failing the role.
386
+ for (const groupId of candidateGroups.filter(groupId => unqualifiedGroups.has(groupId))) {
387
+ const gaps = qualificationGaps.filter(gap => gapGroup(gap) === groupId);
388
+ for (const member of parallelism.groups?.[groupId]?.members ?? []) {
389
+ plan.exclusions.push({ group: groupId, member: member.id, model: member.model, effort: null, diagnostics: gaps });
390
+ }
391
+ }
392
+ }
368
393
  if (plan.diagnostics.length || !role || !guidance)
369
394
  return plan;
370
395
  const wrapperPin = snapshot.models.agentOverrides[MODEL_WRAPPERS[role]]?.['model-override'];
371
- const groups = request.group ? [request.group] : guidance.groups;
396
+ const groups = qualifiedGroups;
372
397
  for (const groupId of groups) {
373
398
  const group = parallelism.groups[groupId];
374
399
  const permissions = { ...group.execution, transport: group.execution?.transport ?? 'shell' };
@@ -420,8 +445,13 @@ export function resolveWorkerPlan(snapshot, request) {
420
445
  try {
421
446
  const { adapter } = resolveWorkerGroupInstallation(parallelism, member.cli, snapshot.registry);
422
447
  const configuredProfile = parallelism.clis?.[member.cli]?.invocationProfile;
423
- if (configuredProfile && configuredProfile !== binding.profile)
424
- throw new Error('Configured invocation profile changed');
448
+ const profilePointer = `/parallelism/clis/${member.cli}/invocationProfile`;
449
+ if (configuredProfile && !Object.hasOwn(adapter.invocationProfiles ?? {}, configuredProfile)) {
450
+ deny('OG_CONFIG_REFERENCE', profilePointer, 'Configured invocation profile is not registered for this adapter; select a registered profile.');
451
+ }
452
+ else if (configuredProfile && configuredProfile !== binding.profile) {
453
+ deny('OG_PIN_MISMATCH', profilePointer, 'Configured invocation profile differs from installation qualification; qualify the selected profile again.');
454
+ }
425
455
  const configuredServer = parallelism.clis?.[member.cli]?.mcpServerRef;
426
456
  if (transport === 'mcp' && configuredServer && configuredServer !== binding.mcp?.serverRef)
427
457
  throw new Error('Configured MCP server changed');
@@ -433,9 +463,15 @@ export function resolveWorkerPlan(snapshot, request) {
433
463
  throw new Error('Requested-only mapping cannot attest backend identity');
434
464
  if (!hasOperatorWorkerNativeRenderer(adapter) && group.execution?.sandbox && !adapter.capabilities.sandboxModes.includes(group.execution.sandbox))
435
465
  throw new Error('Requested sandbox mode is unqualified');
466
+ assertWorkerLaunchMode(adapter, permissions);
436
467
  qualifyWorkerInvocationProfile(adapter, { profile: binding.profile, transport, cliVersion: binding.cliVersion,
437
468
  policy, permissions, effort, boundary: binding.boundary,
438
469
  requiredControls: request.requiredControls, mcp: binding.mcp });
470
+ if (hasOperatorWorkerNativeRenderer(adapter) && transport === 'shell' && permissions.sandbox === 'external') {
471
+ reasons.push({ code: 'OG_SANDBOX_EXTERNAL', blocking: false,
472
+ pointer: `/parallelism/groups/${groupId}/execution/sandbox`,
473
+ message: `${adapter.provider} CLI's own sandbox is disabled by explicit external policy; isolation relies on the per-task worktree and launcher resource scope.` });
474
+ }
439
475
  const { maxAttempts: _maxAttempts, ...budgetLimits } = plan.ceilings;
440
476
  const budgetDimensions = { tokens: 'tokens', cost: 'costUsd', 'wall-time': 'wallSeconds' };
441
477
  const enforceable = adapter.capabilities.hardBudget.map(key => budgetDimensions[key]);
@@ -497,6 +533,7 @@ export function resolveWorkerPlan(snapshot, request) {
497
533
  plan.selected = null;
498
534
  if (plan.selected) {
499
535
  plan.status = 'eligible';
536
+ plan.diagnostics.push(...plan.selected.diagnostics.filter(item => item.code === 'OG_SANDBOX_EXTERNAL'));
500
537
  plan.choice = { source: request.choice ? 'conductor' : 'default', rationale: request.choice?.rationale ?? 'Role group order, member priority/declaration order, then qualified effort ladder',
501
538
  ...(request.substitution && matchesChoice(plan.selected, request.substitution) ? { substitution: structuredClone(request.substitution) } : {}) };
502
539
  }
@@ -9,7 +9,9 @@ export function loadRecordedWorkerInstallations(input: {
9
9
  parallelism?: import('../config/aiwg-config.js').ParallelismConfig;
10
10
  storePath?: string;
11
11
  storeDigest?: string;
12
- }): Promise<{ registry: ProviderModelCapabilityRegistry; installations: Record<string, WorkerResolutionBinding>; drivers: Record<string, WorkerInvocationDriver>; observations: Record<string, import('./model-discovery.js').ProviderModelDiscovery> }>;
12
+ /** Request validation after store presence, before recorded policy/adapter checks. */
13
+ precheckPolicy?: () => void;
14
+ }): Promise<{ registry: ProviderModelCapabilityRegistry; installations: Record<string, WorkerResolutionBinding>; drivers: Record<string, WorkerInvocationDriver>; observations: Record<string, import('./model-discovery.js').ProviderModelDiscovery>; qualificationFailures?: Record<string, string> }>;
13
15
 
14
16
  export function hasProvider(name: string): boolean;
15
17
 
@@ -37,3 +39,8 @@ export function registerWorkerRecoveryCapabilities<T extends object>(driver: T,
37
39
  export function getWorkerRecoveryCapabilities(driver: object | undefined | null): Readonly<WorkerRecoveryCapabilities> | null;
38
40
 
39
41
  export function prepareWorkerInstallationStore(input: { workerWritableRoots: string[]; apply?: boolean }): Promise<{ path: string; action: string }[]>;
42
+
43
+ /** Historical approvals only; no driver import, probe, auth read, or launch authority. */
44
+ export function inspectRecordedWorkerQualifications(input: {
45
+ cwd: string; policy: WorkerSelectionPolicy; parallelism: import('../config/aiwg-config.js').ParallelismConfig;
46
+ }): Promise<Array<{ cli: string; provider: string }>>;