cognitive-modules-cli 2.2.11 → 2.2.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.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Conformance Command
3
+ *
4
+ * Goal: make "official vectors" runnable as a stable contract surface.
5
+ * This is intentionally offline and deterministic: it validates static vectors
6
+ * against the JSON Schemas in `spec/`.
7
+ */
8
+ import type { CommandContext, CommandResult } from '../types.js';
9
+ type ConformanceLevel = 1 | 2 | 3;
10
+ type ConformanceSuite = 'envelope' | 'stream' | 'registry' | 'runtime' | 'all';
11
+ export type ConformanceFailurePhase = 'read' | 'parse' | 'schema' | 'entry' | 'event' | 'end.result' | 'runtime.invoke' | 'runtime.expect';
12
+ export type ConformanceVectorResult = {
13
+ file: string;
14
+ name: string;
15
+ expects: 'accept' | 'reject';
16
+ level: number;
17
+ passed: boolean;
18
+ isValid: boolean;
19
+ phase?: ConformanceFailurePhase;
20
+ error?: string;
21
+ };
22
+ export type ConformanceRunResult = {
23
+ spec_dir: string;
24
+ suite: ConformanceSuite;
25
+ level: ConformanceLevel;
26
+ total: number;
27
+ passed: number;
28
+ failed: number;
29
+ results: ConformanceVectorResult[];
30
+ };
31
+ export type ConformanceOptions = {
32
+ specDir?: string;
33
+ suite?: ConformanceSuite;
34
+ level?: number;
35
+ verbose?: boolean;
36
+ json?: boolean;
37
+ };
38
+ export declare function conformance(ctx: CommandContext, options?: ConformanceOptions): Promise<CommandResult>;
39
+ export {};
@@ -0,0 +1,517 @@
1
+ /**
2
+ * Conformance Command
3
+ *
4
+ * Goal: make "official vectors" runnable as a stable contract surface.
5
+ * This is intentionally offline and deterministic: it validates static vectors
6
+ * against the JSON Schemas in `spec/`.
7
+ */
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import _Ajv from 'ajv';
11
+ const Ajv = _Ajv.default || _Ajv;
12
+ import _addFormats from 'ajv-formats';
13
+ const addFormats = _addFormats.default || _addFormats;
14
+ import { runModule } from '../modules/runner.js';
15
+ import { resolveExecutionPolicy } from '../profile.js';
16
+ function findJsonFiles(dir) {
17
+ const out = [];
18
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ const full = path.join(dir, entry.name);
21
+ if (entry.isDirectory())
22
+ out.push(...findJsonFiles(full));
23
+ else if (entry.isFile() && entry.name.endsWith('.json'))
24
+ out.push(full);
25
+ }
26
+ return out.sort();
27
+ }
28
+ function detectSpecDir(startCwd) {
29
+ // Search upward for `<root>/spec/response-envelope.schema.json`.
30
+ let cur = startCwd;
31
+ for (let i = 0; i < 20; i++) {
32
+ const specDir = path.join(cur, 'spec');
33
+ const schema = path.join(specDir, 'response-envelope.schema.json');
34
+ if (fs.existsSync(schema))
35
+ return specDir;
36
+ const parent = path.dirname(cur);
37
+ if (parent === cur)
38
+ break;
39
+ cur = parent;
40
+ }
41
+ return null;
42
+ }
43
+ function normalizeSpecDir(specDirRaw) {
44
+ const p = path.resolve(specDirRaw);
45
+ // Accept either `<root>/spec` or `<root>` (and normalize to `<root>/spec`).
46
+ if (path.basename(p) !== 'spec') {
47
+ const candidate = path.join(p, 'spec');
48
+ if (fs.existsSync(path.join(candidate, 'response-envelope.schema.json')))
49
+ return candidate;
50
+ }
51
+ return p;
52
+ }
53
+ function readJson(p) {
54
+ const raw = fs.readFileSync(p, 'utf8');
55
+ return JSON.parse(raw);
56
+ }
57
+ function relFromSpec(specDir, file) {
58
+ // Prefer paths like `spec/test-vectors/...` instead of absolute paths.
59
+ const root = path.dirname(specDir);
60
+ return path.relative(root, file);
61
+ }
62
+ function clampLevel(n) {
63
+ const v = Number(n ?? 1);
64
+ if (v === 1 || v === 2 || v === 3)
65
+ return v;
66
+ throw new Error(`Invalid conformance level: ${String(n)} (expected 1|2|3)`);
67
+ }
68
+ function clampSuite(s) {
69
+ const v = (s ?? 'envelope').trim();
70
+ if (v === 'envelope' || v === 'stream' || v === 'registry' || v === 'runtime' || v === 'all')
71
+ return v;
72
+ throw new Error(`Invalid --suite: ${v} (expected envelope|stream|registry|runtime|all)`);
73
+ }
74
+ function formatAjvErrors(errors) {
75
+ const list = errors ?? [];
76
+ return list.map(e => `${e.instancePath ?? ''} ${e.message ?? ''}`.trim()).join('; ');
77
+ }
78
+ function validateEnvelopeVectors(specDir, level) {
79
+ const schemaPath = path.join(specDir, 'response-envelope.schema.json');
80
+ const vectorsDir = path.join(specDir, 'test-vectors');
81
+ const ajv = new Ajv({ allErrors: true, strict: false });
82
+ addFormats(ajv);
83
+ const schema = readJson(schemaPath);
84
+ const validate = ajv.compile(schema);
85
+ const files = fs.existsSync(vectorsDir) ? findJsonFiles(vectorsDir) : [];
86
+ const results = [];
87
+ for (const file of files) {
88
+ let meta;
89
+ let envelope;
90
+ try {
91
+ const parsed = readJson(file);
92
+ meta = parsed?.$test ?? {};
93
+ envelope = parsed?.envelope ?? {};
94
+ }
95
+ catch (e) {
96
+ results.push({
97
+ file: relFromSpec(specDir, file),
98
+ name: path.basename(file),
99
+ expects: 'reject',
100
+ level: 1,
101
+ passed: false,
102
+ isValid: false,
103
+ phase: 'parse',
104
+ error: e instanceof Error ? e.message : String(e),
105
+ });
106
+ continue;
107
+ }
108
+ const vecLevel = Number(meta?.conformance_level ?? 1);
109
+ if (vecLevel > level)
110
+ continue;
111
+ const expects = (meta?.expects ?? 'accept');
112
+ const name = (meta?.name ?? path.basename(file, '.json'));
113
+ const valid = Boolean(validate(envelope));
114
+ const error = valid ? undefined : formatAjvErrors(validate.errors);
115
+ const passed = expects === 'accept' ? valid : !valid;
116
+ results.push({
117
+ file: relFromSpec(specDir, file),
118
+ name,
119
+ expects,
120
+ level: vecLevel,
121
+ passed,
122
+ isValid: valid,
123
+ phase: valid ? undefined : 'schema',
124
+ error,
125
+ });
126
+ }
127
+ return results;
128
+ }
129
+ function validateStreamVectors(specDir, level) {
130
+ const eventsSchemaPath = path.join(specDir, 'stream-events.schema.json');
131
+ const envelopeSchemaPath = path.join(specDir, 'response-envelope.schema.json');
132
+ const vectorsDir = path.join(specDir, 'stream-vectors');
133
+ const ajv = new Ajv({ allErrors: true, strict: false });
134
+ addFormats(ajv);
135
+ const eventsSchema = readJson(eventsSchemaPath);
136
+ const envelopeSchema = readJson(envelopeSchemaPath);
137
+ const validateEvent = ajv.compile(eventsSchema);
138
+ const validateEnvelope = ajv.compile(envelopeSchema);
139
+ const files = fs.existsSync(vectorsDir) ? findJsonFiles(vectorsDir) : [];
140
+ const results = [];
141
+ for (const file of files) {
142
+ let vector;
143
+ try {
144
+ vector = readJson(file);
145
+ }
146
+ catch (e) {
147
+ results.push({
148
+ file: relFromSpec(specDir, file),
149
+ name: path.basename(file),
150
+ expects: 'reject',
151
+ level: 1,
152
+ passed: false,
153
+ isValid: false,
154
+ phase: 'parse',
155
+ error: e instanceof Error ? e.message : String(e),
156
+ });
157
+ continue;
158
+ }
159
+ const meta = vector?.$test ?? {};
160
+ const expects = (meta?.expects ?? 'accept');
161
+ const vecLevel = Number(meta?.conformance_level ?? 1);
162
+ if (vecLevel > level)
163
+ continue;
164
+ const name = (meta?.name ?? path.basename(file, '.json'));
165
+ let ok = true;
166
+ let err = '';
167
+ let phase;
168
+ try {
169
+ if (!Array.isArray(vector?.events)) {
170
+ throw new Error('events must be an array');
171
+ }
172
+ for (const ev of vector.events) {
173
+ if (!validateEvent(ev)) {
174
+ phase = 'event';
175
+ throw new Error(`event invalid: ${formatAjvErrors(validateEvent.errors)}`);
176
+ }
177
+ const evAny = ev;
178
+ if (evAny?.type === 'end') {
179
+ const env = evAny?.result;
180
+ if (!validateEnvelope(env)) {
181
+ phase = 'end.result';
182
+ throw new Error(`end.result invalid envelope: ${formatAjvErrors(validateEnvelope.errors)}`);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ catch (e) {
188
+ ok = false;
189
+ err = e instanceof Error ? e.message : String(e);
190
+ }
191
+ const passed = expects === 'accept' ? ok : !ok;
192
+ results.push({
193
+ file: relFromSpec(specDir, file),
194
+ name,
195
+ expects,
196
+ level: vecLevel,
197
+ passed,
198
+ isValid: ok,
199
+ phase: ok ? undefined : (phase ?? 'schema'),
200
+ error: ok ? undefined : err,
201
+ });
202
+ }
203
+ return results;
204
+ }
205
+ function validateRegistryVectors(specDir, level) {
206
+ const registrySchemaPath = path.join(specDir, 'registry.schema.json');
207
+ const entrySchemaPath = path.join(specDir, 'registry-entry.schema.json');
208
+ const vectorsDir = path.join(specDir, 'registry-vectors');
209
+ const repoRoot = path.dirname(specDir);
210
+ const ajv = new Ajv({ allErrors: true, strict: false });
211
+ addFormats(ajv);
212
+ const registrySchema = readJson(registrySchemaPath);
213
+ const entrySchema = readJson(entrySchemaPath);
214
+ // The registry schema references the entry schema via $ref. Add aliases so Ajv resolves offline.
215
+ ajv.addSchema(entrySchema, 'https://cognitive-modules.dev/schema/registry-entry-v1.json');
216
+ ajv.addSchema(entrySchema, 'registry-entry.schema.json');
217
+ ajv.addSchema(entrySchema, 'https://cognitive-modules.dev/schema/registry-entry.schema.json');
218
+ const validateRegistry = ajv.compile(registrySchema);
219
+ const validateEntry = ajv.compile(entrySchema);
220
+ const files = fs.existsSync(vectorsDir) ? findJsonFiles(vectorsDir) : [];
221
+ // Also validate the repo's published registry file (if present).
222
+ const publishedRegistryPath = path.join(repoRoot, 'cognitive-registry.v2.json');
223
+ if (fs.existsSync(publishedRegistryPath))
224
+ files.unshift(publishedRegistryPath);
225
+ const results = [];
226
+ for (const file of files) {
227
+ let parsed;
228
+ try {
229
+ parsed = readJson(file);
230
+ }
231
+ catch (e) {
232
+ results.push({
233
+ file: relFromSpec(specDir, file),
234
+ name: path.basename(file),
235
+ expects: 'reject',
236
+ level: 1,
237
+ passed: false,
238
+ isValid: false,
239
+ phase: 'parse',
240
+ error: e instanceof Error ? e.message : String(e),
241
+ });
242
+ continue;
243
+ }
244
+ const isVector = parsed && typeof parsed === 'object' && '$test' in parsed && 'registry' in parsed;
245
+ const meta = (isVector ? parsed.$test : null) ?? { name: path.basename(file), expects: 'accept', conformance_level: 1 };
246
+ const expects = (meta?.expects ?? 'accept');
247
+ const vecLevel = Number(meta?.conformance_level ?? 1);
248
+ if (vecLevel > level)
249
+ continue;
250
+ const name = (meta?.name ?? path.basename(file, '.json'));
251
+ let ok = true;
252
+ let err = '';
253
+ let phase;
254
+ try {
255
+ const registryObj = isVector ? parsed.registry : parsed;
256
+ if (!registryObj || typeof registryObj !== 'object')
257
+ throw new Error('registry must be an object');
258
+ if (!validateRegistry(registryObj)) {
259
+ phase = 'schema';
260
+ throw new Error(`registry invalid: ${formatAjvErrors(validateRegistry.errors)}`);
261
+ }
262
+ const modules = registryObj.modules ?? {};
263
+ if (typeof modules !== 'object' || Array.isArray(modules)) {
264
+ phase = 'schema';
265
+ throw new Error('registry.modules must be an object map');
266
+ }
267
+ for (const [modName, entry] of Object.entries(modules)) {
268
+ if (!validateEntry(entry)) {
269
+ phase = 'entry';
270
+ throw new Error(`entry '${modName}' invalid: ${formatAjvErrors(validateEntry.errors)}`);
271
+ }
272
+ }
273
+ }
274
+ catch (e) {
275
+ ok = false;
276
+ err = e instanceof Error ? e.message : String(e);
277
+ }
278
+ const passed = expects === 'accept' ? ok : !ok;
279
+ results.push({
280
+ file: relFromSpec(specDir, file),
281
+ name,
282
+ expects,
283
+ level: vecLevel,
284
+ passed,
285
+ isValid: ok,
286
+ phase: ok ? undefined : (phase ?? 'schema'),
287
+ error: ok ? undefined : err,
288
+ });
289
+ }
290
+ return results;
291
+ }
292
+ class VectorProvider {
293
+ name = 'vector';
294
+ calls = 0;
295
+ caps;
296
+ queue;
297
+ constructor(caps, queue) {
298
+ this.caps = caps;
299
+ this.queue = [...queue];
300
+ }
301
+ isConfigured() {
302
+ return true;
303
+ }
304
+ getCapabilities() {
305
+ return this.caps;
306
+ }
307
+ async invoke() {
308
+ this.calls++;
309
+ const next = this.queue.shift();
310
+ if (!next)
311
+ return { content: '' };
312
+ if (next.error)
313
+ throw new Error(next.error);
314
+ return { content: String(next.content ?? '') };
315
+ }
316
+ }
317
+ async function validateRuntimeVectors(specDir, level) {
318
+ const envelopeSchemaPath = path.join(specDir, 'response-envelope.schema.json');
319
+ const vectorsDir = path.join(specDir, 'runtime-vectors');
320
+ const validDir = path.join(vectorsDir, 'valid');
321
+ const invalidDir = path.join(vectorsDir, 'invalid');
322
+ const ajv = new Ajv({ allErrors: true, strict: false });
323
+ addFormats(ajv);
324
+ const envelopeSchema = readJson(envelopeSchemaPath);
325
+ const validateEnvelope = ajv.compile(envelopeSchema);
326
+ // Only treat `valid/` and `invalid/` as vectors (ignore fixtures and other JSON files).
327
+ const files = [
328
+ ...(fs.existsSync(validDir) ? findJsonFiles(validDir) : []),
329
+ ...(fs.existsSync(invalidDir) ? findJsonFiles(invalidDir) : []),
330
+ ];
331
+ const results = [];
332
+ for (const file of files) {
333
+ let vector;
334
+ try {
335
+ vector = readJson(file);
336
+ }
337
+ catch (e) {
338
+ results.push({
339
+ file: relFromSpec(specDir, file),
340
+ name: path.basename(file),
341
+ expects: 'reject',
342
+ level: 1,
343
+ passed: false,
344
+ isValid: false,
345
+ phase: 'parse',
346
+ error: e instanceof Error ? e.message : String(e),
347
+ });
348
+ continue;
349
+ }
350
+ const meta = vector?.$test ?? {};
351
+ const expects = (meta?.expects ?? 'accept');
352
+ const vecLevel = Number(meta?.conformance_level ?? 2);
353
+ if (vecLevel > level)
354
+ continue;
355
+ const name = (meta?.name ?? path.basename(file, '.json'));
356
+ let ok = true;
357
+ let err = '';
358
+ let phase;
359
+ try {
360
+ const rt = vector.runtime;
361
+ if (!rt)
362
+ throw new Error('runtime must be present');
363
+ const policy = resolveExecutionPolicy({
364
+ profile: rt.policy?.profile ?? 'standard',
365
+ validate: rt.policy?.validate ?? null,
366
+ noValidate: rt.policy?.noValidate ?? false,
367
+ audit: rt.policy?.audit ?? false,
368
+ structured: rt.policy?.structured ?? null,
369
+ });
370
+ const moduleFromVec = rt.module ?? {};
371
+ // Allow vectors to reference repo-relative module locations so the suite is portable.
372
+ const repoRoot = path.dirname(specDir);
373
+ const locRaw = moduleFromVec.location ?? file;
374
+ const loc = typeof locRaw === 'string' && locRaw.trim() && !path.isAbsolute(locRaw)
375
+ ? path.resolve(repoRoot, locRaw)
376
+ : locRaw;
377
+ const mod = {
378
+ name: moduleFromVec.name ?? 'vector',
379
+ version: moduleFromVec.version ?? '0.0.0',
380
+ responsibility: moduleFromVec.responsibility ?? 'vector',
381
+ prompt: moduleFromVec.prompt ?? 'Return JSON only.',
382
+ excludes: moduleFromVec.excludes ?? [],
383
+ format: moduleFromVec.format ?? 'v2',
384
+ formatVersion: moduleFromVec.formatVersion ?? 'v2.2',
385
+ tier: moduleFromVec.tier ?? 'decision',
386
+ ...moduleFromVec,
387
+ // Ensure the resolved location wins over the raw vector string.
388
+ location: loc,
389
+ };
390
+ const caps = rt.provider?.capabilities ?? { structuredOutput: 'prompt', streaming: false };
391
+ const provider = new VectorProvider(caps, rt.provider?.responses ?? []);
392
+ const res = await runModule(mod, provider, {
393
+ args: rt.args,
394
+ input: rt.input,
395
+ useV22: true,
396
+ policy,
397
+ });
398
+ if (!validateEnvelope(res)) {
399
+ phase = 'schema';
400
+ throw new Error(`result invalid envelope: ${formatAjvErrors(validateEnvelope.errors)}`);
401
+ }
402
+ const exp = rt.expect ?? {};
403
+ if (typeof exp.invocations === 'number' && provider.calls !== exp.invocations) {
404
+ phase = 'runtime.expect';
405
+ throw new Error(`expected invocations=${exp.invocations}, got=${provider.calls}`);
406
+ }
407
+ if (typeof exp.ok === 'boolean' && res.ok !== exp.ok) {
408
+ phase = 'runtime.expect';
409
+ throw new Error(`expected ok=${String(exp.ok)}, got=${String(res.ok)}`);
410
+ }
411
+ if (exp.error_code) {
412
+ const code = res?.error?.code;
413
+ if (code !== exp.error_code) {
414
+ phase = 'runtime.expect';
415
+ throw new Error(`expected error.code=${exp.error_code}, got=${String(code)}`);
416
+ }
417
+ }
418
+ if (typeof exp.parse_retries === 'number') {
419
+ const retries = res?.meta?.policy?.parse?.retries;
420
+ if (retries !== exp.parse_retries) {
421
+ phase = 'runtime.expect';
422
+ throw new Error(`expected meta.policy.parse.retries=${exp.parse_retries}, got=${String(retries)}`);
423
+ }
424
+ }
425
+ if (exp.parse_strategy) {
426
+ const strategy = res?.meta?.policy?.parse?.strategy;
427
+ if (strategy !== exp.parse_strategy) {
428
+ phase = 'runtime.expect';
429
+ throw new Error(`expected meta.policy.parse.strategy=${exp.parse_strategy}, got=${String(strategy)}`);
430
+ }
431
+ }
432
+ if (typeof exp.parse_retry_attempted === 'boolean') {
433
+ const attempted = res?.error?.details?.parse_retry?.attempted;
434
+ if (attempted !== exp.parse_retry_attempted) {
435
+ phase = 'runtime.expect';
436
+ throw new Error(`expected error.details.parse_retry.attempted=${String(exp.parse_retry_attempted)}, got=${String(attempted)}`);
437
+ }
438
+ }
439
+ }
440
+ catch (e) {
441
+ ok = false;
442
+ err = e instanceof Error ? e.message : String(e);
443
+ }
444
+ const passed = expects === 'accept' ? ok : !ok;
445
+ results.push({
446
+ file: relFromSpec(specDir, file),
447
+ name,
448
+ expects,
449
+ level: vecLevel,
450
+ passed,
451
+ isValid: ok,
452
+ phase: ok ? undefined : (phase ?? 'runtime.expect'),
453
+ error: ok ? undefined : err,
454
+ });
455
+ }
456
+ return results;
457
+ }
458
+ export async function conformance(ctx, options = {}) {
459
+ const suite = clampSuite(options.suite);
460
+ const level = clampLevel(options.level);
461
+ const specDir = options.specDir ? normalizeSpecDir(options.specDir) : (detectSpecDir(ctx.cwd) ?? '');
462
+ if (!specDir) {
463
+ return {
464
+ success: false,
465
+ error: 'Spec directory not found. Run from a repo checkout or pass --spec-dir <path-to-repo-or-spec>.',
466
+ };
467
+ }
468
+ const envelopeSchema = path.join(specDir, 'response-envelope.schema.json');
469
+ if (!fs.existsSync(envelopeSchema)) {
470
+ return {
471
+ success: false,
472
+ error: `Spec directory is missing response-envelope.schema.json: ${specDir}`,
473
+ };
474
+ }
475
+ let results = [];
476
+ try {
477
+ if (suite === 'envelope' || suite === 'all') {
478
+ results = results.concat(validateEnvelopeVectors(specDir, level));
479
+ }
480
+ if (suite === 'stream' || suite === 'all') {
481
+ results = results.concat(validateStreamVectors(specDir, level));
482
+ }
483
+ if (suite === 'registry' || suite === 'all') {
484
+ results = results.concat(validateRegistryVectors(specDir, level));
485
+ }
486
+ if (suite === 'runtime' || suite === 'all') {
487
+ results = results.concat(await validateRuntimeVectors(specDir, level));
488
+ }
489
+ }
490
+ catch (e) {
491
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
492
+ }
493
+ const passed = results.filter(r => r.passed).length;
494
+ const failed = results.length - passed;
495
+ const data = {
496
+ spec_dir: specDir,
497
+ suite,
498
+ level,
499
+ total: results.length,
500
+ passed,
501
+ failed,
502
+ results,
503
+ };
504
+ if (options.verbose && failed > 0) {
505
+ for (const r of results) {
506
+ if (r.passed)
507
+ continue;
508
+ // eslint-disable-next-line no-console
509
+ console.error(`FAIL [${r.phase ?? 'unknown'}] ${r.file}: ${r.error ?? 'invalid'}`);
510
+ }
511
+ }
512
+ return {
513
+ success: failed === 0,
514
+ data,
515
+ error: failed === 0 ? undefined : `${failed} conformance vector(s) failed`,
516
+ };
517
+ }
@@ -13,5 +13,6 @@ export * from './compose.js';
13
13
  export * from './validate.js';
14
14
  export * from './migrate.js';
15
15
  export * from './test.js';
16
+ export * from './conformance.js';
16
17
  export * from './search.js';
17
18
  export * from './core.js';
@@ -13,5 +13,6 @@ export * from './compose.js';
13
13
  export * from './validate.js';
14
14
  export * from './migrate.js';
15
15
  export * from './test.js';
16
+ export * from './conformance.js';
16
17
  export * from './search.js';
17
18
  export * from './core.js';
@@ -28,7 +28,7 @@ export async function pipe(ctx, options) {
28
28
  const errorEnvelope = attachContext(makeErrorEnvelope({
29
29
  code: ErrorCodes.INVALID_INPUT,
30
30
  message: `Certified profile requires v2.2 modules; got: ${module.formatVersion ?? 'unknown'} (${module.format})`,
31
- suggestion: "Migrate the module to v2.2, or run with `--profile strict` / `--profile default`",
31
+ suggestion: "Migrate the module to v2.2, or rerun with `--profile standard`",
32
32
  }), { module: options.module, provider: ctx.provider.name });
33
33
  console.log(JSON.stringify(errorEnvelope));
34
34
  return { success: false, error: errorEnvelope.error.message, data: errorEnvelope };
@@ -56,26 +56,11 @@ export async function pipe(ctx, options) {
56
56
  catch {
57
57
  // Not JSON, use as args
58
58
  }
59
- const validate = (() => {
60
- if (options.noValidate)
61
- return false;
62
- if (!policy)
63
- return true;
64
- if (policy.validate === 'off')
65
- return false;
66
- if (policy.validate === 'on')
67
- return true;
68
- return policy.profile !== 'core';
69
- })();
70
- const enableRepair = policy?.enableRepair ?? true;
71
59
  // Run module with v2.2 envelope format
72
60
  const result = await runModule(module, ctx.provider, {
73
61
  args: inputData ? undefined : input,
74
62
  input: inputData,
75
- validateInput: validate,
76
- validateOutput: validate,
77
63
  useV22: true, // Always use v2.2 envelope
78
- enableRepair,
79
64
  policy,
80
65
  });
81
66
  const output = attachContext(result, {
@@ -44,7 +44,7 @@ export async function run(moduleName, ctx, options = {}) {
44
44
  const errorEnvelope = attachContext(makeErrorEnvelope({
45
45
  code: ErrorCodes.INVALID_INPUT,
46
46
  message: `Certified profile requires v2.2 modules; got: ${fv ?? 'unknown'} (${module.format})`,
47
- suggestion: "Migrate the module to v2.2 (module.yaml + prompt.md + schema.json), or use `--profile strict` / `--profile default`",
47
+ suggestion: "Migrate the module to v2.2 (module.yaml + prompt.md + schema.json), or rerun with `--profile standard`",
48
48
  }), { module: moduleName, provider: ctx.provider.name });
49
49
  return { success: false, error: errorEnvelope.error.message, data: errorEnvelope };
50
50
  }
@@ -71,21 +71,6 @@ export async function run(moduleName, ctx, options = {}) {
71
71
  };
72
72
  }
73
73
  }
74
- // Resolve validation/repair policy.
75
- // Priority: explicit --no-validate > policy.validate > default(true)
76
- const validate = (() => {
77
- if (options.noValidate)
78
- return false;
79
- if (!policy)
80
- return true;
81
- if (policy.validate === 'off')
82
- return false;
83
- if (policy.validate === 'on')
84
- return true;
85
- // auto
86
- return policy.profile !== 'core';
87
- })();
88
- const enableRepair = policy?.enableRepair ?? true;
89
74
  if (options.stream) {
90
75
  // Stream NDJSON events to stdout. Final exit code is determined by the end event.
91
76
  let finalOk = null;
@@ -93,10 +78,7 @@ export async function run(moduleName, ctx, options = {}) {
93
78
  for await (const ev of runModuleStream(module, ctx.provider, {
94
79
  args: options.args,
95
80
  input: inputData,
96
- validateInput: validate,
97
- validateOutput: validate,
98
81
  useV22: true,
99
- enableRepair,
100
82
  policy,
101
83
  })) {
102
84
  // Write each event as one JSON line (NDJSON).
@@ -132,10 +114,7 @@ export async function run(moduleName, ctx, options = {}) {
132
114
  args: options.args,
133
115
  input: inputData,
134
116
  verbose: options.verbose || ctx.verbose,
135
- validateInput: validate,
136
- validateOutput: validate,
137
117
  useV22: true, // Always use v2.2 envelope
138
- enableRepair,
139
118
  policy,
140
119
  });
141
120
  const output = attachContext(result, {
@@ -185,9 +185,9 @@ export declare class CompositionOrchestrator {
185
185
  private provider;
186
186
  private cwd;
187
187
  private searchPaths;
188
- private validateInput;
189
- private validateOutput;
190
- private enableRepair;
188
+ private validateInput?;
189
+ private validateOutput?;
190
+ private enableRepair?;
191
191
  private policy?;
192
192
  constructor(provider: Provider, cwd?: string, enforcement?: {
193
193
  validateInput?: boolean;
@@ -585,9 +585,10 @@ export class CompositionOrchestrator {
585
585
  this.provider = provider;
586
586
  this.cwd = cwd;
587
587
  this.searchPaths = getDefaultSearchPaths(cwd);
588
- this.validateInput = enforcement.validateInput ?? true;
589
- this.validateOutput = enforcement.validateOutput ?? true;
590
- this.enableRepair = enforcement.enableRepair ?? true;
588
+ // Leave undefined by default so the runner can apply policy-driven defaults (validate=auto).
589
+ this.validateInput = enforcement.validateInput;
590
+ this.validateOutput = enforcement.validateOutput;
591
+ this.enableRepair = enforcement.enableRepair;
591
592
  this.policy = enforcement.policy;
592
593
  }
593
594
  /**