@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.
Files changed (156) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +78 -14
  2. package/agentic/code/providers/model-catalog.v1.json +62 -12
  3. package/dist/src/artifacts/binding-target-identity.js +51 -0
  4. package/dist/src/artifacts/checksum-manifest.js +12 -0
  5. package/dist/src/artifacts/index-builder.js +11 -13
  6. package/dist/src/artifacts/query-engine.js +5 -11
  7. package/dist/src/cli/handlers/doctor-help.js +32 -0
  8. package/dist/src/cli/handlers/gates.js +19 -18
  9. package/dist/src/cli/handlers/models-help.js +7 -1
  10. package/dist/src/cli/handlers/models.js +3 -3
  11. package/dist/src/cli/handlers/script-runner.js +8 -5
  12. package/dist/src/cli/handlers/sessions-intake.js +104 -0
  13. package/dist/src/cli/handlers/sessions-tail.js +172 -0
  14. package/dist/src/cli/handlers/sessions.js +287 -31
  15. package/dist/src/cli/handlers/skill-lint.js +10 -3
  16. package/dist/src/cli/handlers/steward-format.js +100 -0
  17. package/dist/src/cli/handlers/steward-models.js +167 -0
  18. package/dist/src/cli/handlers/steward.js +14 -248
  19. package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
  20. package/dist/src/cli/handlers/use.js +18 -28
  21. package/dist/src/cli/handlers/utilities.js +1 -24
  22. package/dist/src/config/aiwg-config.js +7 -0
  23. package/dist/src/config/authorization-cli.js +89 -0
  24. package/dist/src/config/cli-help.js +1 -0
  25. package/dist/src/config/cli.js +10 -1
  26. package/dist/src/config/worker-context.js +337 -0
  27. package/dist/src/decision/context-pruning.js +3 -2
  28. package/dist/src/decision/ensemble/contract.js +15 -4
  29. package/dist/src/decision/ensemble-study/artifacts.js +5 -0
  30. package/dist/src/decision/ensemble-study/score.js +4 -3
  31. package/dist/src/decision/feature-export.js +3 -9
  32. package/dist/src/decision/heldout/collector.js +2 -2
  33. package/dist/src/decision/issue-triage/runtime.js +3 -9
  34. package/dist/src/decision/qualification/gates.js +2 -1
  35. package/dist/src/decision/qualification/release.js +2 -3
  36. package/dist/src/decision/routing/report.js +2 -0
  37. package/dist/src/decision/sdlc-screening.js +3 -5
  38. package/dist/src/decision/sensitivity/comparative.js +5 -3
  39. package/dist/src/gates/driver.js +6 -77
  40. package/dist/src/gates/evaluate.js +4 -11
  41. package/dist/src/gates/floors.js +4 -4
  42. package/dist/src/gates/index.js +3 -3
  43. package/dist/src/gates/lattice.js +26 -0
  44. package/dist/src/gates/registry.js +2 -1
  45. package/dist/src/gates/report.js +19 -7
  46. package/dist/src/issues/audit-cli.js +120 -0
  47. package/dist/src/issues/audit-contract.js +64 -0
  48. package/dist/src/issues/audit-local.js +82 -0
  49. package/dist/src/issues/audit.js +220 -0
  50. package/dist/src/lint/rule-activation.js +15 -1
  51. package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
  52. package/dist/src/mission-protocol/canonical-codecs.js +270 -0
  53. package/dist/src/mission-protocol/codecs.js +22 -550
  54. package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
  55. package/dist/src/mission-protocol/health-trace-types.js +43 -0
  56. package/dist/src/mission-protocol/types.js +2 -42
  57. package/dist/src/mission-protocol/worker-contract-json.js +10 -0
  58. package/dist/src/mission-protocol/worker-review-gate.js +100 -0
  59. package/dist/src/mission-protocol/worker-review-types.js +2 -0
  60. package/dist/src/models/catalog-policy.js +280 -0
  61. package/dist/src/models/index.js +1 -1
  62. package/dist/src/models/model-capabilities.v1.json +78 -14
  63. package/dist/src/models/model-catalog.v1.json +62 -12
  64. package/dist/src/models/policy-resources.js +28 -0
  65. package/dist/src/models/provider-policy.js +10 -539
  66. package/dist/src/models/resolver.js +26 -8
  67. package/dist/src/models/router.js +15 -2
  68. package/dist/src/models/tiers.js +13 -0
  69. package/dist/src/models/types.js +0 -6
  70. package/dist/src/models/worker-ceilings.js +59 -0
  71. package/dist/src/models/worker-invocation-policy.js +243 -0
  72. package/dist/src/models/worker-plan.js +16 -20
  73. package/dist/src/models/worker-review-lineage.js +55 -0
  74. package/dist/src/models/worker-selection-policy.js +1 -1
  75. package/dist/src/models/wrapper-route.js +6 -3
  76. package/dist/src/policy/authorization.js +30 -1
  77. package/dist/src/policy/hitl-config.js +63 -0
  78. package/dist/src/policy/hitl-receipt.js +57 -0
  79. package/dist/src/policy/hitl-types.js +4 -0
  80. package/dist/src/policy/hitl-validation.js +39 -0
  81. package/dist/src/policy/hitl.js +117 -0
  82. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  83. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  84. package/dist/src/rules/activation.d.mts +1 -1
  85. package/dist/src/rules/activation.mjs +0 -2
  86. package/dist/src/rules/gate-coverage.js +51 -0
  87. package/dist/src/rules/gate-references.js +16 -0
  88. package/dist/src/rules/rule-frontmatter.schema.json +7 -1
  89. package/dist/src/serve/worker-command-request.js +21 -4
  90. package/dist/src/serve/worker-invocation-preparation.js +3 -1
  91. package/dist/src/serve/worker-invocation.js +21 -10
  92. package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
  93. package/dist/src/sessions/adapters/grok-build-native.js +269 -0
  94. package/dist/src/sessions/adapters/grok-build.js +57 -15
  95. package/dist/src/sessions/adapters/grokbot.js +7 -0
  96. package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
  97. package/dist/src/sessions/adapters/hermes.js +42 -3
  98. package/dist/src/sessions/adapters/warp.js +1 -1
  99. package/dist/src/sessions/batch-import.js +44 -6
  100. package/dist/src/sessions/contracts.js +20 -1
  101. package/dist/src/sessions/coverage-preflight.js +7 -1
  102. package/dist/src/sessions/discovery-capability.js +38 -0
  103. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  104. package/dist/src/sessions/host-catalog.js +157 -0
  105. package/dist/src/sessions/image-blobs.js +141 -0
  106. package/dist/src/sessions/importer.js +9 -232
  107. package/dist/src/sessions/index.js +6 -0
  108. package/dist/src/sessions/intake-dir.js +71 -0
  109. package/dist/src/sessions/normalization.js +251 -0
  110. package/dist/src/sessions/refresh.js +25 -2
  111. package/dist/src/sessions/repository.js +164 -22
  112. package/dist/src/sessions/tail.js +91 -0
  113. package/dist/src/sessions/workspace-discovery.js +88 -9
  114. package/dist/src/skills/agent-skill-sidecar.js +28 -0
  115. package/dist/src/skills/agent-skills.js +4 -23
  116. package/dist/src/skills/claude-surface-projection.js +25 -0
  117. package/dist/src/skills/deployer.js +8 -288
  118. package/dist/src/skills/deployment-policy.js +114 -0
  119. package/dist/src/skills/deployment-projection.js +152 -0
  120. package/dist/src/skills/deployment-types.js +20 -0
  121. package/dist/src/skills/exporter.js +8 -31
  122. package/dist/src/skills/provider-extensions.js +41 -0
  123. package/dist/src/skills/run.js +6 -0
  124. package/dist/src/skills/types.js +0 -8
  125. package/dist/src/skills/validator-resources.js +68 -0
  126. package/dist/src/skills/validator.js +6 -55
  127. package/dist/src/storage/cli-migrate-options.js +125 -0
  128. package/dist/src/storage/cli-migrate.js +111 -0
  129. package/dist/src/storage/cli.js +4 -279
  130. package/dist/src/storage/migration-adapter.js +79 -0
  131. package/dist/src/storage/migration-copy.js +108 -0
  132. package/dist/src/storage/migration-protocol.js +14 -197
  133. package/dist/src/storage/migration-records.js +186 -0
  134. package/dist/src/storage/migration-types.js +12 -0
  135. package/dist/src/tracker/forge-http.js +2 -1
  136. package/dist/src/tracker/issue-pages.js +110 -0
  137. package/package.json +7 -1
  138. package/schemas/mission-v1.schema.json +335 -0
  139. package/schemas/models/model-catalog.v1.schema.json +19 -1
  140. package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
  141. package/schemas/models/model-policy.v1.schema.json +1 -1
  142. package/tools/agents/providers/provider-paths-staging.mjs +35 -0
  143. package/tools/agents/providers/rule-binding-targets.mjs +146 -0
  144. package/tools/agents/providers/rule-delivery.mjs +7 -126
  145. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  146. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  147. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  148. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  149. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  150. package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
  151. package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
  152. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  153. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  154. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  155. package/tools/ralph-external/session-launcher.mjs +51 -96
  156. package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
@@ -9,6 +9,7 @@
9
9
  import { access, copyFile, mkdir, readFile, rename } from 'node:fs/promises';
10
10
  import { isAbsolute, join, relative, resolve } from 'node:path';
11
11
  import { load as loadYaml } from 'js-yaml';
12
+ export { resolveHitlAuthorization } from './hitl.js';
12
13
  import { projectAiwgPath, projectControlPath } from '../config/project-artifacts.js';
13
14
  /**
14
15
  * Export provider mappings without inventing provider grants. Stable IDs,
@@ -170,13 +171,41 @@ const LEGACY_MANIFESTS = [
170
171
  '.aiwg/ops/security/repo-access.manifest.yaml',
171
172
  '.aiwg/security/repo-access.manifest.yaml',
172
173
  ];
174
+ /**
175
+ * Factory delivery defaults written by `aiwg init`/`aiwg use`
176
+ * (emptyConfig in src/config/aiwg-config.ts). Kept as a literal here —
177
+ * rather than importing the config module — because aiwg-config already
178
+ * imports this module for authorization validation, so a value import
179
+ * would be a cycle. A unit test pins this literal against
180
+ * emptyConfig().delivery (#3635).
181
+ */
182
+ const DEFAULT_DELIVERY_POLICY = {
183
+ mode: 'pr-required',
184
+ default_branch: 'main',
185
+ require_ci_green: true,
186
+ auto_close_issues: true,
187
+ issue_comment_on_cycle: true,
188
+ force_push_policy: 'never',
189
+ };
190
+ /** True when the delivery block is exactly what the tool writes for a fresh project. */
191
+ export function isDefaultDeliveryPolicy(delivery) {
192
+ if (!delivery || typeof delivery !== 'object')
193
+ return false;
194
+ const actual = delivery;
195
+ const actualKeys = Object.keys(actual);
196
+ return actualKeys.length === Object.keys(DEFAULT_DELIVERY_POLICY).length
197
+ && actualKeys.every((key) => actual[key] === DEFAULT_DELIVERY_POLICY[key]);
198
+ }
173
199
  export async function auditLegacyPermissions(projectDir, config) {
174
200
  const out = validateAuthorization(config?.authorization);
175
201
  if (!config?.authorization && config?.repos?.some(repo => repo.allowed?.length))
176
202
  out.push({ severity: 'warning', code: 'legacy-repos-allowed', source: '.aiwg/aiwg.config', message: 'repos[].allowed should be normalized into authorization roles and assignments.' });
177
203
  if (!config?.authorization && Object.keys(config?.repo_maintainer?.tiers ?? {}).length)
178
204
  out.push({ severity: 'warning', code: 'legacy-maintainer-tiers', source: '.aiwg/aiwg.config', message: 'repo_maintainer.tiers should be represented by normalized role assignments.' });
179
- if (!config?.authorization && config?.delivery)
205
+ // Tool-written delivery defaults are not operator legacy input: a fresh
206
+ // `aiwg use` must not produce something that needs migration (#3635).
207
+ // Operator-edited delivery (any key added, removed, or changed) still flags.
208
+ if (!config?.authorization && config?.delivery && !isDefaultDeliveryPolicy(config.delivery))
180
209
  out.push({ severity: 'warning', code: 'legacy-delivery-policy', source: '.aiwg/aiwg.config', message: 'delivery and signing operations should be normalized into scoped role assignments.' });
181
210
  for (const path of LEGACY_MANIFESTS) {
182
211
  try {
@@ -0,0 +1,63 @@
1
+ // Loads governing workspace/member policies without merging or broadening their grants.
2
+ import { realpath } from 'node:fs/promises';
3
+ import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
4
+ import { getConfigPath, readAiwgConfigCandidate } from '../config/aiwg-config.js';
5
+ export function ensureAuthorization(config) {
6
+ return config.authorization ??= { version: '1', default_effect: 'deny', resources: {}, permissions: {}, roles: {}, assignments: [] };
7
+ }
8
+ const inside = (child, root) => {
9
+ const path = relative(root, child);
10
+ return path === '' || (path !== '..' && !path.startsWith('../') && !isAbsolute(path));
11
+ };
12
+ async function canonicalDirectory(path) {
13
+ try {
14
+ return await realpath(path);
15
+ }
16
+ catch (error) {
17
+ if (error.code !== 'ENOENT')
18
+ throw error;
19
+ const parent = dirname(path);
20
+ if (parent === path)
21
+ throw error;
22
+ return resolve(await canonicalDirectory(parent), basename(path));
23
+ }
24
+ }
25
+ /** Each config boundary narrows grants. A member without HITL policy cannot inherit approval. */
26
+ export async function loadHitlLayers(start) {
27
+ const layers = [];
28
+ const seen = new Set();
29
+ let current = await canonicalDirectory(resolve(start));
30
+ let projectDir;
31
+ let child;
32
+ while (true) {
33
+ if (seen.has(current))
34
+ throw new Error('Cyclic HITL workspace membership');
35
+ seen.add(current);
36
+ const config = await readAiwgConfigCandidate(current);
37
+ if (config) {
38
+ projectDir ??= current;
39
+ if (child && config.repos) {
40
+ const root = await canonicalDirectory(resolve(current, config.workspace?.root ?? '.'));
41
+ const members = await Promise.all(config.repos.map(repo => canonicalDirectory(resolve(root, repo.path))));
42
+ if (!members.some(member => inside(child, member))) {
43
+ throw new Error('HITL workspace does not declare this member');
44
+ }
45
+ }
46
+ layers.unshift({ source: getConfigPath(current), policy: config.authorization?.hitl });
47
+ child = current;
48
+ if (config.workspace?.member_of) {
49
+ current = await canonicalDirectory(resolve(current, config.workspace.member_of));
50
+ const parent = await readAiwgConfigCandidate(current);
51
+ if (!parent?.repos)
52
+ throw new Error('HITL workspace.member_of must name a workspace manifest');
53
+ continue;
54
+ }
55
+ }
56
+ const parent = dirname(current);
57
+ if (parent === current)
58
+ break;
59
+ current = parent;
60
+ }
61
+ return { projectDir: projectDir ?? resolve(start), layers };
62
+ }
63
+ //# sourceMappingURL=hitl-config.js.map
@@ -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
@@ -0,0 +1,15 @@
1
+ export interface AiwgProbeCommand {
2
+ file: string;
3
+ prefix: string[];
4
+ via: 'aiwg-bin' | 'entrypoint' | 'package' | 'path';
5
+ }
6
+
7
+ export function resolveAiwgProbeCommand(options?: {
8
+ env?: NodeJS.ProcessEnv;
9
+ argv1?: string | undefined;
10
+ execPath?: string;
11
+ cwd?: string;
12
+ packageRoot?: string | null;
13
+ }): AiwgProbeCommand;
14
+
15
+ export function probeNotOnPathDetail(): string;
@@ -0,0 +1,37 @@
1
+ // Resolves the aiwg invocation for doctor's discovery self-probes so they
2
+ // run the current entrypoint instead of failing when aiwg is not on PATH.
3
+ import { existsSync } from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ export function resolveAiwgProbeCommand({
7
+ env = process.env,
8
+ argv1 = process.argv[1],
9
+ execPath = process.execPath,
10
+ cwd = process.cwd(),
11
+ packageRoot = null,
12
+ } = {}) {
13
+ const override = env?.AIWG_BIN;
14
+ if (typeof override === 'string' && override.length > 0) {
15
+ return { file: override, prefix: [], via: 'aiwg-bin' };
16
+ }
17
+ const entry = typeof argv1 === 'string' && argv1.length > 0
18
+ ? (path.isAbsolute(argv1) ? argv1 : path.resolve(cwd, argv1))
19
+ : null;
20
+ // Only the aiwg launcher itself is a usable entrypoint; doctor also runs as
21
+ // `node tools/cli/doctor.mjs`, where argv[1] is doctor, not aiwg.
22
+ const isLauncher = entry && ['aiwg', 'aiwg.mjs'].includes(path.basename(entry));
23
+ if (entry && isLauncher && existsSync(entry)) {
24
+ return { file: execPath, prefix: [entry], via: 'entrypoint' };
25
+ }
26
+ if (typeof packageRoot === 'string' && packageRoot.length > 0) {
27
+ const bin = path.join(packageRoot, 'bin', 'aiwg.mjs');
28
+ if (existsSync(bin)) {
29
+ return { file: execPath, prefix: [bin], via: 'package' };
30
+ }
31
+ }
32
+ return { file: 'aiwg', prefix: [], via: 'path' };
33
+ }
34
+
35
+ export function probeNotOnPathDetail() {
36
+ return 'aiwg not on PATH (spawn ENOENT) — add the npm global bin to PATH or invoke doctor via the install entrypoint';
37
+ }
@@ -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?: unknown;
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": "Opaque reserved metadata for #2839. Has no activation or runtime enforcement effect."
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
- : `--${key.replace('_', '-')} must be an absolute path; relative paths are not resolved against cwd or --workdir.`);
10
- this.pointer = `/request/${key}`;
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
- return { ...result, ...(premiumAuthorized ? { authority: { premiumAuthorized: true, maxAutoTier: 3 } } : {}) };
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 worker = `env -i${environment ? ` ${environment}` : ''} ${result.argv.map(quote).join(' ')}`;
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
- ...(brief ? { dataClass: brief.intent.dataClass, member: brief.intent.pins.member,
186
- model: brief.intent.pins.model, effort: brief.intent.pins.effort?.value, ceilings: {
187
- ...brief.intent.limits, costUsd: brief.intent.limits.costUsd ?? undefined
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: Math.min(50000, plan.ceilings.tokens ?? 50000), costUsd: plan.ceilings.costUsd ?? null,
250
- wallSeconds: Math.min(900, plan.ceilings.wallSeconds ?? 900), maxAttempts: plan.ceilings.maxAttempts ?? 3 };
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'];