cognitive-modules-cli 2.2.7 → 2.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +25 -3
- package/bin.js +30 -0
- package/dist/audit.d.ts +13 -0
- package/dist/audit.js +25 -0
- package/dist/cli.js +190 -2
- package/dist/commands/add.js +68 -1
- package/dist/commands/compose.d.ts +2 -0
- package/dist/commands/compose.js +60 -1
- package/dist/commands/core.d.ts +31 -0
- package/dist/commands/core.js +338 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/pipe.js +45 -2
- package/dist/commands/run.js +99 -17
- package/dist/commands/search.js +13 -3
- package/dist/commands/update.js +4 -1
- package/dist/modules/composition.d.ts +15 -2
- package/dist/modules/composition.js +16 -6
- package/dist/modules/loader.d.ts +10 -0
- package/dist/modules/loader.js +168 -0
- package/dist/modules/runner.d.ts +3 -1
- package/dist/modules/runner.js +121 -2
- package/dist/profile.d.ts +8 -0
- package/dist/profile.js +59 -0
- package/dist/provenance.d.ts +50 -0
- package/dist/provenance.js +137 -0
- package/dist/registry/assets.d.ts +48 -0
- package/dist/registry/assets.js +723 -0
- package/dist/registry/client.d.ts +8 -1
- package/dist/registry/client.js +83 -29
- package/dist/types.d.ts +31 -0
- package/package.json +4 -3
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Timeout handling
|
|
13
13
|
* - Circular dependency detection
|
|
14
14
|
*/
|
|
15
|
-
import type { CognitiveModule, ModuleResult, ModuleInput, Provider } from '../types.js';
|
|
15
|
+
import type { CognitiveModule, ModuleResult, ModuleInput, Provider, ExecutionPolicy } from '../types.js';
|
|
16
16
|
/** Composition pattern types */
|
|
17
17
|
export type CompositionPattern = 'sequential' | 'parallel' | 'conditional' | 'iterative';
|
|
18
18
|
/** Aggregation strategy for combining multiple outputs */
|
|
@@ -185,7 +185,16 @@ export declare class CompositionOrchestrator {
|
|
|
185
185
|
private provider;
|
|
186
186
|
private cwd;
|
|
187
187
|
private searchPaths;
|
|
188
|
-
|
|
188
|
+
private validateInput;
|
|
189
|
+
private validateOutput;
|
|
190
|
+
private enableRepair;
|
|
191
|
+
private policy?;
|
|
192
|
+
constructor(provider: Provider, cwd?: string, enforcement?: {
|
|
193
|
+
validateInput?: boolean;
|
|
194
|
+
validateOutput?: boolean;
|
|
195
|
+
enableRepair?: boolean;
|
|
196
|
+
policy?: ExecutionPolicy;
|
|
197
|
+
});
|
|
189
198
|
/**
|
|
190
199
|
* Execute a composed module workflow
|
|
191
200
|
*/
|
|
@@ -241,6 +250,10 @@ export declare function executeComposition(moduleName: string, input: ModuleInpu
|
|
|
241
250
|
cwd?: string;
|
|
242
251
|
maxDepth?: number;
|
|
243
252
|
timeoutMs?: number;
|
|
253
|
+
validateInput?: boolean;
|
|
254
|
+
validateOutput?: boolean;
|
|
255
|
+
enableRepair?: boolean;
|
|
256
|
+
policy?: ExecutionPolicy;
|
|
244
257
|
}): Promise<CompositionResult>;
|
|
245
258
|
/**
|
|
246
259
|
* Validate composition configuration
|
|
@@ -577,10 +577,18 @@ export class CompositionOrchestrator {
|
|
|
577
577
|
provider;
|
|
578
578
|
cwd;
|
|
579
579
|
searchPaths;
|
|
580
|
-
|
|
580
|
+
validateInput;
|
|
581
|
+
validateOutput;
|
|
582
|
+
enableRepair;
|
|
583
|
+
policy;
|
|
584
|
+
constructor(provider, cwd = process.cwd(), enforcement = {}) {
|
|
581
585
|
this.provider = provider;
|
|
582
586
|
this.cwd = cwd;
|
|
583
587
|
this.searchPaths = getDefaultSearchPaths(cwd);
|
|
588
|
+
this.validateInput = enforcement.validateInput ?? true;
|
|
589
|
+
this.validateOutput = enforcement.validateOutput ?? true;
|
|
590
|
+
this.enableRepair = enforcement.enableRepair ?? true;
|
|
591
|
+
this.policy = enforcement.policy;
|
|
584
592
|
}
|
|
585
593
|
/**
|
|
586
594
|
* Execute a composed module workflow
|
|
@@ -1160,9 +1168,11 @@ export class CompositionOrchestrator {
|
|
|
1160
1168
|
try {
|
|
1161
1169
|
const result = await runModule(module, this.provider, {
|
|
1162
1170
|
input,
|
|
1163
|
-
validateInput:
|
|
1164
|
-
validateOutput:
|
|
1165
|
-
useV22: true
|
|
1171
|
+
validateInput: this.validateInput,
|
|
1172
|
+
validateOutput: this.validateOutput,
|
|
1173
|
+
useV22: true,
|
|
1174
|
+
enableRepair: this.enableRepair,
|
|
1175
|
+
policy: this.policy,
|
|
1166
1176
|
});
|
|
1167
1177
|
const endTime = Date.now();
|
|
1168
1178
|
trace.push({
|
|
@@ -1278,8 +1288,8 @@ export class CompositionOrchestrator {
|
|
|
1278
1288
|
* Execute a composed module workflow
|
|
1279
1289
|
*/
|
|
1280
1290
|
export async function executeComposition(moduleName, input, provider, options = {}) {
|
|
1281
|
-
const { cwd = process.cwd(), ...execOptions } = options;
|
|
1282
|
-
const orchestrator = new CompositionOrchestrator(provider, cwd);
|
|
1291
|
+
const { cwd = process.cwd(), validateInput, validateOutput, enableRepair, policy, ...execOptions } = options;
|
|
1292
|
+
const orchestrator = new CompositionOrchestrator(provider, cwd, { validateInput, validateOutput, enableRepair, policy });
|
|
1283
1293
|
return orchestrator.execute(moduleName, input, execOptions);
|
|
1284
1294
|
}
|
|
1285
1295
|
/**
|
package/dist/modules/loader.d.ts
CHANGED
|
@@ -7,6 +7,16 @@ import type { CognitiveModule, ModuleTier, SchemaStrictness } from '../types.js'
|
|
|
7
7
|
* Load a Cognitive Module (auto-detects format)
|
|
8
8
|
*/
|
|
9
9
|
export declare function loadModule(modulePath: string): Promise<CognitiveModule>;
|
|
10
|
+
/**
|
|
11
|
+
* Load a single-file module (Markdown) with optional YAML frontmatter.
|
|
12
|
+
*
|
|
13
|
+
* This enables the "5-minute" path: one file runs, and the runtime generates a loose schema/envelope.
|
|
14
|
+
*
|
|
15
|
+
* File format:
|
|
16
|
+
* - Optional YAML frontmatter between --- ... --- (same as v1 MODULE.md)
|
|
17
|
+
* - Body is treated as prompt
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadSingleFileModule(filePath: string): Promise<CognitiveModule>;
|
|
10
20
|
export declare function findModule(name: string, searchPaths: string[]): Promise<CognitiveModule | null>;
|
|
11
21
|
export declare function listModules(searchPaths: string[]): Promise<CognitiveModule[]>;
|
|
12
22
|
export declare function getDefaultSearchPaths(cwd: string): string[];
|
package/dist/modules/loader.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as path from 'node:path';
|
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import yaml from 'js-yaml';
|
|
9
9
|
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
|
|
10
|
+
const SAFE_MODULE_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
10
11
|
/**
|
|
11
12
|
* Detect module format version
|
|
12
13
|
*/
|
|
@@ -333,6 +334,173 @@ export async function loadModule(modulePath) {
|
|
|
333
334
|
return loadModuleV0(modulePath);
|
|
334
335
|
}
|
|
335
336
|
}
|
|
337
|
+
// =============================================================================
|
|
338
|
+
// Single-File Modules (Ad-hoc)
|
|
339
|
+
// =============================================================================
|
|
340
|
+
function coerceModuleName(name, fallback) {
|
|
341
|
+
const raw = (typeof name === 'string' && name.trim().length > 0) ? name.trim() : fallback;
|
|
342
|
+
const normalized = raw
|
|
343
|
+
.replace(/\s+/g, '-')
|
|
344
|
+
.replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
345
|
+
.replace(/-+/g, '-')
|
|
346
|
+
.replace(/^-|-$/g, '');
|
|
347
|
+
const lower = normalized.toLowerCase();
|
|
348
|
+
if (SAFE_MODULE_NAME_REGEX.test(lower))
|
|
349
|
+
return lower;
|
|
350
|
+
return fallback;
|
|
351
|
+
}
|
|
352
|
+
function defaultMetaSchema() {
|
|
353
|
+
return {
|
|
354
|
+
type: 'object',
|
|
355
|
+
additionalProperties: true,
|
|
356
|
+
required: ['confidence', 'risk', 'explain'],
|
|
357
|
+
properties: {
|
|
358
|
+
confidence: { type: 'number', minimum: 0, maximum: 1 },
|
|
359
|
+
risk: { type: 'string', enum: ['none', 'low', 'medium', 'high'] },
|
|
360
|
+
explain: { type: 'string', maxLength: 280 },
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function defaultDataSchema() {
|
|
365
|
+
// Intentionally loose: the "5-minute" path should not fail on business fields.
|
|
366
|
+
return {
|
|
367
|
+
type: 'object',
|
|
368
|
+
additionalProperties: true,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function defaultInputSchema() {
|
|
372
|
+
// Matches runtime behavior: args are mapped to either query or code.
|
|
373
|
+
return {
|
|
374
|
+
type: 'object',
|
|
375
|
+
additionalProperties: true,
|
|
376
|
+
properties: {
|
|
377
|
+
query: { type: 'string' },
|
|
378
|
+
code: { type: 'string' },
|
|
379
|
+
args: { type: 'string' },
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function defaultEnvelopeSchema(metaSchema) {
|
|
384
|
+
// Used as a hint for structured output (provider jsonSchema). Validation uses metaSchema/dataSchema separately.
|
|
385
|
+
return {
|
|
386
|
+
type: 'object',
|
|
387
|
+
additionalProperties: true,
|
|
388
|
+
oneOf: [
|
|
389
|
+
{
|
|
390
|
+
type: 'object',
|
|
391
|
+
required: ['ok', 'meta', 'data'],
|
|
392
|
+
properties: {
|
|
393
|
+
ok: { const: true },
|
|
394
|
+
version: { type: 'string' },
|
|
395
|
+
meta: metaSchema,
|
|
396
|
+
data: { type: 'object', additionalProperties: true },
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
type: 'object',
|
|
401
|
+
required: ['ok', 'meta', 'error'],
|
|
402
|
+
properties: {
|
|
403
|
+
ok: { const: false },
|
|
404
|
+
version: { type: 'string' },
|
|
405
|
+
meta: metaSchema,
|
|
406
|
+
error: {
|
|
407
|
+
type: 'object',
|
|
408
|
+
additionalProperties: true,
|
|
409
|
+
required: ['code', 'message'],
|
|
410
|
+
properties: {
|
|
411
|
+
code: { type: 'string' },
|
|
412
|
+
message: { type: 'string' },
|
|
413
|
+
recoverable: { type: 'boolean' },
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
partial_data: { type: 'object', additionalProperties: true },
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function looksLikeMarkdownModule(filePath) {
|
|
423
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
424
|
+
return ext === '.md' || ext === '.markdown';
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Load a single-file module (Markdown) with optional YAML frontmatter.
|
|
428
|
+
*
|
|
429
|
+
* This enables the "5-minute" path: one file runs, and the runtime generates a loose schema/envelope.
|
|
430
|
+
*
|
|
431
|
+
* File format:
|
|
432
|
+
* - Optional YAML frontmatter between --- ... --- (same as v1 MODULE.md)
|
|
433
|
+
* - Body is treated as prompt
|
|
434
|
+
*/
|
|
435
|
+
export async function loadSingleFileModule(filePath) {
|
|
436
|
+
const absPath = path.resolve(filePath);
|
|
437
|
+
if (!looksLikeMarkdownModule(absPath)) {
|
|
438
|
+
throw new Error(`Single-file modules currently support Markdown (.md) only: ${absPath}`);
|
|
439
|
+
}
|
|
440
|
+
const content = await fs.readFile(absPath, 'utf-8');
|
|
441
|
+
let manifest = {};
|
|
442
|
+
let prompt = content;
|
|
443
|
+
const match = content.match(FRONTMATTER_REGEX);
|
|
444
|
+
if (match) {
|
|
445
|
+
try {
|
|
446
|
+
const loaded = yaml.load(match[1]);
|
|
447
|
+
manifest = (loaded && typeof loaded === 'object') ? loaded : {};
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
manifest = {};
|
|
451
|
+
}
|
|
452
|
+
prompt = (match[2] ?? '').trim();
|
|
453
|
+
}
|
|
454
|
+
const base = path.basename(absPath, path.extname(absPath));
|
|
455
|
+
const name = coerceModuleName(manifest.name, coerceModuleName(base, 'single-file-module'));
|
|
456
|
+
const tier = manifest.tier ?? 'decision';
|
|
457
|
+
const responsibility = manifest.responsibility
|
|
458
|
+
?? `Ad-hoc single-file module loaded from ${path.basename(absPath)}`;
|
|
459
|
+
const excludes = Array.isArray(manifest.excludes) ? manifest.excludes : [];
|
|
460
|
+
const metaSchema = defaultMetaSchema();
|
|
461
|
+
const dataSchema = defaultDataSchema();
|
|
462
|
+
const inputSchema = defaultInputSchema();
|
|
463
|
+
const envelopeSchema = defaultEnvelopeSchema(metaSchema);
|
|
464
|
+
// Keep validation permissive by default, but still enforce envelope meta shape.
|
|
465
|
+
const schemaStrictness = manifest.schema_strictness ?? 'low';
|
|
466
|
+
return {
|
|
467
|
+
name,
|
|
468
|
+
version: manifest.version ?? '0.1.0',
|
|
469
|
+
responsibility,
|
|
470
|
+
excludes,
|
|
471
|
+
constraints: manifest.constraints,
|
|
472
|
+
policies: manifest.policies,
|
|
473
|
+
tools: manifest.tools,
|
|
474
|
+
output: manifest.output ?? { envelope: true, format: 'json_lenient' },
|
|
475
|
+
failure: manifest.failure,
|
|
476
|
+
runtimeRequirements: manifest.runtime_requirements,
|
|
477
|
+
tier,
|
|
478
|
+
schemaStrictness,
|
|
479
|
+
overflow: {
|
|
480
|
+
enabled: true,
|
|
481
|
+
recoverable: true,
|
|
482
|
+
max_items: 20,
|
|
483
|
+
require_suggested_mapping: false,
|
|
484
|
+
},
|
|
485
|
+
enums: { strategy: 'extensible', unknown_tag: 'custom' },
|
|
486
|
+
compat: { accepts_v21_payload: true, runtime_auto_wrap: true, schema_output_alias: 'data' },
|
|
487
|
+
metaConfig: { risk_rule: 'explicit' },
|
|
488
|
+
composition: undefined,
|
|
489
|
+
context: manifest.context,
|
|
490
|
+
prompt: prompt.length > 0 ? prompt : content.trim(),
|
|
491
|
+
// Schemas:
|
|
492
|
+
// - outputSchema is used as provider jsonSchema hint (we want the full envelope)
|
|
493
|
+
// - metaSchema/dataSchema are used for runtime validation after wrapping/repair
|
|
494
|
+
inputSchema,
|
|
495
|
+
outputSchema: envelopeSchema,
|
|
496
|
+
dataSchema,
|
|
497
|
+
metaSchema,
|
|
498
|
+
errorSchema: undefined,
|
|
499
|
+
location: absPath,
|
|
500
|
+
format: 'v2',
|
|
501
|
+
formatVersion: 'v2.2',
|
|
502
|
+
};
|
|
503
|
+
}
|
|
336
504
|
/**
|
|
337
505
|
* Check if a directory contains a valid module
|
|
338
506
|
*/
|
package/dist/modules/runner.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* v2.2: Envelope format with meta/data separation, risk_rule, repair pass
|
|
4
4
|
* v2.2.1: Version field, enhanced error taxonomy, observability hooks, streaming
|
|
5
5
|
*/
|
|
6
|
-
import type { Provider, CognitiveModule, ModuleResult, ModuleInput, EnvelopeResponseV22, EnvelopeMeta, RiskLevel } from '../types.js';
|
|
6
|
+
import type { Provider, CognitiveModule, ModuleResult, ModuleInput, EnvelopeResponseV22, EnvelopeMeta, RiskLevel, ExecutionPolicy } from '../types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Validate data against JSON schema. Returns list of errors.
|
|
9
9
|
*/
|
|
@@ -366,6 +366,7 @@ export interface RunOptions {
|
|
|
366
366
|
enableRepair?: boolean;
|
|
367
367
|
traceId?: string;
|
|
368
368
|
model?: string;
|
|
369
|
+
policy?: ExecutionPolicy;
|
|
369
370
|
}
|
|
370
371
|
export declare function runModule(module: CognitiveModule, provider: Provider, options?: RunOptions): Promise<ModuleResult>;
|
|
371
372
|
/** Event types emitted during streaming execution */
|
|
@@ -394,6 +395,7 @@ export interface StreamOptions {
|
|
|
394
395
|
enableRepair?: boolean;
|
|
395
396
|
traceId?: string;
|
|
396
397
|
model?: string;
|
|
398
|
+
policy?: ExecutionPolicy;
|
|
397
399
|
}
|
|
398
400
|
/**
|
|
399
401
|
* Run a cognitive module with streaming output.
|
package/dist/modules/runner.js
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import _Ajv from 'ajv';
|
|
7
7
|
const Ajv = _Ajv.default || _Ajv;
|
|
8
|
+
import * as fs from 'node:fs/promises';
|
|
9
|
+
import * as path from 'node:path';
|
|
8
10
|
import { aggregateRisk, isV22Envelope } from '../types.js';
|
|
11
|
+
import { readModuleProvenance, verifyModuleIntegrity } from '../provenance.js';
|
|
9
12
|
// =============================================================================
|
|
10
13
|
// Schema Validation
|
|
11
14
|
// =============================================================================
|
|
@@ -1046,12 +1049,113 @@ function convertLegacyToEnvelope(data, isError = false) {
|
|
|
1046
1049
|
};
|
|
1047
1050
|
}
|
|
1048
1051
|
}
|
|
1052
|
+
async function enforcePolicyGates(module, policy) {
|
|
1053
|
+
if (!policy)
|
|
1054
|
+
return null;
|
|
1055
|
+
if (policy.requireV22) {
|
|
1056
|
+
const fv = module.formatVersion ?? 'unknown';
|
|
1057
|
+
if (fv !== 'v2.2') {
|
|
1058
|
+
return makeErrorResponse({
|
|
1059
|
+
code: 'E4007', // PERMISSION_DENIED
|
|
1060
|
+
message: `Certified policy requires v2.2 modules; got: ${fv} (${module.format})`,
|
|
1061
|
+
explain: 'Refused by execution policy.',
|
|
1062
|
+
confidence: 1.0,
|
|
1063
|
+
risk: 'none',
|
|
1064
|
+
suggestion: 'Migrate the module to v2.2, or run with --profile strict/default.',
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (policy.profile !== 'certified')
|
|
1069
|
+
return null;
|
|
1070
|
+
const loc = module.location;
|
|
1071
|
+
if (typeof loc !== 'string' || loc.trim().length === 0) {
|
|
1072
|
+
return makeErrorResponse({
|
|
1073
|
+
code: 'E4007', // PERMISSION_DENIED
|
|
1074
|
+
message: 'Certified policy requires an installed module with provenance; module location is missing.',
|
|
1075
|
+
explain: 'Refused by execution policy.',
|
|
1076
|
+
confidence: 1.0,
|
|
1077
|
+
risk: 'none',
|
|
1078
|
+
suggestion: 'Reinstall the module from a registry tarball that writes provenance.json.',
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
// Single-file modules (5-minute path) are intentionally not allowed in certified flows.
|
|
1082
|
+
try {
|
|
1083
|
+
const st = await fs.stat(loc);
|
|
1084
|
+
if (!st.isDirectory()) {
|
|
1085
|
+
return makeErrorResponse({
|
|
1086
|
+
code: 'E4007', // PERMISSION_DENIED
|
|
1087
|
+
message: `Certified policy requires module directory provenance; got a non-directory location: ${loc}`,
|
|
1088
|
+
explain: 'Refused by execution policy.',
|
|
1089
|
+
confidence: 1.0,
|
|
1090
|
+
risk: 'none',
|
|
1091
|
+
suggestion: 'Install the module via `cog add <name>` (registry tarball) or use --profile strict/default.',
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
catch {
|
|
1096
|
+
return makeErrorResponse({
|
|
1097
|
+
code: 'E4007',
|
|
1098
|
+
message: `Certified policy requires module directory provenance, but location does not exist: ${loc}`,
|
|
1099
|
+
explain: 'Refused by execution policy.',
|
|
1100
|
+
confidence: 1.0,
|
|
1101
|
+
risk: 'none',
|
|
1102
|
+
suggestion: 'Reinstall the module from a registry tarball and retry.',
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
const prov = await readModuleProvenance(loc);
|
|
1106
|
+
if (!prov) {
|
|
1107
|
+
return makeErrorResponse({
|
|
1108
|
+
code: 'E4007', // PERMISSION_DENIED
|
|
1109
|
+
message: `Certified policy requires provenance.json in the module directory: ${loc}`,
|
|
1110
|
+
explain: 'Refused by execution policy.',
|
|
1111
|
+
confidence: 1.0,
|
|
1112
|
+
risk: 'none',
|
|
1113
|
+
suggestion: 'Reinstall the module from a registry tarball (distribution.tarball + checksum), then retry.',
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
if (prov.source.type !== 'registry') {
|
|
1117
|
+
return makeErrorResponse({
|
|
1118
|
+
code: 'E4007', // PERMISSION_DENIED
|
|
1119
|
+
message: `Certified policy requires registry provenance; module provenance is type=${prov.source.type}`,
|
|
1120
|
+
explain: 'Refused by execution policy.',
|
|
1121
|
+
confidence: 1.0,
|
|
1122
|
+
risk: 'none',
|
|
1123
|
+
suggestion: 'Reinstall the module from a registry tarball and retry, or use --profile strict/default.',
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
// Integrity check (tamper detection).
|
|
1127
|
+
const ok = await verifyModuleIntegrity(loc, prov);
|
|
1128
|
+
if (!ok.ok) {
|
|
1129
|
+
return makeErrorResponse({
|
|
1130
|
+
code: 'E4007', // PERMISSION_DENIED
|
|
1131
|
+
message: `Certified policy integrity check failed: ${ok.reason}`,
|
|
1132
|
+
explain: 'Module contents appear to have been modified after install.',
|
|
1133
|
+
confidence: 1.0,
|
|
1134
|
+
risk: 'none',
|
|
1135
|
+
suggestion: 'Reinstall the module from the registry tarball to restore integrity.',
|
|
1136
|
+
details: { location: loc, reason: ok.reason },
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
// Optional: enforce that the module directory remains within itself (defense-in-depth for weird paths).
|
|
1140
|
+
const resolved = path.resolve(loc);
|
|
1141
|
+
if (!resolved)
|
|
1142
|
+
return null;
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1049
1145
|
// =============================================================================
|
|
1050
1146
|
// Main Runner
|
|
1051
1147
|
// =============================================================================
|
|
1052
1148
|
export async function runModule(module, provider, options = {}) {
|
|
1053
|
-
const { args, input, verbose = false, validateInput = true, validateOutput = true, useEnvelope, useV22, enableRepair = true, traceId, model: modelOverride } = options;
|
|
1149
|
+
const { args, input, verbose = false, validateInput = true, validateOutput = true, useEnvelope, useV22, enableRepair = true, traceId, model: modelOverride, policy } = options;
|
|
1054
1150
|
const startTime = Date.now();
|
|
1151
|
+
const gate = await enforcePolicyGates(module, policy);
|
|
1152
|
+
if (gate) {
|
|
1153
|
+
const msg = gate.ok === false && 'error' in gate && gate.error?.message
|
|
1154
|
+
? String(gate.error.message)
|
|
1155
|
+
: 'Refused by execution policy';
|
|
1156
|
+
_invokeErrorHooks(module.name, new Error(msg), null);
|
|
1157
|
+
return gate;
|
|
1158
|
+
}
|
|
1055
1159
|
// Determine if we should use envelope format
|
|
1056
1160
|
const shouldUseEnvelope = useEnvelope ?? (module.output?.envelope === true || module.format === 'v2');
|
|
1057
1161
|
// Determine if we should use v2.2 format
|
|
@@ -1071,6 +1175,14 @@ export async function runModule(module, provider, options = {}) {
|
|
|
1071
1175
|
inputData.query = args;
|
|
1072
1176
|
}
|
|
1073
1177
|
}
|
|
1178
|
+
// Single-file core modules promise "missing fields are empty".
|
|
1179
|
+
// Ensure common placeholders like `${query}` / `${code}` don't leak into prompts.
|
|
1180
|
+
if (typeof module.location === 'string' && /\.(md|markdown)$/i.test(module.location)) {
|
|
1181
|
+
if (inputData.query === undefined)
|
|
1182
|
+
inputData.query = '';
|
|
1183
|
+
if (inputData.code === undefined)
|
|
1184
|
+
inputData.code = '';
|
|
1185
|
+
}
|
|
1074
1186
|
// Invoke before hooks
|
|
1075
1187
|
_invokeBeforeHooks(module.name, inputData, module);
|
|
1076
1188
|
// Validate input against schema
|
|
@@ -1339,7 +1451,7 @@ export async function runModule(module, provider, options = {}) {
|
|
|
1339
1451
|
* }
|
|
1340
1452
|
*/
|
|
1341
1453
|
export async function* runModuleStream(module, provider, options = {}) {
|
|
1342
|
-
const { input, args, validateInput = true, validateOutput = true, useV22 = true, enableRepair = true, traceId, model } = options;
|
|
1454
|
+
const { input, args, validateInput = true, validateOutput = true, useV22 = true, enableRepair = true, traceId, model, policy } = options;
|
|
1343
1455
|
const startTime = Date.now();
|
|
1344
1456
|
const moduleName = module.name;
|
|
1345
1457
|
const providerName = provider?.name;
|
|
@@ -1356,6 +1468,13 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1356
1468
|
try {
|
|
1357
1469
|
// Emit start event
|
|
1358
1470
|
yield makeEvent('start');
|
|
1471
|
+
const gate = await enforcePolicyGates(module, policy);
|
|
1472
|
+
if (gate) {
|
|
1473
|
+
const errorObj = gate?.error ?? { code: 'E4007', message: 'Refused by execution policy' };
|
|
1474
|
+
yield makeEvent('error', { error: { code: errorObj.code, message: errorObj.message } });
|
|
1475
|
+
yield makeEvent('end', { result: gate });
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1359
1478
|
// Build input data
|
|
1360
1479
|
const inputData = input || {};
|
|
1361
1480
|
if (args && !inputData.code && !inputData.query) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExecutionPolicy } from './types.js';
|
|
2
|
+
export interface ResolvePolicyInput {
|
|
3
|
+
profile?: string | null;
|
|
4
|
+
validate?: string | null;
|
|
5
|
+
noValidate?: boolean;
|
|
6
|
+
audit?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveExecutionPolicy(input: ResolvePolicyInput): ExecutionPolicy;
|
package/dist/profile.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function normalizeProfile(raw) {
|
|
2
|
+
const v = (raw ?? '').trim().toLowerCase();
|
|
3
|
+
if (v === 'core')
|
|
4
|
+
return 'core';
|
|
5
|
+
if (v === 'default' || v === '')
|
|
6
|
+
return 'default';
|
|
7
|
+
if (v === 'strict')
|
|
8
|
+
return 'strict';
|
|
9
|
+
if (v === 'certified' || v === 'cert')
|
|
10
|
+
return 'certified';
|
|
11
|
+
throw new Error(`Invalid --profile: ${raw}. Expected one of: core|default|strict|certified`);
|
|
12
|
+
}
|
|
13
|
+
function normalizeValidate(raw) {
|
|
14
|
+
const v = (raw ?? '').trim().toLowerCase();
|
|
15
|
+
if (v === '' || v === 'auto')
|
|
16
|
+
return 'auto';
|
|
17
|
+
if (v === 'on' || v === 'true' || v === '1')
|
|
18
|
+
return 'on';
|
|
19
|
+
if (v === 'off' || v === 'false' || v === '0')
|
|
20
|
+
return 'off';
|
|
21
|
+
throw new Error(`Invalid --validate: ${raw}. Expected one of: auto|on|off`);
|
|
22
|
+
}
|
|
23
|
+
export function resolveExecutionPolicy(input) {
|
|
24
|
+
const profile = normalizeProfile(input.profile);
|
|
25
|
+
// Base defaults per profile.
|
|
26
|
+
let validate = profile === 'core' ? 'off' : 'on';
|
|
27
|
+
let audit = false;
|
|
28
|
+
let enableRepair = true;
|
|
29
|
+
let requireV22 = false;
|
|
30
|
+
if (profile === 'strict') {
|
|
31
|
+
validate = 'on';
|
|
32
|
+
audit = false;
|
|
33
|
+
enableRepair = true;
|
|
34
|
+
requireV22 = false;
|
|
35
|
+
}
|
|
36
|
+
if (profile === 'certified') {
|
|
37
|
+
validate = 'on';
|
|
38
|
+
audit = true;
|
|
39
|
+
enableRepair = false; // certification prefers fail-fast over runtime repair
|
|
40
|
+
requireV22 = true;
|
|
41
|
+
}
|
|
42
|
+
// CLI overrides.
|
|
43
|
+
const validateExplicit = input.validate != null || Boolean(input.noValidate);
|
|
44
|
+
if (input.validate != null) {
|
|
45
|
+
validate = normalizeValidate(input.validate);
|
|
46
|
+
}
|
|
47
|
+
if (input.noValidate) {
|
|
48
|
+
validate = 'off';
|
|
49
|
+
}
|
|
50
|
+
if (typeof input.audit === 'boolean') {
|
|
51
|
+
audit = input.audit;
|
|
52
|
+
}
|
|
53
|
+
// Trigger rule: if audit is enabled and validate wasn't explicitly turned off,
|
|
54
|
+
// force validation on (auditing without validation is usually not meaningful).
|
|
55
|
+
if (audit && !(validateExplicit && validate === 'off')) {
|
|
56
|
+
validate = 'on';
|
|
57
|
+
}
|
|
58
|
+
return { profile, validate, audit, enableRepair, requireV22 };
|
|
59
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const PROVENANCE_FILENAME = "provenance.json";
|
|
2
|
+
export declare const PROVENANCE_SPEC = "cognitive.module.provenance/v1";
|
|
3
|
+
export type ProvenanceSource = {
|
|
4
|
+
type: 'registry';
|
|
5
|
+
registryUrl?: string | null;
|
|
6
|
+
moduleName: string;
|
|
7
|
+
requestedVersion?: string | null;
|
|
8
|
+
resolvedVersion?: string | null;
|
|
9
|
+
tarballUrl: string;
|
|
10
|
+
checksum: string;
|
|
11
|
+
sha256: string;
|
|
12
|
+
quality?: {
|
|
13
|
+
verified?: boolean;
|
|
14
|
+
conformance_level?: number;
|
|
15
|
+
spec_version?: string;
|
|
16
|
+
};
|
|
17
|
+
} | {
|
|
18
|
+
type: 'github';
|
|
19
|
+
repoUrl: string;
|
|
20
|
+
ref?: string | null;
|
|
21
|
+
modulePath?: string | null;
|
|
22
|
+
};
|
|
23
|
+
export interface ModuleIntegrity {
|
|
24
|
+
algorithm: 'sha256';
|
|
25
|
+
maxFiles: number;
|
|
26
|
+
maxTotalBytes: number;
|
|
27
|
+
maxSingleFileBytes: number;
|
|
28
|
+
totalBytes: number;
|
|
29
|
+
files: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
export interface ModuleProvenance {
|
|
32
|
+
spec: typeof PROVENANCE_SPEC;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
source: ProvenanceSource;
|
|
35
|
+
integrity: ModuleIntegrity;
|
|
36
|
+
}
|
|
37
|
+
export interface IntegrityOptions {
|
|
38
|
+
maxFiles: number;
|
|
39
|
+
maxTotalBytes: number;
|
|
40
|
+
maxSingleFileBytes: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function computeModuleIntegrity(moduleDir: string, options?: Partial<IntegrityOptions>): Promise<ModuleIntegrity>;
|
|
43
|
+
export declare function writeModuleProvenance(moduleDir: string, prov: ModuleProvenance): Promise<void>;
|
|
44
|
+
export declare function readModuleProvenance(moduleDir: string): Promise<ModuleProvenance | null>;
|
|
45
|
+
export declare function verifyModuleIntegrity(moduleDir: string, prov: ModuleProvenance): Promise<{
|
|
46
|
+
ok: true;
|
|
47
|
+
} | {
|
|
48
|
+
ok: false;
|
|
49
|
+
reason: string;
|
|
50
|
+
}>;
|