@aiwg/cli 2026.10.5 → 2026.10.6

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 (212) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +3 -1
  6. package/dist/src/artifacts/browser-export.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +8 -1
  8. package/dist/src/artifacts/cli.js +57 -6
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  10. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  11. package/dist/src/artifacts/graph-query.js +18 -8
  12. package/dist/src/artifacts/index-adapters.js +339 -0
  13. package/dist/src/artifacts/index-builder.js +143 -14
  14. package/dist/src/artifacts/index-files.js +15 -4
  15. package/dist/src/artifacts/index-reader.js +44 -0
  16. package/dist/src/artifacts/index-refresh.js +114 -55
  17. package/dist/src/artifacts/index-status.js +20 -5
  18. package/dist/src/artifacts/memory-maintenance.js +503 -0
  19. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  20. package/dist/src/artifacts/query-engine.js +263 -108
  21. package/dist/src/artifacts/relations-builder.js +2 -2
  22. package/dist/src/artifacts/source-graph.js +2 -2
  23. package/dist/src/artifacts/types.js +9 -1
  24. package/dist/src/cli/handlers/decision.js +19 -1
  25. package/dist/src/cli/handlers/guided-setup.js +470 -0
  26. package/dist/src/cli/handlers/help.js +2 -2
  27. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  28. package/dist/src/cli/handlers/index.js +4 -2
  29. package/dist/src/cli/handlers/init.js +41 -6
  30. package/dist/src/cli/handlers/local-executor.js +5 -3
  31. package/dist/src/cli/handlers/mc.js +509 -53
  32. package/dist/src/cli/handlers/models.js +32 -2
  33. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  34. package/dist/src/cli/handlers/rules.js +1 -1
  35. package/dist/src/cli/handlers/script-runner.js +73 -2
  36. package/dist/src/cli/handlers/serve.js +5 -3
  37. package/dist/src/cli/handlers/sessions.js +1 -0
  38. package/dist/src/cli/handlers/setup.js +428 -1
  39. package/dist/src/cli/handlers/skill-lint.js +1 -1
  40. package/dist/src/cli/handlers/subcommands.js +118 -8
  41. package/dist/src/cli/handlers/use.js +279 -93
  42. package/dist/src/cli/handlers/workspace.js +3 -1
  43. package/dist/src/cli/prompt-utils.js +8 -0
  44. package/dist/src/cli/router.js +16 -1
  45. package/dist/src/config/aiwg-config.js +42 -0
  46. package/dist/src/config/cli.js +7 -3
  47. package/dist/src/config/decision-config.js +14 -1
  48. package/dist/src/config/native-sandbox-warnings.js +25 -0
  49. package/dist/src/contributors/validation.js +6 -5
  50. package/dist/src/decision/admission.js +7 -3
  51. package/dist/src/decision/benchmark.js +469 -0
  52. package/dist/src/decision/ledger.js +22 -3
  53. package/dist/src/decision/pricing.js +147 -0
  54. package/dist/src/decision/qualification/gate-evidence.js +18 -15
  55. package/dist/src/decision/qualification/load-staged.js +539 -0
  56. package/dist/src/decision/qualification/load.js +109 -33
  57. package/dist/src/decision/qualification/runner.js +5 -2
  58. package/dist/src/decision/review/service.js +6 -4
  59. package/dist/src/decision/review/validate.js +23 -2
  60. package/dist/src/decision/savings.js +8 -3
  61. package/dist/src/discovery/index.js +492 -0
  62. package/dist/src/discovery/interview.js +233 -0
  63. package/dist/src/discovery/plan-apply.js +571 -0
  64. package/dist/src/discovery/plan-store.js +171 -0
  65. package/dist/src/discovery/plan-types.js +2 -0
  66. package/dist/src/discovery/plan-validation.js +139 -0
  67. package/dist/src/discovery/types.js +2 -0
  68. package/dist/src/extensions/commands/definitions.js +49 -9
  69. package/dist/src/extensions/manifest.js +71 -44
  70. package/dist/src/extensions/project-local-discovery.js +1 -1
  71. package/dist/src/extensions/project-quickref.js +18 -17
  72. package/dist/src/extensions/validation.js +32 -29
  73. package/dist/src/housekeeper/daemon-config.js +148 -0
  74. package/dist/src/housekeeper/ipc.js +84 -0
  75. package/dist/src/housekeeper/lifecycle.js +148 -0
  76. package/dist/src/housekeeper/platform.js +35 -0
  77. package/dist/src/housekeeper/status.js +70 -0
  78. package/dist/src/housekeeper/units.js +114 -0
  79. package/dist/src/jobs/flow.js +22 -21
  80. package/dist/src/lint/rule-activation.js +20 -5
  81. package/dist/src/lint/runner.js +1 -1
  82. package/dist/src/mcp/cli.mjs +5 -6
  83. package/dist/src/mcp/config-file.mjs +6 -2
  84. package/dist/src/mcp/config-layers.mjs +23 -7
  85. package/dist/src/mcp/profiles.js +18 -31
  86. package/dist/src/mcp/profiles.mjs +18 -32
  87. package/dist/src/mcp/registry.js +7 -27
  88. package/dist/src/mcp/registry.mjs +7 -28
  89. package/dist/src/mcp/server.mjs +34 -1
  90. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  91. package/dist/src/mcp/tools/command-run.mjs +3 -2
  92. package/dist/src/mcp/tools/decision.mjs +49 -48
  93. package/dist/src/mcp/tools/discovery.mjs +46 -21
  94. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  95. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  96. package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
  97. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  98. package/dist/src/memory/audit.js +719 -0
  99. package/dist/src/memory/canonical-context.js +392 -13
  100. package/dist/src/memory/canonical-maintenance.js +95 -0
  101. package/dist/src/memory/cli-result.js +22 -0
  102. package/dist/src/memory/cli.js +176 -0
  103. package/dist/src/memory/context-pack.js +57 -6
  104. package/dist/src/memory/cost.js +512 -0
  105. package/dist/src/memory/firewall-classification.d.mts +7 -0
  106. package/dist/src/memory/firewall-classification.mjs +32 -0
  107. package/dist/src/memory/index.js +8 -0
  108. package/dist/src/memory/line-maintenance.js +68 -0
  109. package/dist/src/memory/maintenance-cli.js +286 -0
  110. package/dist/src/memory/maintenance-contract.js +136 -0
  111. package/dist/src/memory/maintenance-index.js +18 -0
  112. package/dist/src/memory/maintenance-owner.js +2 -0
  113. package/dist/src/memory/maintenance-record.js +138 -0
  114. package/dist/src/memory/maintenance.js +254 -0
  115. package/dist/src/memory/provider-memory-adapter.js +154 -0
  116. package/dist/src/memory/recommend-cli.js +83 -0
  117. package/dist/src/memory/recommend.js +269 -0
  118. package/dist/src/memory/scheduled.js +81 -0
  119. package/dist/src/memory/taxonomy.js +44 -0
  120. package/dist/src/mission-protocol/codecs.js +19 -9
  121. package/dist/src/models/config-loader.js +11 -1
  122. package/dist/src/models/model-capabilities.v1.json +74 -50
  123. package/dist/src/models/provider-policy.js +125 -62
  124. package/dist/src/models/resolver.js +45 -8
  125. package/dist/src/models/worker-installation.d.mts +7 -0
  126. package/dist/src/models/worker-installation.mjs +169 -37
  127. package/dist/src/models/wrapper-route.js +1 -1
  128. package/dist/src/output-modes/registry.js +8 -7
  129. package/dist/src/plugin/metadata-validator.js +1 -1
  130. package/dist/src/providers/deploy-version.d.mts +1 -0
  131. package/dist/src/providers/deploy-version.mjs +17 -0
  132. package/dist/src/providers/provider-definitions.js +19 -16
  133. package/dist/src/providers/skill-ownership.d.mts +11 -2
  134. package/dist/src/providers/skill-ownership.mjs +35 -17
  135. package/dist/src/rules/activation.d.mts +11 -2
  136. package/dist/src/rules/activation.mjs +82 -6
  137. package/dist/src/rules/inventory.js +1 -1
  138. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  139. package/dist/src/schema/zod-validation.d.mts +6 -0
  140. package/dist/src/schema/zod-validation.mjs +50 -0
  141. package/dist/src/serve/executor-registry.js +297 -20
  142. package/dist/src/serve/executor-token.d.mts +1 -0
  143. package/dist/src/serve/executor-token.mjs +8 -0
  144. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  145. package/dist/src/serve/worker-invocation.js +114 -15
  146. package/dist/src/sessions/adapters/claude.js +19 -18
  147. package/dist/src/sessions/adapters/codex.js +10 -9
  148. package/dist/src/sessions/adapters/copilot.js +14 -13
  149. package/dist/src/sessions/adapters/cursor.js +19 -18
  150. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  151. package/dist/src/sessions/adapters/factory.js +11 -10
  152. package/dist/src/sessions/adapters/generic.js +32 -23
  153. package/dist/src/sessions/adapters/grokbot.js +35 -35
  154. package/dist/src/sessions/adapters/hermes.js +20 -19
  155. package/dist/src/sessions/adapters/muse.js +50 -49
  156. package/dist/src/sessions/adapters/omp.js +5 -4
  157. package/dist/src/sessions/adapters/openclaw.js +30 -29
  158. package/dist/src/sessions/adapters/opencode.js +26 -26
  159. package/dist/src/sessions/adapters/openhuman.js +24 -23
  160. package/dist/src/sessions/adapters/pi.js +5 -4
  161. package/dist/src/sessions/adapters/windsurf.js +8 -8
  162. package/dist/src/sessions/analytics.js +10 -9
  163. package/dist/src/sessions/candidates.js +7 -6
  164. package/dist/src/sessions/contracts.js +63 -53
  165. package/dist/src/sessions/output-registration.js +11 -10
  166. package/dist/src/sessions/workspace-discovery.js +3 -4
  167. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  168. package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
  169. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  170. package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
  171. package/dist/src/update/checker.d.mts +4 -0
  172. package/dist/src/update/checker.mjs +14 -0
  173. package/dist/src/writing/exemplar-selection.js +6 -6
  174. package/dist/src/writing/voice-evaluation.js +24 -23
  175. package/dist/src/writing/writer-migration.js +14 -13
  176. package/dist/src/writing/writer-profile.js +19 -18
  177. package/dist/src/writing/writing-brief.js +18 -17
  178. package/dist/src/writing/writing-receipt.js +47 -46
  179. package/package.json +5 -2
  180. package/schemas/decision/DecisionReview.schema.json +78 -1
  181. package/schemas/mission-v1.schema.json +3 -0
  182. package/tools/agents/deploy-agents.mjs +67 -39
  183. package/tools/agents/providers/antigravity.mjs +6 -1
  184. package/tools/agents/providers/base.mjs +19 -13
  185. package/tools/agents/providers/claude.mjs +1 -1
  186. package/tools/agents/providers/codex.mjs +50 -56
  187. package/tools/agents/providers/copilot.mjs +8 -2
  188. package/tools/agents/providers/cursor.mjs +1 -1
  189. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  190. package/tools/agents/providers/factory.mjs +7 -1
  191. package/tools/agents/providers/grok-build.mjs +18 -2
  192. package/tools/agents/providers/grokbot.mjs +1 -1
  193. package/tools/agents/providers/hermes.mjs +7 -13
  194. package/tools/agents/providers/muse-hooks.mjs +21 -14
  195. package/tools/agents/providers/muse.mjs +7 -2
  196. package/tools/agents/providers/omp.mjs +28 -4
  197. package/tools/agents/providers/opencode.mjs +14 -5
  198. package/tools/agents/providers/openhuman.mjs +1 -1
  199. package/tools/agents/providers/pi.mjs +1 -1
  200. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  201. package/tools/agents/providers/rule-delivery.mjs +263 -71
  202. package/tools/agents/providers/windsurf.mjs +7 -0
  203. package/tools/cli/workspace-status.mjs +5 -0
  204. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  205. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  206. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  207. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  208. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  209. package/tools/ralph-external/process-monitor.mjs +7 -4
  210. package/tools/ralph-external/session-launcher.mjs +280 -32
  211. package/tools/skills/deploy-skills-codex.mjs +11 -7
  212. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -17,8 +17,8 @@
17
17
 
18
18
  import { lstat, mkdtemp, readFile, readlink, realpath, rmdir } from 'fs/promises';
19
19
  import { basename, dirname, isAbsolute, posix, relative, resolve, sep, win32 } from 'path';
20
- import { assertConfigDestination, assertNoSymlinkParents, writeConfigAtomic } from './config-file.mjs';
21
- import { resolveCredentialPolicy, stricterCredentialPolicy, warnCredentialPolicyRelaxation, warnDroppedOverlayPolicy } from './credentials.mjs';
20
+ import { assertConfigDestination, assertConfigObject, assertNoSymlinkParents, writeConfigAtomic } from './config-file.mjs';
21
+ import { credentialPolicyViolations, resolveCredentialPolicy, stricterCredentialPolicy, warnCredentialPolicyRelaxation, warnDroppedOverlayPolicy } from './credentials.mjs';
22
22
 
23
23
  /**
24
24
  * `platform` lets tests validate Windows path semantics (`C:\org`, UNC
@@ -62,9 +62,15 @@ export function resolveConfigLayers(configDirOverride, env = process.env, { plat
62
62
  return layers.length > 0 ? layers : null;
63
63
  }
64
64
 
65
- async function readLayer(path) {
65
+ async function readLayer(path, collection) {
66
66
  try {
67
- return JSON.parse(await readFile(path, 'utf-8'));
67
+ const parsed = JSON.parse(await readFile(path, 'utf-8'));
68
+ try {
69
+ assertConfigObject(parsed, collection);
70
+ } catch {
71
+ throw new Error(`Invalid MCP configuration layer ${path}: expected an object root and an object ${collection} map`);
72
+ }
73
+ return parsed;
68
74
  } catch (error) {
69
75
  if (error?.code === 'ENOENT') return null;
70
76
  if (error instanceof SyntaxError) throw new Error(`Invalid JSON in MCP configuration layer ${path}`);
@@ -115,7 +121,7 @@ export async function loadLayered(layers, filename, collection, defaults) {
115
121
  let lowerPolicySource;
116
122
  const writeLayer = layers.length - 1;
117
123
  for (const [index, dir] of layers.entries()) {
118
- const parsed = await readLayer(resolve(dir, filename));
124
+ const parsed = await readLayer(resolve(dir, filename), collection);
119
125
  if (!parsed) continue;
120
126
  const { [collection]: entries, ...rest } = parsed;
121
127
  if (index === writeLayer) {
@@ -298,16 +304,26 @@ export async function assertLayerWriteDestination(layers, filename) {
298
304
  /** Persist atomically, then refresh ownership only after a successful write. */
299
305
  export async function saveLayerData(file, data, collection, layers, layering, explicitPolicy = false) {
300
306
  if (layers) await assertLayerWriteDestination(layers, basename(file));
307
+ // Validate the current bytes even if the caller loaded and cached an older
308
+ // version, or called save(data) on a fresh standalone registry.
309
+ await readLayer(file, collection);
301
310
  const persisted = layering ? writeLayerData(data, collection, layering, explicitPolicy) : data;
302
311
  // The active identity layer file is always 0600: it is the personal write
303
312
  // layer of an active AIWG_CONFIG_LAYERS configuration and may carry secrets.
304
313
  // Without layering, `file` is whatever single directory the caller
305
314
  // configured (which may be a shared, group-readable organisation checkout
306
315
  // maintained directly, not a private identity directory): an existing file
307
- // keeps its mode, while a newly created file starts private at 0600.
316
+ // keeps its mode unless the persisted servers carry literal credentials,
317
+ // while a newly created file starts private at 0600.
318
+ const info = layers ? null : await assertConfigDestination(file);
319
+ const tighten = info && (info.mode & 0o077) !== 0 && collection === 'servers'
320
+ && Object.values(persisted[collection] || {}).some(server => credentialPolicyViolations(server, 'references').length > 0);
308
321
  await writeConfigAtomic(file, JSON.stringify(persisted, null, 2) + '\n', layers
309
322
  ? { userScope: true, rejectSymlinkParents: true }
310
- : { newFileMode: 0o600, rejectSymlinkParents: true });
323
+ : { newFileMode: 0o600, rejectSymlinkParents: true, ...(tighten ? { mode: 0o600 } : {}) });
324
+ if (tighten) {
325
+ console.warn(`Warning: tightened MCP registry ${file} from ${(info.mode & 0o7777).toString(8).padStart(4, '0')} to 0600 because it contains literal credentials.`);
326
+ }
311
327
  if (layering) {
312
328
  layering.own = new Set(Object.keys(persisted[collection] || {}));
313
329
  layering.ownFields = new Set(Object.keys(persisted).filter(field => field !== collection));
@@ -9,10 +9,10 @@
9
9
  *
10
10
  * @implements #889
11
11
  */
12
- import { resolveCredentialPolicy } from './credentials.mjs';
13
12
  import { isLowerLayerEntry, loadLayered, resolveProfileExtends, resolveConfigLayers, saveLayerData } from './config-layers.mjs';
14
- import { readFile, writeFile } from 'fs/promises';
15
- import { resolve } from 'path';
13
+ import { writeConfigAtomic } from './config-file.mjs';
14
+ import { readFile } from 'fs/promises';
15
+ import { parse, resolve } from 'path';
16
16
  import { resolveConfigDir } from '../config/user-config.js';
17
17
  // ─────────────────────────────────────────────
18
18
  // Constants
@@ -81,38 +81,21 @@ export class McpProfileRegistry {
81
81
  async load() {
82
82
  if (this.cache)
83
83
  return this.cache;
84
- if (this.layers) {
85
- const layered = await loadLayered(this.layers, PROFILES_FILENAME, 'profiles', DEFAULT_DATA);
86
- this.cache = layered.data;
87
- this.layering = layered.layering;
88
- return this.cache;
89
- }
90
- const filePath = this.getPath();
91
- try {
92
- const content = await readFile(filePath, 'utf-8');
93
- const parsed = JSON.parse(content);
94
- this.cache = {
95
- ...DEFAULT_DATA,
96
- ...parsed,
97
- profiles: parsed.profiles || {},
98
- };
99
- }
100
- catch {
101
- this.cache = { ...DEFAULT_DATA, profiles: {} };
102
- }
103
- try {
104
- resolveCredentialPolicy({ registryPolicy: this.cache.credentialPolicy, env: {} });
105
- }
106
- catch (error) {
107
- this.cache = null;
108
- throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
109
- }
84
+ const loaded = await loadLayered(this.layers ?? [this.configDir], PROFILES_FILENAME, 'profiles', DEFAULT_DATA);
85
+ this.cache = loaded.data;
86
+ this.layering = this.layers ? loaded.layering : null;
110
87
  return this.cache;
111
88
  }
112
89
  async save(data = this.cache) {
113
90
  if (!data)
114
91
  return;
115
- await saveLayerData(this.getPath(), data, 'profiles', this.layers, this.layering);
92
+ // Load ownership before a fresh save so inherited fields stay in their layer.
93
+ let layering = this.layering;
94
+ if (this.layers && !layering) {
95
+ await this.load();
96
+ layering = this.layering;
97
+ }
98
+ await saveLayerData(this.getPath(), data, 'profiles', this.layers, layering);
116
99
  this.cache = data;
117
100
  if (this.layers) {
118
101
  this.clearCache();
@@ -311,7 +294,11 @@ export class McpProfileRegistry {
311
294
  const toExport = profileName
312
295
  ? { [profileName]: data.profiles[profileName] }
313
296
  : data.profiles;
314
- await writeFile(filePath, JSON.stringify({ profiles: toExport }, null, 2) + '\n', 'utf-8');
297
+ await writeConfigAtomic(filePath, JSON.stringify({ profiles: toExport }, null, 2) + '\n', {
298
+ userScope: true,
299
+ rejectSymlinkParents: true,
300
+ symlinkRoot: parse(resolve(filePath)).root,
301
+ });
315
302
  }
316
303
  async initPresets() {
317
304
  const data = structuredClone(await this.load());
@@ -10,10 +10,10 @@
10
10
  * @implements #889
11
11
  */
12
12
 
13
- import { resolveCredentialPolicy } from './credentials.mjs';
14
13
  import { isLowerLayerEntry, loadLayered, resolveProfileExtends, resolveConfigLayers, saveLayerData } from './config-layers.mjs';
15
- import { readFile, writeFile } from 'fs/promises';
16
- import { resolve } from 'path';
14
+ import { writeConfigAtomic } from './config-file.mjs';
15
+ import { readFile } from 'fs/promises';
16
+ import { parse, resolve } from 'path';
17
17
  import { homedir } from 'os';
18
18
  import { existsSync } from 'fs';
19
19
 
@@ -112,39 +112,21 @@ export class McpProfileRegistry {
112
112
  async load() {
113
113
  if (this.#cache) return this.#cache;
114
114
 
115
- if (this.#layers) {
116
- const layered = await loadLayered(this.#layers, PROFILES_FILENAME, 'profiles', DEFAULT_DATA);
117
- this.#cache = layered.data;
118
- this.#layering = layered.layering;
119
- return this.#cache;
120
- }
121
-
122
- const filePath = this.getPath();
123
- try {
124
- const content = await readFile(filePath, 'utf-8');
125
- const parsed = JSON.parse(content);
126
- this.#cache = {
127
- ...DEFAULT_DATA,
128
- ...parsed,
129
- profiles: parsed.profiles || {},
130
- };
131
- } catch {
132
- this.#cache = { ...DEFAULT_DATA, profiles: {} };
133
- }
134
-
135
- try {
136
- resolveCredentialPolicy({ registryPolicy: this.#cache.credentialPolicy, env: {} });
137
- } catch (error) {
138
- this.#cache = null;
139
- throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
140
- }
141
-
115
+ const loaded = await loadLayered(this.#layers ?? [this.#configDir], PROFILES_FILENAME, 'profiles', DEFAULT_DATA);
116
+ this.#cache = loaded.data;
117
+ this.#layering = this.#layers ? loaded.layering : null;
142
118
  return this.#cache;
143
119
  }
144
120
 
145
121
  async save(data = this.#cache) {
146
122
  if (!data) return;
147
- await saveLayerData(this.getPath(), data, 'profiles', this.#layers, this.#layering);
123
+ // Load ownership before a fresh save so inherited fields stay in their layer.
124
+ let layering = this.#layering;
125
+ if (this.#layers && !layering) {
126
+ await this.load();
127
+ layering = this.#layering;
128
+ }
129
+ await saveLayerData(this.getPath(), data, 'profiles', this.#layers, layering);
148
130
  this.#cache = data;
149
131
  if (this.#layers) {
150
132
  this.clearCache();
@@ -402,7 +384,11 @@ export class McpProfileRegistry {
402
384
  throw new Error(`Profile "${profileName}" not found.`);
403
385
  }
404
386
 
405
- await writeFile(filePath, JSON.stringify({ profiles: toExport }, null, 2) + '\n', 'utf-8');
387
+ await writeConfigAtomic(filePath, JSON.stringify({ profiles: toExport }, null, 2) + '\n', {
388
+ userScope: true,
389
+ rejectSymlinkParents: true,
390
+ symlinkRoot: parse(resolve(filePath)).root,
391
+ });
406
392
  }
407
393
 
408
394
  /** Install preset profiles (does not overwrite existing) */
@@ -120,32 +120,9 @@ export class McpServerRegistry {
120
120
  async load() {
121
121
  if (this.cache)
122
122
  return this.cache;
123
- if (this.layers) {
124
- const layered = await loadLayered(this.layers, REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
125
- this.cache = layered.data;
126
- this.layering = layered.layering;
127
- return this.cache;
128
- }
129
- const filePath = this.getPath();
130
- try {
131
- const content = await readFile(filePath, 'utf-8');
132
- const parsed = JSON.parse(content);
133
- this.cache = {
134
- ...DEFAULT_REGISTRY,
135
- ...parsed,
136
- servers: parsed.servers || {},
137
- };
138
- }
139
- catch {
140
- this.cache = { ...DEFAULT_REGISTRY, servers: {} };
141
- }
142
- try {
143
- resolveCredentialPolicy({ registryPolicy: this.cache.credentialPolicy, env: {} });
144
- }
145
- catch (error) {
146
- this.cache = null;
147
- throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
148
- }
123
+ const loaded = await loadLayered(this.layers ?? [this.configDir], REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
124
+ this.cache = loaded.data;
125
+ this.layering = this.layers ? loaded.layering : null;
149
126
  return this.cache;
150
127
  }
151
128
  /** Save the registry to disk */
@@ -536,6 +513,9 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
536
513
  return result;
537
514
  }
538
515
  async function injectToml(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null) {
516
+ // A deliberately aliased Codex home is trusted; descendants below it are not.
517
+ const destinationRoot = projectRoot ?? (process.env.CODEX_HOME || resolve(process.env.HOME || process.env.USERPROFILE || homedir(), '.codex'));
518
+ await assertConfigDestination(configPath, destinationRoot);
539
519
  let existing = '';
540
520
  try {
541
521
  existing = await readFile(configPath, 'utf-8');
@@ -553,7 +533,7 @@ async function injectToml(registry, servers, configPath, provider, dryRun, resul
553
533
  result.serversInjected.push(server.name);
554
534
  }
555
535
  if (!dryRun) {
556
- await writeConfigAtomic(configPath, existing, { userScope, projectRoot });
536
+ await writeConfigAtomic(configPath, existing, { userScope, projectRoot: destinationRoot });
557
537
  for (const server of servers) {
558
538
  await registry.recordInjection(server.name, provider);
559
539
  }
@@ -115,33 +115,9 @@ export class McpServerRegistry {
115
115
  async load() {
116
116
  if (this.#cache) return this.#cache;
117
117
 
118
- if (this.#layers) {
119
- const layered = await loadLayered(this.#layers, REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
120
- this.#cache = layered.data;
121
- this.#layering = layered.layering;
122
- return this.#cache;
123
- }
124
-
125
- const filePath = this.getPath();
126
- try {
127
- const content = await readFile(filePath, 'utf-8');
128
- const parsed = JSON.parse(content);
129
- this.#cache = {
130
- ...DEFAULT_REGISTRY,
131
- ...parsed,
132
- servers: parsed.servers || {},
133
- };
134
- } catch {
135
- this.#cache = { ...DEFAULT_REGISTRY, servers: {} };
136
- }
137
-
138
- try {
139
- resolveCredentialPolicy({ registryPolicy: this.#cache.credentialPolicy, env: {} });
140
- } catch (error) {
141
- this.#cache = null;
142
- throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
143
- }
144
-
118
+ const loaded = await loadLayered(this.#layers ?? [this.#configDir], REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
119
+ this.#cache = loaded.data;
120
+ this.#layering = this.#layers ? loaded.layering : null;
145
121
  return this.#cache;
146
122
  }
147
123
 
@@ -488,6 +464,9 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
488
464
  }
489
465
 
490
466
  async function injectToml(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null) {
467
+ // A deliberately aliased Codex home is trusted; descendants below it are not.
468
+ const destinationRoot = projectRoot ?? (process.env.CODEX_HOME || resolve(process.env.HOME || process.env.USERPROFILE || homedir(), '.codex'));
469
+ await assertConfigDestination(configPath, destinationRoot);
491
470
  let existing = '';
492
471
  try {
493
472
  existing = await readFile(configPath, 'utf-8');
@@ -504,7 +483,7 @@ async function injectToml(registry, servers, configPath, provider, dryRun, resul
504
483
  }
505
484
 
506
485
  if (!dryRun) {
507
- await writeConfigAtomic(configPath, existing, { userScope, projectRoot });
486
+ await writeConfigAtomic(configPath, existing, { userScope, projectRoot: destinationRoot });
508
487
 
509
488
  for (const server of servers) {
510
489
  await registry.recordInjection(server.name, provider);
@@ -34,6 +34,27 @@ const FRAMEWORKS = {
34
34
  'media-marketing-kit': path.join(AIWG_ROOT, 'agentic/code/frameworks/media-marketing-kit')
35
35
  };
36
36
 
37
+ /** Keep the published object contracts stable across native Zod converters. */
38
+ function retainToolJsonSchema(schema, seen = new Set()) {
39
+ if (!(schema instanceof z.ZodType) || seen.has(schema)) return;
40
+ seen.add(schema);
41
+ const definition = schema._zod.def;
42
+ if (schema instanceof z.ZodObject) {
43
+ const catchall = definition.catchall;
44
+ if ((!catchall && Object.keys(schema.shape).length > 0) || catchall instanceof z.ZodNever || catchall instanceof z.ZodUnknown) {
45
+ z.globalRegistry.add(schema, { ...z.globalRegistry.get(schema), additionalProperties: catchall instanceof z.ZodUnknown });
46
+ }
47
+ }
48
+ if (schema instanceof z.ZodRecord && definition.keyType instanceof z.ZodString && !definition.keyType._zod.def.checks?.length) {
49
+ z.globalRegistry.add(schema, { ...z.globalRegistry.get(schema), propertyNames: undefined });
50
+ }
51
+ for (const value of Object.values(definition)) {
52
+ if (value instanceof z.ZodType) retainToolJsonSchema(value, seen);
53
+ else if (Array.isArray(value)) for (const child of value) retainToolJsonSchema(child, seen);
54
+ else if (value && typeof value === 'object') for (const child of Object.values(value)) retainToolJsonSchema(child, seen);
55
+ }
56
+ }
57
+
37
58
  /**
38
59
  * Create and configure the AIWG MCP Server
39
60
  */
@@ -43,6 +64,18 @@ export async function createServer() {
43
64
  version: '1.0.0'
44
65
  });
45
66
 
67
+ const registerTool = server.registerTool.bind(server);
68
+ server.registerTool = (name, config, handler) => {
69
+ const schemas = {};
70
+ for (const key of ['inputSchema', 'outputSchema']) {
71
+ if (!config[key]) continue;
72
+ const schema = config[key] instanceof z.ZodType ? config[key] : z.object(config[key]);
73
+ retainToolJsonSchema(schema);
74
+ schemas[key] = schema;
75
+ }
76
+ return registerTool(name, { ...config, ...schemas }, handler);
77
+ };
78
+
46
79
  // ============================================
47
80
  // TOOLS
48
81
  // ============================================
@@ -248,7 +281,7 @@ export async function createServer() {
248
281
  description: 'Render AIWG template with provided variables',
249
282
  inputSchema: {
250
283
  template: z.string().describe('Template path (e.g., "sdlc/intake/project-intake-template.md")'),
251
- variables: z.record(z.string()).optional().describe('Template variable substitutions')
284
+ variables: z.record(z.string(), z.string()).optional().describe('Template variable substitutions')
252
285
  }
253
286
  }, async ({ template, variables = {} }) => {
254
287
  try {
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { readFile, stat } from 'node:fs/promises';
12
12
  import { z } from 'zod';
13
+ import { integer, dateTime } from '../../schema/zod-validation.mjs';
13
14
  import { assertNoWorkerBinding, mcpError, mcpJson } from '../helpers.mjs';
14
15
 
15
16
  const FLEET_VERSION = 'agentic-orchestration/v1';
@@ -173,15 +174,15 @@ const scopeSchema = {
173
174
  instance_id: z.string().min(1).max(255).regex(/^[^\r\n]+$/),
174
175
  agent_id: z.string().min(1).max(255).regex(/^[^\r\n]+$/),
175
176
  };
176
- const activityFilterSchema = z.object({
177
+ const activityFilterSchema = z.strictObject({
177
178
  event_name: z.string().min(1).max(255).optional(), collector: z.string().min(1).max(255).optional(),
178
179
  trust: z.string().min(1).max(255).optional(), plane: z.string().min(1).max(255).optional(),
179
180
  outcome: z.string().min(1).max(255).optional(), session_id: z.string().min(1).max(255).optional(),
180
181
  mission_id: z.string().min(1).max(255).optional(), task_id: z.string().min(1).max(255).optional(),
181
182
  tool_call_id: z.string().min(1).max(255).optional(), command_id: z.string().min(1).max(255).optional(),
182
183
  process_id: z.string().min(1).max(255).optional(), trace_id: z.string().regex(/^[0-9a-f]{32}$/).optional(),
183
- since: z.string().datetime().optional(), until: z.string().datetime().optional(), limit: z.number().int().min(1).max(1000).optional(),
184
- }).strict().optional();
184
+ since: dateTime().optional(), until: dateTime().optional(), limit: integer().min(1).max(1000).optional(),
185
+ }).optional();
185
186
 
186
187
  function scopeHeaders(args) {
187
188
  return Object.fromEntries(Object.entries(SCOPE_HEADERS).map(([key, header]) => [header, args[key]]));
@@ -206,17 +207,17 @@ export function registerAgenticSandboxToolset(server, { client = new AgenticSand
206
207
  () => client.request('/api/v2/fleet/workloads', { validate: requireInventory }));
207
208
  register('sandbox-fleet-get', { title: 'Get Agentic Sandbox fleet workload', description: 'Read one revisioned fleet workload by child identity.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), child_id: z.string().min(1).max(255) }, annotations: { readOnlyHint: true, destructiveHint: false } },
208
209
  ({ child_id }) => client.request(`/api/v2/fleet/workloads/${encodeURIComponent(child_id)}`, { validate: requireFleetRecord }));
209
- register('sandbox-fleet-reconcile-preview', { title: 'Preview fleet reconciliation', description: 'Compute a read-only reconciliation preview from inventory; never calls POST /reconcile.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), before_revision: z.number().int().min(0), child_ids: z.array(z.string().min(1).max(255)).max(1000) }, annotations: { readOnlyHint: true, destructiveHint: false } },
210
+ register('sandbox-fleet-reconcile-preview', { title: 'Preview fleet reconciliation', description: 'Compute a read-only reconciliation preview from inventory; never calls POST /reconcile.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), before_revision: integer().min(0), child_ids: z.array(z.string().min(1).max(255)).max(1000) }, annotations: { readOnlyHint: true, destructiveHint: false } },
210
211
  async ({ before_revision, child_ids }) => client.request('/api/v2/fleet/workloads', { validate: (value) => {
211
212
  const inventory = requireInventory(value);
212
213
  const byId = new Map(inventory.records.map((record) => [record.lineage.child_id, record]));
213
214
  return { document_type: 'reconciliation-preview', api_version: FLEET_VERSION, before_revision, inventory_revision: inventory.inventory_revision, stale: before_revision !== inventory.inventory_revision, rows: child_ids.map((child_id) => ({ child_id, present: byId.has(child_id), observed_state: byId.get(child_id)?.status?.observed_state ?? 'unknown', revision: byId.get(child_id)?.status?.revision ?? null })) };
214
215
  } }));
215
- register('sandbox-fleet-admit', { title: 'Admit Agentic Sandbox fleet workload', description: 'Mutating fleet admission; requires an exact v1 workload and confirmed=true.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), workload: z.record(z.unknown()), confirmed: z.boolean().default(false) }, annotations: { readOnlyHint: false, destructiveHint: true } },
216
+ register('sandbox-fleet-admit', { title: 'Admit Agentic Sandbox fleet workload', description: 'Mutating fleet admission; requires an exact v1 workload and confirmed=true.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), workload: z.record(z.string(), z.unknown()), confirmed: z.boolean().default(false) }, annotations: { readOnlyHint: false, destructiveHint: true } },
216
217
  ({ workload, confirmed }) => confirmed ? client.request('/api/v2/fleet/workloads', { method: 'POST', body: requireFleetRecord(workload), validate: (value) => ({ replayed: value?.replayed === true, workload: requireFleetRecord(value?.workload) }) }) : confirmationError('sandbox-fleet-admit'));
217
- register('sandbox-fleet-observe', { title: 'Record fleet workload observation', description: 'Mutating monotonic observation update; requires confirmed=true and expected revision.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), child_id: z.string().min(1).max(255), expected_revision: z.number().int().min(0), status: z.record(z.unknown()), runtime_identity: z.object({ session_id: z.string().optional(), task_id: z.string().optional(), command_id: z.string().optional() }).strict().optional(), confirmed: z.boolean().default(false) }, annotations: { readOnlyHint: false, destructiveHint: true } },
218
+ register('sandbox-fleet-observe', { title: 'Record fleet workload observation', description: 'Mutating monotonic observation update; requires confirmed=true and expected revision.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), child_id: z.string().min(1).max(255), expected_revision: integer().min(0), status: z.record(z.string(), z.unknown()), runtime_identity: z.strictObject({ session_id: z.string().optional(), task_id: z.string().optional(), command_id: z.string().optional() }).optional(), confirmed: z.boolean().default(false) }, annotations: { readOnlyHint: false, destructiveHint: true } },
218
219
  ({ child_id, expected_revision, status, runtime_identity, confirmed }) => confirmed ? client.request(`/api/v2/fleet/workloads/${encodeURIComponent(child_id)}/observations`, { method: 'POST', body: requireSafePayload({ expected_revision, status, ...(runtime_identity ? { runtime_identity } : {}) }, 'fleet observation'), validate: requireFleetRecord }) : confirmationError('sandbox-fleet-observe'));
219
- register('sandbox-fleet-reconcile', { title: 'Reconcile fleet workloads', description: 'Mutating restart reconciliation; requires confirmed=true.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), before_revision: z.number().int().min(0), child_ids: z.array(z.string().min(1).max(255)).max(1000), confirmed: z.boolean().default(false) }, annotations: { readOnlyHint: false, destructiveHint: true } },
220
+ register('sandbox-fleet-reconcile', { title: 'Reconcile fleet workloads', description: 'Mutating restart reconciliation; requires confirmed=true.', inputSchema: { contract_version: z.literal(FLEET_VERSION).default(FLEET_VERSION), before_revision: integer().min(0), child_ids: z.array(z.string().min(1).max(255)).max(1000), confirmed: z.boolean().default(false) }, annotations: { readOnlyHint: false, destructiveHint: true } },
220
221
  ({ before_revision, child_ids, confirmed }) => confirmed ? client.request('/api/v2/fleet/reconcile', { method: 'POST', body: { before_revision, child_ids }, validate: requireReconciliation }) : confirmationError('sandbox-fleet-reconcile'));
221
222
 
222
223
  for (const [kind, eventsRequired] of [['coverage', false], ['timeline', true]]) {
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import { z } from 'zod';
20
+ import { integer } from '../../schema/zod-validation.mjs';
20
21
  import {
21
22
  runAiwgCli,
22
23
  loadCommandAllowList,
@@ -31,10 +32,10 @@ export function registerCommandRunTool(server) {
31
32
  description: 'Dispatch to any allow-listed AIWG CLI command (`aiwg <command> [args...]`). The full surface of ~94 commands is reachable. Destructive commands require `confirmed: true`.',
32
33
  inputSchema: {
33
34
  command: z.string().describe('Command name from `command-list` (e.g. "use", "discover", "doctor")'),
34
- args: z.array(z.string()).default([]).describe('Argument array (never shell-interpreted)'),
35
+ args: z.array(z.string()).prefault([]).describe('Argument array (never shell-interpreted)'),
35
36
  project_dir: z.string().optional().describe('Working directory for the command'),
36
37
  confirmed: z.boolean().default(false).describe('Required for destructive commands (true to proceed)'),
37
- timeout_ms: z.number().int().min(1000).max(600_000).default(120_000).describe('Timeout (1s–10min, default 2min)'),
38
+ timeout_ms: integer().min(1000).max(600_000).default(120_000).describe('Timeout (1s–10min, default 2min)'),
38
39
  },
39
40
  annotations: {
40
41
  destructiveHint: true, // conservative default; many subcommands are read-only but the surface is broad
@@ -1,102 +1,103 @@
1
1
  import { z } from 'zod';
2
+ import { integer, number } from '../../schema/zod-validation.mjs';
2
3
  import { mcpError, mcpJson } from '../helpers.mjs';
3
4
 
4
5
  const STATUS = z.enum(['success', 'abstained', 'denied', 'unavailable', 'error', 'cancelled']);
5
- const ENVELOPE = z.object({ schema: z.string() }).passthrough();
6
- const LIVE_PLAN = z.object({ mode: z.literal('live'), status: z.enum(['ready', 'skipped', 'denied']), reason: z.string(), executes: z.literal(false) }).passthrough();
7
- const PATTERN_SUMMARY = z.object({
6
+ const ENVELOPE = z.looseObject({ schema: z.string() });
7
+ const LIVE_PLAN = z.looseObject({ mode: z.literal('live'), status: z.enum(['ready', 'skipped', 'denied']), reason: z.string(), executes: z.literal(false) });
8
+ const PATTERN_SUMMARY = z.looseObject({
8
9
  id: z.string(),
9
10
  version: z.string(),
10
11
  status: z.string(),
11
12
  primitive: z.string().optional(),
12
- }).passthrough();
13
- const PATTERN_LIST = z.object({
13
+ });
14
+ const PATTERN_LIST = z.looseObject({
14
15
  schema: z.literal('aiwg-decision-pattern-list/v1'),
15
16
  patterns: z.array(PATTERN_SUMMARY),
16
- }).passthrough();
17
- const PATTERN_SHOW = z.object({
17
+ });
18
+ const PATTERN_SHOW = z.looseObject({
18
19
  schema: z.literal('aiwg-decision-pattern-show/v1'),
19
20
  pack: PATTERN_SUMMARY,
20
- inputSchema: z.unknown(),
21
- outputSchema: z.unknown(),
21
+ inputSchema: z.unknown().optional(),
22
+ outputSchema: z.unknown().optional(),
22
23
  livePlan: LIVE_PLAN,
23
- }).passthrough();
24
- const COMPACT_EVALUATION = z.object({
24
+ });
25
+ const COMPACT_EVALUATION = z.looseObject({
25
26
  status: STATUS,
26
27
  reason: z.string(),
27
- attempts: z.number(),
28
- value: z.unknown().nullable(),
29
- }).passthrough();
30
- const COMPACT_RESULT = z.object({
28
+ attempts: number(),
29
+ value: z.unknown().nullable().optional(),
30
+ });
31
+ const COMPACT_RESULT = z.looseObject({
31
32
  status: STATUS,
32
33
  rulesetStatus: z.string(),
33
34
  reason: z.string(),
34
35
  matchedRules: z.array(z.string()),
35
36
  evaluations: z.record(z.string(), COMPACT_EVALUATION),
36
- outcome: z.unknown().nullable(),
37
- }).passthrough();
38
- const EVALUATE = z.object({
37
+ outcome: z.unknown().nullable().optional(),
38
+ });
39
+ const EVALUATE = z.looseObject({
39
40
  schema: z.literal('aiwg-decision-evaluate/v1'),
40
41
  status: STATUS,
41
42
  reason: z.string(),
42
- exitCode: z.number(),
43
+ exitCode: number(),
43
44
  compact: COMPACT_RESULT.nullable().optional(),
44
- result: z.unknown().nullable(),
45
- }).passthrough();
46
- const VALIDATION = z.object({
45
+ result: z.unknown().nullable().optional(),
46
+ });
47
+ const VALIDATION = z.looseObject({
47
48
  schema: z.literal('aiwg-decision-validation/v1'),
48
49
  target: z.enum(['request', 'definition', 'ruleset', 'binding']),
49
50
  valid: z.boolean(),
50
- summary: z.object({
51
+ summary: z.looseObject({
51
52
  kind: z.string().nullable().optional(),
52
53
  apiVersion: z.string().nullable().optional(),
53
54
  id: z.string().nullable().optional(),
54
55
  version: z.string().nullable().optional(),
55
- }).passthrough(),
56
+ }),
56
57
  errors: z.array(z.string()),
57
- }).passthrough();
58
- const CAPABILITIES = z.object({
58
+ });
59
+ const CAPABILITIES = z.looseObject({
59
60
  schema: z.literal('aiwg-decision-driver-capabilities/v1'),
60
61
  enabled: z.boolean(),
61
62
  offlineReady: z.boolean(),
62
63
  primitives: z.array(z.string()),
63
- backend: z.object({ configured: z.boolean(), probed: z.boolean(), status: z.literal('not-probed') }).passthrough(),
64
- }).passthrough();
65
- const OFFLINE_RUN = z.object({
64
+ backend: z.looseObject({ configured: z.boolean(), probed: z.boolean(), status: z.literal('not-probed') }),
65
+ });
66
+ const OFFLINE_RUN = z.looseObject({
66
67
  schema: z.literal('aiwg-decision-offline-run/v1'),
67
68
  status: STATUS,
68
69
  route: z.enum(['accept', 'review', 'deny']),
69
70
  reason: z.string(),
70
- evaluations: z.array(z.object({
71
+ evaluations: z.array(z.looseObject({
71
72
  alias: z.string(),
72
73
  primitive: z.string(),
73
74
  status: z.string(),
74
75
  reason: z.string(),
75
- attempts: z.number(),
76
- value: z.unknown(),
77
- }).passthrough()),
76
+ attempts: number(),
77
+ value: z.unknown().optional(),
78
+ })),
78
79
  checks: z.array(z.unknown()),
79
- }).passthrough();
80
- const SYNTHETIC_SETUP = z.object({
80
+ });
81
+ const SYNTHETIC_SETUP = z.looseObject({
81
82
  schema: z.literal('aiwg-decision-synthetic-setup/v1'),
82
- pattern: z.object({ id: z.string(), version: z.string() }).passthrough(),
83
+ pattern: z.looseObject({ id: z.string(), version: z.string() }),
83
84
  files: z.record(z.string(), z.unknown()),
84
- }).passthrough();
85
- const ASK = z.object({
85
+ });
86
+ const ASK = z.looseObject({
86
87
  schema: z.literal('aiwg-decision-ask/v1'),
87
88
  status: z.enum(['answered', 'fallback']),
88
- answer: z.union([z.string(), z.boolean(), z.number()]).nullable(),
89
+ answer: z.union([z.string(), z.boolean(), number()]).nullable(),
89
90
  confidence: z.number().min(0).max(1).nullable(),
90
91
  probability: z.number().min(0).max(1).optional(),
91
92
  fallback: z.enum(['llm']).nullable(),
92
93
  reason: z.string(),
93
94
  model: z.string().nullable(),
94
95
  usage: z.object({
95
- inputTokens: z.number().int().nonnegative().nullable(),
96
- outputTokens: z.number().int().nonnegative().nullable(),
96
+ inputTokens: integer().nonnegative().nullable(),
97
+ outputTokens: integer().nonnegative().nullable(),
97
98
  }).nullable(),
98
- latencyMs: z.number().nonnegative(),
99
- }).passthrough();
99
+ latencyMs: number().check(z.gte(0)).meta({ type: 'number', minimum: 0 }),
100
+ });
100
101
 
101
102
  let driverPromise;
102
103
 
@@ -176,7 +177,7 @@ export function registerDecisionToolset(server) {
176
177
  description: 'Validate a request, definition, ruleset or binding without importing modules or resolving secrets.',
177
178
  inputSchema: {
178
179
  target: z.enum(['request', 'definition', 'ruleset', 'binding']),
179
- document: z.unknown().describe('Inline decision document or dispatcher request object'),
180
+ document: z.unknown().optional().describe('Inline decision document or dispatcher request object'),
180
181
  },
181
182
  annotations: { readOnlyHint: true },
182
183
  }, async ({ target, document }) => (await driver()).validateDecisionValue(target, document, 'mcp-inline'), VALIDATION);
@@ -198,12 +199,12 @@ export function registerDecisionToolset(server) {
198
199
  question: z.string().min(1),
199
200
  yes_no: z.boolean().default(false),
200
201
  choices: z.array(z.string()).min(2).max(255).optional().describe('Choice ids, each optionally written as id=description so Jev knows what the option means'),
201
- scale: z.object({ low: z.number().int(), high: z.number().int() }).optional(),
202
+ scale: z.object({ low: integer(), high: integer() }).optional(),
202
203
  context: z.string().optional(),
203
204
  threshold: z.number().min(0).max(1).default(0.8),
204
- timeout_ms: z.number().int().positive().default(15000),
205
- passes: z.number().int().positive().optional(),
206
- escalation_passes: z.number().int().positive().optional(),
205
+ timeout_ms: integer().positive().default(15000),
206
+ passes: integer().positive().optional(),
207
+ escalation_passes: integer().positive().optional(),
207
208
  caller: z.string().optional().describe('Optional decision-point attribution tag, recorded in the decision ledger (#2946)'),
208
209
  },
209
210
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },