cognitive-modules-cli 2.2.5 → 2.2.8

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 (42) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +25 -3
  3. package/dist/audit.d.ts +13 -0
  4. package/dist/audit.js +25 -0
  5. package/dist/cli.js +188 -3
  6. package/dist/commands/add.js +232 -7
  7. package/dist/commands/compose.d.ts +2 -0
  8. package/dist/commands/compose.js +60 -1
  9. package/dist/commands/core.d.ts +31 -0
  10. package/dist/commands/core.js +338 -0
  11. package/dist/commands/index.d.ts +1 -0
  12. package/dist/commands/index.js +1 -0
  13. package/dist/commands/pipe.js +45 -2
  14. package/dist/commands/run.d.ts +1 -0
  15. package/dist/commands/run.js +136 -31
  16. package/dist/commands/search.js +13 -3
  17. package/dist/commands/update.js +4 -1
  18. package/dist/errors/index.d.ts +7 -0
  19. package/dist/errors/index.js +48 -40
  20. package/dist/modules/composition.d.ts +15 -2
  21. package/dist/modules/composition.js +16 -6
  22. package/dist/modules/loader.d.ts +10 -0
  23. package/dist/modules/loader.js +168 -0
  24. package/dist/modules/runner.d.ts +10 -6
  25. package/dist/modules/runner.js +130 -16
  26. package/dist/profile.d.ts +8 -0
  27. package/dist/profile.js +59 -0
  28. package/dist/provenance.d.ts +50 -0
  29. package/dist/provenance.js +137 -0
  30. package/dist/registry/assets.d.ts +48 -0
  31. package/dist/registry/assets.js +723 -0
  32. package/dist/registry/client.d.ts +20 -5
  33. package/dist/registry/client.js +87 -30
  34. package/dist/registry/tar.d.ts +8 -0
  35. package/dist/registry/tar.js +353 -0
  36. package/dist/server/http.js +167 -42
  37. package/dist/server/index.d.ts +2 -0
  38. package/dist/server/index.js +1 -0
  39. package/dist/server/sse.d.ts +13 -0
  40. package/dist/server/sse.js +22 -0
  41. package/dist/types.d.ts +31 -0
  42. package/package.json +1 -1
@@ -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
@@ -1324,9 +1428,9 @@ export async function runModule(module, provider, options = {}) {
1324
1428
  *
1325
1429
  * Yields StreamEvent objects as the module executes:
1326
1430
  * - type="start": Module execution started
1327
- * - type="chunk": Incremental data chunk (if LLM supports streaming)
1431
+ * - type="delta": Incremental output delta (provider streaming chunk)
1328
1432
  * - type="meta": Meta information available early
1329
- * - type="complete": Final complete result
1433
+ * - type="end": Final result envelope (always emitted)
1330
1434
  * - type="error": Error occurred
1331
1435
  *
1332
1436
  * @example
@@ -1339,20 +1443,30 @@ export async function runModule(module, provider, options = {}) {
1339
1443
  * }
1340
1444
  */
1341
1445
  export async function* runModuleStream(module, provider, options = {}) {
1342
- const { input, args, validateInput = true, validateOutput = true, useV22 = true, enableRepair = true, traceId, model } = options;
1446
+ const { input, args, validateInput = true, validateOutput = true, useV22 = true, enableRepair = true, traceId, model, policy } = options;
1343
1447
  const startTime = Date.now();
1344
1448
  const moduleName = module.name;
1449
+ const providerName = provider?.name;
1345
1450
  function makeEvent(type, extra = {}) {
1346
1451
  return {
1347
1452
  type,
1453
+ version: ENVELOPE_VERSION,
1348
1454
  timestamp_ms: Date.now() - startTime,
1349
- module_name: moduleName,
1455
+ module: moduleName,
1456
+ ...(providerName ? { provider: providerName } : {}),
1350
1457
  ...extra,
1351
1458
  };
1352
1459
  }
1353
1460
  try {
1354
1461
  // Emit start event
1355
1462
  yield makeEvent('start');
1463
+ const gate = await enforcePolicyGates(module, policy);
1464
+ if (gate) {
1465
+ const errorObj = gate?.error ?? { code: 'E4007', message: 'Refused by execution policy' };
1466
+ yield makeEvent('error', { error: { code: errorObj.code, message: errorObj.message } });
1467
+ yield makeEvent('end', { result: gate });
1468
+ return;
1469
+ }
1356
1470
  // Build input data
1357
1471
  const inputData = input || {};
1358
1472
  if (args && !inputData.code && !inputData.query) {
@@ -1378,7 +1492,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1378
1492
  _invokeErrorHooks(module.name, new Error(inputErrors.join('; ')), null);
1379
1493
  const errorObj = errorResult.error;
1380
1494
  yield makeEvent('error', { error: errorObj });
1381
- yield makeEvent('complete', { result: errorResult });
1495
+ yield makeEvent('end', { result: errorResult });
1382
1496
  return;
1383
1497
  }
1384
1498
  }
@@ -1415,7 +1529,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1415
1529
  let streamResult;
1416
1530
  while (!(streamResult = await stream.next()).done) {
1417
1531
  const chunk = streamResult.value;
1418
- yield makeEvent('chunk', { chunk });
1532
+ yield makeEvent('delta', { delta: chunk });
1419
1533
  }
1420
1534
  // Get the final result (returned from the generator)
1421
1535
  fullContent = streamResult.value.content;
@@ -1429,7 +1543,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1429
1543
  });
1430
1544
  fullContent = result.content;
1431
1545
  // Emit chunk event with full response
1432
- yield makeEvent('chunk', { chunk: result.content });
1546
+ yield makeEvent('delta', { delta: result.content });
1433
1547
  }
1434
1548
  // Parse response
1435
1549
  let parsed;
@@ -1448,7 +1562,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1448
1562
  // errorResult is always an error response from makeErrorResponse
1449
1563
  const errorObj = errorResult.error;
1450
1564
  yield makeEvent('error', { error: errorObj });
1451
- yield makeEvent('complete', { result: errorResult });
1565
+ yield makeEvent('end', { result: errorResult });
1452
1566
  return;
1453
1567
  }
1454
1568
  // Convert to v2.2 envelope
@@ -1502,7 +1616,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1502
1616
  _invokeErrorHooks(module.name, new Error(dataErrors.join('; ')), response.data);
1503
1617
  const errorObj = errorResult.error;
1504
1618
  yield makeEvent('error', { error: errorObj });
1505
- yield makeEvent('complete', { result: errorResult });
1619
+ yield makeEvent('end', { result: errorResult });
1506
1620
  return;
1507
1621
  }
1508
1622
  const overflowErrors = validateOverflowLimits(dataToValidate, module);
@@ -1517,7 +1631,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1517
1631
  _invokeErrorHooks(module.name, new Error(overflowErrors.join('; ')), dataToValidate);
1518
1632
  const errorObj = errorResult.error;
1519
1633
  yield makeEvent('error', { error: errorObj });
1520
- yield makeEvent('complete', { result: errorResult });
1634
+ yield makeEvent('end', { result: errorResult });
1521
1635
  return;
1522
1636
  }
1523
1637
  const enumErrors = validateEnumStrategy(dataToValidate, module);
@@ -1532,7 +1646,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1532
1646
  _invokeErrorHooks(module.name, new Error(enumErrors.join('; ')), dataToValidate);
1533
1647
  const errorObj = errorResult.error;
1534
1648
  yield makeEvent('error', { error: errorObj });
1535
- yield makeEvent('complete', { result: errorResult });
1649
+ yield makeEvent('end', { result: errorResult });
1536
1650
  return;
1537
1651
  }
1538
1652
  }
@@ -1554,7 +1668,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1554
1668
  _invokeErrorHooks(module.name, new Error(metaErrors.join('; ')), response.data);
1555
1669
  const errorObj = errorResult.error;
1556
1670
  yield makeEvent('error', { error: errorObj });
1557
- yield makeEvent('complete', { result: errorResult });
1671
+ yield makeEvent('end', { result: errorResult });
1558
1672
  return;
1559
1673
  }
1560
1674
  }
@@ -1566,8 +1680,8 @@ export async function* runModuleStream(module, provider, options = {}) {
1566
1680
  }
1567
1681
  const finalLatencyMs = Date.now() - startTime;
1568
1682
  _invokeAfterHooks(module.name, response, finalLatencyMs);
1569
- // Emit complete event
1570
- yield makeEvent('complete', { result: response });
1683
+ // Emit end event
1684
+ yield makeEvent('end', { result: response });
1571
1685
  }
1572
1686
  catch (e) {
1573
1687
  _invokeErrorHooks(module.name, e, null);
@@ -1579,7 +1693,7 @@ export async function* runModuleStream(module, provider, options = {}) {
1579
1693
  // errorResult is always an error response from makeErrorResponse
1580
1694
  const errorObj = errorResult.error;
1581
1695
  yield makeEvent('error', { error: errorObj });
1582
- yield makeEvent('complete', { result: errorResult });
1696
+ yield makeEvent('end', { result: errorResult });
1583
1697
  }
1584
1698
  }
1585
1699
  // =============================================================================
@@ -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;
@@ -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
+ }>;
@@ -0,0 +1,137 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import { createReadStream } from 'node:fs';
3
+ import { createHash } from 'node:crypto';
4
+ import * as path from 'node:path';
5
+ export const PROVENANCE_FILENAME = 'provenance.json';
6
+ export const PROVENANCE_SPEC = 'cognitive.module.provenance/v1';
7
+ const DEFAULT_INTEGRITY_LIMITS = {
8
+ maxFiles: 5_000,
9
+ maxTotalBytes: 50 * 1024 * 1024, // 50MB
10
+ maxSingleFileBytes: 20 * 1024 * 1024, // 20MB
11
+ };
12
+ async function hashFileSha256(filePath, size) {
13
+ const h = createHash('sha256');
14
+ await new Promise((resolve, reject) => {
15
+ const rs = createReadStream(filePath);
16
+ rs.on('data', (chunk) => h.update(chunk));
17
+ rs.on('error', reject);
18
+ rs.on('end', resolve);
19
+ });
20
+ // Include size in the hash domain separation to make accidental truncation obvious.
21
+ h.update(`\nsize:${size}\n`);
22
+ return h.digest('hex');
23
+ }
24
+ async function walkFiles(rootDir, relDir) {
25
+ const absDir = path.join(rootDir, relDir);
26
+ const entries = await fs.readdir(absDir, { withFileTypes: true });
27
+ const out = [];
28
+ for (const ent of entries) {
29
+ const rel = relDir ? path.posix.join(relDir.replace(/\\/g, '/'), ent.name) : ent.name;
30
+ const abs = path.join(rootDir, rel);
31
+ // Never hash our own provenance.
32
+ if (rel === PROVENANCE_FILENAME)
33
+ continue;
34
+ if (ent.name === '.DS_Store' || ent.name === '__MACOSX')
35
+ continue;
36
+ if (ent.isSymbolicLink()) {
37
+ // Refuse to hash symlinks. Tar extraction also rejects them.
38
+ continue;
39
+ }
40
+ if (ent.isDirectory()) {
41
+ out.push(...await walkFiles(rootDir, rel));
42
+ continue;
43
+ }
44
+ if (ent.isFile()) {
45
+ out.push(rel);
46
+ }
47
+ }
48
+ return out;
49
+ }
50
+ export async function computeModuleIntegrity(moduleDir, options = {}) {
51
+ const limits = {
52
+ ...DEFAULT_INTEGRITY_LIMITS,
53
+ ...options,
54
+ };
55
+ const relFiles = (await walkFiles(moduleDir, '')).sort((a, b) => a.localeCompare(b));
56
+ if (relFiles.length > limits.maxFiles) {
57
+ throw new Error(`Module has too many files to hash (max ${limits.maxFiles}): ${relFiles.length}`);
58
+ }
59
+ const files = {};
60
+ let totalBytes = 0;
61
+ for (const rel of relFiles) {
62
+ const abs = path.join(moduleDir, rel);
63
+ const st = await fs.stat(abs);
64
+ if (!st.isFile())
65
+ continue;
66
+ if (st.size > limits.maxSingleFileBytes) {
67
+ throw new Error(`Module file too large for integrity hashing (max ${limits.maxSingleFileBytes} bytes): ${rel}`);
68
+ }
69
+ totalBytes += st.size;
70
+ if (totalBytes > limits.maxTotalBytes) {
71
+ throw new Error(`Module too large for integrity hashing (max ${limits.maxTotalBytes} bytes)`);
72
+ }
73
+ const sha256 = await hashFileSha256(abs, st.size);
74
+ files[rel] = sha256;
75
+ }
76
+ return {
77
+ algorithm: 'sha256',
78
+ maxFiles: limits.maxFiles,
79
+ maxTotalBytes: limits.maxTotalBytes,
80
+ maxSingleFileBytes: limits.maxSingleFileBytes,
81
+ totalBytes,
82
+ files,
83
+ };
84
+ }
85
+ export async function writeModuleProvenance(moduleDir, prov) {
86
+ const filePath = path.join(moduleDir, PROVENANCE_FILENAME);
87
+ const json = JSON.stringify(prov, null, 2) + '\n';
88
+ await fs.writeFile(filePath, json, 'utf-8');
89
+ }
90
+ export async function readModuleProvenance(moduleDir) {
91
+ const filePath = path.join(moduleDir, PROVENANCE_FILENAME);
92
+ try {
93
+ const raw = await fs.readFile(filePath, 'utf-8');
94
+ const parsed = JSON.parse(raw);
95
+ if (!parsed || typeof parsed !== 'object')
96
+ return null;
97
+ if (parsed.spec !== PROVENANCE_SPEC)
98
+ return null;
99
+ if (!parsed.source || typeof parsed.source !== 'object')
100
+ return null;
101
+ if (!parsed.integrity || typeof parsed.integrity !== 'object')
102
+ return null;
103
+ return parsed;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ export async function verifyModuleIntegrity(moduleDir, prov) {
110
+ const expected = prov.integrity?.files ?? {};
111
+ const expectedKeys = Object.keys(expected).sort();
112
+ if (expectedKeys.length === 0) {
113
+ return { ok: false, reason: 'Provenance integrity.files is empty' };
114
+ }
115
+ const computed = await computeModuleIntegrity(moduleDir, {
116
+ maxFiles: prov.integrity.maxFiles,
117
+ maxTotalBytes: prov.integrity.maxTotalBytes,
118
+ maxSingleFileBytes: prov.integrity.maxSingleFileBytes,
119
+ });
120
+ const computedKeys = Object.keys(computed.files).sort();
121
+ if (expectedKeys.length !== computedKeys.length) {
122
+ return { ok: false, reason: 'Integrity file list changed (file count mismatch)' };
123
+ }
124
+ for (let i = 0; i < expectedKeys.length; i++) {
125
+ if (expectedKeys[i] !== computedKeys[i]) {
126
+ return { ok: false, reason: `Integrity file list changed (mismatch at ${i}: ${expectedKeys[i]} vs ${computedKeys[i]})` };
127
+ }
128
+ }
129
+ for (const rel of expectedKeys) {
130
+ const a = expected[rel];
131
+ const b = computed.files[rel];
132
+ if (a !== b) {
133
+ return { ok: false, reason: `Integrity mismatch for ${rel}` };
134
+ }
135
+ }
136
+ return { ok: true };
137
+ }
@@ -0,0 +1,48 @@
1
+ export interface BuildRegistryOptions {
2
+ tag?: string | null;
3
+ tarballBaseUrl?: string | null;
4
+ modulesDir: string;
5
+ v1RegistryPath: string;
6
+ outDir: string;
7
+ registryOut: string;
8
+ namespace: string;
9
+ runtimeMin: string;
10
+ repository: string;
11
+ homepage: string;
12
+ license: string;
13
+ timestamp?: string | null;
14
+ only?: string[];
15
+ }
16
+ export interface VerifyRegistryOptions {
17
+ registryIndexPath: string;
18
+ assetsDir?: string;
19
+ maxTarballBytes?: number;
20
+ remote?: boolean;
21
+ fetchTimeoutMs?: number;
22
+ maxIndexBytes?: number;
23
+ concurrency?: number;
24
+ }
25
+ export interface RegistryBuildResult {
26
+ registryOut: string;
27
+ outDir: string;
28
+ updated: string;
29
+ modules: Array<{
30
+ name: string;
31
+ version: string;
32
+ file: string;
33
+ sha256: string;
34
+ size_bytes: number;
35
+ }>;
36
+ }
37
+ export interface RegistryVerifyResult {
38
+ ok: boolean;
39
+ checked: number;
40
+ passed: number;
41
+ failed: number;
42
+ failures: Array<{
43
+ module: string;
44
+ reason: string;
45
+ }>;
46
+ }
47
+ export declare function buildRegistryAssets(opts: BuildRegistryOptions): Promise<RegistryBuildResult>;
48
+ export declare function verifyRegistryAssets(opts: VerifyRegistryOptions): Promise<RegistryVerifyResult>;