@aiwg/cli 2026.10.11 → 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-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 +90 -17
- 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 +6 -24
- 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/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/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-catalog.v1.json +62 -12
- package/dist/src/models/policy-resources.js +28 -0
- package/dist/src/models/provider-policy.js +10 -546
- 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 +1 -0
- 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/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/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 +10 -0
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/discovery-capability.js +38 -0
- package/dist/src/sessions/importer.js +8 -251
- package/dist/src/sessions/index.js +4 -0
- package/dist/src/sessions/intake-dir.js +71 -0
- package/dist/src/sessions/normalization.js +251 -0
- package/dist/src/sessions/tail.js +91 -0
- package/dist/src/sessions/workspace-discovery.js +83 -7
- 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 +1 -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/vscode-extension/schemas/aiwg.config.v1.json +177 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Records a credential-free decision artifact through existing effect intent/reconcile receipts.
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { projectAiwgWritePath, resolveProjectAiwgDirForWrite } from '../config/project-artifacts.js';
|
|
5
|
+
import { recordIntent, reconcileEffect } from '../effects/ledger.js';
|
|
6
|
+
import { payloadDigest } from '../effects/identity.js';
|
|
7
|
+
import { assertDigestOnly } from '../effects/redaction.js';
|
|
8
|
+
import { fileDigestVerifier } from '../effects/verifiers/file.js';
|
|
9
|
+
import { createVerifierRegistry } from '../effects/verifiers/registry.js';
|
|
10
|
+
import { hitlDigest, resolveHitlAuthorization, validHitlAction } from './hitl.js';
|
|
11
|
+
import { HITL_CATEGORIES, HITL_ENVIRONMENTS } from './hitl-types.js';
|
|
12
|
+
/** Resolve again at receipt time. A preview result supplied by an agent is never trusted. */
|
|
13
|
+
export async function recordHitlAuthorization(ledger, action, context) {
|
|
14
|
+
const resolution = resolveHitlAuthorization(action, context);
|
|
15
|
+
const receipt = {
|
|
16
|
+
kind: 'hitl-authorization', evaluatedAtDigest: hitlDigest(context.now),
|
|
17
|
+
sessionGrantDigest: hitlDigest(context.sessionAuthorization ?? null), ...resolution,
|
|
18
|
+
// Arbitrary user strings become digests, including paths, repo identity and profile names.
|
|
19
|
+
matchedProfile: resolution.matchedProfile ? hitlDigest(resolution.matchedProfile) : null,
|
|
20
|
+
action: validHitlAction(action) ? {
|
|
21
|
+
category: HITL_CATEGORIES.find(category => category === action.category) ?? 'restricted',
|
|
22
|
+
descriptorDigest: hitlDigest(action), categoryDigest: hitlDigest(action.category),
|
|
23
|
+
reversible: action.reversible, impact: action.impact,
|
|
24
|
+
} : { descriptorDigest: hitlDigest(action) },
|
|
25
|
+
scope: validHitlAction(action) ? {
|
|
26
|
+
environmentClass: HITL_ENVIRONMENTS.find(environment => environment === action.environment) ?? 'restricted',
|
|
27
|
+
repoDigest: hitlDigest(action.repo), environmentDigest: hitlDigest(action.environment),
|
|
28
|
+
resourceDigests: action.resources.map(hitlDigest),
|
|
29
|
+
} : null,
|
|
30
|
+
origins: context.layers.map(layer => ({ sourceDigest: hitlDigest(layer.source), policyDigest: hitlDigest(layer.policy ?? null) })),
|
|
31
|
+
};
|
|
32
|
+
assertDigestOnly(receipt);
|
|
33
|
+
const bytes = JSON.stringify(receipt) + '\n';
|
|
34
|
+
const digest = payloadDigest(bytes);
|
|
35
|
+
const root = resolveProjectAiwgDirForWrite(ledger.projectDir);
|
|
36
|
+
const relativePath = join('effects', 'authorization', `${digest.slice(7)}.json`);
|
|
37
|
+
const file = projectAiwgWritePath(ledger.projectDir, relativePath);
|
|
38
|
+
await mkdir(dirname(file), { recursive: true, mode: 0o700 });
|
|
39
|
+
try {
|
|
40
|
+
await writeFile(file, bytes, { flag: 'wx', mode: 0o600 });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code !== 'EEXIST')
|
|
44
|
+
throw error;
|
|
45
|
+
if (await readFile(file, 'utf8') !== bytes)
|
|
46
|
+
throw new Error('HITL receipt identity conflict');
|
|
47
|
+
}
|
|
48
|
+
const intent = await recordIntent(ledger, {
|
|
49
|
+
kind: 'file.digest', target: `file:${file}@${digest}`, payloadDigest: digest,
|
|
50
|
+
context: { policyVersion: resolution.policyVersion, decision: resolution.decision, provenance: resolution.provenance, receiptDigest: digest },
|
|
51
|
+
});
|
|
52
|
+
const reconciled = await reconcileEffect(ledger, intent.effectId, {
|
|
53
|
+
verifiers: createVerifierRegistry([fileDigestVerifier({ root })]),
|
|
54
|
+
});
|
|
55
|
+
return { resolution, file, intent, reconciled };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=hitl-receipt.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Defines bounded HITL grants and trusted resolver inputs, separate from RBAC.
|
|
2
|
+
export const HITL_CATEGORIES = ['repo.read', 'repo.edit', 'docs.edit', 'test.run', 'testnet.configure'];
|
|
3
|
+
export const HITL_ENVIRONMENTS = ['local', 'development', 'test', 'testnet'];
|
|
4
|
+
//# sourceMappingURL=hitl-types.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Validates HITL with the existing config schema definitions, shared by loader and resolver.
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import Ajv from 'ajv';
|
|
4
|
+
import addFormats from 'ajv-formats';
|
|
5
|
+
function compiled() {
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
let schema;
|
|
8
|
+
// Source and compiled modules share the shipped config schema; no cwd or home lookup.
|
|
9
|
+
for (const relative of ['../../vscode-extension/schemas/aiwg.config.v1.json',
|
|
10
|
+
'../../../vscode-extension/schemas/aiwg.config.v1.json']) {
|
|
11
|
+
try {
|
|
12
|
+
schema = require(relative);
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error?.code !== 'MODULE_NOT_FOUND')
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (!schema)
|
|
21
|
+
throw new Error('HITL config schema unavailable');
|
|
22
|
+
const ajv = new Ajv({ strict: true, allErrors: true });
|
|
23
|
+
addFormats(ajv);
|
|
24
|
+
return ajv.compile({
|
|
25
|
+
$ref: '#/definitions/HitlPolicy',
|
|
26
|
+
definitions: Object.fromEntries(['HitlPolicy', 'HitlProfile', 'HitlSelector'].map(key => [key, schema.definitions[key]])),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// Compile at module initialization; resolving an action performs no filesystem I/O.
|
|
30
|
+
const validator = compiled();
|
|
31
|
+
export function validateHitlPolicy(value) {
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return [];
|
|
34
|
+
if (validator(value))
|
|
35
|
+
return [];
|
|
36
|
+
// Never echo supplied values, which may contain credentials or injected text.
|
|
37
|
+
return ['authorization.hitl: invalid bounded policy (scope, categories, expiry or unknown fields)'];
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=hitl-validation.js.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Resolves bounded HITL authorization without I/O, clocks, or prompt-text authority.
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { HITL_CATEGORIES, HITL_ENVIRONMENTS } from './hitl-types.js';
|
|
4
|
+
import { validateHitlPolicy } from './hitl-validation.js';
|
|
5
|
+
export function hitlDigest(value) {
|
|
6
|
+
const canonical = (v) => Array.isArray(v) ? v.map(canonical)
|
|
7
|
+
: v && typeof v === 'object' ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== undefined)
|
|
8
|
+
.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, x]) => [k, canonical(x)])) : v;
|
|
9
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex')}`;
|
|
10
|
+
}
|
|
11
|
+
export const hitlPolicyVersion = (layers) => hitlDigest(layers);
|
|
12
|
+
const identifier = /^[A-Za-z0-9][A-Za-z0-9_.:/@-]{0,255}$/;
|
|
13
|
+
const pathValid = (path) => path.length > 0 && path.length <= 512 && !path.startsWith('/') && !path.includes(':')
|
|
14
|
+
&& !/[\\\x00-\x20*?\[\]{}!]/.test(path) && path.split('/').every(p => p !== '.' && p !== '..' && p !== '');
|
|
15
|
+
export function validHitlAction(value) {
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
17
|
+
return false;
|
|
18
|
+
return actionFieldsValid(value);
|
|
19
|
+
}
|
|
20
|
+
function actionFieldsValid(a) {
|
|
21
|
+
const keys = ['category', 'repo', 'environment', 'resources', 'reversible', 'impact'];
|
|
22
|
+
return Object.keys(a).length === keys.length && keys.every(k => Object.hasOwn(a, k))
|
|
23
|
+
&& [a.category, a.repo, a.environment].every(x => typeof x === 'string' && identifier.test(x))
|
|
24
|
+
&& Array.isArray(a.resources) && a.resources.length > 0 && a.resources.every(p => typeof p === 'string' && pathValid(p))
|
|
25
|
+
&& typeof a.reversible === 'boolean' && ['low', 'medium', 'high'].includes(a.impact);
|
|
26
|
+
}
|
|
27
|
+
/** Deliberately small glob language: * within a segment; ** crosses directories; ? one character. */
|
|
28
|
+
function glob(pattern, path) {
|
|
29
|
+
// Memoized matching bounds work by pattern length × path length, without regex backtracking.
|
|
30
|
+
const memo = new Map();
|
|
31
|
+
const match = (i, j) => {
|
|
32
|
+
const key = `${i}:${j}`;
|
|
33
|
+
if (memo.has(key))
|
|
34
|
+
return memo.get(key);
|
|
35
|
+
let result;
|
|
36
|
+
if (i === pattern.length)
|
|
37
|
+
result = j === path.length;
|
|
38
|
+
else if (pattern[i] === '*' && pattern[i + 1] === '*') {
|
|
39
|
+
if (pattern[i + 2] === '/') {
|
|
40
|
+
const slash = path.indexOf('/', j);
|
|
41
|
+
result = match(i + 3, j) || (slash >= 0 && match(i, slash + 1));
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
result = match(i + 2, j) || (j < path.length && match(i, j + 1));
|
|
45
|
+
}
|
|
46
|
+
else if (pattern[i] === '*')
|
|
47
|
+
result = match(i + 1, j)
|
|
48
|
+
|| (j < path.length && path[j] !== '/' && match(i, j + 1));
|
|
49
|
+
else
|
|
50
|
+
result = j < path.length && (pattern[i] === path[j] || (pattern[i] === '?' && path[j] !== '/'))
|
|
51
|
+
&& match(i + 1, j + 1);
|
|
52
|
+
memo.set(key, result);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
return match(0, 0);
|
|
56
|
+
}
|
|
57
|
+
function selected(s, a) {
|
|
58
|
+
return (!s.categories || s.categories.includes(a.category)) && (!s.repos || s.repos.includes(a.repo))
|
|
59
|
+
&& (!s.environments || s.environments.includes(a.environment))
|
|
60
|
+
&& (!s.resources || a.resources.some(p => s.resources.some(g => glob(g, p))));
|
|
61
|
+
}
|
|
62
|
+
function grant(p, a, now) {
|
|
63
|
+
return now < Date.parse(p.expiresAt) && p.reversible === true && a.reversible
|
|
64
|
+
&& p.categories.some(c => c === a.category) && p.repos.includes(a.repo)
|
|
65
|
+
&& p.environments.some(e => e === a.environment)
|
|
66
|
+
&& a.resources.every(path => p.resources.some(g => glob(g, path)))
|
|
67
|
+
&& (a.impact === 'low' || (a.impact === 'medium' && p.maxImpact === 'medium'));
|
|
68
|
+
}
|
|
69
|
+
export function resolveHitlAuthorization(action, context) {
|
|
70
|
+
const policyVersion = hitlPolicyVersion(context.layers);
|
|
71
|
+
const result = (decision, reason, provenance, matchedProfile = null) => ({ decision, reason, policyVersion, matchedProfile, provenance });
|
|
72
|
+
if (context.platform)
|
|
73
|
+
return result(context.platform === 'deny' ? 'deny' : 'escalate', 'platform-constraint', 'platform');
|
|
74
|
+
if (!validHitlAction(action))
|
|
75
|
+
return result('escalate', 'invalid-action-descriptor', 'default');
|
|
76
|
+
if (!HITL_CATEGORIES.some(c => c === action.category) || !HITL_ENVIRONMENTS.some(e => e === action.environment)
|
|
77
|
+
|| !action.reversible || action.impact === 'high')
|
|
78
|
+
return result('escalate', 'safety-category-requires-explicit-review', 'platform');
|
|
79
|
+
// Revalidate even when invoked directly with objects that bypassed the config loader.
|
|
80
|
+
if (context.layers.some(l => validateHitlPolicy(l.policy).length))
|
|
81
|
+
return result('escalate', 'invalid-policy', 'default');
|
|
82
|
+
if (context.layers.some(l => l.policy?.deny.some(s => selected(s, action))))
|
|
83
|
+
return result('deny', 'explicit-deny', 'explicit-deny');
|
|
84
|
+
const now = Date.parse(context.now);
|
|
85
|
+
if (!Number.isFinite(now))
|
|
86
|
+
return result('escalate', 'invalid-clock', 'default');
|
|
87
|
+
const session = context.sessionAuthorization;
|
|
88
|
+
if (session?.source === 'session-user') {
|
|
89
|
+
const probe = { version: '1', enabled: true, reviewedAt: context.now, maxAgeSeconds: 1,
|
|
90
|
+
expiresAt: session.profile.expiresAt, profiles: { session: session.profile }, deny: [], requireReview: [] };
|
|
91
|
+
if (!validateHitlPolicy(probe).length && grant(session.profile, action, now))
|
|
92
|
+
return result('auto-authorized', 'session-user-authorization', 'session-user', 'session');
|
|
93
|
+
}
|
|
94
|
+
if (context.expectedPolicyVersion && policyVersion !== context.expectedPolicyVersion)
|
|
95
|
+
return result('escalate', 'stale-policy-version', 'project-policy');
|
|
96
|
+
if (!context.layers.length)
|
|
97
|
+
return result('escalate', 'no-project-policy', 'default');
|
|
98
|
+
let matched = null;
|
|
99
|
+
for (const layer of context.layers) {
|
|
100
|
+
const p = layer.policy;
|
|
101
|
+
if (!p?.enabled)
|
|
102
|
+
return result('escalate', p ? 'policy-revoked' : 'missing-member-policy', 'project-policy');
|
|
103
|
+
const reviewed = Date.parse(p.reviewedAt);
|
|
104
|
+
if (now >= Date.parse(p.expiresAt))
|
|
105
|
+
return result('escalate', 'policy-expired', 'project-policy');
|
|
106
|
+
if (reviewed > now || now - reviewed >= p.maxAgeSeconds * 1000)
|
|
107
|
+
return result('escalate', 'stale-policy-review', 'project-policy');
|
|
108
|
+
if (p.requireReview.some(s => selected(s, action)))
|
|
109
|
+
return result('escalate', 'policy-requires-review', 'project-policy');
|
|
110
|
+
const entry = Object.entries(p.profiles).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).find(([, profile]) => grant(profile, action, now));
|
|
111
|
+
if (!entry)
|
|
112
|
+
return result('escalate', 'outside-profile-or-expired', 'project-policy');
|
|
113
|
+
matched = entry[0];
|
|
114
|
+
}
|
|
115
|
+
return result('auto-authorized', 'bounded-project-policy', 'project-policy', matched);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=hitl.js.map
|
|
@@ -7,7 +7,7 @@ export interface RuleFrontmatter extends Record<string, unknown> {
|
|
|
7
7
|
summary?: string;
|
|
8
8
|
bound_to: RuleBindings;
|
|
9
9
|
paths?: string[];
|
|
10
|
-
enforcedBy?:
|
|
10
|
+
enforcedBy?: string[];
|
|
11
11
|
}
|
|
12
12
|
export interface RuleDiagnostic { severity: 'error' | 'warn' | 'info'; code: string; message: string }
|
|
13
13
|
export type RuleValidationResult =
|
|
@@ -44,8 +44,6 @@ export function validateRuleFrontmatter(data) {
|
|
|
44
44
|
}
|
|
45
45
|
if (!tagged) diagnostics.push({ severity: 'warn', code: 'missing-activation',
|
|
46
46
|
message: `Missing activation; inferred ${inferRuleActivation(data.enforcement)} delivery (migration fallback); add explicit activation frontmatter.` });
|
|
47
|
-
if (Object.hasOwn(data, 'enforcedBy')) diagnostics.push({ severity: 'info', code: 'reserved-enforcedBy',
|
|
48
|
-
message: 'enforcedBy is opaque reserved metadata (#2839); it has no effect.' });
|
|
49
47
|
const valid = !diagnostics.some(item => item.severity === 'error');
|
|
50
48
|
return {
|
|
51
49
|
valid, diagnostics,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Read rule sources and report declared gate coverage without executing gates. */
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { buildGatesRegistry, installedAiwgRoot } from '../gates/driver.js';
|
|
5
|
+
import { parseRuleFrontmatter } from './activation.mjs';
|
|
6
|
+
import { ruleFiles } from './pin-state.js';
|
|
7
|
+
import { ruleGateReferenceErrors } from './gate-references.js';
|
|
8
|
+
function bundleRuleDirs(root) {
|
|
9
|
+
if (!existsSync(root))
|
|
10
|
+
return [];
|
|
11
|
+
return readdirSync(root, { withFileTypes: true }).filter(entry => entry.isDirectory())
|
|
12
|
+
.map(entry => path.join(root, entry.name, 'rules'));
|
|
13
|
+
}
|
|
14
|
+
export function listRuleGateCoverage(options = {}, ruleId) {
|
|
15
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
16
|
+
const root = path.resolve(options.aiwgRoot ?? installedAiwgRoot());
|
|
17
|
+
const dirs = [path.join(cwd, 'rules'), path.join(cwd, '.aiwg/rules')];
|
|
18
|
+
for (const group of ['frameworks', 'addons', 'extensions']) {
|
|
19
|
+
dirs.push(...bundleRuleDirs(path.join(root, 'agentic/code', group)), ...bundleRuleDirs(path.join(cwd, '.aiwg', group)));
|
|
20
|
+
}
|
|
21
|
+
for (const candidate of options.packDirs ?? []) {
|
|
22
|
+
const dir = path.resolve(cwd, candidate);
|
|
23
|
+
if (statSync(dir).isDirectory())
|
|
24
|
+
dirs.push(path.join(dir, 'rules'));
|
|
25
|
+
}
|
|
26
|
+
const files = [...new Set(dirs.flatMap(ruleFiles))].sort().filter(file => !/^(?:README|RULES-INDEX|RULES-ONDEMAND)\.md$/.test(path.basename(file)) && !file.endsWith('.core.md'));
|
|
27
|
+
const { registry } = buildGatesRegistry(options);
|
|
28
|
+
const rules = [];
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const name = path.basename(file, '.md');
|
|
31
|
+
const id = path.relative(file.startsWith(`${root}${path.sep}`) ? root : cwd, file).split(path.sep).join('/');
|
|
32
|
+
// Match exact names or source identities; suffix guesses can select another rule.
|
|
33
|
+
if (ruleId && ![name, id, file].includes(ruleId))
|
|
34
|
+
continue;
|
|
35
|
+
const parsed = parseRuleFrontmatter(readFileSync(file, 'utf8'));
|
|
36
|
+
if (!parsed.valid)
|
|
37
|
+
throw new Error(`${file}: ${parsed.diagnostics.map(item => item.message).join('; ')}`);
|
|
38
|
+
const enforcedBy = parsed.rule.enforcedBy ?? [];
|
|
39
|
+
const errors = ruleGateReferenceErrors(enforcedBy, registry);
|
|
40
|
+
if (errors.length)
|
|
41
|
+
throw new Error(`${file}: ${errors.join('; ')}`);
|
|
42
|
+
rules.push({ id, name, file, enforcedBy, status: enforcedBy.length ? 'gate-backed' : 'guidance-only' });
|
|
43
|
+
}
|
|
44
|
+
return rules;
|
|
45
|
+
}
|
|
46
|
+
export function formatRuleGateCoverage(rules) {
|
|
47
|
+
if (!rules.length)
|
|
48
|
+
return 'No rules found.';
|
|
49
|
+
return rules.map(rule => `${rule.id}: ${rule.status}${rule.enforcedBy.length ? `\n${rule.enforcedBy.map(gate => ` ${gate}`).join('\n')}` : ''}`).join('\n');
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=gate-coverage.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function ruleGateReferenceErrors(references, registry) {
|
|
2
|
+
const errors = [];
|
|
3
|
+
for (const reference of references) {
|
|
4
|
+
const [packId, gateId] = reference.split('#');
|
|
5
|
+
try {
|
|
6
|
+
const pack = registry.resolvePack(packId).resolved;
|
|
7
|
+
if (!pack.spec.gates.some(gate => gate.id === gateId))
|
|
8
|
+
throw new Error(`unknown gate '${gateId}' in ${packId}`);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
errors.push(`enforcedBy '${reference}': ${error instanceof Error ? error.message : String(error)}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return errors;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=gate-references.js.map
|
|
@@ -66,7 +66,13 @@
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
"enforcedBy": {
|
|
69
|
-
"description": "
|
|
69
|
+
"description": "Gate references as <namespaced-pack-id>#<gate-id>. Lint resolves each reference against the gate registry. Declares coverage without changing activation or executing gates.",
|
|
70
|
+
"type": "array",
|
|
71
|
+
"uniqueItems": true,
|
|
72
|
+
"items": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"pattern": "^(?:(?:aiwg|framework|addon|extension):[^/\\s:#]+/[^/\\s:#]+|project:[^/\\s:#]+)#[^\\s#]+$"
|
|
75
|
+
}
|
|
70
76
|
}
|
|
71
77
|
},
|
|
72
78
|
"allOf": [
|
|
@@ -6,14 +6,18 @@ export class WorkerRequestArgumentError extends WorkerPolicyError {
|
|
|
6
6
|
constructor(key) {
|
|
7
7
|
super('OG_CONFIG_REFERENCE', key === 'task'
|
|
8
8
|
? `Missing required --task. Valid task classes: ${Object.keys(DEFAULT_WORKER_SELECTION.taskClasses).join(', ')}, plus classes configured in defaults.workerSelection.taskClasses.`
|
|
9
|
-
:
|
|
10
|
-
|
|
9
|
+
: key === 'wallSeconds' ? '--timeout-ms must be a positive safe integer multiple of 1000 (whole seconds).'
|
|
10
|
+
: key === 'maxAttempts' || key === 'tokens' ? `--${key === 'tokens' ? 'max-output-tokens' : 'max-attempts'} must be a positive safe integer.`
|
|
11
|
+
: key === 'costUsd' ? '--max-usd must be a finite nonnegative number.'
|
|
12
|
+
: `--${key.replace('_', '-')} must be an absolute path; relative paths are not resolved against cwd or --workdir.`);
|
|
13
|
+
this.pointer = `/request/${['maxAttempts', 'tokens', 'costUsd', 'wallSeconds'].includes(key) ? 'ceilings/' : ''}${key}`;
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
/** Reject unsupported syntax before discovery or any path reads. */
|
|
14
17
|
export function parseWorkerCommandArgs(args) {
|
|
15
18
|
const names = { role: 'role', task: 'task', group: 'group', brief: 'brief',
|
|
16
|
-
workdir: 'workdir', 'final-output': 'final_output', transport: 'transport', format: 'format'
|
|
19
|
+
workdir: 'workdir', 'final-output': 'final_output', transport: 'transport', format: 'format',
|
|
20
|
+
'max-attempts': 'maxAttempts', 'timeout-ms': 'wallSeconds', 'max-output-tokens': 'tokens', 'max-usd': 'costUsd' };
|
|
17
21
|
const result = {};
|
|
18
22
|
let json = false;
|
|
19
23
|
let premiumAuthorized = false;
|
|
@@ -63,6 +67,19 @@ export function parseWorkerCommandArgs(args) {
|
|
|
63
67
|
throw new Error('Unsupported transport');
|
|
64
68
|
if (result.format && !['shell', 'json'].includes(result.format))
|
|
65
69
|
throw new Error('Unsupported renderer; use POSIX shell or JSON');
|
|
66
|
-
|
|
70
|
+
const ceilings = {};
|
|
71
|
+
for (const key of ['maxAttempts', 'wallSeconds', 'tokens', 'costUsd']) {
|
|
72
|
+
if (result[key] === undefined)
|
|
73
|
+
continue;
|
|
74
|
+
const value = Number(result[key]);
|
|
75
|
+
if (!result[key].trim() || !Number.isFinite(value) || value < 0
|
|
76
|
+
|| key !== 'costUsd' && (!Number.isSafeInteger(value) || value === 0)
|
|
77
|
+
|| key === 'wallSeconds' && value % 1000 !== 0)
|
|
78
|
+
throw new WorkerRequestArgumentError(key);
|
|
79
|
+
ceilings[key] = key === 'wallSeconds' ? value / 1000 : value;
|
|
80
|
+
delete result[key];
|
|
81
|
+
}
|
|
82
|
+
return { ...result, ...(Object.keys(ceilings).length ? { ceilings } : {}),
|
|
83
|
+
...(premiumAuthorized ? { authority: { premiumAuthorized: true, maxAutoTier: 3 } } : {}) };
|
|
67
84
|
}
|
|
68
85
|
//# sourceMappingURL=worker-command-request.js.map
|
|
@@ -104,7 +104,9 @@ export async function prepareWorkerInvocation(invocation, qualification) {
|
|
|
104
104
|
// The frozen 'closed' value means empty stdin (immediate EOF). 'brief-json'
|
|
105
105
|
// pipes a conductor-built params document framed from the brief file
|
|
106
106
|
// (worker-stdin-frame.mjs), so brief text never enters any argv.
|
|
107
|
-
const
|
|
107
|
+
const deadline = qualification.ceilings?.wallSeconds === undefined ? ''
|
|
108
|
+
: `timeout --signal=KILL ${quote(`${Math.min(result.health?.hardTimeoutSeconds ?? Infinity, result.limits.wallSeconds)}s`)} `;
|
|
109
|
+
const worker = `${deadline}env -i${environment ? ` ${environment}` : ''} ${result.argv.map(quote).join(' ')}`;
|
|
108
110
|
const outputs = `> ${quote(result.stdoutPath)} 2> ${quote(result.stderrPath)}`;
|
|
109
111
|
if (result.stdin === 'brief-json') {
|
|
110
112
|
const framer = fileURLToPath(new URL('./worker-stdin-frame.mjs', import.meta.url));
|
|
@@ -7,6 +7,7 @@ export { inspectWorkerPath } from './worker-invocation-paths.js';
|
|
|
7
7
|
import { createHash, randomUUID } from 'node:crypto';
|
|
8
8
|
import { resolveWorkerPlan } from '../models/resolver.js';
|
|
9
9
|
import { workerConfigDigest, validateWorkerHealthConfig } from '../config/aiwg-config.js';
|
|
10
|
+
import { intersectWorkerCeilings } from '../models/worker-ceilings.js';
|
|
10
11
|
import { assertValidWorkerPayload } from '../mission-protocol/codecs.js';
|
|
11
12
|
import { qualificationDigest } from '../models/qualification-digest.mjs';
|
|
12
13
|
import { prepareWorkerInvocation } from './worker-invocation-preparation.js';
|
|
@@ -180,19 +181,19 @@ export async function resolveWorkerInvocation(snapshot, request, drivers, now =
|
|
|
180
181
|
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Brief role/task/group/workspace/output differs from request');
|
|
181
182
|
}
|
|
182
183
|
}
|
|
184
|
+
// Brief and parent authority restrict caller ceilings; neither can overwrite them.
|
|
185
|
+
const intent = managedIntent ?? brief?.intent;
|
|
186
|
+
if (intent)
|
|
187
|
+
snapshot.constraints = { ...snapshot.constraints, ceilings: intersectWorkerCeilings(snapshot.constraints?.ceilings, { ...intent.limits, costUsd: intent.limits.costUsd ?? undefined }) };
|
|
183
188
|
plan = resolveWorkerPlan(snapshot, { role: request.role, taskClass: request.task, group: request.group,
|
|
184
189
|
transport: request.transport, authority: request.authority, now, expectedDigests: expected?.digests,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
} } : {}),
|
|
189
|
-
...(managedIntent ? { dataClass: managedIntent.dataClass, member: managedIntent.pins.member,
|
|
190
|
-
model: managedIntent.pins.model, effort: managedIntent.pins.effort?.value,
|
|
191
|
-
ceilings: { ...managedIntent.limits, costUsd: managedIntent.limits.costUsd ?? undefined } } : {}),
|
|
190
|
+
ceilings: request.ceilings,
|
|
191
|
+
...(intent ? { dataClass: intent.dataClass, member: intent.pins.member,
|
|
192
|
+
model: intent.pins.model, effort: intent.pins.effort?.value } : {}),
|
|
192
193
|
context: { estimator: 'utf8-byte-upper-bound-v1', briefTokens: bytes?.length ?? 0,
|
|
193
194
|
inputTokens: 0, toolOutputAllowance: 1024, reservedOutput: 1024 },
|
|
194
195
|
requiredControls: snapshot.config.parallelism?.health?.requireStepCap ? ['stepCap'] : [],
|
|
195
|
-
});
|
|
196
|
+
}, { tokens: 50000, wallSeconds: 900 });
|
|
196
197
|
plan.request = structuredClone(request);
|
|
197
198
|
if (!plan.selected)
|
|
198
199
|
return plan;
|
|
@@ -246,8 +247,8 @@ export async function resolveWorkerInvocation(snapshot, request, drivers, now =
|
|
|
246
247
|
if (!await inspectPath(path.dirname(output), 'directory'))
|
|
247
248
|
outputParentsExist = false;
|
|
248
249
|
}
|
|
249
|
-
const limits = { tokens:
|
|
250
|
-
wallSeconds:
|
|
250
|
+
const limits = { tokens: plan.ceilings.tokens, costUsd: plan.ceilings.costUsd ?? null,
|
|
251
|
+
wallSeconds: plan.ceilings.wallSeconds, maxAttempts: plan.ceilings.maxAttempts };
|
|
251
252
|
const observation = snapshot.catalog.discovery.instances[tuple.cli].models.find(row => row.id === tuple.model || row.apiModelId === tuple.model).worker;
|
|
252
253
|
const profile = adapter.invocationProfiles[tuple.profile];
|
|
253
254
|
const expiresAt = new Date(Math.min(Date.parse(now) + 300000, Date.parse(tuple.observedAt) + (plan.policy?.freshness?.blockHours ?? DEFAULT_WORKER_SELECTION.freshness.blockHours) * 3600000, Math.min(Date.parse(tuple.verifiedAt), Date.parse(adapter.qualification.verifiedAt), profile.installationQualification ? Date.parse(profile.installationQualification.verifiedAt) : Infinity)
|
|
@@ -290,6 +291,15 @@ export async function resolveWorkerInvocation(snapshot, request, drivers, now =
|
|
|
290
291
|
throw new Error('Installed adapter stdin framer is required');
|
|
291
292
|
if (transport.mcp?.timeoutSeconds !== undefined)
|
|
292
293
|
effectiveHealth = resolveWorkerEffectiveHealth(healthConfig, { ...healthInput, transportSeconds: Math.min(healthInput.transportSeconds ?? Infinity, transport.mcp.timeoutSeconds) });
|
|
294
|
+
if (request.ceilings?.wallSeconds !== undefined && request.ceilings.wallSeconds > effectiveHealth.hardTimeoutSeconds) {
|
|
295
|
+
plan.status = 'blocked';
|
|
296
|
+
plan.selected = null;
|
|
297
|
+
plan.diagnostics.push({ code: 'OG_POLICY_RELAXATION', pointer: '/request/ceilings/wallSeconds',
|
|
298
|
+
message: `Requested wallSeconds ceiling ${request.ceilings.wallSeconds} exceeds resolved policy ceiling ${effectiveHealth.hardTimeoutSeconds}.` });
|
|
299
|
+
return plan;
|
|
300
|
+
}
|
|
301
|
+
if (effectiveHealth.hardTimeoutSeconds === limits.wallSeconds)
|
|
302
|
+
effectiveHealth.origins?.hardTimeoutSeconds.push(...(plan.origins?.['/ceilings/wallSeconds'] ?? []).map(origin => origin.pointer));
|
|
293
303
|
if (workerConfigDigest(driver.qualification) !== qualificationDigest)
|
|
294
304
|
throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Installation profile digest changed during resolution');
|
|
295
305
|
const invocation = { version: 1, mode: managedIntent ? 'managed' : 'direct', transport: tuple.transport, selected,
|
|
@@ -306,6 +316,7 @@ export async function resolveWorkerInvocation(snapshot, request, drivers, now =
|
|
|
306
316
|
controlEvidence: isOperatorQualifiedWorkerAdapter(adapter) ? [adapter.qualification.evidenceRef, ...controls.map(name => profile.controls[name].evidence)] : controls.map(name => profile.controls[name].evidence),
|
|
307
317
|
health: effectiveHealth, };
|
|
308
318
|
const invocationQualification = { ...qualification, now: Date.parse(now),
|
|
319
|
+
ceilings: request.ceilings,
|
|
309
320
|
policy: plan.policy, permissions: tuple.permissions, role: request.role, modelObservation: observation,
|
|
310
321
|
expectedDigests: { policyDigest, catalogDigest: plan.digests.catalogDigest, profileDigest: plan.digests.profileDigest, briefDigest },
|
|
311
322
|
requiredControls: controls };
|
|
@@ -25,6 +25,7 @@ export class DeepSeekHarnessSessionAdapter {
|
|
|
25
25
|
limits;
|
|
26
26
|
maxFiles;
|
|
27
27
|
provider = 'deepseek-harness';
|
|
28
|
+
sourceSchemaMajor = 2;
|
|
28
29
|
adapterVersion = DEEPSEEK_HARNESS_ADAPTER_VERSION;
|
|
29
30
|
disposition = 'implemented';
|
|
30
31
|
supportedOperations = ['discover', 'inspect', 'stream'];
|