@aiwg/cli 2026.10.10 → 2026.10.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agentic/code/providers/model-capabilities.v1.json +78 -14
- package/agentic/code/providers/model-catalog.v1.json +62 -12
- package/dist/src/artifacts/binding-target-identity.js +51 -0
- package/dist/src/artifacts/checksum-manifest.js +12 -0
- package/dist/src/artifacts/index-builder.js +11 -13
- package/dist/src/artifacts/query-engine.js +5 -11
- package/dist/src/cli/handlers/doctor-help.js +32 -0
- package/dist/src/cli/handlers/gates.js +19 -18
- package/dist/src/cli/handlers/models-help.js +7 -1
- package/dist/src/cli/handlers/models.js +3 -3
- package/dist/src/cli/handlers/script-runner.js +8 -5
- package/dist/src/cli/handlers/sessions-intake.js +104 -0
- package/dist/src/cli/handlers/sessions-tail.js +172 -0
- package/dist/src/cli/handlers/sessions.js +287 -31
- package/dist/src/cli/handlers/skill-lint.js +10 -3
- package/dist/src/cli/handlers/steward-format.js +100 -0
- package/dist/src/cli/handlers/steward-models.js +167 -0
- package/dist/src/cli/handlers/steward.js +14 -248
- package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
- package/dist/src/cli/handlers/use.js +18 -28
- package/dist/src/cli/handlers/utilities.js +1 -24
- package/dist/src/config/aiwg-config.js +7 -0
- package/dist/src/config/authorization-cli.js +89 -0
- package/dist/src/config/cli-help.js +1 -0
- package/dist/src/config/cli.js +10 -1
- package/dist/src/config/worker-context.js +337 -0
- package/dist/src/decision/context-pruning.js +3 -2
- package/dist/src/decision/ensemble/contract.js +15 -4
- package/dist/src/decision/ensemble-study/artifacts.js +5 -0
- package/dist/src/decision/ensemble-study/score.js +4 -3
- package/dist/src/decision/feature-export.js +3 -9
- package/dist/src/decision/heldout/collector.js +2 -2
- package/dist/src/decision/issue-triage/runtime.js +3 -9
- package/dist/src/decision/qualification/gates.js +2 -1
- package/dist/src/decision/qualification/release.js +2 -3
- package/dist/src/decision/routing/report.js +2 -0
- package/dist/src/decision/sdlc-screening.js +3 -5
- package/dist/src/decision/sensitivity/comparative.js +5 -3
- package/dist/src/gates/driver.js +6 -77
- package/dist/src/gates/evaluate.js +4 -11
- package/dist/src/gates/floors.js +4 -4
- package/dist/src/gates/index.js +3 -3
- package/dist/src/gates/lattice.js +26 -0
- package/dist/src/gates/registry.js +2 -1
- package/dist/src/gates/report.js +19 -7
- package/dist/src/issues/audit-cli.js +120 -0
- package/dist/src/issues/audit-contract.js +64 -0
- package/dist/src/issues/audit-local.js +82 -0
- package/dist/src/issues/audit.js +220 -0
- package/dist/src/lint/rule-activation.js +15 -1
- package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
- package/dist/src/mission-protocol/canonical-codecs.js +270 -0
- package/dist/src/mission-protocol/codecs.js +22 -550
- package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
- package/dist/src/mission-protocol/health-trace-types.js +43 -0
- package/dist/src/mission-protocol/types.js +2 -42
- package/dist/src/mission-protocol/worker-contract-json.js +10 -0
- package/dist/src/mission-protocol/worker-review-gate.js +100 -0
- package/dist/src/mission-protocol/worker-review-types.js +2 -0
- package/dist/src/models/catalog-policy.js +280 -0
- package/dist/src/models/index.js +1 -1
- package/dist/src/models/model-capabilities.v1.json +78 -14
- package/dist/src/models/model-catalog.v1.json +62 -12
- package/dist/src/models/policy-resources.js +28 -0
- package/dist/src/models/provider-policy.js +10 -539
- package/dist/src/models/resolver.js +26 -8
- package/dist/src/models/router.js +15 -2
- package/dist/src/models/tiers.js +13 -0
- package/dist/src/models/types.js +0 -6
- package/dist/src/models/worker-ceilings.js +59 -0
- package/dist/src/models/worker-invocation-policy.js +243 -0
- package/dist/src/models/worker-plan.js +16 -20
- package/dist/src/models/worker-review-lineage.js +55 -0
- package/dist/src/models/worker-selection-policy.js +1 -1
- package/dist/src/models/wrapper-route.js +6 -3
- package/dist/src/policy/authorization.js +30 -1
- package/dist/src/policy/hitl-config.js +63 -0
- package/dist/src/policy/hitl-receipt.js +57 -0
- package/dist/src/policy/hitl-types.js +4 -0
- package/dist/src/policy/hitl-validation.js +39 -0
- package/dist/src/policy/hitl.js +117 -0
- package/dist/src/process/aiwg-probe-command.d.mts +15 -0
- package/dist/src/process/aiwg-probe-command.mjs +37 -0
- package/dist/src/rules/activation.d.mts +1 -1
- package/dist/src/rules/activation.mjs +0 -2
- package/dist/src/rules/gate-coverage.js +51 -0
- package/dist/src/rules/gate-references.js +16 -0
- package/dist/src/rules/rule-frontmatter.schema.json +7 -1
- package/dist/src/serve/worker-command-request.js +21 -4
- package/dist/src/serve/worker-invocation-preparation.js +3 -1
- package/dist/src/serve/worker-invocation.js +21 -10
- package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
- package/dist/src/sessions/adapters/grok-build-native.js +269 -0
- package/dist/src/sessions/adapters/grok-build.js +57 -15
- package/dist/src/sessions/adapters/grokbot.js +7 -0
- package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
- package/dist/src/sessions/adapters/hermes.js +42 -3
- package/dist/src/sessions/adapters/warp.js +1 -1
- package/dist/src/sessions/batch-import.js +44 -6
- package/dist/src/sessions/contracts.js +20 -1
- package/dist/src/sessions/coverage-preflight.js +7 -1
- package/dist/src/sessions/discovery-capability.js +38 -0
- package/dist/src/sessions/fortemi-export-mapping.js +33 -3
- package/dist/src/sessions/host-catalog.js +157 -0
- package/dist/src/sessions/image-blobs.js +141 -0
- package/dist/src/sessions/importer.js +9 -232
- package/dist/src/sessions/index.js +6 -0
- package/dist/src/sessions/intake-dir.js +71 -0
- package/dist/src/sessions/normalization.js +251 -0
- package/dist/src/sessions/refresh.js +25 -2
- package/dist/src/sessions/repository.js +164 -22
- package/dist/src/sessions/tail.js +91 -0
- package/dist/src/sessions/workspace-discovery.js +88 -9
- package/dist/src/skills/agent-skill-sidecar.js +28 -0
- package/dist/src/skills/agent-skills.js +4 -23
- package/dist/src/skills/claude-surface-projection.js +25 -0
- package/dist/src/skills/deployer.js +8 -288
- package/dist/src/skills/deployment-policy.js +114 -0
- package/dist/src/skills/deployment-projection.js +152 -0
- package/dist/src/skills/deployment-types.js +20 -0
- package/dist/src/skills/exporter.js +8 -31
- package/dist/src/skills/provider-extensions.js +41 -0
- package/dist/src/skills/run.js +6 -0
- package/dist/src/skills/types.js +0 -8
- package/dist/src/skills/validator-resources.js +68 -0
- package/dist/src/skills/validator.js +6 -55
- package/dist/src/storage/cli-migrate-options.js +125 -0
- package/dist/src/storage/cli-migrate.js +111 -0
- package/dist/src/storage/cli.js +4 -279
- package/dist/src/storage/migration-adapter.js +79 -0
- package/dist/src/storage/migration-copy.js +108 -0
- package/dist/src/storage/migration-protocol.js +14 -197
- package/dist/src/storage/migration-records.js +186 -0
- package/dist/src/storage/migration-types.js +12 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/tracker/issue-pages.js +110 -0
- package/package.json +7 -1
- package/schemas/mission-v1.schema.json +335 -0
- package/schemas/models/model-catalog.v1.schema.json +19 -1
- package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
- package/schemas/models/model-policy.v1.schema.json +1 -1
- package/tools/agents/providers/provider-paths-staging.mjs +35 -0
- package/tools/agents/providers/rule-binding-targets.mjs +146 -0
- package/tools/agents/providers/rule-delivery.mjs +7 -126
- package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
- package/tools/ralph-external/lib/worker-context.mjs +237 -0
- package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
- package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
- package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
- package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
- package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
- package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
- package/tools/ralph-external/lib/worker-termination.mjs +82 -0
- package/tools/ralph-external/session-launcher.mjs +51 -96
- package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* @implements @.aiwg/architecture/ADR-015-enhanced-model-selection.md
|
|
6
6
|
* @architecture @.aiwg/architecture/enhanced-model-selection-design.md
|
|
7
7
|
*/
|
|
8
|
+
import { normalizeModelTier, TIER_ORDER, flagshipElected, isFlagshipModel } from './tiers.js';
|
|
9
|
+
import { compileModelPolicy } from './provider-policy.js';
|
|
8
10
|
import { ConfigLoader } from './config-loader.js';
|
|
9
11
|
import { resolveWorkerPlan } from './worker-plan.js';
|
|
10
12
|
export * from './worker-plan.js';
|
|
@@ -39,23 +41,39 @@ export class ModelResolver {
|
|
|
39
41
|
const config = await this.getConfig();
|
|
40
42
|
// Determine effective tier and role
|
|
41
43
|
const { tier, role, source } = this.determineEffectiveTierAndRole(agentName, agentRole, agentTier, config);
|
|
44
|
+
if (normalizeModelTier(tier) === 'flagship' && !flagshipElected({ ...this.options, agentName })) {
|
|
45
|
+
throw new Error('Flagship models are user-elected only');
|
|
46
|
+
}
|
|
42
47
|
// Check for direct model override (bypasses tier system)
|
|
43
48
|
const override = config.agentOverrides[agentName];
|
|
44
49
|
if (override?.['model-override']) {
|
|
50
|
+
if (isFlagshipModel(override['model-override']) && !flagshipElected({ ...this.options, agentName }))
|
|
51
|
+
throw new Error('Flagship models are user-elected only');
|
|
45
52
|
return {
|
|
46
53
|
modelId: override['model-override'],
|
|
47
54
|
provider: this.options.provider || config.defaults.provider,
|
|
48
|
-
tier,
|
|
55
|
+
tier: normalizeModelTier(tier),
|
|
49
56
|
role,
|
|
50
57
|
source: 'agent',
|
|
51
58
|
};
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
const modelId =
|
|
60
|
+
const provider = this.options.provider || config.defaults.provider;
|
|
61
|
+
const modelId = normalizeModelTier(tier) === 'flagship'
|
|
62
|
+
? compileModelPolicy({
|
|
63
|
+
provider: provider === 'openai' ? 'codex' : provider,
|
|
64
|
+
artifact: 'agent', policy: { tier: 'flagship', role },
|
|
65
|
+
election: this.options.election, flagshipAllowlist: this.options.flagshipAllowlist, agentName,
|
|
66
|
+
}).effectiveModel
|
|
67
|
+
: this.resolveModelId(tier, role, config);
|
|
68
|
+
if (!modelId)
|
|
69
|
+
throw new Error(`No supported model binding for ${provider}/${tier}`);
|
|
70
|
+
if (isFlagshipModel(modelId) && !flagshipElected({ ...this.options, agentName })) {
|
|
71
|
+
throw new Error('Flagship models are user-elected only');
|
|
72
|
+
}
|
|
55
73
|
return {
|
|
56
74
|
modelId,
|
|
57
75
|
provider: this.options.provider || config.defaults.provider,
|
|
58
|
-
tier,
|
|
76
|
+
tier: normalizeModelTier(tier),
|
|
59
77
|
role,
|
|
60
78
|
source,
|
|
61
79
|
};
|
|
@@ -132,9 +150,9 @@ export class ModelResolver {
|
|
|
132
150
|
* Return the higher of two tiers
|
|
133
151
|
*/
|
|
134
152
|
maxTier(tier1, tier2) {
|
|
135
|
-
const tierOrder =
|
|
136
|
-
const index1 = tierOrder.indexOf(tier1);
|
|
137
|
-
const index2 = tierOrder.indexOf(tier2);
|
|
153
|
+
const tierOrder = TIER_ORDER;
|
|
154
|
+
const index1 = tierOrder.indexOf(normalizeModelTier(tier1));
|
|
155
|
+
const index2 = tierOrder.indexOf(normalizeModelTier(tier2));
|
|
138
156
|
return tierOrder[Math.max(index1, index2)];
|
|
139
157
|
}
|
|
140
158
|
/**
|
|
@@ -227,7 +245,7 @@ export class ModelResolver {
|
|
|
227
245
|
async listTiers() {
|
|
228
246
|
const config = await this.getConfig();
|
|
229
247
|
return Object.entries(config.tiers).map(([tierName, tierDef]) => ({
|
|
230
|
-
tier: tierName,
|
|
248
|
+
tier: normalizeModelTier(tierName),
|
|
231
249
|
description: tierDef.description,
|
|
232
250
|
costMultiplier: tierDef.costMultiplier,
|
|
233
251
|
}));
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @implements #1185
|
|
10
10
|
*/
|
|
11
|
+
import { flagshipElected, normalizeModelTier } from './tiers.js';
|
|
11
12
|
const RUNTIME_TO_MODEL_TIER = {
|
|
12
13
|
0: null,
|
|
13
14
|
1: 'economy',
|
|
14
15
|
2: 'standard',
|
|
15
16
|
3: 'premium',
|
|
17
|
+
4: 'flagship',
|
|
16
18
|
};
|
|
17
19
|
function clampRuntimeTier(tier, fallback) {
|
|
18
|
-
return tier === 0 || tier === 1 || tier === 2 || tier === 3 ? tier : fallback;
|
|
20
|
+
return tier === 0 || tier === 1 || tier === 2 || tier === 3 || tier === 4 ? tier : fallback;
|
|
19
21
|
}
|
|
20
22
|
export function routeModelTier(request = {}) {
|
|
21
23
|
const defaultTier = clampRuntimeTier(request.defaultTier, 1);
|
|
@@ -28,6 +30,11 @@ export function routeModelTier(request = {}) {
|
|
|
28
30
|
source = 'deterministic';
|
|
29
31
|
rationale.push('deterministic tool/cache/existing answer is sufficient');
|
|
30
32
|
}
|
|
33
|
+
else if (request.requestedTier) {
|
|
34
|
+
tier = { economy: 1, standard: 2, premium: 3, flagship: 4 }[normalizeModelTier(request.requestedTier)];
|
|
35
|
+
source = 'request';
|
|
36
|
+
rationale.push('explicit tier request');
|
|
37
|
+
}
|
|
31
38
|
else if (request.requestedPremium) {
|
|
32
39
|
tier = 3;
|
|
33
40
|
source = 'request';
|
|
@@ -51,7 +58,13 @@ export function routeModelTier(request = {}) {
|
|
|
51
58
|
else {
|
|
52
59
|
rationale.push('no escalation signal present');
|
|
53
60
|
}
|
|
54
|
-
const
|
|
61
|
+
const elected = flagshipElected(request);
|
|
62
|
+
if (tier === 4 && !elected) {
|
|
63
|
+
tier = 3;
|
|
64
|
+
source = 'policy';
|
|
65
|
+
rationale.push('flagship is user-elected only; capped at premium');
|
|
66
|
+
}
|
|
67
|
+
const requiresConfirmation = tier === 4 ? false : (tier === 3 && !request.premiumAuthorized) || tier > maxAutoTier;
|
|
55
68
|
if (requiresConfirmation) {
|
|
56
69
|
rationale.push(tier === 3
|
|
57
70
|
? 'Tier 3 requires explicit human confirmation'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const MODEL_TIERS = ['economy', 'standard', 'premium', 'flagship', 'max-quality'];
|
|
2
|
+
export const TIER_ORDER = ['economy', 'standard', 'premium', 'flagship'];
|
|
3
|
+
export function normalizeModelTier(tier) {
|
|
4
|
+
return tier === 'max-quality' ? 'flagship' : tier;
|
|
5
|
+
}
|
|
6
|
+
export function flagshipElected(request) {
|
|
7
|
+
return request.election === 'user'
|
|
8
|
+
|| Boolean(request.agentName && request.flagshipAllowlist?.includes(request.agentName));
|
|
9
|
+
}
|
|
10
|
+
export function isFlagshipModel(id) {
|
|
11
|
+
return /(?:^|[/:._-])(?:fable|astra)(?:$|[/:._-]|\d)/i.test(id);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=tiers.js.map
|
package/dist/src/models/types.js
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript types for Enhanced Model Selection System (Phase 1)
|
|
3
|
-
*
|
|
4
|
-
* @implements @.aiwg/architecture/ADR-015-enhanced-model-selection.md
|
|
5
|
-
* @architecture @.aiwg/architecture/enhanced-model-selection-design.md
|
|
6
|
-
*/
|
|
7
1
|
export {};
|
|
8
2
|
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { workerConfigDigest } from '../config/worker-config-digest.js';
|
|
2
|
+
export const WORKER_CEILING_KEYS = ['tokens', 'costUsd', 'wallSeconds', 'maxAttempts'];
|
|
3
|
+
/** Parent/brief restrictions intersect; they never replace a stricter restriction. */
|
|
4
|
+
export function intersectWorkerCeilings(...layers) {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (const key of WORKER_CEILING_KEYS) {
|
|
7
|
+
const values = layers.map(layer => layer?.[key]).filter((value) => value !== undefined);
|
|
8
|
+
if (values.length)
|
|
9
|
+
result[key] = Math.min(...values);
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
export function resolveWorkerCeilings(parallelism, constraints, request, defaults = {}) {
|
|
14
|
+
const layers = [
|
|
15
|
+
{ values: defaults, pointer: 'defaults/invocation', source: 'builtin', authority: 'builtin' },
|
|
16
|
+
{ values: parallelism.budgets?.perRun ?? {}, pointer: '/parallelism/budgets/perRun', source: 'config', authority: 'project' },
|
|
17
|
+
{ values: parallelism.budgets?.perTask ?? {}, pointer: '/parallelism/budgets/perTask', source: 'config', authority: 'project' },
|
|
18
|
+
{ values: { maxAttempts: parallelism.retry?.maxAttempts ?? 3 },
|
|
19
|
+
pointer: parallelism.retry?.maxAttempts === undefined ? 'defaults/parallelism/retry' : '/parallelism/retry',
|
|
20
|
+
source: parallelism.retry?.maxAttempts === undefined ? 'builtin' : 'config',
|
|
21
|
+
authority: parallelism.retry?.maxAttempts === undefined ? 'builtin' : 'project' },
|
|
22
|
+
{ values: constraints ?? {}, pointer: '/constraints/ceilings', source: 'constraints', authority: 'project' },
|
|
23
|
+
];
|
|
24
|
+
const ceilings = {};
|
|
25
|
+
const origins = {};
|
|
26
|
+
const diagnostics = [];
|
|
27
|
+
for (const key of WORKER_CEILING_KEYS) {
|
|
28
|
+
const entries = layers.map(layer => ({ ...layer, value: layer.values[key], pointer: `${layer.pointer}/${key}` }));
|
|
29
|
+
if (key === 'wallSeconds' && parallelism.health?.hardTimeoutSeconds !== undefined)
|
|
30
|
+
entries.push({
|
|
31
|
+
values: { wallSeconds: parallelism.health.hardTimeoutSeconds }, value: parallelism.health.hardTimeoutSeconds,
|
|
32
|
+
pointer: '/parallelism/health/hardTimeoutSeconds', source: 'config', authority: 'project',
|
|
33
|
+
});
|
|
34
|
+
const caller = request?.[key];
|
|
35
|
+
const values = [...entries.map(entry => entry.value), caller].filter((value) => value !== undefined);
|
|
36
|
+
const pointer = `/request/ceilings/${key}`;
|
|
37
|
+
if (values.some(value => !Number.isFinite(value) || value < 0 || key !== 'costUsd' && !Number.isSafeInteger(value))) {
|
|
38
|
+
diagnostics.push({ code: 'OG_CONFIG_REFERENCE', pointer,
|
|
39
|
+
message: 'Ceilings must be finite nonnegative values; counts and seconds must be integers' });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const resolved = Math.min(...entries.map(entry => entry.value ?? Infinity));
|
|
43
|
+
if (caller !== undefined && caller > resolved)
|
|
44
|
+
diagnostics.push({ code: 'OG_POLICY_RELAXATION', pointer,
|
|
45
|
+
message: `Requested ${key} ceiling ${caller} exceeds resolved policy ceiling ${resolved}.` });
|
|
46
|
+
if (!values.length)
|
|
47
|
+
continue;
|
|
48
|
+
ceilings[key] = Math.min(...values);
|
|
49
|
+
if (caller !== undefined)
|
|
50
|
+
entries.push({ values: request, value: caller, pointer,
|
|
51
|
+
source: 'request', authority: 'project' });
|
|
52
|
+
origins[`/ceilings/${key}`] = entries.filter(entry => entry.value === ceilings[key]).map(entry => ({
|
|
53
|
+
source: entry.source, authority: entry.authority, pointer: entry.pointer,
|
|
54
|
+
digest: workerConfigDigest(entry.values), contribution: entry.value,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
return { ceilings, origins, diagnostics };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=worker-ceilings.js.map
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// Checks invocation controls, native sandbox rendering and scoped worker health.
|
|
2
|
+
import { WorkerAdapterSchema, WorkerPolicyError } from './provider-policy.js';
|
|
3
|
+
import { normalizeProviderDefinitionId } from '../providers/provider-definitions.js';
|
|
4
|
+
import { workerVersionRangeMismatch } from './worker-installation.mjs';
|
|
5
|
+
import { workerModelKey } from './worker-selection-policy.js';
|
|
6
|
+
import { qualificationDigest } from './qualification-digest.mjs';
|
|
7
|
+
const WORKER_CONTROLS = ['model', 'effort', 'sandbox', 'approval', 'workdir',
|
|
8
|
+
'finalOutput', 'stdin', 'structuredOutput', 'stepCap', 'trust', 'children', 'fallback'];
|
|
9
|
+
/** Caller controls only add restrictions. Absent resolved authority, require all controls. */
|
|
10
|
+
export function requiredWorkerControls(context) {
|
|
11
|
+
if (context.requiredControls !== undefined && (!Array.isArray(context.requiredControls)
|
|
12
|
+
|| context.requiredControls.some(name => typeof name !== 'string' || !name))) {
|
|
13
|
+
throw new Error('Worker required controls must be an array of control names');
|
|
14
|
+
}
|
|
15
|
+
const controls = new Set(WORKER_CONTROLS);
|
|
16
|
+
if (context.policy && context.permissions) {
|
|
17
|
+
if (context.effort === null)
|
|
18
|
+
controls.delete('effort');
|
|
19
|
+
// Step limits are observed unless explicitly required by the trusted route.
|
|
20
|
+
controls.delete('stepCap');
|
|
21
|
+
if (context.policy.guards?.denySelfDelegation === false)
|
|
22
|
+
controls.delete('children');
|
|
23
|
+
}
|
|
24
|
+
for (const name of context.requiredControls ?? [])
|
|
25
|
+
controls.add(name);
|
|
26
|
+
return [...controls];
|
|
27
|
+
}
|
|
28
|
+
/** The same sandbox requirement governs inspection, resolution and rendering. */
|
|
29
|
+
export function requiresWorkerExternalBoundary(adapter, selection) {
|
|
30
|
+
if (hasOperatorWorkerNativeRenderer(adapter)
|
|
31
|
+
&& (selection.permissions?.sandbox !== 'external' || selection.transport === 'shell'))
|
|
32
|
+
return false;
|
|
33
|
+
const providers = [adapter.id, adapter.provider].map(normalizeProviderDefinitionId);
|
|
34
|
+
return requiredWorkerControls(selection).includes('sandbox')
|
|
35
|
+
&& (providers.some(provider => provider && ['claude', 'muse', 'hermes'].includes(provider))
|
|
36
|
+
|| adapter.invocationProfiles?.[selection.profile]?.controls.sandbox?.support === 'external'
|
|
37
|
+
|| selection.permissions?.sandbox === 'external');
|
|
38
|
+
}
|
|
39
|
+
/** Explicit local operator approval for the direct CLI baseline (decision 17).
|
|
40
|
+
* This is not backend attestation or a live negative-fixture result. */
|
|
41
|
+
export const WORKER_OPERATOR_APPROVAL = 'aiwg doctor --probe-workers --qualify';
|
|
42
|
+
export function isOperatorQualifiedWorkerAdapter(adapter) {
|
|
43
|
+
return adapter.qualification.status === 'qualified'
|
|
44
|
+
&& adapter.qualification.evidenceRef === WORKER_OPERATOR_APPROVAL;
|
|
45
|
+
}
|
|
46
|
+
/** Bundled renderers with an independently checked native direct baseline. */
|
|
47
|
+
export function hasOperatorWorkerNativeRenderer(adapter) {
|
|
48
|
+
return isOperatorQualifiedWorkerAdapter(adapter) && ['codex', 'muse'].includes(adapter.provider)
|
|
49
|
+
&& adapter.invocation.kind === 'process';
|
|
50
|
+
}
|
|
51
|
+
/** The process-group launch boundary requires the independently verified native renderer. */
|
|
52
|
+
export function assertWorkerLaunchMode(adapter, permissions = {}) {
|
|
53
|
+
const mode = permissions.launchMode ?? 'systemd-unit';
|
|
54
|
+
if (!['systemd-unit', 'native-sandbox'].includes(mode)) {
|
|
55
|
+
throw new WorkerPolicyError('OG_CONFIG_REFERENCE', 'Unknown worker launch mode');
|
|
56
|
+
}
|
|
57
|
+
if (mode === 'native-sandbox' && (!hasOperatorWorkerNativeRenderer(adapter)
|
|
58
|
+
|| !['read-only', 'workspace-write'].includes(permissions.sandbox ?? '')
|
|
59
|
+
|| (permissions.transport ?? 'shell') !== 'shell')) {
|
|
60
|
+
throw new WorkerPolicyError('OG_POLICY_RELAXATION', 'native-sandbox requires a verified native read-only or workspace-write shell renderer');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Check the actual argv before granting the direct renderer exception. */
|
|
64
|
+
export function assertOperatorWorkerNativeArgv(adapter, argv, selected, permissions) {
|
|
65
|
+
if (!hasOperatorWorkerNativeRenderer(adapter))
|
|
66
|
+
return;
|
|
67
|
+
const args = argv.slice(1);
|
|
68
|
+
const requirePair = (flag, value) => {
|
|
69
|
+
if (args.filter(token => token === flag).length !== 1 || args[args.indexOf(flag) + 1] !== value) {
|
|
70
|
+
throw new Error(`Requested native worker control is absent or ambiguous: ${flag}`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const sandbox = permissions?.sandbox ?? 'workspace-write';
|
|
74
|
+
if (!['read-only', 'workspace-write', 'external'].includes(sandbox))
|
|
75
|
+
throw new Error('Requested sandbox has no verified native renderer');
|
|
76
|
+
if (adapter.provider === 'muse') {
|
|
77
|
+
requirePair('--provider', 'meta');
|
|
78
|
+
requirePair('--model', selected.model);
|
|
79
|
+
if (selected.effort.value === null)
|
|
80
|
+
throw new Error('Muse requires explicit effort');
|
|
81
|
+
requirePair('--reasoning-effort', selected.effort.value);
|
|
82
|
+
if ((permissions?.approval ?? 'never') !== 'never')
|
|
83
|
+
throw new Error('Muse direct worker approval requires never');
|
|
84
|
+
requirePair('--approval-mode', 'never');
|
|
85
|
+
requirePair('--approval-judge', 'off');
|
|
86
|
+
// #3685: Tier 0 Muse workers run in conductor-prepared worker trees, so
|
|
87
|
+
// project context must load. An argv without exactly one
|
|
88
|
+
// --trust-workspace would run with project_sources=0 (no project
|
|
89
|
+
// rules/skills/hooks) and is refused rather than silently degraded.
|
|
90
|
+
if (args.filter(token => token === '--trust-workspace').length !== 1) {
|
|
91
|
+
throw new Error('Requested native worker control is absent or ambiguous: --trust-workspace');
|
|
92
|
+
}
|
|
93
|
+
if (args.includes('--disable-sandbox') !== (sandbox === 'external')) {
|
|
94
|
+
throw new Error('Requested native worker sandbox control does not match');
|
|
95
|
+
}
|
|
96
|
+
if (sandbox === 'read-only' && (!args.includes('--disable-write') || !args.includes('--disable-shell'))) {
|
|
97
|
+
throw new Error('Requested native worker read-only control is absent');
|
|
98
|
+
}
|
|
99
|
+
if (args[0] !== 'exec')
|
|
100
|
+
throw new Error('Native worker controls could be overridden');
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
for (let index = 1; index < args.length; index++) {
|
|
103
|
+
const flag = args[index];
|
|
104
|
+
if (seen.has(flag))
|
|
105
|
+
throw new Error('Native worker controls could be overridden');
|
|
106
|
+
seen.add(flag);
|
|
107
|
+
if (['--json', '--trust-workspace', '--disable-write', '--disable-shell', '--disable-sandbox'].includes(flag))
|
|
108
|
+
continue;
|
|
109
|
+
if (!['--provider', '--model', '--reasoning-effort', '--workspace', '--approval-mode', '--approval-judge',
|
|
110
|
+
'--max-model-steps', '--output-schema', '--prompt-file'].includes(flag) || index + 1 >= args.length) {
|
|
111
|
+
throw new Error('Native worker controls could be overridden');
|
|
112
|
+
}
|
|
113
|
+
index++;
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
requirePair('-m', selected.model);
|
|
118
|
+
requirePair('-s', sandbox === 'external' ? 'danger-full-access' : sandbox);
|
|
119
|
+
if ((permissions?.approval ?? 'never') !== 'never')
|
|
120
|
+
throw new Error('Codex direct worker approval requires never');
|
|
121
|
+
for (const [name, value] of [['model_reasoning_effort', selected.effort.value], ['approval_policy', permissions?.approval ?? 'never']]) {
|
|
122
|
+
const overrides = args.flatMap((token, index) => token === '-c' ? [args[index + 1]] : [])
|
|
123
|
+
.filter(token => token?.split('=')[0] === name);
|
|
124
|
+
if (value === null || overrides.length !== 1 || overrides[0] !== `${name}=${JSON.stringify(value)}`) {
|
|
125
|
+
throw new Error(`Requested native worker control is absent or ambiguous: ${name}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Permit only the bounded native command grammar. Alternate/combined flags,
|
|
129
|
+
// profiles and additional config overrides must not undo the checked pairs.
|
|
130
|
+
if (args[0] !== '--no-daemon' || args[1] !== 'exec' || !args.includes('--ignore-user-config')) {
|
|
131
|
+
throw new Error('Native worker controls could be overridden');
|
|
132
|
+
}
|
|
133
|
+
for (let index = 2; index < args.length; index++) {
|
|
134
|
+
const flag = args[index];
|
|
135
|
+
if (['--ignore-user-config', '--skip-git-repo-check', '--json'].includes(flag))
|
|
136
|
+
continue;
|
|
137
|
+
if (flag === '-' && index === args.length - 1)
|
|
138
|
+
continue;
|
|
139
|
+
if (!['-m', '-s', '-c', '-C', '-o'].includes(flag) || index + 1 >= args.length) {
|
|
140
|
+
throw new Error('Native worker controls could be overridden');
|
|
141
|
+
}
|
|
142
|
+
const value = args[++index];
|
|
143
|
+
if (flag === '-c' && !['model_reasoning_effort', 'approval_policy'].includes(value.split('=')[0])) {
|
|
144
|
+
throw new Error('Native worker controls could be overridden');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** Inspect declared controls before installation evidence can be produced. This
|
|
149
|
+
* does not authorize invocation; consumers must use qualifyWorkerInvocationProfile. */
|
|
150
|
+
export function inspectWorkerInvocationProfile(adapter, selection) {
|
|
151
|
+
WorkerAdapterSchema.parse(adapter);
|
|
152
|
+
const profile = adapter.invocationProfiles?.[selection.profile];
|
|
153
|
+
if (adapter.qualification.status !== 'qualified' || !profile || profile.transport !== selection.transport) {
|
|
154
|
+
throw new Error('Worker transport/profile is unqualified');
|
|
155
|
+
}
|
|
156
|
+
// Shared range predicate (worker-installation.mjs): read-only surfaces
|
|
157
|
+
// (worker-direct-readiness.ts) reuse the same helper instead of
|
|
158
|
+
// re-deriving this inspector's range logic.
|
|
159
|
+
if (workerVersionRangeMismatch(selection.cliVersion, [adapter.supportedVersions, profile.supportedVersions]) !== null) {
|
|
160
|
+
throw new Error('Worker CLI version is outside qualified profile');
|
|
161
|
+
}
|
|
162
|
+
// The direct CLI baseline trusts the installed driver's bounded renderer.
|
|
163
|
+
// Explicit extra controls still require the existing qualified mappings.
|
|
164
|
+
if (hasOperatorWorkerNativeRenderer(adapter) && profile.transport === 'shell') {
|
|
165
|
+
if (selection.permissions?.sandbox && !['read-only', 'workspace-write', 'external'].includes(selection.permissions.sandbox)) {
|
|
166
|
+
throw new Error('Requested sandbox has no verified native renderer');
|
|
167
|
+
}
|
|
168
|
+
for (const name of selection.requiredControls ?? []) {
|
|
169
|
+
if (!['native', 'external'].includes(profile.controls[name]?.support)) {
|
|
170
|
+
throw new Error(`Required worker control is unqualified: ${name}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return profile;
|
|
174
|
+
}
|
|
175
|
+
const requiredControls = requiredWorkerControls(selection);
|
|
176
|
+
if (requiresWorkerExternalBoundary(adapter, selection)
|
|
177
|
+
&& (!selection.boundary || !['os', 'executor'].includes(selection.boundary.kind)
|
|
178
|
+
|| !selection.boundary.identity || !/^sha256:[a-f0-9]{64}$/.test(selection.boundary.digest)
|
|
179
|
+
|| !selection.boundary.controls?.includes('sandbox'))) {
|
|
180
|
+
throw new Error('Required worker control sandbox needs a qualified external enforcing boundary');
|
|
181
|
+
}
|
|
182
|
+
for (const name of requiredControls) {
|
|
183
|
+
if ((name === 'children' && !adapter.capabilities.nestedAgentsControllable)
|
|
184
|
+
|| (name === 'fallback' && !adapter.capabilities.nativeFallbackControllable)) {
|
|
185
|
+
throw new Error(`Required worker control is unqualified: ${name}`);
|
|
186
|
+
}
|
|
187
|
+
const control = profile.controls[name];
|
|
188
|
+
if (!control || !['native', 'external'].includes(control.support) || !control.mapping || !control.evidence) {
|
|
189
|
+
throw new Error(`Required worker control is unqualified: ${name}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (selection.transport === 'mcp' && (!selection.mcp || !profile.mcp
|
|
193
|
+
|| Object.keys(profile.mcp).some(key => profile.mcp[key]
|
|
194
|
+
!== selection.mcp[key]))) {
|
|
195
|
+
throw new Error('MCP server/tool/schema/session qualification changed');
|
|
196
|
+
}
|
|
197
|
+
return profile;
|
|
198
|
+
}
|
|
199
|
+
/** Qualification is transport/version scoped; declaration or boundary claims
|
|
200
|
+
* alone cannot qualify an external sandbox. The invocation owner rechecks the
|
|
201
|
+
* installation scope, including this boundary, against the recorded digest. */
|
|
202
|
+
export function qualifyWorkerInvocationProfile(adapter, selection) {
|
|
203
|
+
const profile = inspectWorkerInvocationProfile(adapter, selection);
|
|
204
|
+
if (isOperatorQualifiedWorkerAdapter(adapter) && selection.policy?.guards?.denySelfDelegation !== false
|
|
205
|
+
&& !adapter.capabilities.nestedAgentsControllable) {
|
|
206
|
+
throw new Error('Required worker control is unqualified: children');
|
|
207
|
+
}
|
|
208
|
+
if (requiresWorkerExternalBoundary(adapter, selection) && !profile.installationQualification) {
|
|
209
|
+
throw new Error('Required worker control sandbox needs matching installation qualification for its external boundary');
|
|
210
|
+
}
|
|
211
|
+
return profile;
|
|
212
|
+
}
|
|
213
|
+
/** Health timing evidence belongs to the exact execution profile and tuple.
|
|
214
|
+
* Fixture qualification does not become authenticated/live qualification. */
|
|
215
|
+
export function workerHealthScopeDigest(profile, scope) {
|
|
216
|
+
const { installationQualification: _installation, health, ...executionProfile } = profile;
|
|
217
|
+
const { qualification: _qualification, ...signals } = health ?? {};
|
|
218
|
+
return qualificationDigest({ profile: executionProfile, health: signals,
|
|
219
|
+
profileRef: scope.profileRef, cliVersion: scope.cliVersion, accountScope: scope.accountScope,
|
|
220
|
+
modelKey: workerModelKey(scope.inferenceProvider, scope.model), effort: scope.effort });
|
|
221
|
+
}
|
|
222
|
+
/** Reused by observation and invocation owners before using qualified health.
|
|
223
|
+
* Older profiles have no qualification record and retain degraded observation. */
|
|
224
|
+
export function assertWorkerHealthQualification(profile, scope) {
|
|
225
|
+
const evidence = profile.health?.qualification;
|
|
226
|
+
if (!evidence)
|
|
227
|
+
return;
|
|
228
|
+
if (evidence.status === 'unqualified') {
|
|
229
|
+
if (profile.health?.coverage === 'full' || profile.health?.handshake === 'semantic') {
|
|
230
|
+
throw new Error('Unqualified worker health cannot assert full or semantic coverage');
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const modelKey = workerModelKey(scope.inferenceProvider, scope.model);
|
|
235
|
+
if (evidence.cliVersion !== scope.cliVersion || evidence.accountScope !== scope.accountScope
|
|
236
|
+
|| evidence.profileRef !== scope.profileRef || evidence.modelKey !== modelKey || evidence.effort !== scope.effort
|
|
237
|
+
|| !profile.health?.qualifiedAt || !Number.isFinite(Date.parse(profile.health.qualifiedAt))
|
|
238
|
+
|| evidence.scopeDigest !== workerHealthScopeDigest(profile, scope)
|
|
239
|
+
|| (evidence.status === 'live-qualified' && evidence.authorizedLive !== true)) {
|
|
240
|
+
throw new Error('Worker health qualification scope changed or lacks live authorization');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=worker-invocation-policy.js.map
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { workerReviewLineageDiagnostics } from './worker-review-lineage.js';
|
|
1
2
|
// Resolves eligible worker tuples against current config, catalog and installation snapshots.
|
|
3
|
+
import { isFlagshipModel, flagshipElected, normalizeModelTier } from './tiers.js';
|
|
2
4
|
import { ownWorkerRoleGuidance } from './worker-selection-policy.js';
|
|
5
|
+
import { resolveWorkerCeilings } from './worker-ceilings.js';
|
|
3
6
|
import { mergeGroupWorkerPolicy } from '../config/worker-group-templates.js';
|
|
4
7
|
import { WORKER_DISPATCH_ENABLE_COMMAND, WORKER_DISPATCH_READINESS_COMMAND } from './worker-dispatch-offer.js';
|
|
5
8
|
import { resolveWorkerGroupInstallation, resolveParallelism, validateWorkerGroupQualifications, validateWorkerGroupReferences, validateWorkerBudgetEnforcement, workerActivationDigests, workerConfigDigest, } from '../config/aiwg-config.js';
|
|
6
|
-
import { qualifyWorkerInvocationProfile, validateWorkerPolicyRoute, validateWorkerModelObservation, assertWorkerLaunchMode, hasOperatorWorkerNativeRenderer,
|
|
9
|
+
import { qualifyWorkerInvocationProfile, validateWorkerPolicyRoute, validateWorkerModelObservation, assertWorkerLaunchMode, hasOperatorWorkerNativeRenderer, workerContextHeadroom, workerModelKey, isCanonicalWorkerModelKey, WorkerSelectionPolicySchema, validateWorkerCatalogDiscovery, } from './provider-policy.js';
|
|
7
10
|
import { MODEL_WRAPPERS } from './provider-models.js';
|
|
8
11
|
import { routeModelTier } from './router.js';
|
|
9
12
|
const matchesChoice = (tuple, choice) => tuple.group === choice.group && tuple.member === choice.member && tuple.model === choice.model && tuple.effort === choice.effort;
|
|
@@ -21,7 +24,7 @@ function workerCatalogDigest(catalog) {
|
|
|
21
24
|
}
|
|
22
25
|
/** Pure S1 resolution. No probing, launch, activation, admission, or writes.
|
|
23
26
|
* Recheck against freshly loaded snapshots before preparing an invocation. */
|
|
24
|
-
export function resolveWorkerPlan(snapshot, request) {
|
|
27
|
+
export function resolveWorkerPlan(snapshot, request, ceilingDefaults = {}) {
|
|
25
28
|
let parallelism = snapshot.config.parallelism ?? {};
|
|
26
29
|
const policy = snapshot.models.defaults.workerSelection;
|
|
27
30
|
const digests = {
|
|
@@ -85,16 +88,10 @@ export function resolveWorkerPlan(snapshot, request) {
|
|
|
85
88
|
if (plan.diagnostics.length)
|
|
86
89
|
return plan;
|
|
87
90
|
parallelism = resolveParallelism(parallelism, snapshot.config.providers?.[0]);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (values.some(value => !Number.isFinite(value) || value < 0 || key !== 'costUsd' && !Number.isSafeInteger(value))) {
|
|
93
|
-
block('OG_CONFIG_REFERENCE', `/request/ceilings/${key}`, 'Ceilings must be finite nonnegative values; counts must be integers');
|
|
94
|
-
}
|
|
95
|
-
else if (values.length)
|
|
96
|
-
plan.ceilings[key] = Math.min(...values);
|
|
97
|
-
}
|
|
91
|
+
const resolvedCeilings = resolveWorkerCeilings(parallelism, snapshot.constraints?.ceilings, request.ceilings, ceilingDefaults);
|
|
92
|
+
plan.ceilings = resolvedCeilings.ceilings;
|
|
93
|
+
plan.origins = { ...plan.origins, ...resolvedCeilings.origins };
|
|
94
|
+
plan.diagnostics.push(...resolvedCeilings.diagnostics);
|
|
98
95
|
if (plan.ceilings.maxAttempts === 0)
|
|
99
96
|
block('OG_BUDGET_UNMEASURABLE', '/request/ceilings/maxAttempts', 'No task attempts remain');
|
|
100
97
|
const qualifiedRoles = {};
|
|
@@ -125,10 +122,7 @@ export function resolveWorkerPlan(snapshot, request) {
|
|
|
125
122
|
block('OG_CONFIG_REFERENCE', '/request/group', 'Group is outside role guidance');
|
|
126
123
|
if ([request.maxParallel, snapshot.constraints?.maxParallel].some(value => value !== undefined && (!Number.isSafeInteger(value) || value < 0)))
|
|
127
124
|
block('OG_CONFIG_REFERENCE', '/request/maxParallel', 'Capacity ceilings must be nonnegative integers');
|
|
128
|
-
|
|
129
|
-
|| hasSelectionRelevantConflicts(request.reviewOf) || !request.reviewOf.family || request.reviewOf.family === 'unknown')) {
|
|
130
|
-
block('OG_MODEL_UNAPPROVED', '/request/reviewOf', 'Required review needs unambiguous observed source lineage');
|
|
131
|
-
}
|
|
125
|
+
plan.diagnostics.push(...workerReviewLineageDiagnostics(request));
|
|
132
126
|
const candidateGroups = request.group ? [request.group] : guidance?.groups ?? [];
|
|
133
127
|
const unqualifiedGroups = new Set(qualificationGaps.map(gapGroup));
|
|
134
128
|
const qualifiedGroups = candidateGroups.filter(groupId => !unqualifiedGroups.has(groupId));
|
|
@@ -213,8 +207,7 @@ export function resolveWorkerPlan(snapshot, request) {
|
|
|
213
207
|
reasons.push(...validateWorkerPolicyRoute({ policy: groupPolicy, role, inferenceProvider: row.llmProvider, apiModelId: row.apiModelId,
|
|
214
208
|
selectedModel: member.model, effort, identityEvidence: binding.identityEvidence, observation,
|
|
215
209
|
wrapperPin, ...(substitution ? { substitution: request.substitution } : {}), dataClass: request.dataClass, now, context: request.context }));
|
|
216
|
-
|
|
217
|
-
deny('OG_MODEL_UNAPPROVED', '/worker/family', 'Required review must use a different observed family');
|
|
210
|
+
reasons.push(...workerReviewLineageDiagnostics({ ...request, reviewer: observation }));
|
|
218
211
|
try {
|
|
219
212
|
const { adapter } = resolveWorkerGroupInstallation(parallelism, member.cli, snapshot.registry);
|
|
220
213
|
const configuredProfile = parallelism.clis?.[member.cli]?.invocationProfile;
|
|
@@ -266,9 +259,12 @@ export function resolveWorkerPlan(snapshot, request) {
|
|
|
266
259
|
deny('OG_MODEL_UNAPPROVED', '/constraints/revokedModels', 'Current model revocation');
|
|
267
260
|
if (snapshot.constraints?.unavailableMembers?.includes(`${groupId}/${member.id}`))
|
|
268
261
|
deny('OG_ADAPTER_UNQUALIFIED', pointer, 'Member is currently unavailable');
|
|
269
|
-
const
|
|
262
|
+
const flagship = normalizeModelTier(guidance.tier) === 'flagship' || isFlagshipModel(row.apiModelId);
|
|
263
|
+
if (flagship && !flagshipElected(request.authority ?? {}))
|
|
264
|
+
deny('OG_MODEL_UNAPPROVED', '/request/authority', 'Flagship models are user-elected only');
|
|
265
|
+
const premium = ['premium', 'flagship', 'max-quality'].includes(guidance.tier) || observation.pricing.costClass === 'premium';
|
|
270
266
|
// Worker candidates always invoke a model; deterministic/routine hints must not bypass premium authority.
|
|
271
|
-
const authority = routeModelTier({ ...request.authority, deterministic: false, routine: false,
|
|
267
|
+
const authority = routeModelTier({ ...request.authority, deterministic: false, routine: false, requestedTier: flagship ? 'flagship' : undefined,
|
|
272
268
|
defaultTier: guidance.tier === 'economy' ? 1 : 2, requestedPremium: premium || request.authority?.requestedPremium });
|
|
273
269
|
if (authority.requiresConfirmation)
|
|
274
270
|
deny('OG_MODEL_UNAPPROVED', '/request/authority', `${authority.rationale.join('; ')}; agents may pass --authorize-premium only after the human explicitly confirmed the premium escalation (escalation-discipline rule)`);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Shared observed-lineage validation for worker resolution and final review.
|
|
2
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
3
|
+
import addFormats from 'ajv-formats';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
let catalogValidators;
|
|
8
|
+
export function getCatalogValidators() {
|
|
9
|
+
if (!catalogValidators) {
|
|
10
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const file = [resolve(here, '../../schemas/models/model-catalog.v1.schema.json'), resolve(here, '../../../schemas/models/model-catalog.v1.schema.json')].find(existsSync);
|
|
12
|
+
if (!file)
|
|
13
|
+
throw new Error('Model catalog schema is unavailable');
|
|
14
|
+
const schema = JSON.parse(readFileSync(file, 'utf8'));
|
|
15
|
+
const ajv = addFormats(new Ajv2020({ strict: false, allErrors: true }));
|
|
16
|
+
ajv.addSchema(schema);
|
|
17
|
+
catalogValidators = { catalog: ajv.getSchema(schema.$id), observation: ajv.compile({ $ref: `${schema.$id}#/$defs/workerObservation` }), discovery: ajv.compile({ $ref: `${schema.$id}#/properties/discovery` }) };
|
|
18
|
+
}
|
|
19
|
+
return catalogValidators;
|
|
20
|
+
}
|
|
21
|
+
export function validateWorkerModelObservation(input) {
|
|
22
|
+
const validate = getCatalogValidators().observation;
|
|
23
|
+
return validate(input) ? [] : (validate.errors ?? []).map(error => `${error.instancePath}: ${error.message}`);
|
|
24
|
+
}
|
|
25
|
+
/** Optional catalog facts do not veto otherwise qualified selection. */
|
|
26
|
+
export function hasSelectionRelevantConflicts(observation) {
|
|
27
|
+
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)(?:\/|$)/;
|
|
28
|
+
return observation.conflicts?.some(conflict => relevant.test(conflict.field)) ?? false;
|
|
29
|
+
}
|
|
30
|
+
/** Same lineage rule for resolve and acceptance; CLI names never prove diversity. */
|
|
31
|
+
export function workerReviewLineageDiagnostics(input) {
|
|
32
|
+
if (!input.requireDifferentFamily)
|
|
33
|
+
return [];
|
|
34
|
+
const diagnostics = [];
|
|
35
|
+
const deny = (pointer, message) => {
|
|
36
|
+
diagnostics.push({ code: 'OG_MODEL_UNAPPROVED', pointer, message });
|
|
37
|
+
};
|
|
38
|
+
const unambiguous = (observation) => !!observation && validateWorkerModelObservation(observation).length === 0
|
|
39
|
+
&& !hasSelectionRelevantConflicts(observation) && !!observation.family && observation.family !== 'unknown';
|
|
40
|
+
if (!unambiguous(input.reviewOf)) {
|
|
41
|
+
deny('/request/reviewOf', 'Required review needs unambiguous observed source lineage');
|
|
42
|
+
}
|
|
43
|
+
// Omission here is intentional during resolver preflight. Acceptance explicitly
|
|
44
|
+
// requires a reviewer before calling this shared predicate.
|
|
45
|
+
if (input.reviewer !== undefined) {
|
|
46
|
+
if (!unambiguous(input.reviewer)) {
|
|
47
|
+
deny('/worker/family', 'Required review needs unambiguous observed reviewer lineage');
|
|
48
|
+
}
|
|
49
|
+
else if (input.reviewer.family === input.reviewOf?.family) {
|
|
50
|
+
deny('/worker/family', 'Required review must use a different observed family');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return diagnostics;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=worker-review-lineage.js.map
|
|
@@ -14,7 +14,7 @@ const baseRoles = ['efficiency', 'coding', 'reasoning'];
|
|
|
14
14
|
const WorkerRoleSchema = z.strictObject({
|
|
15
15
|
baseRole: z.enum(baseRoles).optional(),
|
|
16
16
|
groups: uniqueStrings.refine(values => values.length > 0),
|
|
17
|
-
tier: z.enum(['economy', 'standard', 'premium', 'max-quality']),
|
|
17
|
+
tier: z.enum(['economy', 'standard', 'premium', 'flagship', 'max-quality']),
|
|
18
18
|
initialEffort: z.string().min(1).nullable(),
|
|
19
19
|
effortLadder: uniqueStrings,
|
|
20
20
|
escalateAfter: integer().positive().optional(),
|
|
@@ -5,7 +5,7 @@ import { resolveWorkerPlan } from './resolver.js';
|
|
|
5
5
|
function roleForTier(tier) {
|
|
6
6
|
if (tier === 'economy')
|
|
7
7
|
return 'efficiency';
|
|
8
|
-
if (tier === 'premium' || tier === 'max-quality')
|
|
8
|
+
if (tier === 'premium' || tier === 'flagship' || tier === 'max-quality')
|
|
9
9
|
return 'reasoning';
|
|
10
10
|
if (tier === 'standard')
|
|
11
11
|
return 'coding';
|
|
@@ -14,7 +14,7 @@ function roleForTier(tier) {
|
|
|
14
14
|
export function buildWrapperRouteEnvelope(request) {
|
|
15
15
|
if (!request.assignment.trim())
|
|
16
16
|
throw new Error('Wrapper routing requires a bounded assignment.');
|
|
17
|
-
const decision = routeModelTier(request);
|
|
17
|
+
const decision = routeModelTier({ ...request, agentName: request.agentName ?? request.capability.name });
|
|
18
18
|
if (request.worker?.snapshot.config.parallelism?.worker_dispatch && !request.deterministic) {
|
|
19
19
|
const worker = resolveWorkerPlan(request.worker.snapshot, {
|
|
20
20
|
...request.worker.request,
|
|
@@ -39,7 +39,10 @@ export function buildWrapperRouteEnvelope(request) {
|
|
|
39
39
|
? compileModelPolicy({
|
|
40
40
|
provider: request.provider,
|
|
41
41
|
artifact: 'agent',
|
|
42
|
-
policy: { role, tier
|
|
42
|
+
policy: { role, tier },
|
|
43
|
+
election: request.election,
|
|
44
|
+
flagshipAllowlist: request.flagshipAllowlist,
|
|
45
|
+
agentName: request.agentName ?? request.capability.name,
|
|
43
46
|
...(request.catalog ? { catalog: request.catalog } : {}),
|
|
44
47
|
})
|
|
45
48
|
: null;
|