@aiwg/cli 2026.10.8 → 2026.10.10

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 (104) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +197 -3
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/artifacts/cli.js +9 -5
  4. package/dist/src/auth/config.js +3 -8
  5. package/dist/src/auth/resource-credentials.js +35 -2
  6. package/dist/src/cli/handlers/diagnose.js +13 -0
  7. package/dist/src/cli/handlers/mc-admission.js +39 -0
  8. package/dist/src/cli/handlers/mc.js +2 -31
  9. package/dist/src/cli/handlers/models-help.js +52 -0
  10. package/dist/src/cli/handlers/models.js +11 -81
  11. package/dist/src/cli/handlers/resource-versions.js +8 -5
  12. package/dist/src/cli/handlers/script-runner.js +13 -1
  13. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  14. package/dist/src/cli/handlers/use.js +209 -233
  15. package/dist/src/cli/ui.js +1 -0
  16. package/dist/src/config/aiwg-config.js +38 -184
  17. package/dist/src/config/cli-help.js +54 -0
  18. package/dist/src/config/cli.js +5 -69
  19. package/dist/src/config/config-normalization.js +10 -0
  20. package/dist/src/config/worker-activation-gate.js +26 -0
  21. package/dist/src/config/worker-adoption-cli.js +102 -0
  22. package/dist/src/config/worker-adoption.js +368 -0
  23. package/dist/src/config/worker-config-digest.js +14 -0
  24. package/dist/src/config/worker-group-templates.js +150 -0
  25. package/dist/src/config/worker-guidance-warning.js +28 -0
  26. package/dist/src/config/worker-owner-lock.js +152 -0
  27. package/dist/src/config/worker-parallelism.js +154 -0
  28. package/dist/src/config/worker-write.js +75 -0
  29. package/dist/src/decision/telemetry/exporter.js +2 -1
  30. package/dist/src/decision/telemetry/index.js +1 -0
  31. package/dist/src/decision/telemetry/mapping.js +43 -0
  32. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  33. package/dist/src/decision/telemetry/redaction.js +6 -1
  34. package/dist/src/decision/telemetry/types.js +3 -0
  35. package/dist/src/decision/telemetry/worker.js +69 -0
  36. package/dist/src/lint/rule-activation.js +15 -7
  37. package/dist/src/lint/runner.js +2 -2
  38. package/dist/src/mcp/server.mjs +2 -11
  39. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  40. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  41. package/dist/src/mcp/tools/worker-invoke.mjs +6 -6
  42. package/dist/src/mission-protocol/codecs.js +8 -38
  43. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  44. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  45. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  46. package/dist/src/models/conductor-data-policy.mjs +190 -0
  47. package/dist/src/models/config-loader.js +19 -3
  48. package/dist/src/models/config-writer.js +27 -0
  49. package/dist/src/models/model-capabilities.v1.json +197 -3
  50. package/dist/src/models/provider-policy.js +7 -155
  51. package/dist/src/models/resolver.js +2 -293
  52. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  53. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  54. package/dist/src/models/worker-direct-readiness.js +9 -218
  55. package/dist/src/models/worker-health-readiness.js +105 -26
  56. package/dist/src/models/worker-installation.mjs +12 -14
  57. package/dist/src/models/worker-member-references.js +64 -0
  58. package/dist/src/models/worker-plan.js +317 -0
  59. package/dist/src/models/worker-selection-policy.js +217 -0
  60. package/dist/src/models/wrapper-deployment.js +1 -1
  61. package/dist/src/resources/web-release.js +76 -65
  62. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  63. package/dist/src/serve/shared-host-scheduler.js +25 -107
  64. package/dist/src/serve/shared-host-starvation.js +73 -0
  65. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  66. package/dist/src/serve/worker-artifacts.mjs +56 -0
  67. package/dist/src/serve/worker-command-request.js +68 -0
  68. package/dist/src/serve/worker-invocation-paths.js +144 -0
  69. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  70. package/dist/src/serve/worker-invocation.js +24 -291
  71. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  72. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  73. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  74. package/dist/src/storage/backends/fortemi.js +33 -29
  75. package/dist/src/storage/backends/logseq.js +16 -3
  76. package/dist/src/storage/backends/postgres.js +74 -33
  77. package/dist/src/storage/backends/postgrest.js +37 -13
  78. package/dist/src/storage/config.js +6 -1
  79. package/dist/src/uhp/config.js +4 -0
  80. package/package.json +9 -1
  81. package/schemas/mission-v1.schema.json +20 -19
  82. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  83. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  84. package/tools/agents/providers/claude.mjs +3 -30
  85. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  86. package/tools/agents/providers/codex.mjs +2 -8
  87. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  88. package/tools/agents/providers/rule-delivery.mjs +3 -16
  89. package/tools/agents/providers/rule-presentation.mjs +38 -0
  90. package/tools/plugin/package-plugins.mjs +1 -1
  91. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  92. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  93. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  94. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  95. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  96. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  97. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  98. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  99. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  100. package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
  101. package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
  102. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  103. package/tools/ralph-external/session-launcher.mjs +73 -217
  104. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -510,7 +510,7 @@
510
510
  "approval_policy=\"${approval}\"",
511
511
  "--skip-git-repo-check",
512
512
  "-o",
513
- "${final}",
513
+ "${final}.last-message",
514
514
  "--json",
515
515
  "-"
516
516
  ],
@@ -575,7 +575,7 @@
575
575
  },
576
576
  "finalOutput": {
577
577
  "support": "unqualified",
578
- "mapping": "[-o/--output-last-message F] last-message file; --json carries the JSONL thread/turn/item/error event stream on stdout",
578
+ "mapping": "[-o/--output-last-message F.last-message] launcher-owned last-message capture, separate from worker-written workerClaims JSON at F (brief output.resultPath / --final-output); --json carries the JSONL thread/turn/item/error event stream on stdout",
579
579
  "evidence": "H: codex-cli 0.161.0 exec --help documents -o/--output-last-message and --json; unqualified until live evidence"
580
580
  },
581
581
  "stdin": {
@@ -1606,7 +1606,7 @@
1606
1606
  "Unknown model, effort, permission or required step mappings block dispatch.",
1607
1607
  "Native MCP/client, MSP/ACP, gateway and conversation capabilities do not establish worker authority.",
1608
1608
  "There is NO `oz` symlink on the evidenced host: the real executable is `warp-terminal`, with `agent run` etc. as its subcommands; `runtimeDriver`/`executable` are pinned to that, not to a bare `oz`.",
1609
- "BRIEF TRANSPORT IS BLOCKED PENDING A SHARED CHANGE: `--prompt` places the brief on argv (never emitted by this profile); `-f/--file` is NOT a member of the required `<--prompt|--saved-prompt|--task-id|--skill>` group (live probe, 2026-10-11) and its own config-file schema is undocumented, so it cannot be assumed to carry the brief; `--skill` is the only remaining required-group member that can carry free text (`agent run --help`: 'Use a skill as the base prompt for the agent' when given without `--prompt`), but its search root relative to `--cwd` and behavior when the skill is missing are UNVERIFIED behind the login gate. The pure adapter derives a distinct `aiwg-brief-<invocationId>` path without writes and refuses unsafe IDs. `buildWarpBriefSkillFile()` (tools/ralph-external/lib/warp-adapter.mjs) exports the exact path/content an execution-time file-generation primitive would need to write before this argv runs; that primitive does not exist in the shared session-launcher today (the same class of gap as #2998 for Antigravity's stdin frame). Needed shared change: an execution-time generated-artifact primitive the session-launcher can call with the adapter-exported `{ relativePath, content }` immediately before spawning the worker process, with invocation identity supplied to the adapter, refusal to overwrite an existing path, cleanup afterward, and no commits of generated skills (pending #3002). Brief transport is NOT available today.",
1609
+ "Brief delivery is wired through buildWorkerArtifacts and the shared session launcher: one bounded workspace-relative .agents/skills/aiwg-brief-<invocationId>/SKILL.md file, exclusive creation, no symlink components, collision preservation and cleanup on failure, completion or stop. The same invocation ID binds --skill and the artifact path; brief text never rides argv. Real skill search relative to --cwd and missing-skill behavior remain UNVERIFIED behind authentication; synthetic file-delivery tests do not qualify the profile.",
1610
1610
  "`--environment`/`-e` (cloud environment/runner) and `--harness claude|codex` (third-party harness mode) are documented but NEVER emitted by this profile; the cloud subcommand is also never emitted. Execution location is unverified until an authenticated run.",
1611
1611
  "`--no-snapshot` is always emitted: `agent run --help` documents it as disabling the end-of-run workspace snapshot upload. This flag does not establish execution location or absence of other uploads.",
1612
1612
  "No live success/error output was observed (no login on the evidenced host): `finalOutput`/`structuredOutput` support is grade H/R only, never upgraded past `unqualified`."
@@ -3326,6 +3326,200 @@
3326
3326
  }
3327
3327
  }
3328
3328
  },
3329
+ "workerAdapters": {
3330
+ "local-http": {
3331
+ "version": 1,
3332
+ "id": "local-http",
3333
+ "provider": "ollama",
3334
+ "adapterVersion": "1",
3335
+ "supportedVersions": "8.18.0",
3336
+ "runtimeDriver": "installed:local-http",
3337
+ "catalogAdapter": {
3338
+ "kind": "http",
3339
+ "locator": "/api/tags",
3340
+ "parser": "ollama-model-list-v1",
3341
+ "timeoutSeconds": 10,
3342
+ "maxBytes": 65536
3343
+ },
3344
+ "invocation": {
3345
+ "kind": "process",
3346
+ "executable": "curl",
3347
+ "argv": [
3348
+ "-q",
3349
+ "-sS",
3350
+ "--max-redirs",
3351
+ "0",
3352
+ "--proto",
3353
+ "=http",
3354
+ "--noproxy",
3355
+ "*",
3356
+ "--connect-timeout",
3357
+ "10",
3358
+ "--max-time",
3359
+ "300",
3360
+ "--max-filesize",
3361
+ "10485760",
3362
+ "--fail-with-body",
3363
+ "-H",
3364
+ "Content-Type: application/json",
3365
+ "-X",
3366
+ "POST",
3367
+ "http://${endpoint}/api/generate",
3368
+ "--data-binary",
3369
+ "@-"
3370
+ ],
3371
+ "stdin": "adapter-frame",
3372
+ "cwd": "${workspace}",
3373
+ "envAllowlist": [
3374
+ "OLLAMA_HOST"
3375
+ ],
3376
+ "versionArgv": [
3377
+ "--version"
3378
+ ],
3379
+ "versionPattern": "^curl ([0-9A-Za-z.+-]{1,128}) [\\s\\S]{1,4096}$",
3380
+ "maxOutputBytes": 10485760
3381
+ },
3382
+ "capabilities": {
3383
+ "output": "validated-file",
3384
+ "modelPin": "requested",
3385
+ "effort": "not-applicable",
3386
+ "cancel": "process-group",
3387
+ "resume": false,
3388
+ "sandboxModes": [
3389
+ "external"
3390
+ ],
3391
+ "nestedAgentsControllable": true,
3392
+ "nativeFallbackControllable": true,
3393
+ "hardBudget": []
3394
+ },
3395
+ "qualification": {
3396
+ "status": "unqualified"
3397
+ },
3398
+ "invocationProfiles": {
3399
+ "direct-http": {
3400
+ "transport": "shell",
3401
+ "supportedVersions": "8.18.0",
3402
+ "controls": {
3403
+ "model": {
3404
+ "support": "native",
3405
+ "mapping": "[\"--data-binary\",\"@-\"]",
3406
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3407
+ },
3408
+ "effort": {
3409
+ "support": "not-applicable",
3410
+ "mapping": "unavailable: explicit effort is refused; null effort and empty effort lists only",
3411
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3412
+ },
3413
+ "sandbox": {
3414
+ "support": "external",
3415
+ "mapping": "unavailable natively: requires a host OS/executor boundary; local endpoint does not prove local inference",
3416
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3417
+ },
3418
+ "approval": {
3419
+ "support": "external",
3420
+ "mapping": "unavailable natively: curl has no approval or permission system; the host authorizes the single text-only POST",
3421
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3422
+ },
3423
+ "workdir": {
3424
+ "support": "external",
3425
+ "mapping": "host spawns curl in the resolved absolute workspace; no server-side workspace control",
3426
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3427
+ },
3428
+ "finalOutput": {
3429
+ "support": "external",
3430
+ "mapping": "host captures stdout and validates the original envelope and extracted worker claims; no server output-file control",
3431
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3432
+ },
3433
+ "stdin": {
3434
+ "support": "native",
3435
+ "mapping": "[\"--data-binary\",\"@-\"]",
3436
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3437
+ },
3438
+ "structuredOutput": {
3439
+ "support": "external",
3440
+ "mapping": "unavailable server schema control; host strict claims parser validates the extracted response",
3441
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3442
+ },
3443
+ "stepCap": {
3444
+ "support": "unqualified",
3445
+ "mapping": "unavailable: no model step cap; curl time and size bounds are not a step cap",
3446
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3447
+ },
3448
+ "trust": {
3449
+ "support": "native",
3450
+ "mapping": "-q",
3451
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3452
+ },
3453
+ "children": {
3454
+ "support": "native",
3455
+ "mapping": "[\"--data-binary\",\"@-\"]",
3456
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3457
+ },
3458
+ "fallback": {
3459
+ "support": "native",
3460
+ "mapping": "[\"--max-redirs\",\"0\"]",
3461
+ "evidence": "curl 8.18.0 installed help; tools/ralph-external/lib/ollama-adapter.mjs and offline Ollama fixtures; server 0.33.3 API assumptions provisional"
3462
+ }
3463
+ },
3464
+ "envAllowlist": [
3465
+ "OLLAMA_HOST"
3466
+ ],
3467
+ "pitfalls": [
3468
+ "One text-only POST /api/generate, stream:false, explicit model and no tools, child-agent API or fallback request. Coding and explicit effort are refused.",
3469
+ "The model control binds the resolved model in the adapter-frame JSON body; argv contains no model or brief.",
3470
+ "No Ollama CLI, server start or weight-download command is invoked, including for disabled members. Server-side downloads, inference locality and backend identity remain unqualified.",
3471
+ "curl 8.18.0 is the executable version; Ollama 0.33.3 response fields and done_reason=stop remain provisional pending the conductor smoke.",
3472
+ "No native sandbox or approval system exists; external workspace/capture/validation controls require an enforcing host boundary.",
3473
+ "No streaming health grammar, progress, session log, server cancellation, resume or model step cap is available. Owned curl exit is not proof of remote server stop."
3474
+ ],
3475
+ "health": {
3476
+ "version": 1,
3477
+ "handshake": "process-only",
3478
+ "outputFraming": "final-json",
3479
+ "signalParser": "unavailable",
3480
+ "sessionLogLocator": null,
3481
+ "progressEvidence": [],
3482
+ "stopEvidence": "owned curl process identity and observed exit only; remote server cancellation unavailable",
3483
+ "fixtureRef": "test/fixtures/providers/local-http/health/manifest.json",
3484
+ "qualifiedAt": null,
3485
+ "statusOperation": null,
3486
+ "cancelOperation": null,
3487
+ "coverage": "degraded",
3488
+ "qualificationTarget": "unobserved",
3489
+ "evidenceGrades": [
3490
+ "H",
3491
+ "R"
3492
+ ],
3493
+ "provenance": [
3494
+ {
3495
+ "kind": "help",
3496
+ "ref": "docs/providers/ollama-direct.md",
3497
+ "observedAt": "2026-10-09T00:00:00Z",
3498
+ "cliVersion": "8.18.0"
3499
+ },
3500
+ {
3501
+ "kind": "repository",
3502
+ "ref": "tools/ralph-external/lib/ollama-adapter.mjs",
3503
+ "observedAt": "2026-10-26T00:00:00Z",
3504
+ "cliVersion": null
3505
+ }
3506
+ ],
3507
+ "qualificationOwner": "B-OG-09",
3508
+ "qualification": {
3509
+ "status": "unqualified",
3510
+ "scopeDigest": null,
3511
+ "cliVersion": null,
3512
+ "accountScope": null,
3513
+ "profileRef": null,
3514
+ "modelKey": null,
3515
+ "effort": null,
3516
+ "authorizedLive": false
3517
+ }
3518
+ }
3519
+ }
3520
+ }
3521
+ }
3522
+ },
3329
3523
  "workerHealthAdapters": {
3330
3524
  "local-http": {
3331
3525
  "version": 1,
@@ -14,6 +14,8 @@ import { createRequire } from 'node:module';
14
14
  import { compileWorkerVersionPattern, workerVersionRangeMismatch } from './worker-installation.mjs';
15
15
  import { normalizeProviderDefinitionId } from '../providers/provider-definitions.js';
16
16
  import { qualificationDigest } from './qualification-digest.mjs';
17
+ import { DEFAULT_WORKER_SELECTION, WorkerSelectionPolicySchema, workerModelKey, ownWorkerRoleGuidance } from './worker-selection-policy.js';
18
+ export { DEFAULT_WORKER_SELECTION, WorkerSelectionPolicySchema, mergeWorkerSelection, workerModelKey, isCanonicalWorkerModelKey } from './worker-selection-policy.js';
17
19
  const require = createRequire(import.meta.url);
18
20
  function requireModelResource(filename) {
19
21
  const candidates = [
@@ -183,156 +185,6 @@ const PolicySchema = z.strictObject({
183
185
  effort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
184
186
  override: z.string().min(1).optional(),
185
187
  });
186
- const uniqueStrings = z.array(z.string().min(1)).refine(values => new Set(values).size === values.length, 'Duplicate entries');
187
- const WorkerDataPolicySchema = z.strictObject({
188
- allowedProcessing: z.array(z.enum(['local', 'remote'])).refine(values => new Set(values).size === values.length).optional(),
189
- allowProductImprovement: z.boolean().optional(),
190
- allowUnknown: z.boolean().optional(),
191
- maxRetentionDays: integer().nonnegative().optional(),
192
- });
193
- const WorkerRoleSchema = z.strictObject({
194
- groups: uniqueStrings.refine(values => values.length > 0),
195
- tier: z.enum(['economy', 'standard', 'premium', 'max-quality']),
196
- initialEffort: z.string().min(1).nullable(),
197
- effortLadder: uniqueStrings,
198
- escalateAfter: integer().positive().optional(),
199
- maxEscalations: integer().nonnegative().optional(),
200
- deescalateAfter: integer().positive().optional(),
201
- allowNotApplicableEffort: z.boolean().optional(),
202
- });
203
- export const WorkerSelectionPolicySchema = z.strictObject({
204
- roleGuidance: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerRoleSchema).optional(),
205
- guards: z.strictObject({
206
- deniedEfforts: uniqueStrings.optional(),
207
- deniedModels: uniqueStrings.optional(),
208
- denySelfDelegation: z.boolean().optional(),
209
- requireExactModel: z.boolean().optional(),
210
- allowedVisibility: z.array(z.enum(['listed', 'hidden'])).refine(v => new Set(v).size === v.length).optional(),
211
- allowedLifecycle: z.array(z.enum(['active', 'deprecated'])).refine(v => new Set(v).size === v.length).optional(),
212
- minimumIdentityEvidence: z.enum(['runtime-configured', 'backend-attested']).optional(),
213
- }).optional(),
214
- dataPolicy: WorkerDataPolicySchema.optional(),
215
- dataClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), WorkerDataPolicySchema).optional(),
216
- freshness: z.strictObject({
217
- warnHours: integer().positive().optional(),
218
- blockHours: integer().positive().optional(),
219
- policyDays: integer().positive().optional(),
220
- }).optional(),
221
- taskClasses: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), z.string().min(1)).optional(),
222
- }).superRefine((policy, context) => {
223
- for (const [role, guidance] of Object.entries(policy.roleGuidance ?? {})) {
224
- if (!['efficiency', 'coding', 'reasoning'].includes(role))
225
- context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Unknown worker role' });
226
- if (guidance.effortLadder.includes('none') || (guidance.initialEffort === 'none' ? guidance.effortLadder.length !== 0 : guidance.initialEffort !== null && !guidance.effortLadder.includes(guidance.initialEffort))) {
227
- context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Initial effort must belong to the ladder; real none requires an empty ladder' });
228
- }
229
- if (guidance.initialEffort === null && (guidance.allowNotApplicableEffort !== true || guidance.effortLadder.length !== 0)) {
230
- context.addIssue({ code: 'custom', path: ['roleGuidance', role], message: 'Null effort requires an empty ladder and allowNotApplicableEffort=true' });
231
- }
232
- }
233
- for (const key of policy.guards?.deniedModels ?? []) {
234
- if (!isCanonicalWorkerModelKey(key))
235
- context.addIssue({ code: 'custom', path: ['guards', 'deniedModels'], message: 'Noncanonical inference model key' });
236
- }
237
- if (policy.freshness?.warnHours !== undefined && policy.freshness.blockHours !== undefined && policy.freshness.warnHours > policy.freshness.blockHours) {
238
- context.addIssue({ code: 'custom', path: ['freshness'], message: 'warnHours must not exceed blockHours' });
239
- }
240
- for (const role of Object.values(policy.taskClasses ?? {})) {
241
- if (!['coding', 'reasoning', 'efficiency'].includes(role))
242
- context.addIssue({ code: 'custom', path: ['taskClasses'], message: 'Unknown worker role' });
243
- }
244
- });
245
- /** Canonical RFC3986 identity; installation and account never enter the key. */
246
- export function workerModelKey(inferenceProvider, apiModelId) {
247
- if (!/^[a-z][a-z0-9-]{0,62}$/.test(inferenceProvider) || !apiModelId)
248
- throw new Error('OG_CONFIG_REFERENCE: invalid inference model identity');
249
- return `${inferenceProvider}/${encodeURIComponent(apiModelId).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`;
250
- }
251
- export function isCanonicalWorkerModelKey(key) {
252
- const slash = key.indexOf('/');
253
- try {
254
- return slash > 0 && workerModelKey(key.slice(0, slash), decodeURIComponent(key.slice(slash + 1))) === key;
255
- }
256
- catch {
257
- return false;
258
- }
259
- }
260
- export const DEFAULT_WORKER_SELECTION = {
261
- guards: { deniedEfforts: ['ultra'], deniedModels: [], denySelfDelegation: true, requireExactModel: true, allowedVisibility: ['listed'], allowedLifecycle: ['active'], minimumIdentityEvidence: 'runtime-configured' },
262
- dataPolicy: { allowedProcessing: ['local', 'remote'], allowProductImprovement: false, allowUnknown: false },
263
- freshness: { warnHours: 24, blockHours: 168, policyDays: 30 },
264
- taskClasses: { inventory: 'efficiency', implementation: 'coding', architecture: 'reasoning', review: 'reasoning' },
265
- };
266
- /** Merge leaves, preserving pre-merge explicit origins and attempted relaxations. */
267
- export function mergeWorkerSelection(layers) {
268
- const policy = {};
269
- const origins = {};
270
- const diagnostics = [];
271
- const visit = (target, input, parts, layer) => {
272
- for (const [key, value] of Object.entries(input)) {
273
- const path = [...parts, key];
274
- const pointer = `/defaults/workerSelection/${path.map(p => p.replace(/~/g, '~0').replace(/\//g, '~1')).join('/')}`;
275
- if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
276
- target[key] ??= {};
277
- visit(target[key], value, path, layer);
278
- continue;
279
- }
280
- const origin = { source: layer.source, authority: layer.authority, digest: layer.digest, pointer, contribution: structuredClone(value) };
281
- const prior = origins[pointer] ?? [];
282
- const explicit = prior.filter(o => o.authority !== 'builtin');
283
- let effective = structuredClone(value);
284
- let relaxation = false;
285
- if (layer.authority !== 'builtin' && explicit.length > 0) {
286
- const previous = target[key];
287
- if (['deniedEfforts', 'deniedModels'].includes(key))
288
- effective = [...new Set([...previous, ...value])];
289
- else if (['allowedVisibility', 'allowedLifecycle', 'allowedProcessing'].includes(key)) {
290
- relaxation = value.some((v) => !previous.includes(v));
291
- effective = previous.filter((v) => value.includes(v));
292
- }
293
- else if (['denySelfDelegation', 'requireExactModel'].includes(key)) {
294
- relaxation = previous === true && value === false;
295
- effective = previous || value;
296
- }
297
- else if (['allowUnknown', 'allowProductImprovement'].includes(key)) {
298
- relaxation = previous === false && value === true;
299
- effective = previous && value;
300
- }
301
- else if (['maxRetentionDays', 'warnHours', 'blockHours', 'policyDays'].includes(key)) {
302
- relaxation = value > previous;
303
- effective = Math.min(previous, value);
304
- }
305
- else if (key === 'minimumIdentityEvidence') {
306
- relaxation = previous === 'backend-attested' && value !== previous;
307
- effective = previous === 'backend-attested' ? previous : value;
308
- }
309
- }
310
- if (relaxation)
311
- diagnostics.push({ code: 'OG_POLICY_RELAXATION', pointer, message: 'Explicit restriction cannot relax an inherited restriction', origins: [...explicit, origin] });
312
- target[key] = effective;
313
- origins[pointer] = [...prior, origin];
314
- }
315
- };
316
- for (const layer of layers)
317
- if (layer.policy)
318
- visit(policy, layer.policy, [], layer);
319
- const validation = WorkerSelectionPolicySchema.safeParse(policy);
320
- if (!validation.success)
321
- for (const issue of validation.error.issues)
322
- diagnostics.push({ code: 'OG_CONFIG_REFERENCE', pointer: `/defaults/workerSelection/${issue.path.join('/')}`, message: issue.message });
323
- // Data classes are applied as further restrictions to the base path.
324
- for (const dataClass of Object.values(policy.dataClasses ?? {})) {
325
- const base = policy.dataPolicy ?? {};
326
- if (dataClass.allowedProcessing)
327
- dataClass.allowedProcessing = dataClass.allowedProcessing.filter(p => !base.allowedProcessing || base.allowedProcessing.includes(p));
328
- for (const key of ['allowUnknown', 'allowProductImprovement'])
329
- if (base[key] === false)
330
- dataClass[key] = false;
331
- if (base.maxRetentionDays !== undefined)
332
- dataClass.maxRetentionDays = Math.min(dataClass.maxRetentionDays ?? base.maxRetentionDays, base.maxRetentionDays);
333
- }
334
- return { policy, origins, diagnostics };
335
- }
336
188
  const UserProjectModelConfigSchema = z.looseObject({
337
189
  defaults: z.strictObject({
338
190
  tier: z.enum(['economy', 'standard', 'premium', 'max-quality']).optional(),
@@ -435,7 +287,7 @@ export class WorkerRoleEffortError extends WorkerPolicyError {
435
287
  /** Validate policy input before entering the sanitized adapter boundary. Null
436
288
  * remains valid for models whose effort is explicitly not applicable. */
437
289
  export function assertWorkerRoleEffort(policy, role, requiresExplicit = false) {
438
- const effort = policy?.roleGuidance?.[role]?.initialEffort;
290
+ const effort = ownWorkerRoleGuidance(policy, role)?.initialEffort;
439
291
  if (effort === undefined || requiresExplicit && (effort === null || !effort.trim()))
440
292
  throw new WorkerRoleEffortError(role);
441
293
  }
@@ -459,7 +311,7 @@ export function assertWorkerSelectionAllowed(selected, context) {
459
311
  const observation = context.modelObservation ?? context.defaultObservation;
460
312
  if (!observation)
461
313
  throw new WorkerPolicyError('OG_MODEL_UNAPPROVED', 'Worker invocation requires model or default observation evidence');
462
- const guidance = context.role ? policy.roleGuidance?.[context.role] : undefined;
314
+ const guidance = context.role ? ownWorkerRoleGuidance(policy, context.role) : undefined;
463
315
  if (isWorkerNullEffortDenied(selected.effort.value, observation, guidance)) {
464
316
  throw new WorkerPolicyError('OG_EFFORT_DENIED', 'Worker invocation denied effort: null effort requires qualified absence of an effort control');
465
317
  }
@@ -907,7 +759,7 @@ function isWorkerNullEffortDenied(effort, observation, guidance) {
907
759
  }
908
760
  function workerRouteDataPolicy(policy, dataClass) {
909
761
  const data = { ...DEFAULT_WORKER_SELECTION.dataPolicy, ...policy.dataPolicy };
910
- const narrowed = dataClass ? policy.dataClasses?.[dataClass] : undefined;
762
+ const narrowed = dataClass && policy.dataClasses && Object.hasOwn(policy.dataClasses, dataClass) ? policy.dataClasses[dataClass] : undefined;
911
763
  if (narrowed) {
912
764
  if (narrowed.allowedProcessing)
913
765
  data.allowedProcessing = data.allowedProcessing?.filter(p => narrowed.allowedProcessing.includes(p));
@@ -956,8 +808,8 @@ export function validateWorkerPolicyRoute(input) {
956
808
  message: `Conflicting optional model evidence remains unknown: ${observation.conflicts.map(conflict => conflict.field).join(', ')}`,
957
809
  });
958
810
  const guards = input.policy.guards ?? DEFAULT_WORKER_SELECTION.guards;
959
- const guidance = input.policy.roleGuidance?.[input.role];
960
- if (observation.qualification.status !== 'qualified' || !observation.qualification.roles.includes(input.role))
811
+ const guidance = ownWorkerRoleGuidance(input.policy, input.role);
812
+ if (observation.qualification.status !== 'qualified' || !observation.qualification.roles.includes(guidance?.baseRole ?? input.role))
961
813
  deny('OG_ADAPTER_UNQUALIFIED', '/worker/qualification', 'Role lacks qualified evidence. Run aiwg doctor --probe-workers --qualify.');
962
814
  if (!guidance)
963
815
  deny('OG_CONFIG_REFERENCE', `/defaults/workerSelection/roleGuidance/${input.role}`, 'Role guidance is unavailable');