@altopelago/aeon-cli 0.9.0

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/dist/main.js ADDED
@@ -0,0 +1,2731 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @altopelago/aeon-cli - AEON Command Line Interface
4
+ *
5
+ * Commands:
6
+ * - aeon version Show version
7
+ * - aeon check <file> Validate AEON document (CI-friendly)
8
+ * - aeon doctor Check environment and contract wiring
9
+ * - aeon fmt [file] Format AEON document (stdout by default)
10
+ * - aeon inspect <file> Inspect AEON document (human-readable)
11
+ * - aeon finalize <file> Finalize AEON document to JSON
12
+ * - aeon bind <file> Run typed runtime binding with schema JSON
13
+ * - aeon integrity validate <file> Validate integrity envelope
14
+ * - aeon integrity verify <file> Verify integrity envelope hashes
15
+ * - aeon integrity sign <file> Generate integrity envelope snippet
16
+ *
17
+ * Flags:
18
+ * - --json Output as JSON (inspect/finalize/integrity)
19
+ * - --contract-registry Trusted contract registry JSON path (doctor/bind)
20
+ * - --write Write formatted output back to file (fmt only)
21
+ * - --annotations Include annotation stream records in inspect/bind output
22
+ * - --annotations-only Output only annotation stream records in inspect output
23
+ * - --sort-annotations Sort annotation records deterministically before output (inspect/bind)
24
+ * - --map Output finalized map (finalize only)
25
+ * - --scope Finalization scope: payload|header|full (finalize/bind)
26
+ * - --projected Materialize only explicitly included canonical paths (finalize/bind)
27
+ * - --include-path Canonical path to include in projected materialization (repeatable; finalize/bind)
28
+ * - --recovery Enable recovery mode (partial results with errors)
29
+ * - --max-input-bytes Maximum UTF-8 input size in bytes
30
+ * - --max-attribute-depth Maximum attribute selector depth
31
+ * - --max-separator-depth Maximum separator-spec depth
32
+ * - --max-generic-depth Maximum nested generic type depth
33
+ * - --max-nesting-depth Maximum container nesting depth
34
+ * - --max-materialized-weight Maximum cumulative clone materialization weight
35
+ * - --max-reference-depth Maximum clone resolution depth
36
+ * - --schema Schema JSON path (bind only)
37
+ * - --profile Profile id (bind only)
38
+ * - --contract-registry Trusted contract registry JSON path (bind only)
39
+ * - --trailing-separator-delimiter-policy off|warn|error (bind only)
40
+ * - --datatype-policy reserved_only|allow_custom (check/inspect/finalize/bind)
41
+ * - --rich Preset alias for --datatype-policy allow_custom
42
+ * - --strict Strict mode (default)
43
+ * - --loose Loose mode (warnings only)
44
+ * - --public-key Public key path for signature verification
45
+ * - --private-key Private key path for signing
46
+ * - --receipt Receipt sidecar path override
47
+ * - --write Write generated envelope to file (sign only)
48
+ * - --replace Replace existing envelope (sign only)
49
+ * - --include-bytes Include bytes_hash in envelope (sign only)
50
+ * - --include-checksum Include checksum_value in envelope (sign only)
51
+ */
52
+ import * as fs from 'node:fs';
53
+ import * as path from 'node:path';
54
+ import { fileURLToPath } from 'node:url';
55
+ import { createHash } from 'node:crypto';
56
+ import { canonicalize } from '@altopelago/aeon-canonical';
57
+ import { compile, VERSION, formatPath } from '@altopelago/aeon-core';
58
+ import { finalizeJson, finalizeMap } from '@altopelago/aeon-finalize';
59
+ import { buildCanonicalReceipt, computeCanonicalHash, computeByteHash, signStringPayload, validateEnvelopeEvents, verifyStringPayloadSignature, } from '@altopelago/aeon-integrity';
60
+ import { tokenize } from '@altopelago/aeon-lexer';
61
+ import { parse } from '@altopelago/aeon-parser';
62
+ import { inspectHeader } from '@altopelago/aeon-transport';
63
+ import { runTypedRuntime } from './runtime-bind.js';
64
+ const GP_SECURITY_CONVENTIONS = [
65
+ 'aeon.gp.security.v1',
66
+ 'aeon.gp.integrity.v1',
67
+ 'aeon.gp.signature.v1',
68
+ ];
69
+ const ENVELOPE_DATATYPE = 'envelope';
70
+ const ENVELOPE_CONVENTION_KEY = 'close';
71
+ // =============================================================================
72
+ // MAIN CLI ENTRY POINT
73
+ // =============================================================================
74
+ const args = process.argv.slice(2);
75
+ const command = args[0];
76
+ const __filename = fileURLToPath(import.meta.url);
77
+ const __dirname = path.dirname(__filename);
78
+ const cliPackageRoot = path.resolve(__dirname, '..');
79
+ const workspaceRoot = path.resolve(__dirname, '../../..');
80
+ const repoRoot = path.resolve(__dirname, '../../../../../');
81
+ switch (command) {
82
+ case 'version':
83
+ case '--version':
84
+ case '-v':
85
+ showVersion();
86
+ break;
87
+ case 'check':
88
+ check(args.slice(1));
89
+ break;
90
+ case 'doctor':
91
+ doctor(args.slice(1));
92
+ break;
93
+ case 'fmt':
94
+ fmt(args.slice(1));
95
+ break;
96
+ case 'inspect':
97
+ inspect(args.slice(1));
98
+ break;
99
+ case 'finalize':
100
+ finalize(args.slice(1));
101
+ break;
102
+ case 'bind':
103
+ bind(args.slice(1));
104
+ break;
105
+ case 'integrity':
106
+ integrity(args.slice(1));
107
+ break;
108
+ case 'help':
109
+ case '--help':
110
+ case '-h':
111
+ case undefined:
112
+ showHelp();
113
+ break;
114
+ default:
115
+ console.error(`Error: Unknown command: ${command}`);
116
+ console.error('Usage: aeon <command> [options] [file]');
117
+ process.exit(2);
118
+ }
119
+ // =============================================================================
120
+ // COMMANDS
121
+ // =============================================================================
122
+ function showVersion() {
123
+ console.log(`aeon ${VERSION}`);
124
+ }
125
+ function showHelp() {
126
+ console.log(`
127
+ AEON CLI v${VERSION}
128
+
129
+ Usage: aeon <command> [options] [file]
130
+
131
+ Commands:
132
+ version Show version
133
+ check <file> Validate AEON document (CI-friendly)
134
+ doctor Check environment and contract wiring
135
+ fmt [file] Format AEON document (stdout by default)
136
+ inspect <file> Inspect AEON document
137
+ finalize <file> Finalize AEON document to JSON
138
+ bind <file> Run typed runtime binding with schema JSON
139
+ integrity validate <file> Validate integrity envelope
140
+ integrity verify <file> Verify integrity envelope hashes
141
+ integrity sign <file> Generate integrity envelope snippet
142
+
143
+ Options:
144
+ --write Write formatted output back to file (fmt only)
145
+ --contract-registry Trusted contract registry JSON path (doctor/bind)
146
+ --json Output as JSON (inspect/finalize)
147
+ --annotations Include annotation stream records in inspect/bind output
148
+ --annotations-only Output only annotation stream records in inspect output
149
+ --sort-annotations Sort annotation records deterministically before output (inspect/bind)
150
+ --map Output finalized map (finalize only)
151
+ --scope Finalization scope: payload|header|full (finalize/bind)
152
+ --projected Materialize only explicitly included canonical paths (finalize/bind)
153
+ --include-path Canonical path to include in projected materialization (repeatable; finalize/bind)
154
+ --recovery Enable recovery mode (partial results)
155
+ --max-input-bytes Maximum UTF-8 input size in bytes
156
+ --max-attribute-depth Maximum attribute selector depth
157
+ --max-separator-depth Maximum separator-spec depth
158
+ --max-generic-depth Maximum nested generic type depth
159
+ --max-nesting-depth Maximum container nesting depth
160
+ --schema Schema JSON path (bind only)
161
+ --profile Profile id (bind only)
162
+ --contract-registry Trusted contract registry JSON path (bind only)
163
+ --trailing-separator-delimiter-policy off|warn|error (bind only)
164
+ --datatype-policy reserved_only|allow_custom (check/inspect/finalize/bind)
165
+ --rich Preset alias for --datatype-policy allow_custom
166
+ --strict Strict mode (default)
167
+ --loose Loose mode (warnings only)
168
+ --public-key Public key path for signature verification
169
+ --private-key Private key path for signing
170
+ --receipt Receipt sidecar path override
171
+ --write Write generated envelope to file (sign only)
172
+ --replace Replace existing envelope (sign only)
173
+ --include-bytes Include bytes_hash in envelope (sign only)
174
+ --include-checksum Include checksum_value in envelope (sign only)
175
+
176
+ Examples:
177
+ aeon check config.aeon
178
+ aeon doctor
179
+ aeon doctor --json
180
+ aeon doctor --contract-registry ./contracts/registry.json
181
+ aeon fmt config.aeon
182
+ cat config.aeon | aeon fmt
183
+ aeon fmt config.aeon --write
184
+ aeon inspect config.aeon
185
+ aeon inspect config.aeon --json
186
+ aeon inspect config.aeon --json --annotations
187
+ aeon inspect config.aeon --json --annotations-only
188
+ aeon inspect config.aeon --json --annotations-only --sort-annotations
189
+ aeon inspect config.aeon --recovery
190
+ aeon finalize config.aeon
191
+ aeon finalize config.aeon --json
192
+ aeon finalize config.aeon --map
193
+ aeon finalize config.aeon --scope full
194
+ aeon finalize config.aeon --loose
195
+ aeon finalize config.aeon --projected --include-path '$.app.name'
196
+ aeon finalize config.aeon --map --include-path '$.app.name' --include-path '$.app.port'
197
+ aeon bind config.aeon --schema config.schema.json
198
+ aeon bind config.aeon --schema config.schema.json --profile aeon.gp.profile.v1
199
+ aeon bind config.aeon --contract-registry contracts/registry.json
200
+ aeon bind config.aeon --schema config.schema.json --include-path '$.app.name'
201
+ aeon bind config.aeon --schema config.schema.json --include-path '$.app.name' --include-path '$.app.port'
202
+ aeon bind config.aeon --schema config.schema.json --trailing-separator-delimiter-policy warn
203
+ aeon inspect config.aeon --datatype-policy allow_custom
204
+ aeon inspect config.aeon --rich
205
+ aeon bind config.aeon --schema config.schema.json --datatype-policy allow_custom
206
+ aeon bind config.aeon --schema config.schema.json --rich
207
+ aeon bind config.aeon --schema config.schema.json --loose
208
+ aeon bind config.aeon --schema config.schema.json --annotations
209
+ aeon bind config.aeon --schema config.schema.json --annotations --sort-annotations
210
+ aeon integrity validate config.aeon
211
+ aeon integrity verify config.aeon
212
+ aeon integrity verify config.aeon --public-key ./aeon.pub
213
+ aeon integrity verify config.aeon --receipt ./config.aeon.receipt.json
214
+ aeon integrity sign config.aeon --private-key ./aeon.key
215
+ aeon integrity sign config.aeon --private-key ./aeon.key --write
216
+ aeon integrity sign config.aeon --private-key ./aeon.key --write --receipt ./config.aeon.receipt.json
217
+ aeon integrity sign config.aeon --private-key ./aeon.key --write --replace
218
+ aeon integrity sign config.aeon --private-key ./aeon.key --include-bytes
219
+ aeon integrity sign config.aeon --private-key ./aeon.key --include-checksum
220
+ `.trim());
221
+ }
222
+ /**
223
+ * aeon check <file>
224
+ * Purpose: validation only (CI-friendly)
225
+ * Exit code: 0 = valid, 1 = errors
226
+ */
227
+ function check(args) {
228
+ const file = findFile(args);
229
+ if (!file) {
230
+ console.error('Error: No file specified');
231
+ console.error('Usage: aeon check <file>');
232
+ process.exit(2);
233
+ }
234
+ const datatypePolicy = resolveDatatypePolicy(args);
235
+ const maxInputBytes = resolveMaxInputBytes(args);
236
+ if (args.includes('--datatype-policy') && !datatypePolicy) {
237
+ console.error('Error: Invalid value for --datatype-policy (expected reserved_only or allow_custom)');
238
+ console.error('Usage: aeon check <file> [--datatype-policy <reserved_only|allow_custom>]');
239
+ process.exit(2);
240
+ }
241
+ if (maxInputBytes === null) {
242
+ console.error('Error: Invalid value for --max-input-bytes (expected a non-negative integer)');
243
+ process.exit(2);
244
+ }
245
+ const input = readFile(file);
246
+ enforceInputByteLimitOrExit(input, maxInputBytes);
247
+ const result = compile(input, {
248
+ ...(datatypePolicy ? { datatypePolicy } : {}),
249
+ ...(maxInputBytes !== undefined ? { maxInputBytes } : {}),
250
+ });
251
+ if (result.errors.length === 0) {
252
+ console.log('OK');
253
+ process.exit(0);
254
+ }
255
+ else {
256
+ for (const error of result.errors) {
257
+ console.log(formatErrorLine(error));
258
+ }
259
+ process.exit(1);
260
+ }
261
+ }
262
+ /**
263
+ * aeon doctor [--json] [--contract-registry <registry.json>]
264
+ * Purpose: environment and contract wiring diagnostics
265
+ */
266
+ function doctor(args) {
267
+ const jsonOutput = args.includes('--json');
268
+ const registryFlagPresent = args.includes('--contract-registry');
269
+ const registryFlagValue = getFlagValue(args, '--contract-registry');
270
+ if (registryFlagPresent && !registryFlagValue) {
271
+ console.error('Error: Missing value for --contract-registry <registry.json>');
272
+ console.error('Usage: aeon doctor [--json] [--contract-registry <registry.json>]');
273
+ process.exit(2);
274
+ }
275
+ const doctorResult = runDoctor({
276
+ contractRegistryPath: registryFlagValue
277
+ ? path.resolve(process.cwd(), registryFlagValue)
278
+ : getDefaultContractRegistryPath(),
279
+ });
280
+ if (jsonOutput) {
281
+ console.log(JSON.stringify(doctorResult, null, 2));
282
+ }
283
+ else {
284
+ outputDoctorHuman(doctorResult);
285
+ }
286
+ process.exit(doctorResult.ok ? 0 : 1);
287
+ }
288
+ function getDefaultContractRegistryPath() {
289
+ const specsRoot = process.env.AEONITE_SPECS_ROOT
290
+ ? path.resolve(process.env.AEONITE_SPECS_ROOT)
291
+ : path.resolve(repoRoot, '..', '..', 'aeonite-org', 'aeonite-specs');
292
+ return path.resolve(specsRoot, 'aeon/v1/drafts/contracts/registry.json');
293
+ }
294
+ /**
295
+ * aeon fmt [file] [--write]
296
+ * Purpose: deterministic, idempotent formatting
297
+ */
298
+ function fmt(args) {
299
+ const writeOutput = args.includes('--write');
300
+ const file = findFile(args);
301
+ const maxInputBytes = resolveMaxInputBytes(args);
302
+ if (writeOutput && !file) {
303
+ console.error('Error: --write requires a file path');
304
+ console.error('Usage: aeon fmt [file] [--write]');
305
+ process.exit(2);
306
+ }
307
+ if (maxInputBytes === null) {
308
+ console.error('Error: Invalid value for --max-input-bytes (expected a non-negative integer)');
309
+ process.exit(2);
310
+ }
311
+ const input = file ? readFile(file) : readStdin();
312
+ enforceInputByteLimitOrExit(input, maxInputBytes);
313
+ const result = canonicalize(input);
314
+ if (result.errors.length > 0) {
315
+ for (const error of result.errors) {
316
+ console.log(formatGenericErrorLine(error));
317
+ }
318
+ process.exit(1);
319
+ }
320
+ const formatted = ensureTrailingNewline(result.text);
321
+ if (writeOutput) {
322
+ if (input !== formatted) {
323
+ writeFileWithBackup(file, formatted);
324
+ }
325
+ return;
326
+ }
327
+ process.stdout.write(formatted);
328
+ }
329
+ /**
330
+ * aeon inspect <file> [--json] [--recovery] [--annotations] [--annotations-only] [--sort-annotations]
331
+ * Purpose: human inspection (default) or JSON output
332
+ */
333
+ function inspect(args) {
334
+ const inspectUsage = 'Usage: aeon inspect <file> [--json] [--recovery] [--annotations] [--annotations-only] [--sort-annotations] [--datatype-policy <reserved_only|allow_custom>] [--max-input-bytes <n>] [--max-attribute-depth <n>] [--max-separator-depth <n>] [--max-generic-depth <n>] [--max-nesting-depth <n>]';
335
+ const file = findFileWithValueFlags(args, ['--datatype-policy', '--max-input-bytes', '--max-attribute-depth', '--max-separator-depth', '--max-generic-depth', '--max-nesting-depth']);
336
+ const jsonOutput = args.includes('--json');
337
+ const recovery = args.includes('--recovery');
338
+ const annotationsOnly = args.includes('--annotations-only');
339
+ const includeAnnotations = args.includes('--annotations');
340
+ const sortAnnotations = args.includes('--sort-annotations');
341
+ const datatypePolicy = resolveDatatypePolicy(args);
342
+ const maxInputBytes = resolveMaxInputBytes(args);
343
+ const maxAttributeDepth = resolveDepthOption(args, '--max-attribute-depth');
344
+ const maxSeparatorDepth = resolveDepthOption(args, '--max-separator-depth');
345
+ const maxGenericDepth = resolveDepthOption(args, '--max-generic-depth');
346
+ const maxNestingDepth = resolveDepthOption(args, '--max-nesting-depth');
347
+ if (!file) {
348
+ console.error('Error: No file specified');
349
+ console.error(inspectUsage);
350
+ process.exit(2);
351
+ }
352
+ if (args.includes('--datatype-policy') && !datatypePolicy) {
353
+ console.error('Error: Invalid value for --datatype-policy (expected reserved_only or allow_custom)');
354
+ console.error(inspectUsage);
355
+ process.exit(2);
356
+ }
357
+ if (maxInputBytes === null) {
358
+ console.error('Error: Invalid value for --max-input-bytes (expected a non-negative integer)');
359
+ process.exit(2);
360
+ }
361
+ if (maxAttributeDepth === null) {
362
+ console.error('Error: Invalid value for --max-attribute-depth (expected a non-negative integer)');
363
+ process.exit(2);
364
+ }
365
+ if (maxSeparatorDepth === null) {
366
+ console.error('Error: Invalid value for --max-separator-depth (expected a non-negative integer)');
367
+ process.exit(2);
368
+ }
369
+ if (maxGenericDepth === null) {
370
+ console.error('Error: Invalid value for --max-generic-depth (expected a non-negative integer)');
371
+ process.exit(2);
372
+ }
373
+ if (maxNestingDepth === null) {
374
+ console.error('Error: Invalid value for --max-nesting-depth (expected a non-negative integer)');
375
+ process.exit(2);
376
+ }
377
+ const input = readFile(file);
378
+ enforceInputByteLimitOrExit(input, maxInputBytes);
379
+ const result = compile(input, {
380
+ recovery,
381
+ emitAnnotations: includeAnnotations || annotationsOnly,
382
+ ...(datatypePolicy ? { datatypePolicy } : {}),
383
+ ...(maxInputBytes !== undefined ? { maxInputBytes } : {}),
384
+ ...(maxAttributeDepth !== undefined ? { maxAttributeDepth } : {}),
385
+ ...(maxSeparatorDepth !== undefined ? { maxSeparatorDepth } : {}),
386
+ ...(maxGenericDepth !== undefined ? { maxGenericDepth } : {}),
387
+ ...(maxNestingDepth !== undefined ? { maxNestingDepth } : {}),
388
+ });
389
+ const headerInfo = extractHeaderInfo(input);
390
+ const mode = headerInfo.mode;
391
+ if (jsonOutput) {
392
+ outputJSON(result, { includeAnnotations, annotationsOnly, sortAnnotations }, headerInfo);
393
+ }
394
+ else {
395
+ outputMarkdown(file, result, {
396
+ recovery,
397
+ mode,
398
+ version: headerInfo.version,
399
+ profile: headerInfo.profile,
400
+ schema: headerInfo.schema,
401
+ schemas: headerInfo.schemas,
402
+ includeAnnotations: includeAnnotations || annotationsOnly,
403
+ annotationsOnly,
404
+ sortAnnotations,
405
+ });
406
+ }
407
+ // Exit with error code if errors present
408
+ if (result.errors.length > 0) {
409
+ process.exit(1);
410
+ }
411
+ }
412
+ /**
413
+ * aeon finalize <file> [--json|--map] [--recovery] [--strict|--loose]
414
+ * Purpose: finalize AES into JSON output
415
+ */
416
+ function finalize(args) {
417
+ const finalizeUsage = 'Usage: aeon finalize <file> [--json|--map] [--recovery] [--strict|--loose] [--projected] [--include-path <$.path>] [--scope <payload|header|full>] [--datatype-policy <reserved_only|allow_custom>] [--max-input-bytes <n>] [--max-materialized-weight <n>] [--max-reference-depth <n>]';
418
+ const file = findFileWithValueFlags(args, ['--datatype-policy', '--include-path', '--scope', '--max-input-bytes', '--max-materialized-weight', '--max-reference-depth']);
419
+ const recovery = args.includes('--recovery');
420
+ const mode = resolveFinalizeMode(args);
421
+ const outputMap = args.includes('--map');
422
+ const datatypePolicy = resolveDatatypePolicy(args);
423
+ const scope = resolveFinalizeScope(args);
424
+ const maxInputBytes = resolveMaxInputBytes(args);
425
+ const maxMaterializedWeight = resolveDepthOption(args, '--max-materialized-weight');
426
+ const maxReferenceDepth = resolveDepthOption(args, '--max-reference-depth');
427
+ const includePaths = getFlagValues(args, '--include-path');
428
+ const projected = args.includes('--projected') || includePaths.length > 0;
429
+ if (!file) {
430
+ console.error('Error: No file specified');
431
+ console.error(finalizeUsage);
432
+ process.exit(2);
433
+ }
434
+ if (!mode) {
435
+ console.error('Error: Cannot use both --strict and --loose');
436
+ console.error(finalizeUsage);
437
+ process.exit(2);
438
+ }
439
+ if (args.includes('--datatype-policy') && !datatypePolicy) {
440
+ console.error('Error: Invalid value for --datatype-policy (expected reserved_only or allow_custom)');
441
+ console.error(finalizeUsage);
442
+ process.exit(2);
443
+ }
444
+ if (!scope) {
445
+ console.error('Error: Invalid value for --scope (expected payload, header, or full)');
446
+ process.exit(2);
447
+ }
448
+ if (maxInputBytes === null) {
449
+ console.error('Error: Invalid value for --max-input-bytes (expected a non-negative integer)');
450
+ process.exit(2);
451
+ }
452
+ if (maxMaterializedWeight === null) {
453
+ console.error('Error: Invalid value for --max-materialized-weight (expected a non-negative integer)');
454
+ process.exit(2);
455
+ }
456
+ if (maxReferenceDepth === null) {
457
+ console.error('Error: Invalid value for --max-reference-depth (expected a non-negative integer)');
458
+ process.exit(2);
459
+ }
460
+ if (args.includes('--include-path') && includePaths.length === 0) {
461
+ console.error('Error: Missing value for --include-path <$.path>');
462
+ console.error(finalizeUsage);
463
+ process.exit(2);
464
+ }
465
+ if (projected && includePaths.length === 0) {
466
+ console.error('Error: --projected requires at least one --include-path <$.path>');
467
+ console.error(finalizeUsage);
468
+ process.exit(2);
469
+ }
470
+ const input = readFile(file);
471
+ enforceInputByteLimitOrExit(input, maxInputBytes);
472
+ const result = compile(input, {
473
+ recovery,
474
+ ...(datatypePolicy ? { datatypePolicy } : {}),
475
+ ...(maxInputBytes !== undefined ? { maxInputBytes } : {}),
476
+ });
477
+ const finalizeOptions = {
478
+ mode,
479
+ scope,
480
+ ...(result.header ? { header: result.header } : {}),
481
+ ...(projected ? { materialization: 'projected', includePaths } : {}),
482
+ ...(maxMaterializedWeight !== undefined ? { maxMaterializedWeight } : {}),
483
+ ...(maxReferenceDepth !== undefined ? { maxReferenceDepth } : {}),
484
+ };
485
+ const output = outputMap
486
+ ? finalizeMapOutput(result, finalizeOptions)
487
+ : finalizeJsonOutput(result, finalizeOptions);
488
+ console.log(JSON.stringify(output, null, 2));
489
+ const hasErrors = (output.meta?.errors?.length ?? 0) > 0;
490
+ if (hasErrors) {
491
+ process.exit(1);
492
+ }
493
+ }
494
+ /**
495
+ * aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]
496
+ * Purpose: run phase-ordered runtime binding with schema validation
497
+ */
498
+ function bind(args) {
499
+ const mode = resolveFinalizeMode(args);
500
+ if (!mode) {
501
+ console.error('Error: Cannot use both --strict and --loose');
502
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
503
+ process.exit(2);
504
+ }
505
+ const file = findFileWithValueFlags(args, ['--schema', '--profile', '--contract-registry', '--trailing-separator-delimiter-policy', '--datatype-policy', '--include-path']);
506
+ if (!file) {
507
+ console.error('Error: No file specified');
508
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
509
+ process.exit(2);
510
+ }
511
+ const schemaPath = getFlagValue(args, '--schema');
512
+ const hasProfileFlag = args.includes('--profile');
513
+ const profile = getFlagValue(args, '--profile');
514
+ const hasRegistryFlag = args.includes('--contract-registry');
515
+ const contractRegistryPath = getFlagValue(args, '--contract-registry');
516
+ const trailingSeparatorPolicyFlag = '--trailing-separator-delimiter-policy';
517
+ const hasTrailingSeparatorPolicy = args.includes(trailingSeparatorPolicyFlag);
518
+ const trailingSeparatorPolicyValue = getFlagValue(args, trailingSeparatorPolicyFlag);
519
+ const trailingSeparatorDelimiterPolicy = trailingSeparatorPolicyValue === 'off' ||
520
+ trailingSeparatorPolicyValue === 'warn' ||
521
+ trailingSeparatorPolicyValue === 'error'
522
+ ? trailingSeparatorPolicyValue
523
+ : undefined;
524
+ const datatypePolicy = resolveDatatypePolicy(args);
525
+ const scope = resolveFinalizeScope(args);
526
+ const includeAnnotations = args.includes('--annotations');
527
+ const sortAnnotations = args.includes('--sort-annotations');
528
+ const maxInputBytes = resolveMaxInputBytes(args);
529
+ const includePaths = getFlagValues(args, '--include-path');
530
+ const projected = args.includes('--projected') || includePaths.length > 0;
531
+ if (hasProfileFlag && !profile) {
532
+ console.error('Error: Missing value for --profile <id>');
533
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
534
+ process.exit(2);
535
+ }
536
+ if (hasRegistryFlag && !contractRegistryPath) {
537
+ console.error('Error: Missing value for --contract-registry <registry.json>');
538
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
539
+ process.exit(2);
540
+ }
541
+ if (hasTrailingSeparatorPolicy && !trailingSeparatorPolicyValue) {
542
+ console.error('Error: Missing value for --trailing-separator-delimiter-policy <off|warn|error>');
543
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
544
+ process.exit(2);
545
+ }
546
+ if (hasTrailingSeparatorPolicy && !trailingSeparatorDelimiterPolicy) {
547
+ console.error(`Error: Invalid value for --trailing-separator-delimiter-policy: ${trailingSeparatorPolicyValue}`);
548
+ console.error('Allowed values: off, warn, error');
549
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
550
+ process.exit(2);
551
+ }
552
+ if (args.includes('--datatype-policy') && !datatypePolicy) {
553
+ console.error('Error: Invalid value for --datatype-policy (expected reserved_only or allow_custom)');
554
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
555
+ process.exit(2);
556
+ }
557
+ if (!scope) {
558
+ console.error('Error: Invalid value for --scope (expected payload, header, or full)');
559
+ process.exit(2);
560
+ }
561
+ if (maxInputBytes === null) {
562
+ console.error('Error: Invalid value for --max-input-bytes (expected a non-negative integer)');
563
+ process.exit(2);
564
+ }
565
+ if (args.includes('--include-path') && includePaths.length === 0) {
566
+ console.error('Error: Missing value for --include-path <$.path>');
567
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
568
+ process.exit(2);
569
+ }
570
+ if (projected && includePaths.length === 0) {
571
+ console.error('Error: --projected requires at least one --include-path <$.path>');
572
+ console.error('Usage: aeon bind <file> [--schema <schema.json>] [--profile <id>] [--contract-registry <registry.json>] [--trailing-separator-delimiter-policy <off|warn|error>] [--datatype-policy <reserved_only|allow_custom>] [--strict|--loose] [--projected] [--include-path <$.path>] [--annotations] [--sort-annotations]');
573
+ process.exit(2);
574
+ }
575
+ const input = readFile(file);
576
+ enforceInputByteLimitOrExit(input, maxInputBytes);
577
+ const headerInfo = extractHeaderInfo(input);
578
+ const resolvedRegistryPath = contractRegistryPath
579
+ ? path.resolve(process.cwd(), contractRegistryPath)
580
+ : null;
581
+ const registry = resolvedRegistryPath ? readContractRegistryFile(resolvedRegistryPath) : null;
582
+ const effectiveProfile = profile ?? headerInfo.profile;
583
+ if (registry && effectiveProfile) {
584
+ const entry = resolveContractEntry(registry, effectiveProfile, 'profile');
585
+ if (!entry) {
586
+ failContract('CONTRACT_UNKNOWN_PROFILE_ID', `Unknown profile contract id in registry: ${effectiveProfile}`);
587
+ }
588
+ const verified = verifyContractArtifact(entry, resolvedRegistryPath);
589
+ if (!verified.ok) {
590
+ failContract(verified.code, verified.error);
591
+ }
592
+ }
593
+ let loadedSchema;
594
+ if (schemaPath) {
595
+ loadedSchema = isSchemaContractAeonPath(schemaPath)
596
+ ? readSchemaContractAeonFile(schemaPath)
597
+ : readSchemaFile(schemaPath);
598
+ }
599
+ else {
600
+ if (!registry || !resolvedRegistryPath) {
601
+ console.error('Error: Missing required --schema <schema.json> (or provide --contract-registry with aeon:schema header id)');
602
+ process.exit(2);
603
+ }
604
+ if (!headerInfo.schema) {
605
+ console.error('Error: Missing schema contract id (aeon:schema) for registry resolution');
606
+ process.exit(2);
607
+ }
608
+ const entry = resolveContractEntry(registry, headerInfo.schema, 'schema');
609
+ if (!entry) {
610
+ failContract('CONTRACT_UNKNOWN_SCHEMA_ID', `Unknown schema contract id in registry: ${headerInfo.schema}`);
611
+ }
612
+ const verified = verifyContractArtifact(entry, resolvedRegistryPath);
613
+ if (!verified.ok) {
614
+ failContract(verified.code, verified.error);
615
+ }
616
+ loadedSchema = readSchemaContractAeonFile(verified.resolvedPath, entry.id);
617
+ }
618
+ const result = runTypedRuntime(input, {
619
+ schema: loadedSchema.schema,
620
+ mode,
621
+ ...(datatypePolicy ? { datatypePolicy } : {}),
622
+ includeAnnotations,
623
+ scope,
624
+ ...(maxInputBytes !== undefined ? { maxInputBytes } : {}),
625
+ ...(projected ? { materialization: 'projected', includePaths } : {}),
626
+ ...(trailingSeparatorDelimiterPolicy ? { trailingSeparatorDelimiterPolicy } : {}),
627
+ ...(effectiveProfile ? { profile: effectiveProfile } : {}),
628
+ });
629
+ const contractWarnings = collectDeclaredContractOverrideWarnings(headerInfo, loadedSchema.schemaId, effectiveProfile);
630
+ const contractMeta = buildBindContractMeta(headerInfo, loadedSchema.schemaId, effectiveProfile);
631
+ const meta = {
632
+ ...result.meta,
633
+ warnings: [...result.meta.warnings, ...contractWarnings],
634
+ ...(contractMeta ? { contracts: contractMeta } : {}),
635
+ };
636
+ const annotations = includeAnnotations && result.annotations
637
+ ? (sortAnnotations ? sortAnnotationRecords(result.annotations) : result.annotations)
638
+ : undefined;
639
+ console.log(JSON.stringify({
640
+ ...(result.document !== undefined ? { document: result.document } : {}),
641
+ ...(annotations !== undefined ? { annotations } : {}),
642
+ meta,
643
+ }, null, 2));
644
+ if (meta.errors.length > 0) {
645
+ process.exit(1);
646
+ }
647
+ }
648
+ /**
649
+ * aeon integrity <validate|verify> <file> [--strict|--loose] [--public-key <path>]
650
+ * Purpose: validate/verify integrity envelopes
651
+ */
652
+ function integrity(args) {
653
+ const subcommand = args[0];
654
+ if (!subcommand) {
655
+ console.error('Error: Missing integrity subcommand');
656
+ console.error('Usage: aeon integrity <validate|verify> <file> [--strict|--loose]');
657
+ process.exit(2);
658
+ }
659
+ switch (subcommand) {
660
+ case 'validate':
661
+ integrityValidate(args.slice(1));
662
+ break;
663
+ case 'verify':
664
+ integrityVerify(args.slice(1));
665
+ break;
666
+ case 'sign':
667
+ integritySign(args.slice(1));
668
+ break;
669
+ default:
670
+ console.error(`Error: Unknown integrity subcommand: ${subcommand}`);
671
+ console.error('Usage: aeon integrity <validate|verify|sign> <file> [options]');
672
+ process.exit(2);
673
+ }
674
+ }
675
+ function integrityValidate(args) {
676
+ const mode = resolveIntegrityMode(args);
677
+ const jsonOutput = args.includes('--json');
678
+ const maxInputBytes = resolveMaxInputBytes(args);
679
+ if (!mode) {
680
+ console.error('Error: Cannot use both --strict and --loose');
681
+ console.error('Usage: aeon integrity validate <file> [--strict|--loose]');
682
+ process.exit(2);
683
+ }
684
+ if (maxInputBytes === null) {
685
+ console.error('Error: Invalid value for --max-input-bytes (expected a non-negative integer)');
686
+ process.exit(2);
687
+ }
688
+ const file = findFileWithValueFlags(args, ['--public-key', '--pubkey', '--receipt']);
689
+ if (!file) {
690
+ console.error('Error: No file specified');
691
+ console.error('Usage: aeon integrity validate <file> [--strict|--loose]');
692
+ process.exit(2);
693
+ }
694
+ const input = readFile(file);
695
+ enforceInputByteLimitOrExit(input, maxInputBytes);
696
+ const compileResult = compile(input, {
697
+ ...(maxInputBytes !== undefined ? { maxInputBytes } : {}),
698
+ });
699
+ if (compileResult.errors.length > 0) {
700
+ const errors = [{
701
+ level: 'error',
702
+ code: 'ENVELOPE_PARSE_ERROR',
703
+ message: 'Invalid AEON document for envelope validation',
704
+ }];
705
+ if (jsonOutput) {
706
+ outputEnvelopeJson(errors, [], false);
707
+ }
708
+ else {
709
+ outputEnvelopeDiagnostics(errors, 'ERROR');
710
+ }
711
+ process.exit(1);
712
+ }
713
+ const result = validateEnvelopeEvents(compileResult.events, { mode });
714
+ if (jsonOutput) {
715
+ outputEnvelopeJson(result.errors, result.warnings, result.errors.length === 0);
716
+ process.exit(result.errors.length === 0 ? 0 : 1);
717
+ }
718
+ else {
719
+ if (result.errors.length === 0) {
720
+ if (result.warnings.length > 0) {
721
+ outputEnvelopeDiagnostics(result.warnings, 'WARN');
722
+ }
723
+ console.log('OK');
724
+ process.exit(0);
725
+ }
726
+ outputEnvelopeDiagnostics(result.errors, 'ERROR');
727
+ process.exit(1);
728
+ }
729
+ }
730
+ function integrityVerify(args) {
731
+ const mode = resolveIntegrityMode(args);
732
+ const jsonOutput = args.includes('--json');
733
+ const maxInputBytes = resolveMaxInputBytes(args);
734
+ if (!mode) {
735
+ console.error('Error: Cannot use both --strict and --loose');
736
+ console.error('Usage: aeon integrity verify <file> [--strict|--loose] [--public-key <path>] [--receipt <path>]');
737
+ process.exit(2);
738
+ }
739
+ if (maxInputBytes === null) {
740
+ console.error('Error: Invalid value for --max-input-bytes (expected a non-negative integer)');
741
+ process.exit(2);
742
+ }
743
+ const file = findFileWithValueFlags(args, ['--public-key', '--pubkey']);
744
+ if (!file) {
745
+ console.error('Error: No file specified');
746
+ console.error('Usage: aeon integrity verify <file> [--strict|--loose] [--public-key <path>] [--receipt <path>]');
747
+ process.exit(2);
748
+ }
749
+ const input = readFile(file);
750
+ enforceInputByteLimitOrExit(input, maxInputBytes);
751
+ const baseInput = removeEnvelope(input);
752
+ const compileResult = compile(input, {
753
+ ...(maxInputBytes !== undefined ? { maxInputBytes } : {}),
754
+ });
755
+ const baseCompileResult = compile(baseInput, {
756
+ ...(maxInputBytes !== undefined ? { maxInputBytes } : {}),
757
+ });
758
+ if (compileResult.errors.length > 0) {
759
+ const errors = [{
760
+ level: 'error',
761
+ code: 'ENVELOPE_PARSE_ERROR',
762
+ message: 'Invalid AEON document for envelope validation',
763
+ }];
764
+ if (jsonOutput) {
765
+ outputEnvelopeJson(errors, [], false);
766
+ }
767
+ else {
768
+ outputEnvelopeDiagnostics(errors, 'ERROR');
769
+ }
770
+ process.exit(1);
771
+ }
772
+ if (baseCompileResult.errors.length > 0) {
773
+ const errors = [{
774
+ level: 'error',
775
+ code: 'ENVELOPE_PARSE_ERROR',
776
+ message: 'Invalid AEON document body for envelope verification',
777
+ }];
778
+ if (jsonOutput) {
779
+ outputEnvelopeJson(errors, [], false);
780
+ }
781
+ else {
782
+ outputEnvelopeDiagnostics(errors, 'ERROR');
783
+ }
784
+ process.exit(1);
785
+ }
786
+ const validation = validateEnvelopeEvents(compileResult.events, { mode });
787
+ if (validation.errors.length > 0) {
788
+ if (jsonOutput) {
789
+ outputEnvelopeJson(validation.errors, validation.warnings, false);
790
+ process.exit(1);
791
+ }
792
+ outputEnvelopeDiagnostics(validation.errors, 'ERROR');
793
+ process.exit(1);
794
+ }
795
+ const diagnostics = {
796
+ errors: [...validation.errors],
797
+ warnings: [...validation.warnings],
798
+ };
799
+ const { fields, errors: parseErrors } = extractEnvelopeFields(input);
800
+ diagnostics.errors.push(...parseErrors);
801
+ if (!fields) {
802
+ diagnostics.errors.push({
803
+ level: 'error',
804
+ code: 'ENVELOPE_MISSING',
805
+ message: 'an :envelope binding is required for verification',
806
+ });
807
+ }
808
+ const verificationMeta = {
809
+ canonical: { present: false },
810
+ bytes: { present: false },
811
+ checksum: { present: false },
812
+ signature: { present: false },
813
+ replay: { performed: false, status: 'unavailable' },
814
+ };
815
+ let receipt = resolveReceiptSidecarForVerify(file, args);
816
+ if (fields) {
817
+ const strict = mode === 'strict';
818
+ let hasVerificationTarget = false;
819
+ const canonicalHashValue = readEnvelopeFieldAny(fields, [
820
+ 'canonical_hash',
821
+ 'canonical:hash',
822
+ 'integrity.hash',
823
+ 'integrity.hash:string',
824
+ 'integrity:integrityBlock.hash',
825
+ 'integrity:integrityBlock.hash:string',
826
+ ], diagnostics);
827
+ const canonicalAlgValue = readEnvelopeFieldAny(fields, [
828
+ 'canonical_hash_alg',
829
+ 'canonical:hash_alg',
830
+ 'integrity.alg',
831
+ 'integrity.alg:string',
832
+ 'integrity:integrityBlock.alg',
833
+ 'integrity:integrityBlock.alg:string',
834
+ ], diagnostics);
835
+ if (canonicalHashValue) {
836
+ hasVerificationTarget = true;
837
+ verificationMeta.canonical.present = true;
838
+ const alg = canonicalAlgValue ?? 'sha-256';
839
+ if (!canonicalAlgValue) {
840
+ pushModeDiagnostic(diagnostics, strict, 'ENVELOPE_HASH_ALG_DEFAULTED', 'canonical_hash_alg missing; defaulting to sha-256');
841
+ }
842
+ const normalizedAlg = normalizeHashAlgorithm(alg, diagnostics, strict, 'canonical_hash_alg');
843
+ if (normalizedAlg) {
844
+ const computed = computeCanonicalHash(baseCompileResult.events, { algorithm: normalizedAlg });
845
+ verificationMeta.canonical.algorithm = normalizedAlg;
846
+ verificationMeta.canonical.expected = canonicalHashValue;
847
+ verificationMeta.canonical.computed = computed.hash;
848
+ verificationMeta.canonicalStream = { length: computed.stream.length };
849
+ verificationMeta.replay = {
850
+ performed: true,
851
+ status: normalizeHash(canonicalHashValue) === normalizeHash(computed.hash) ? 'match' : 'divergent',
852
+ expected: canonicalHashValue,
853
+ computed: computed.hash,
854
+ };
855
+ receipt ??= createCanonicalReceipt(baseInput, baseCompileResult.events, {
856
+ embedCanonicalPayload: false,
857
+ canonicalHashAlgorithm: normalizedAlg,
858
+ receiptDigestOverride: canonicalHashValue,
859
+ });
860
+ if (normalizeHash(canonicalHashValue) !== normalizeHash(computed.hash)) {
861
+ diagnostics.errors.push({
862
+ level: 'error',
863
+ code: 'ENVELOPE_HASH_MISMATCH',
864
+ message: 'canonical_hash does not match computed AES hash',
865
+ });
866
+ }
867
+ }
868
+ }
869
+ else if (canonicalAlgValue) {
870
+ pushModeDiagnostic(diagnostics, strict, 'ENVELOPE_HASH_MISSING', 'canonical_hash_alg present but canonical_hash is missing');
871
+ }
872
+ const bytesHashValue = readEnvelopeFieldAny(fields, [
873
+ 'bytes_hash',
874
+ 'bytes:hash',
875
+ 'integrity.bytes_hash',
876
+ 'integrity.bytes_hash:string',
877
+ 'integrity:integrityBlock.bytes_hash',
878
+ 'integrity:integrityBlock.bytes_hash:string',
879
+ ], diagnostics);
880
+ const bytesAlgValue = readEnvelopeFieldAny(fields, [
881
+ 'bytes_hash_alg',
882
+ 'bytes:hash_alg',
883
+ 'integrity.bytes_hash_alg',
884
+ 'integrity.bytes_hash_alg:string',
885
+ 'integrity:integrityBlock.bytes_hash_alg',
886
+ 'integrity:integrityBlock.bytes_hash_alg:string',
887
+ ], diagnostics);
888
+ if (bytesHashValue) {
889
+ hasVerificationTarget = true;
890
+ verificationMeta.bytes.present = true;
891
+ const alg = bytesAlgValue ?? 'sha-256';
892
+ if (!bytesAlgValue) {
893
+ pushModeDiagnostic(diagnostics, strict, 'ENVELOPE_BYTES_ALG_DEFAULTED', 'bytes_hash_alg missing; defaulting to sha-256');
894
+ }
895
+ const normalizedAlg = normalizeHashAlgorithm(alg, diagnostics, strict, 'bytes_hash_alg');
896
+ if (normalizedAlg) {
897
+ const computed = computeByteHash(baseInput, { algorithm: normalizedAlg });
898
+ verificationMeta.bytes.algorithm = normalizedAlg;
899
+ verificationMeta.bytes.expected = bytesHashValue;
900
+ verificationMeta.bytes.computed = computed.hash;
901
+ if (normalizeHash(bytesHashValue) !== normalizeHash(computed.hash)) {
902
+ diagnostics.errors.push({
903
+ level: 'error',
904
+ code: 'ENVELOPE_BYTES_MISMATCH',
905
+ message: 'bytes_hash does not match computed document hash',
906
+ });
907
+ }
908
+ }
909
+ }
910
+ else if (bytesAlgValue) {
911
+ pushModeDiagnostic(diagnostics, strict, 'ENVELOPE_BYTES_HASH_MISSING', 'bytes_hash_alg present but bytes_hash is missing');
912
+ }
913
+ const checksumValue = readEnvelopeFieldAny(fields, [
914
+ 'checksum_value',
915
+ 'checksum:value',
916
+ 'integrity.checksum_value',
917
+ 'integrity.checksum_value:string',
918
+ 'integrity:integrityBlock.checksum_value',
919
+ 'integrity:integrityBlock.checksum_value:string',
920
+ ], diagnostics);
921
+ const checksumAlg = readEnvelopeFieldAny(fields, [
922
+ 'checksum_alg',
923
+ 'checksum:alg',
924
+ 'integrity.checksum_alg',
925
+ 'integrity.checksum_alg:string',
926
+ 'integrity:integrityBlock.checksum_alg',
927
+ 'integrity:integrityBlock.checksum_alg:string',
928
+ ], diagnostics);
929
+ if (checksumValue) {
930
+ hasVerificationTarget = true;
931
+ verificationMeta.checksum.present = true;
932
+ const alg = checksumAlg ?? 'sha-256';
933
+ if (!checksumAlg) {
934
+ pushModeDiagnostic(diagnostics, strict, 'ENVELOPE_CHECKSUM_ALG_DEFAULTED', 'checksum_alg missing; defaulting to sha-256');
935
+ }
936
+ const normalizedAlg = normalizeHashAlgorithm(alg, diagnostics, strict, 'checksum_alg');
937
+ if (normalizedAlg) {
938
+ const computed = computeByteHash(baseInput, { algorithm: normalizedAlg });
939
+ verificationMeta.checksum.algorithm = normalizedAlg;
940
+ verificationMeta.checksum.expected = checksumValue;
941
+ verificationMeta.checksum.computed = computed.hash;
942
+ if (normalizeHash(checksumValue) !== normalizeHash(computed.hash)) {
943
+ diagnostics.errors.push({
944
+ level: 'error',
945
+ code: 'ENVELOPE_CHECKSUM_MISMATCH',
946
+ message: 'checksum_value does not match computed document hash',
947
+ });
948
+ }
949
+ }
950
+ }
951
+ else if (checksumAlg) {
952
+ pushModeDiagnostic(diagnostics, strict, 'ENVELOPE_CHECKSUM_MISSING', 'checksum_alg present but checksum_value is missing');
953
+ }
954
+ const signature = readEnvelopeFieldAny(fields, [
955
+ 'sig',
956
+ 'signatures[0].sig',
957
+ 'signatures[0].sig:string',
958
+ 'signatures:signatureSet[0].sig',
959
+ 'signatures:signatureSet[0].sig:string',
960
+ ], diagnostics);
961
+ if (signature) {
962
+ hasVerificationTarget = true;
963
+ verificationMeta.signature.present = true;
964
+ const publicKeyPath = getFlagValue(args, '--public-key') ?? getFlagValue(args, '--pubkey');
965
+ if (!publicKeyPath) {
966
+ pushModeDiagnostic(diagnostics, strict, 'ENVELOPE_SIGNATURE_KEY_MISSING', 'sig present but no --public-key provided; signature not verified');
967
+ }
968
+ else {
969
+ const publicKey = readFile(publicKeyPath);
970
+ const payload = verificationMeta.canonical.expected ?? computeCanonicalHash(baseCompileResult.events, { algorithm: 'sha-256' }).hash;
971
+ const ok = verifyStringPayloadSignature(payload, signature, publicKey, { algorithm: 'ed25519' });
972
+ verificationMeta.signature.verified = ok;
973
+ if (!ok) {
974
+ diagnostics.errors.push({
975
+ level: 'error',
976
+ code: 'ENVELOPE_SIGNATURE_INVALID',
977
+ message: 'signature verification failed',
978
+ });
979
+ }
980
+ }
981
+ }
982
+ if (!hasVerificationTarget) {
983
+ pushModeDiagnostic(diagnostics, strict, 'ENVELOPE_NO_HASH', 'envelope contains no verifiable hash fields');
984
+ }
985
+ if (!verificationMeta.canonicalStream) {
986
+ const stream = computeCanonicalHash(baseCompileResult.events, { algorithm: 'sha-256' }).stream;
987
+ verificationMeta.canonicalStream = { length: stream.length };
988
+ }
989
+ }
990
+ if (diagnostics.errors.length > 0) {
991
+ if (jsonOutput) {
992
+ outputEnvelopeJson(diagnostics.errors, diagnostics.warnings, false, verificationMeta, receipt);
993
+ process.exit(1);
994
+ }
995
+ else {
996
+ outputEnvelopeDiagnostics(diagnostics.errors, 'ERROR');
997
+ if (diagnostics.warnings.length > 0) {
998
+ outputEnvelopeDiagnostics(diagnostics.warnings, 'WARN');
999
+ }
1000
+ process.exit(1);
1001
+ }
1002
+ }
1003
+ if (diagnostics.warnings.length > 0) {
1004
+ if (jsonOutput) {
1005
+ outputEnvelopeJson(diagnostics.errors, diagnostics.warnings, true, verificationMeta, receipt);
1006
+ process.exit(0);
1007
+ }
1008
+ else {
1009
+ outputEnvelopeDiagnostics(diagnostics.warnings, 'WARN');
1010
+ }
1011
+ }
1012
+ if (jsonOutput) {
1013
+ outputEnvelopeJson(diagnostics.errors, diagnostics.warnings, true, verificationMeta, receipt);
1014
+ }
1015
+ else {
1016
+ console.log('OK');
1017
+ }
1018
+ }
1019
+ function integritySign(args) {
1020
+ const file = findFileWithValueFlags(args, ['--private-key', '--privkey', '--receipt']);
1021
+ const jsonOutput = args.includes('--json');
1022
+ const writeOutput = args.includes('--write');
1023
+ const replaceOutput = args.includes('--replace');
1024
+ const includeBytes = args.includes('--include-bytes');
1025
+ const includeChecksum = args.includes('--include-checksum');
1026
+ const maxInputBytes = resolveMaxInputBytes(args);
1027
+ if (!file) {
1028
+ console.error('Error: No file specified');
1029
+ console.error('Usage: aeon integrity sign <file> --private-key <path> [--receipt <path>]');
1030
+ process.exit(2);
1031
+ }
1032
+ if (maxInputBytes === null) {
1033
+ console.error('Error: Invalid value for --max-input-bytes (expected a non-negative integer)');
1034
+ process.exit(2);
1035
+ }
1036
+ const privateKeyPath = getFlagValue(args, '--private-key') ?? getFlagValue(args, '--privkey');
1037
+ if (!privateKeyPath) {
1038
+ console.error('Error: Missing --private-key');
1039
+ console.error('Usage: aeon integrity sign <file> --private-key <path> [--receipt <path>]');
1040
+ process.exit(2);
1041
+ }
1042
+ const input = readFile(file);
1043
+ enforceInputByteLimitOrExit(input, maxInputBytes);
1044
+ const baseInput = removeEnvelope(input);
1045
+ const envelopePresence = extractEnvelopeFields(input);
1046
+ if (envelopePresence.errors.length > 0) {
1047
+ if (jsonOutput) {
1048
+ outputEnvelopeJson(envelopePresence.errors, [], false);
1049
+ }
1050
+ else {
1051
+ outputEnvelopeDiagnostics(envelopePresence.errors, 'ERROR');
1052
+ }
1053
+ process.exit(1);
1054
+ }
1055
+ if (envelopePresence.fields && !replaceOutput) {
1056
+ const errors = [{
1057
+ level: 'error',
1058
+ code: 'ENVELOPE_EXISTS',
1059
+ message: 'document already contains an :envelope binding',
1060
+ }];
1061
+ if (jsonOutput) {
1062
+ outputEnvelopeJson(errors, [], false);
1063
+ }
1064
+ else {
1065
+ outputEnvelopeDiagnostics(errors, 'ERROR');
1066
+ }
1067
+ process.exit(1);
1068
+ }
1069
+ const compileResult = compile(baseInput, {
1070
+ datatypePolicy: 'allow_custom',
1071
+ ...(maxInputBytes !== undefined ? { maxInputBytes } : {}),
1072
+ });
1073
+ if (compileResult.errors.length > 0) {
1074
+ if (jsonOutput) {
1075
+ const errors = compileResult.errors.map((error) => ({
1076
+ level: 'error',
1077
+ code: error.code ?? 'AEON_ERROR',
1078
+ message: error.message,
1079
+ }));
1080
+ outputEnvelopeJson(errors, [], false);
1081
+ }
1082
+ else {
1083
+ for (const error of compileResult.errors) {
1084
+ console.log(formatErrorLine(error));
1085
+ }
1086
+ }
1087
+ process.exit(1);
1088
+ }
1089
+ const receipt = createCanonicalReceipt(baseInput, compileResult.events, {
1090
+ embedCanonicalPayload: true,
1091
+ });
1092
+ const receiptPath = resolveReceiptSidecarPathForSign(file, args, writeOutput);
1093
+ const canonical = computeCanonicalHash(compileResult.events, { algorithm: 'sha-256' });
1094
+ const bytes = includeBytes ? computeByteHash(baseInput, { algorithm: 'sha-256' }) : null;
1095
+ const checksum = includeChecksum ? computeByteHash(baseInput, { algorithm: 'sha-256' }) : null;
1096
+ const privateKey = readFile(privateKeyPath);
1097
+ const signature = signStringPayload(canonical.hash, privateKey, { algorithm: 'ed25519' });
1098
+ const lines = [
1099
+ `${ENVELOPE_CONVENTION_KEY}:envelope = {`,
1100
+ ' integrity:integrityBlock = {',
1101
+ ' alg:string = "sha-256"',
1102
+ ` hash:string = "${canonical.hash}"`,
1103
+ ];
1104
+ if (bytes) {
1105
+ lines.push(' bytes_hash_alg:string = "sha-256"');
1106
+ lines.push(` bytes_hash:string = "${bytes.hash}"`);
1107
+ }
1108
+ if (checksum) {
1109
+ lines.push(' checksum_alg:string = "sha-256"');
1110
+ lines.push(` checksum_value:string = "${checksum.hash}"`);
1111
+ }
1112
+ lines.push(' }');
1113
+ lines.push(' signatures:signatureSet = [');
1114
+ lines.push(' {');
1115
+ lines.push(' alg:string = "ed25519"');
1116
+ lines.push(' kid:string = "default"');
1117
+ lines.push(` sig:string = "${signature.signature}"`);
1118
+ lines.push(' }');
1119
+ lines.push(' ]');
1120
+ lines.push('}');
1121
+ const snippet = lines.join('\n');
1122
+ if (writeOutput) {
1123
+ const prepared = ensureGpSecurityConventions(baseInput);
1124
+ const nextContent = appendEnvelope(prepared.source, snippet);
1125
+ writeFileWithBackup(file, nextContent);
1126
+ if (receiptPath) {
1127
+ writeReceiptSidecar(receiptPath, receipt);
1128
+ }
1129
+ if (jsonOutput) {
1130
+ const envelope = {
1131
+ integrity: {
1132
+ alg: 'sha-256',
1133
+ hash: canonical.hash,
1134
+ ...(bytes ? { bytes_hash_alg: 'sha-256', bytes_hash: bytes.hash } : {}),
1135
+ ...(checksum ? { checksum_alg: 'sha-256', checksum_value: checksum.hash } : {}),
1136
+ },
1137
+ signatures: [
1138
+ {
1139
+ alg: 'ed25519',
1140
+ kid: 'default',
1141
+ sig: signature.signature,
1142
+ },
1143
+ ],
1144
+ };
1145
+ console.log(JSON.stringify({
1146
+ ok: true,
1147
+ written: true,
1148
+ replaced: replaceOutput,
1149
+ conventionsApplied: prepared.changed,
1150
+ receipt,
1151
+ envelope,
1152
+ }, null, 2));
1153
+ }
1154
+ else {
1155
+ console.log(`Wrote envelope to ${file}`);
1156
+ }
1157
+ return;
1158
+ }
1159
+ if (jsonOutput) {
1160
+ if (receiptPath) {
1161
+ writeReceiptSidecar(receiptPath, receipt);
1162
+ }
1163
+ const envelope = {
1164
+ integrity: {
1165
+ alg: 'sha-256',
1166
+ hash: canonical.hash,
1167
+ ...(bytes ? { bytes_hash_alg: 'sha-256', bytes_hash: bytes.hash } : {}),
1168
+ ...(checksum ? { checksum_alg: 'sha-256', checksum_value: checksum.hash } : {}),
1169
+ },
1170
+ signatures: [
1171
+ {
1172
+ alg: 'ed25519',
1173
+ kid: 'default',
1174
+ sig: signature.signature,
1175
+ },
1176
+ ],
1177
+ };
1178
+ console.log(JSON.stringify({ ok: true, receipt, envelope }, null, 2));
1179
+ return;
1180
+ }
1181
+ if (receiptPath) {
1182
+ writeReceiptSidecar(receiptPath, receipt);
1183
+ }
1184
+ console.log(snippet);
1185
+ }
1186
+ // =============================================================================
1187
+ // OUTPUT FORMATTERS
1188
+ // =============================================================================
1189
+ /**
1190
+ * Markdown output (default for inspect)
1191
+ */
1192
+ /**
1193
+ * JSON output (--json flag)
1194
+ */
1195
+ function outputJSON(result, options, headerInfo) {
1196
+ const visibleEvents = result.events.filter(e => !e.key.startsWith('aeon:'));
1197
+ const annotations = options.sortAnnotations
1198
+ ? sortAnnotationRecords(result.annotations ?? [])
1199
+ : (result.annotations ?? []);
1200
+ if (options.annotationsOnly) {
1201
+ console.log(JSON.stringify({ annotations }, null, 2));
1202
+ return;
1203
+ }
1204
+ const output = {
1205
+ events: visibleEvents.map(event => ({
1206
+ path: formatPath(event.path),
1207
+ key: event.key,
1208
+ datatype: event.datatype ?? null,
1209
+ span: event.span,
1210
+ // Preserve AST-like shape (no coercion/inference)
1211
+ value: jsonSafe(event.value),
1212
+ })),
1213
+ errors: result.errors.map(error => ({
1214
+ code: error.code,
1215
+ path: getErrorPath(error) ?? '$',
1216
+ span: error.span,
1217
+ ...(getPhaseLabel(error)
1218
+ ? { phaseLabel: getPhaseLabel(error) }
1219
+ : {}),
1220
+ message: error.message,
1221
+ })),
1222
+ };
1223
+ if (options.includeAnnotations) {
1224
+ output.annotations = annotations;
1225
+ }
1226
+ const contractMeta = headerInfo ? buildDeclaredInspectContractMeta(headerInfo) : null;
1227
+ if (contractMeta) {
1228
+ Object.assign(output, { contracts: contractMeta });
1229
+ }
1230
+ console.log(JSON.stringify(output, null, 2));
1231
+ }
1232
+ function jsonSafe(value) {
1233
+ if (value instanceof Map) {
1234
+ return Object.fromEntries(Array.from(value.entries(), ([key, entry]) => [String(key), jsonSafe(entry)]));
1235
+ }
1236
+ if (Array.isArray(value)) {
1237
+ return value.map(jsonSafe);
1238
+ }
1239
+ if (value && typeof value === 'object') {
1240
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsonSafe(entry)]));
1241
+ }
1242
+ return value;
1243
+ }
1244
+ function resolveFinalizeMode(args) {
1245
+ const hasStrict = args.includes('--strict');
1246
+ const hasLoose = args.includes('--loose');
1247
+ if (hasStrict && hasLoose)
1248
+ return null;
1249
+ if (hasLoose)
1250
+ return 'loose';
1251
+ return 'strict';
1252
+ }
1253
+ function resolveFinalizeScope(args) {
1254
+ const value = getFlagValue(args, '--scope');
1255
+ if (!value)
1256
+ return 'payload';
1257
+ if (value === 'payload' || value === 'header' || value === 'full') {
1258
+ return value;
1259
+ }
1260
+ return null;
1261
+ }
1262
+ function resolveDatatypePolicy(args) {
1263
+ const hasRichPreset = args.includes('--rich');
1264
+ const value = getFlagValue(args, '--datatype-policy');
1265
+ if (value === undefined) {
1266
+ if (args.includes('--datatype-policy'))
1267
+ return null;
1268
+ return hasRichPreset ? 'allow_custom' : null;
1269
+ }
1270
+ if (hasRichPreset && value === 'reserved_only')
1271
+ return null;
1272
+ if (value === 'reserved_only' || value === 'allow_custom') {
1273
+ return value;
1274
+ }
1275
+ return null;
1276
+ }
1277
+ function resolveIntegrityMode(args) {
1278
+ const hasStrict = args.includes('--strict');
1279
+ const hasLoose = args.includes('--loose');
1280
+ if (hasStrict && hasLoose)
1281
+ return null;
1282
+ if (hasLoose)
1283
+ return 'loose';
1284
+ return 'strict';
1285
+ }
1286
+ function mergeDiagnostics(finalized, errors) {
1287
+ const mergedErrors = [];
1288
+ const mergedWarnings = [];
1289
+ if (finalized.meta?.errors)
1290
+ mergedErrors.push(...finalized.meta.errors.map(toOutputDiagnostic));
1291
+ if (finalized.meta?.warnings)
1292
+ mergedWarnings.push(...finalized.meta.warnings.map(toOutputDiagnostic));
1293
+ for (const error of errors) {
1294
+ mergedErrors.push(toDiagnosticFromError(error));
1295
+ }
1296
+ const meta = {};
1297
+ if (mergedErrors.length > 0)
1298
+ meta.errors = mergedErrors;
1299
+ if (mergedWarnings.length > 0)
1300
+ meta.warnings = mergedWarnings;
1301
+ return meta;
1302
+ }
1303
+ function toOutputDiagnostic(diagnostic) {
1304
+ const phaseLabel = getPhaseLabel(diagnostic);
1305
+ return {
1306
+ ...diagnostic,
1307
+ ...(phaseLabel ? { phaseLabel } : {}),
1308
+ };
1309
+ }
1310
+ function finalizeJsonOutput(result, options) {
1311
+ const finalized = finalizeJson(result.events, {
1312
+ ...options,
1313
+ ...(result.header ? { header: result.header } : {}),
1314
+ });
1315
+ const meta = mergeDiagnostics(finalized, result.errors);
1316
+ return Object.keys(meta).length > 0
1317
+ ? { document: finalized.document, meta }
1318
+ : { document: finalized.document };
1319
+ }
1320
+ function finalizeMapOutput(result, options) {
1321
+ const finalized = finalizeMap(result.events, {
1322
+ ...options,
1323
+ ...(result.header ? { header: result.header } : {}),
1324
+ });
1325
+ const meta = mergeDiagnostics(finalized, result.errors);
1326
+ const entries = Array.from(finalized.document.entries.values()).map(entryToJson);
1327
+ const document = { entries };
1328
+ return Object.keys(meta).length > 0
1329
+ ? { document, meta }
1330
+ : { document };
1331
+ }
1332
+ function entryToJson(entry) {
1333
+ return {
1334
+ path: entry.path,
1335
+ value: entry.value,
1336
+ span: entry.span,
1337
+ ...(entry.datatype ? { datatype: entry.datatype } : {}),
1338
+ ...(entry.annotations ? { annotations: mapAnnotations(entry.annotations) } : {}),
1339
+ };
1340
+ }
1341
+ function mapAnnotations(annotations) {
1342
+ const entries = {};
1343
+ for (const [key, value] of annotations.entries()) {
1344
+ entries[key] = {
1345
+ value: value.value,
1346
+ ...(value.datatype ? { datatype: value.datatype } : {}),
1347
+ };
1348
+ }
1349
+ return entries;
1350
+ }
1351
+ function toDiagnosticFromError(error) {
1352
+ const code = error.code;
1353
+ const span = error.span;
1354
+ const path = getErrorPath(error);
1355
+ const phaseLabel = getPhaseLabel(error);
1356
+ return {
1357
+ level: 'error',
1358
+ message: error.message,
1359
+ ...(code ? { code } : {}),
1360
+ ...(path ? { path } : {}),
1361
+ ...(span ? { span } : {}),
1362
+ ...(phaseLabel ? { phaseLabel } : {}),
1363
+ };
1364
+ }
1365
+ function runDoctor(options) {
1366
+ const checks = [];
1367
+ const nodeMajor = Number.parseInt(process.version.replace(/^v/, '').split('.')[0] ?? '', 10);
1368
+ const workspacePackage = readJsonFileSafe(path.resolve(workspaceRoot, 'package.json'));
1369
+ const declaredPnpm = typeof workspacePackage?.packageManager === 'string'
1370
+ ? workspacePackage.packageManager
1371
+ : null;
1372
+ checks.push({
1373
+ name: 'node-version',
1374
+ status: Number.isFinite(nodeMajor) && nodeMajor >= 20 ? 'pass' : 'fail',
1375
+ message: Number.isFinite(nodeMajor) && nodeMajor >= 20
1376
+ ? `Node ${process.version} satisfies workspace requirement >=20`
1377
+ : `Node ${process.version} does not satisfy workspace requirement >=20`,
1378
+ details: {
1379
+ actual: process.version,
1380
+ required: '>=20.0.0',
1381
+ },
1382
+ });
1383
+ checks.push({
1384
+ name: 'pnpm-version',
1385
+ status: declaredPnpm ? 'pass' : 'warn',
1386
+ message: declaredPnpm
1387
+ ? `Workspace declares ${declaredPnpm}`
1388
+ : 'Workspace packageManager field is not set',
1389
+ ...(declaredPnpm ? { details: { declared: declaredPnpm } } : {}),
1390
+ });
1391
+ const requiredPackages = [
1392
+ '@altopelago/aeon-core',
1393
+ '@altopelago/aeon-finalize',
1394
+ '@altopelago/aeon-integrity',
1395
+ '@altopelago/aeon-profiles',
1396
+ '@altopelago/aeon-tonic',
1397
+ '@altopelago/aeos-core',
1398
+ ];
1399
+ const packageStatuses = requiredPackages.map((packageName) => resolveInstalledPackage(packageName));
1400
+ const missingPackages = packageStatuses.filter((entry) => !entry.ok);
1401
+ checks.push({
1402
+ name: 'package-availability',
1403
+ status: missingPackages.length === 0 ? 'pass' : 'fail',
1404
+ message: missingPackages.length === 0
1405
+ ? 'Required CLI/runtime packages are installed'
1406
+ : `Missing required packages: ${missingPackages.map((entry) => entry.name).join(', ')}`,
1407
+ details: {
1408
+ packages: packageStatuses.map((entry) => ({
1409
+ name: entry.name,
1410
+ ok: entry.ok,
1411
+ ...(entry.version ? { version: entry.version } : {}),
1412
+ })),
1413
+ },
1414
+ });
1415
+ checks.push(inspectContractRegistry(options.contractRegistryPath));
1416
+ checks.push({
1417
+ name: 'policy-surface',
1418
+ status: 'pass',
1419
+ message: 'CLI/runtime policy surface is available',
1420
+ details: {
1421
+ datatypePolicy: ['reserved_only', 'allow_custom'],
1422
+ finalizeMode: ['strict', 'loose'],
1423
+ trailingSeparatorDelimiterPolicy: ['off', 'warn', 'error'],
1424
+ recovery: true,
1425
+ },
1426
+ });
1427
+ return {
1428
+ ok: checks.every((check) => check.status !== 'fail'),
1429
+ checks,
1430
+ };
1431
+ }
1432
+ function outputDoctorHuman(result) {
1433
+ for (const check of result.checks) {
1434
+ const label = check.status === 'pass' ? 'PASS' : check.status === 'warn' ? 'WARN' : 'FAIL';
1435
+ console.log(`[${label}] ${check.name} ${check.message}`);
1436
+ }
1437
+ }
1438
+ function inspectContractRegistry(registryPath) {
1439
+ if (!fs.existsSync(registryPath)) {
1440
+ return {
1441
+ name: 'contract-registry',
1442
+ status: 'fail',
1443
+ message: `Contract registry not found: ${registryPath}`,
1444
+ };
1445
+ }
1446
+ const registry = readContractRegistryFileSafe(registryPath);
1447
+ if (!registry) {
1448
+ return {
1449
+ name: 'contract-registry',
1450
+ status: 'fail',
1451
+ message: `Contract registry is unreadable: ${registryPath}`,
1452
+ };
1453
+ }
1454
+ const entryResults = registry.contracts.map((entry) => {
1455
+ const verified = verifyContractArtifact(entry, registryPath);
1456
+ return {
1457
+ id: entry.id,
1458
+ kind: entry.kind,
1459
+ status: verified.ok ? 'pass' : 'fail',
1460
+ ...(verified.ok
1461
+ ? { path: verified.resolvedPath }
1462
+ : { error: verified.error, code: verified.code }),
1463
+ };
1464
+ });
1465
+ const failures = entryResults.filter((entry) => entry.status === 'fail');
1466
+ return {
1467
+ name: 'contract-registry',
1468
+ status: failures.length === 0 ? 'pass' : 'fail',
1469
+ message: failures.length === 0
1470
+ ? `Verified ${entryResults.length} contract artifact(s) from ${registryPath}`
1471
+ : `Registry verification failed for ${failures.length} contract artifact(s)`,
1472
+ details: {
1473
+ path: registryPath,
1474
+ entries: entryResults,
1475
+ },
1476
+ };
1477
+ }
1478
+ function resolveInstalledPackage(packageName) {
1479
+ const packageJsonPath = path.resolve(cliPackageRoot, 'node_modules', ...packageName.split('/'), 'package.json');
1480
+ if (!fs.existsSync(packageJsonPath)) {
1481
+ return { name: packageName, ok: false };
1482
+ }
1483
+ const pkg = readJsonFileSafe(packageJsonPath);
1484
+ return {
1485
+ name: packageName,
1486
+ ok: true,
1487
+ ...(typeof pkg?.version === 'string' ? { version: pkg.version } : {}),
1488
+ };
1489
+ }
1490
+ function readJsonFileSafe(file) {
1491
+ try {
1492
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
1493
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
1494
+ return null;
1495
+ }
1496
+ return parsed;
1497
+ }
1498
+ catch {
1499
+ return null;
1500
+ }
1501
+ }
1502
+ function readContractRegistryFileSafe(file) {
1503
+ const parsed = readJsonFileSafe(file);
1504
+ if (!parsed || !Array.isArray(parsed.contracts)) {
1505
+ return null;
1506
+ }
1507
+ const contracts = parsed.contracts;
1508
+ for (const entry of contracts) {
1509
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
1510
+ return null;
1511
+ }
1512
+ const candidate = entry;
1513
+ if (typeof candidate.id !== 'string' ||
1514
+ (candidate.kind !== 'profile' && candidate.kind !== 'schema') ||
1515
+ typeof candidate.version !== 'string' ||
1516
+ typeof candidate.path !== 'string' ||
1517
+ typeof candidate.sha256 !== 'string' ||
1518
+ (candidate.status !== 'active' && candidate.status !== 'deprecated')) {
1519
+ return null;
1520
+ }
1521
+ }
1522
+ return parsed;
1523
+ }
1524
+ // =============================================================================
1525
+ // HELPERS
1526
+ // =============================================================================
1527
+ function formatGenericErrorLine(error) {
1528
+ const code = error.code ?? 'UNKNOWN';
1529
+ const span = formatSpan(error.span);
1530
+ const message = String(error.message).replace(/[\r\n]+/g, ' ');
1531
+ const phaseLabel = getPhaseLabel(error);
1532
+ const prefix = phaseLabel ? `${phaseLabel}: ` : '';
1533
+ return `${prefix}${message} [${code}] path=$ span=${span}`;
1534
+ }
1535
+ function findFile(args) {
1536
+ return args.find(arg => !arg.startsWith('--'));
1537
+ }
1538
+ function findFileWithValueFlags(args, valueFlags) {
1539
+ const skip = new Set();
1540
+ for (let i = 0; i < args.length; i++) {
1541
+ if (valueFlags.includes(args[i] ?? '') && i + 1 < args.length) {
1542
+ skip.add(i + 1);
1543
+ }
1544
+ }
1545
+ for (let i = 0; i < args.length; i++) {
1546
+ const arg = args[i] ?? '';
1547
+ if (arg.startsWith('--'))
1548
+ continue;
1549
+ if (skip.has(i))
1550
+ continue;
1551
+ return arg;
1552
+ }
1553
+ return undefined;
1554
+ }
1555
+ function getFlagValue(args, flag) {
1556
+ const index = args.indexOf(flag);
1557
+ if (index === -1)
1558
+ return undefined;
1559
+ const value = args[index + 1];
1560
+ if (!value || value.startsWith('--'))
1561
+ return undefined;
1562
+ return value;
1563
+ }
1564
+ function getFlagValues(args, flag) {
1565
+ const values = [];
1566
+ for (let i = 0; i < args.length; i += 1) {
1567
+ if (args[i] !== flag)
1568
+ continue;
1569
+ const value = args[i + 1];
1570
+ if (!value || value.startsWith('--'))
1571
+ continue;
1572
+ values.push(value);
1573
+ }
1574
+ return values;
1575
+ }
1576
+ function resolveMaxInputBytes(args) {
1577
+ if (!args.includes('--max-input-bytes'))
1578
+ return undefined;
1579
+ const value = getFlagValue(args, '--max-input-bytes');
1580
+ if (value === undefined)
1581
+ return null;
1582
+ if (!/^\d+$/.test(value))
1583
+ return null;
1584
+ return Number.parseInt(value, 10);
1585
+ }
1586
+ function resolveDepthOption(args, flag) {
1587
+ if (!args.includes(flag))
1588
+ return undefined;
1589
+ const value = getFlagValue(args, flag);
1590
+ if (value === undefined)
1591
+ return null;
1592
+ if (!/^\d+$/.test(value))
1593
+ return null;
1594
+ return Number.parseInt(value, 10);
1595
+ }
1596
+ function enforceInputByteLimitOrExit(input, maxInputBytes) {
1597
+ if (maxInputBytes === undefined)
1598
+ return;
1599
+ const actualBytes = Buffer.byteLength(input, 'utf8');
1600
+ if (actualBytes <= maxInputBytes)
1601
+ return;
1602
+ console.error(`Error: Input size ${actualBytes} bytes exceeds configured limit of ${maxInputBytes} bytes`);
1603
+ process.exit(1);
1604
+ }
1605
+ function readFile(file) {
1606
+ try {
1607
+ return fs.readFileSync(file, 'utf-8');
1608
+ }
1609
+ catch (err) {
1610
+ console.error(`Error: Cannot read file: ${file}`);
1611
+ process.exit(2);
1612
+ }
1613
+ }
1614
+ function readStdin() {
1615
+ try {
1616
+ return fs.readFileSync(0, 'utf-8');
1617
+ }
1618
+ catch {
1619
+ console.error('Error: Cannot read stdin');
1620
+ process.exit(2);
1621
+ }
1622
+ }
1623
+ function ensureTrailingNewline(text) {
1624
+ return text.endsWith('\n') ? text : `${text}\n`;
1625
+ }
1626
+ function readSchemaFile(file) {
1627
+ const raw = readFile(file);
1628
+ let parsed;
1629
+ try {
1630
+ parsed = JSON.parse(raw);
1631
+ }
1632
+ catch {
1633
+ console.error(`Error: Schema file is not valid JSON: ${file}`);
1634
+ process.exit(2);
1635
+ }
1636
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
1637
+ console.error(`Error: Schema file must be a JSON object: ${file}`);
1638
+ process.exit(2);
1639
+ }
1640
+ return normalizeLegacySchemaContractDoc(parsed, file);
1641
+ }
1642
+ function normalizeLegacySchemaContractDoc(doc, file, expectedSchemaId) {
1643
+ const schemaId = doc['schema_id'];
1644
+ const schemaVersion = doc['schema_version'];
1645
+ const rulesRaw = doc['rules'];
1646
+ const world = doc['world'];
1647
+ const referencePolicy = doc['reference_policy'];
1648
+ const datatypeRules = doc['datatype_rules'];
1649
+ const datatypeAllowlist = doc['datatype_allowlist'];
1650
+ const allowedTopLevel = new Set([
1651
+ 'schema_id',
1652
+ 'schema_version',
1653
+ 'rules',
1654
+ 'world',
1655
+ 'reference_policy',
1656
+ 'datatype_rules',
1657
+ 'datatype_allowlist',
1658
+ ]);
1659
+ for (const key of Object.keys(doc)) {
1660
+ if (!allowedTopLevel.has(key)) {
1661
+ console.error(`Error: Unknown schema contract key '${key}' in ${file}`);
1662
+ process.exit(2);
1663
+ }
1664
+ }
1665
+ if (typeof schemaId !== 'string' || schemaId.length === 0) {
1666
+ console.error(`Error: Schema contract missing required string field 'schema_id': ${file}`);
1667
+ process.exit(2);
1668
+ }
1669
+ if (expectedSchemaId && schemaId !== expectedSchemaId) {
1670
+ console.error(`Error: Schema contract id mismatch. Expected '${expectedSchemaId}', found '${schemaId}' in ${file}`);
1671
+ process.exit(2);
1672
+ }
1673
+ if (typeof schemaVersion !== 'string' || schemaVersion.length === 0) {
1674
+ console.error(`Error: Schema contract missing required string field 'schema_version': ${file}`);
1675
+ process.exit(2);
1676
+ }
1677
+ if (!Array.isArray(rulesRaw)) {
1678
+ console.error(`Error: Schema contract missing required array field 'rules': ${file}`);
1679
+ process.exit(2);
1680
+ }
1681
+ if (world !== undefined && world !== 'open' && world !== 'closed') {
1682
+ console.error(`Error: Schema contract field 'world' must be "open" or "closed": ${file}`);
1683
+ process.exit(2);
1684
+ }
1685
+ if (referencePolicy !== undefined && referencePolicy !== 'allow' && referencePolicy !== 'forbid') {
1686
+ console.error(`Error: Schema contract field 'reference_policy' must be "allow" or "forbid": ${file}`);
1687
+ process.exit(2);
1688
+ }
1689
+ if (datatypeRules !== undefined) {
1690
+ if (!datatypeRules || typeof datatypeRules !== 'object' || Array.isArray(datatypeRules)) {
1691
+ console.error(`Error: Schema contract field 'datatype_rules' must be object: ${file}`);
1692
+ process.exit(2);
1693
+ }
1694
+ for (const [key, value] of Object.entries(datatypeRules)) {
1695
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1696
+ console.error(`Error: Schema contract datatype_rules['${key}'] must be object: ${file}`);
1697
+ process.exit(2);
1698
+ }
1699
+ }
1700
+ }
1701
+ if (datatypeAllowlist !== undefined) {
1702
+ if (!Array.isArray(datatypeAllowlist) || datatypeAllowlist.some((v) => typeof v !== 'string')) {
1703
+ console.error(`Error: Schema contract field 'datatype_allowlist' must be array<string>: ${file}`);
1704
+ process.exit(2);
1705
+ }
1706
+ }
1707
+ const rules = rulesRaw.map((rule, index) => {
1708
+ if (!rule || typeof rule !== 'object' || Array.isArray(rule)) {
1709
+ console.error(`Error: Schema contract rule at index ${index} is not an object: ${file}`);
1710
+ process.exit(2);
1711
+ }
1712
+ const ruleObj = rule;
1713
+ if (typeof ruleObj.path !== 'string' || !ruleObj.path) {
1714
+ console.error(`Error: Schema contract rule at index ${index} missing string 'path': ${file}`);
1715
+ process.exit(2);
1716
+ }
1717
+ if (!ruleObj.constraints || typeof ruleObj.constraints !== 'object' || Array.isArray(ruleObj.constraints)) {
1718
+ console.error(`Error: Schema contract rule at index ${index} missing object 'constraints': ${file}`);
1719
+ process.exit(2);
1720
+ }
1721
+ return {
1722
+ path: ruleObj.path,
1723
+ constraints: projectConstraints(ruleObj.constraints, String(ruleObj.path), file),
1724
+ };
1725
+ });
1726
+ const schema = {
1727
+ rules,
1728
+ ...(world !== undefined ? { world: world } : {}),
1729
+ ...(referencePolicy !== undefined ? { reference_policy: referencePolicy } : {}),
1730
+ ...(datatypeRules && typeof datatypeRules === 'object' && !Array.isArray(datatypeRules)
1731
+ ? { datatype_rules: projectDatatypeRules(datatypeRules, file) }
1732
+ : {}),
1733
+ ...(Array.isArray(datatypeAllowlist)
1734
+ ? { datatype_allowlist: datatypeAllowlist }
1735
+ : {}),
1736
+ };
1737
+ return { schema, schemaId };
1738
+ }
1739
+ function readSchemaContractAeonFile(file, expectedSchemaId) {
1740
+ const source = readFile(file);
1741
+ const compiled = compile(source, { datatypePolicy: 'allow_custom' });
1742
+ if (compiled.errors.length > 0) {
1743
+ console.error(`Error: Schema contract AEON file failed to parse: ${file}`);
1744
+ for (const error of compiled.errors) {
1745
+ console.error(` - ${error.code ?? 'AEON_ERROR'}: ${error.message}`);
1746
+ }
1747
+ process.exit(2);
1748
+ }
1749
+ const finalized = finalizeJson(compiled.events, { mode: 'strict' });
1750
+ if ((finalized.meta?.errors?.length ?? 0) > 0) {
1751
+ console.error(`Error: Schema contract AEON file failed to finalize: ${file}`);
1752
+ for (const error of finalized.meta?.errors ?? []) {
1753
+ console.error(` - ${error.message}`);
1754
+ }
1755
+ process.exit(2);
1756
+ }
1757
+ const document = finalized.document;
1758
+ const rootEvent = findAeosSchemaRootEvent(compiled.events);
1759
+ if (!rootEvent) {
1760
+ return normalizeLegacySchemaContractDoc(document, file, expectedSchemaId);
1761
+ }
1762
+ const aeosRoot = document['aeos'];
1763
+ if (!aeosRoot || typeof aeosRoot !== 'object' || Array.isArray(aeosRoot)) {
1764
+ console.error(`Error: Schema document missing required '$.aeos' object: ${file}`);
1765
+ process.exit(2);
1766
+ }
1767
+ return normalizeAeosSchemaDoc(aeosRoot, file, expectedSchemaId);
1768
+ }
1769
+ function normalizeAeosSchemaDoc(doc, file, expectedSchemaId) {
1770
+ const schemaId = doc['id'];
1771
+ const schemaVersion = doc['version'];
1772
+ const rulesRaw = doc['rules'];
1773
+ const world = doc['world'];
1774
+ const referencePolicy = doc['reference_policy'];
1775
+ const datatypeRules = doc['datatype_rules'];
1776
+ const datatypeAllowlist = doc['datatype_allowlist'];
1777
+ const allowedTopLevel = new Set([
1778
+ 'id',
1779
+ 'version',
1780
+ 'rules',
1781
+ 'patterns',
1782
+ 'charsets',
1783
+ 'world',
1784
+ 'reference_policy',
1785
+ 'datatype_rules',
1786
+ 'datatype_allowlist',
1787
+ ]);
1788
+ for (const key of Object.keys(doc)) {
1789
+ if (!allowedTopLevel.has(key)) {
1790
+ console.error(`Error: Unknown schema document key '${key}' in ${file}`);
1791
+ process.exit(2);
1792
+ }
1793
+ }
1794
+ if (typeof schemaId !== 'string' || schemaId.length === 0) {
1795
+ console.error(`Error: Schema document missing required string field 'id': ${file}`);
1796
+ process.exit(2);
1797
+ }
1798
+ if (expectedSchemaId && schemaId !== expectedSchemaId) {
1799
+ console.error(`Error: Schema contract id mismatch. Expected '${expectedSchemaId}', found '${schemaId}' in ${file}`);
1800
+ process.exit(2);
1801
+ }
1802
+ if (typeof schemaVersion !== 'string' || schemaVersion.length === 0) {
1803
+ console.error(`Error: Schema document missing required string field 'version': ${file}`);
1804
+ process.exit(2);
1805
+ }
1806
+ if (!rulesRaw || (typeof rulesRaw !== 'object')) {
1807
+ console.error(`Error: Schema document missing required field 'rules': ${file}`);
1808
+ process.exit(2);
1809
+ }
1810
+ if (world !== undefined && world !== 'open' && world !== 'closed') {
1811
+ console.error(`Error: Schema document field 'world' must be "open" or "closed": ${file}`);
1812
+ process.exit(2);
1813
+ }
1814
+ if (referencePolicy !== undefined && referencePolicy !== 'allow' && referencePolicy !== 'forbid') {
1815
+ console.error(`Error: Schema document field 'reference_policy' must be "allow" or "forbid": ${file}`);
1816
+ process.exit(2);
1817
+ }
1818
+ if (datatypeRules !== undefined && (!datatypeRules || typeof datatypeRules !== 'object' || Array.isArray(datatypeRules))) {
1819
+ console.error(`Error: Schema document field 'datatype_rules' must be object: ${file}`);
1820
+ process.exit(2);
1821
+ }
1822
+ if (datatypeAllowlist !== undefined) {
1823
+ if (!Array.isArray(datatypeAllowlist) || datatypeAllowlist.some((v) => typeof v !== 'string')) {
1824
+ console.error(`Error: Schema document field 'datatype_allowlist' must be array<string>: ${file}`);
1825
+ process.exit(2);
1826
+ }
1827
+ }
1828
+ const rules = normalizeAeosRules(rulesRaw, file);
1829
+ const schema = {
1830
+ rules,
1831
+ ...(world !== undefined ? { world: world } : {}),
1832
+ ...(referencePolicy !== undefined ? { reference_policy: referencePolicy } : {}),
1833
+ ...(datatypeRules && typeof datatypeRules === 'object' && !Array.isArray(datatypeRules)
1834
+ ? { datatype_rules: projectDatatypeRules(datatypeRules, file) }
1835
+ : {}),
1836
+ ...(Array.isArray(datatypeAllowlist)
1837
+ ? { datatype_allowlist: datatypeAllowlist }
1838
+ : {}),
1839
+ };
1840
+ return { schema, schemaId };
1841
+ }
1842
+ function normalizeAeosRules(rulesRaw, file) {
1843
+ return Object.entries(rulesRaw).map(([rulePath, constraints]) => {
1844
+ if (!constraints || typeof constraints !== 'object' || Array.isArray(constraints)) {
1845
+ console.error(`Error: Schema rule '${rulePath}' must be an object of constraints: ${file}`);
1846
+ process.exit(2);
1847
+ }
1848
+ return {
1849
+ path: rulePath,
1850
+ constraints: projectConstraints(constraints, rulePath, file),
1851
+ };
1852
+ });
1853
+ }
1854
+ function projectDatatypeRules(datatypeRules, file) {
1855
+ const projected = {};
1856
+ for (const [key, value] of Object.entries(datatypeRules)) {
1857
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1858
+ console.error(`Error: Schema contract datatype_rules['${key}'] must be object: ${file}`);
1859
+ process.exit(2);
1860
+ }
1861
+ projected[key] = projectConstraints(value, `datatype_rules.${key}`, file);
1862
+ }
1863
+ return projected;
1864
+ }
1865
+ function projectConstraints(constraints, owner, file) {
1866
+ const projected = { ...constraints };
1867
+ const pathSelector = projected.reference_target_path;
1868
+ delete projected.reference_target_path;
1869
+ if (pathSelector !== undefined) {
1870
+ if (projected.reference_target_pattern !== undefined) {
1871
+ console.error(`Error: Schema rule '${owner}' cannot declare both 'reference_target_path' and 'reference_target_pattern': ${file}`);
1872
+ process.exit(2);
1873
+ }
1874
+ if (typeof pathSelector !== 'string' || pathSelector.length === 0) {
1875
+ console.error(`Error: Schema rule '${owner}' field 'reference_target_path' must be a non-empty string: ${file}`);
1876
+ process.exit(2);
1877
+ }
1878
+ projected.reference_target_pattern = referenceTargetPathToPattern(pathSelector);
1879
+ }
1880
+ return projected;
1881
+ }
1882
+ function referenceTargetPathToPattern(selector) {
1883
+ if (selector.replaceAll('[*]', '').includes('*')) {
1884
+ console.error(`Error: Unsupported reference_target_path selector: ${selector}`);
1885
+ process.exit(2);
1886
+ }
1887
+ const placeholder = '__AEOS_WILDCARD_INDEX__';
1888
+ return `^${escapeRegex(selector.replaceAll('[*]', placeholder)).replace(escapeRegex(placeholder), String.raw `\[\d+\]`)}$`;
1889
+ }
1890
+ function escapeRegex(value) {
1891
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1892
+ }
1893
+ function findAeosSchemaRootEvent(events) {
1894
+ for (const event of events) {
1895
+ if (event.key === 'aeos' && event.datatype === 'schema' && formatPath(event.path) === '$.aeos') {
1896
+ return event;
1897
+ }
1898
+ }
1899
+ return null;
1900
+ }
1901
+ function isSchemaContractAeonPath(file) {
1902
+ const lower = file.toLowerCase();
1903
+ return lower.endsWith('.aeon') || lower.endsWith('.aeos');
1904
+ }
1905
+ function readContractRegistryFile(file) {
1906
+ const raw = readFile(file);
1907
+ let parsed;
1908
+ try {
1909
+ parsed = JSON.parse(raw);
1910
+ }
1911
+ catch {
1912
+ console.error(`Error: Contract registry file is not valid JSON: ${file}`);
1913
+ process.exit(2);
1914
+ }
1915
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.contracts)) {
1916
+ console.error(`Error: Contract registry JSON must contain a top-level 'contracts' array: ${file}`);
1917
+ process.exit(2);
1918
+ }
1919
+ const contracts = parsed.contracts;
1920
+ for (let i = 0; i < contracts.length; i++) {
1921
+ const entry = contracts[i];
1922
+ if (!entry || typeof entry !== 'object') {
1923
+ console.error(`Error: Invalid contract registry entry at index ${i}`);
1924
+ process.exit(2);
1925
+ }
1926
+ const candidate = entry;
1927
+ const kind = candidate.kind;
1928
+ const status = candidate.status;
1929
+ if (typeof candidate.id !== 'string' ||
1930
+ (kind !== 'profile' && kind !== 'schema') ||
1931
+ typeof candidate.version !== 'string' ||
1932
+ typeof candidate.path !== 'string' ||
1933
+ !candidate.path.toLowerCase().endsWith('.aeon') ||
1934
+ typeof candidate.sha256 !== 'string' ||
1935
+ !/^[a-f0-9]{64}$/i.test(candidate.sha256) ||
1936
+ (status !== 'active' && status !== 'deprecated')) {
1937
+ console.error(`Error: Invalid contract registry entry shape at index ${i}`);
1938
+ process.exit(2);
1939
+ }
1940
+ }
1941
+ return parsed;
1942
+ }
1943
+ function resolveContractEntry(registry, id, kind) {
1944
+ const entry = registry.contracts.find((contract) => contract.id === id && contract.kind === kind);
1945
+ if (!entry)
1946
+ return null;
1947
+ if (entry.status !== 'active')
1948
+ return null;
1949
+ return entry;
1950
+ }
1951
+ function failContract(code, message) {
1952
+ console.error(`Error [${code}]: ${message}`);
1953
+ process.exit(2);
1954
+ }
1955
+ function verifyContractArtifact(entry, registryPath) {
1956
+ const baseDir = path.dirname(path.resolve(registryPath));
1957
+ const resolvedPath = path.resolve(baseDir, entry.path);
1958
+ let fileBuffer;
1959
+ try {
1960
+ fileBuffer = fs.readFileSync(resolvedPath);
1961
+ }
1962
+ catch {
1963
+ return {
1964
+ ok: false,
1965
+ code: 'CONTRACT_ARTIFACT_MISSING',
1966
+ error: `Missing contract artifact for '${entry.id}' at ${resolvedPath}`,
1967
+ };
1968
+ }
1969
+ const actual = createHash('sha256').update(fileBuffer).digest('hex');
1970
+ if (actual !== entry.sha256.toLowerCase()) {
1971
+ return {
1972
+ ok: false,
1973
+ code: 'CONTRACT_ARTIFACT_HASH_MISMATCH',
1974
+ error: `Contract artifact hash mismatch for '${entry.id}' at ${resolvedPath}`,
1975
+ };
1976
+ }
1977
+ return { ok: true, resolvedPath };
1978
+ }
1979
+ function appendEnvelope(source, envelope) {
1980
+ const trimmed = source.trimEnd();
1981
+ const separator = trimmed.length === 0 || trimmed.endsWith('\n') ? '' : '\n';
1982
+ return `${trimmed}${separator}\n${envelope}\n`;
1983
+ }
1984
+ function removeEnvelope(source) {
1985
+ const trimmed = source.trimEnd();
1986
+ const lines = trimmed.split('\n');
1987
+ let startIndex = -1;
1988
+ let openBraces = 0;
1989
+ for (let i = 0; i < lines.length; i++) {
1990
+ const line = lines[i] ?? '';
1991
+ if (startIndex === -1) {
1992
+ if (isEnvelopeStartLine(line)) {
1993
+ startIndex = i;
1994
+ openBraces += countChar(line, '{') - countChar(line, '}');
1995
+ if (openBraces <= 0) {
1996
+ const before = lines.slice(0, i);
1997
+ const after = lines.slice(i + 1);
1998
+ return [...before, ...after].join('\n').trimEnd();
1999
+ }
2000
+ }
2001
+ }
2002
+ else {
2003
+ openBraces += countChar(line, '{') - countChar(line, '}');
2004
+ if (openBraces <= 0) {
2005
+ const before = lines.slice(0, startIndex);
2006
+ const after = lines.slice(i + 1);
2007
+ return [...before, ...after].join('\n').trimEnd();
2008
+ }
2009
+ }
2010
+ }
2011
+ return source.trimEnd();
2012
+ }
2013
+ function countChar(value, ch) {
2014
+ let count = 0;
2015
+ for (let i = 0; i < value.length; i++) {
2016
+ if (value[i] === ch)
2017
+ count += 1;
2018
+ }
2019
+ return count;
2020
+ }
2021
+ function writeFileWithBackup(file, contents) {
2022
+ const backupPath = nextBackupPath(file);
2023
+ fs.copyFileSync(file, backupPath);
2024
+ fs.writeFileSync(file, contents, 'utf-8');
2025
+ }
2026
+ function nextBackupPath(file) {
2027
+ let candidate = `${file}.bak`;
2028
+ if (!fs.existsSync(candidate))
2029
+ return candidate;
2030
+ let index = 1;
2031
+ while (fs.existsSync(`${file}.bak${index}`)) {
2032
+ index += 1;
2033
+ }
2034
+ return `${file}.bak${index}`;
2035
+ }
2036
+ function ensureGpSecurityConventions(source) {
2037
+ const structured = findStructuredHeaderRange(source);
2038
+ if (!structured) {
2039
+ const header = renderSecurityHeader();
2040
+ return {
2041
+ source: `${header}\n\n${source.trimStart()}`.trimEnd(),
2042
+ changed: true,
2043
+ };
2044
+ }
2045
+ const headerBlock = source.slice(structured.start, structured.end);
2046
+ const existing = new Set(extractHeaderConventions(headerBlock));
2047
+ const missing = GP_SECURITY_CONVENTIONS.filter((entry) => !existing.has(entry));
2048
+ if (missing.length === 0) {
2049
+ return { source, changed: false };
2050
+ }
2051
+ const updated = mergeSecurityConventionsIntoHeader(headerBlock, missing);
2052
+ return {
2053
+ source: `${source.slice(0, structured.start)}${updated}${source.slice(structured.end)}`,
2054
+ changed: true,
2055
+ };
2056
+ }
2057
+ function renderSecurityHeader() {
2058
+ return [
2059
+ 'aeon:header = {',
2060
+ ' conventions:conventionSet = [',
2061
+ ...GP_SECURITY_CONVENTIONS.map((entry) => ` "${entry}"`),
2062
+ ' ]',
2063
+ '}',
2064
+ ].join('\n');
2065
+ }
2066
+ function findStructuredHeaderRange(source) {
2067
+ const marker = /aeon:header\s*=\s*\{/g;
2068
+ const match = marker.exec(source);
2069
+ if (!match)
2070
+ return null;
2071
+ const start = match.index;
2072
+ const openIndex = source.indexOf('{', match.index);
2073
+ if (openIndex === -1)
2074
+ return null;
2075
+ let depth = 0;
2076
+ let inString = null;
2077
+ let escaping = false;
2078
+ for (let i = openIndex; i < source.length; i++) {
2079
+ const ch = source[i];
2080
+ if (inString) {
2081
+ if (escaping) {
2082
+ escaping = false;
2083
+ continue;
2084
+ }
2085
+ if (ch === '\\') {
2086
+ escaping = true;
2087
+ continue;
2088
+ }
2089
+ if (ch === inString) {
2090
+ inString = null;
2091
+ }
2092
+ continue;
2093
+ }
2094
+ if (ch === '"' || ch === '\'' || ch === '`') {
2095
+ inString = ch;
2096
+ continue;
2097
+ }
2098
+ if (ch === '{')
2099
+ depth += 1;
2100
+ if (ch === '}') {
2101
+ depth -= 1;
2102
+ if (depth === 0) {
2103
+ let end = i + 1;
2104
+ while (end < source.length && (source[end] === '\n' || source[end] === '\r')) {
2105
+ end += 1;
2106
+ }
2107
+ return { start, end };
2108
+ }
2109
+ }
2110
+ }
2111
+ return null;
2112
+ }
2113
+ function extractHeaderConventions(headerBlock) {
2114
+ const match = headerBlock.match(/(^|\n)([ \t]*)conventions(?:\s*:[^=\n]+)?\s*=\s*\[([\s\S]*?)\n\2\]/);
2115
+ if (!match)
2116
+ return [];
2117
+ const body = match[3] ?? '';
2118
+ return [...body.matchAll(/"([^"]+)"/g)].map((entry) => entry[1]).filter(Boolean);
2119
+ }
2120
+ function mergeSecurityConventionsIntoHeader(headerBlock, missing) {
2121
+ const listPattern = /(^|\n)([ \t]*)conventions(?:\s*:[^=\n]+)?\s*=\s*\[([\s\S]*?)\n\2\]/;
2122
+ const match = headerBlock.match(listPattern);
2123
+ if (!match) {
2124
+ const insertAt = headerBlock.indexOf('{') + 1;
2125
+ const prefix = headerBlock.slice(0, insertAt);
2126
+ const suffix = headerBlock.slice(insertAt);
2127
+ const snippet = [
2128
+ '',
2129
+ ' conventions:conventionSet = [',
2130
+ ...missing.map((entry) => ` "${entry}"`),
2131
+ ' ]',
2132
+ ].join('\n');
2133
+ return `${prefix}${snippet}${suffix}`;
2134
+ }
2135
+ const indent = match[2] ?? '';
2136
+ const existingBody = (match[3] ?? '').trimEnd();
2137
+ const extraEntries = missing.map((entry) => `${indent} "${entry}"`);
2138
+ const body = existingBody.length > 0
2139
+ ? `${existingBody}\n${extraEntries.join('\n')}`
2140
+ : extraEntries.join('\n');
2141
+ const replacement = `${match[1]}${indent}conventions:conventionSet = [\n${body}\n${indent}]`;
2142
+ return headerBlock.replace(listPattern, replacement);
2143
+ }
2144
+ function declaredContractOverrideWarning(phase, code, message) {
2145
+ return {
2146
+ level: 'warning',
2147
+ phase,
2148
+ phaseLabel: phase === 5 ? 'Profile Compilation' : 'Schema Validation',
2149
+ code,
2150
+ message,
2151
+ };
2152
+ }
2153
+ function collectDeclaredContractOverrideWarnings(headerInfo, appliedSchemaId, appliedProfile) {
2154
+ const warnings = [];
2155
+ if (headerInfo.schema && appliedSchemaId && headerInfo.schema !== appliedSchemaId) {
2156
+ warnings.push(declaredContractOverrideWarning(6, 'DECLARED_SCHEMA_OVERRIDDEN', `Applied schema '${appliedSchemaId}' overrides document-declared schema '${headerInfo.schema}'`));
2157
+ }
2158
+ if (appliedProfile && headerInfo.profile && appliedProfile !== headerInfo.profile) {
2159
+ warnings.push(declaredContractOverrideWarning(5, 'DECLARED_PROFILE_OVERRIDDEN', `Applied profile '${appliedProfile}' overrides document-declared profile '${headerInfo.profile}'`));
2160
+ }
2161
+ return warnings;
2162
+ }
2163
+ function buildBindContractMeta(headerInfo, appliedSchemaId, appliedProfile) {
2164
+ const declared = {
2165
+ ...(headerInfo.schema ? { schema: headerInfo.schema } : {}),
2166
+ ...(headerInfo.profile ? { profile: headerInfo.profile } : {}),
2167
+ ...(Object.keys(headerInfo.schemas).length > 0 ? { schemas: headerInfo.schemas } : {}),
2168
+ };
2169
+ const applied = {
2170
+ ...(appliedSchemaId ? { schema: appliedSchemaId } : {}),
2171
+ ...(appliedProfile ? { profile: appliedProfile } : {}),
2172
+ };
2173
+ if (Object.keys(declared).length === 0 && Object.keys(applied).length === 0) {
2174
+ return null;
2175
+ }
2176
+ return {
2177
+ ...(Object.keys(declared).length > 0 ? { declared } : {}),
2178
+ ...(Object.keys(applied).length > 0 ? { applied } : {}),
2179
+ };
2180
+ }
2181
+ function buildDeclaredInspectContractMeta(headerInfo) {
2182
+ const declared = {
2183
+ ...(headerInfo.schema ? { schema: headerInfo.schema } : {}),
2184
+ ...(headerInfo.profile ? { profile: headerInfo.profile } : {}),
2185
+ ...(Object.keys(headerInfo.schemas).length > 0 ? { schemas: headerInfo.schemas } : {}),
2186
+ };
2187
+ return Object.keys(declared).length > 0 ? { declared } : null;
2188
+ }
2189
+ function extractHeaderInfo(input) {
2190
+ try {
2191
+ const header = inspectHeader(input).header;
2192
+ return {
2193
+ mode: header.mode ?? 'transport',
2194
+ version: header.version ?? null,
2195
+ profile: header.profile ?? null,
2196
+ schema: header.schema ?? null,
2197
+ schemas: header.schemas ?? {},
2198
+ };
2199
+ }
2200
+ catch {
2201
+ return { mode: 'transport', version: null, profile: null, schema: null, schemas: {} };
2202
+ }
2203
+ }
2204
+ function createCanonicalReceipt(source, events, overrides = {}) {
2205
+ const header = extractHeaderInfo(source);
2206
+ const receipt = buildCanonicalReceipt(source, events, {
2207
+ canonicalMode: header.mode,
2208
+ canonicalProfile: header.profile ?? 'core',
2209
+ canonicalSpecRelease: 'v1',
2210
+ canonicalHashAlgorithm: overrides.canonicalHashAlgorithm ?? 'sha-256',
2211
+ embedCanonicalPayload: overrides.embedCanonicalPayload ?? true,
2212
+ producer: {
2213
+ implementation: 'aeon-cli-ts',
2214
+ version: VERSION,
2215
+ },
2216
+ });
2217
+ if (!overrides.receiptDigestOverride) {
2218
+ return receipt;
2219
+ }
2220
+ return {
2221
+ ...receipt,
2222
+ canonical: {
2223
+ ...receipt.canonical,
2224
+ digest: overrides.receiptDigestOverride,
2225
+ },
2226
+ };
2227
+ }
2228
+ function defaultReceiptSidecarPath(file) {
2229
+ return `${file}.receipt.json`;
2230
+ }
2231
+ function resolveReceiptSidecarPathForSign(file, args, writeOutput) {
2232
+ const explicit = getFlagValue(args, '--receipt');
2233
+ if (args.includes('--receipt') && explicit === undefined) {
2234
+ console.error('Error: Missing value for --receipt <path>');
2235
+ process.exit(2);
2236
+ }
2237
+ if (explicit)
2238
+ return explicit;
2239
+ return writeOutput ? defaultReceiptSidecarPath(file) : undefined;
2240
+ }
2241
+ function resolveReceiptSidecarForVerify(file, args) {
2242
+ const explicit = getFlagValue(args, '--receipt');
2243
+ if (args.includes('--receipt') && explicit === undefined) {
2244
+ console.error('Error: Missing value for --receipt <path>');
2245
+ process.exit(2);
2246
+ }
2247
+ const candidate = explicit ?? defaultReceiptSidecarPath(file);
2248
+ if (!fs.existsSync(candidate)) {
2249
+ return undefined;
2250
+ }
2251
+ return readReceiptSidecar(candidate);
2252
+ }
2253
+ function readReceiptSidecar(file) {
2254
+ let parsed;
2255
+ try {
2256
+ parsed = JSON.parse(readFile(file));
2257
+ }
2258
+ catch {
2259
+ console.error(`Error: Receipt file is not valid JSON: ${file}`);
2260
+ process.exit(2);
2261
+ }
2262
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
2263
+ console.error(`Error: Receipt file must be a JSON object: ${file}`);
2264
+ process.exit(2);
2265
+ }
2266
+ return parsed;
2267
+ }
2268
+ function writeReceiptSidecar(file, receipt) {
2269
+ fs.writeFileSync(file, JSON.stringify(receipt, null, 2), 'utf-8');
2270
+ }
2271
+ function extractEnvelopeFields(input) {
2272
+ const lex = tokenize(input);
2273
+ const parseResult = parse(lex.tokens);
2274
+ if (lex.errors.length > 0 || parseResult.errors.length > 0 || !parseResult.document) {
2275
+ return {
2276
+ fields: null,
2277
+ errors: [{ level: 'error', code: 'ENVELOPE_PARSE_ERROR', message: 'Unable to parse envelope source' }],
2278
+ };
2279
+ }
2280
+ const binding = parseResult.document.bindings.find((entry) => isEnvelopeBinding(entry));
2281
+ if (!binding) {
2282
+ return { fields: null, errors: [] };
2283
+ }
2284
+ if (binding.value.type !== 'ObjectNode') {
2285
+ return {
2286
+ fields: null,
2287
+ errors: [{ level: 'error', code: 'ENVELOPE_NOT_OBJECT', message: 'envelope binding must be an object' }],
2288
+ };
2289
+ }
2290
+ const fields = new Map();
2291
+ for (const entry of binding.value.bindings) {
2292
+ collectEnvelopeFields(fields, entry, []);
2293
+ }
2294
+ return { fields, errors: [] };
2295
+ }
2296
+ function isEnvelopeStartLine(line) {
2297
+ return /^\s*[A-Za-z_][A-Za-z0-9_]*\s*:\s*envelope\b/.test(line);
2298
+ }
2299
+ function isEnvelopeBinding(binding) {
2300
+ return datatypeBase(binding.datatype?.name) === ENVELOPE_DATATYPE;
2301
+ }
2302
+ function datatypeBase(datatype) {
2303
+ if (!datatype)
2304
+ return null;
2305
+ const genericIdx = datatype.indexOf('<');
2306
+ const separatorIdx = datatype.indexOf('[');
2307
+ const endIdx = [genericIdx, separatorIdx]
2308
+ .filter((idx) => idx >= 0)
2309
+ .reduce((min, idx) => Math.min(min, idx), datatype.length);
2310
+ return datatype.slice(0, endIdx).toLowerCase();
2311
+ }
2312
+ function collectEnvelopeFields(fields, binding, parents) {
2313
+ const prefixes = formatEnvelopePrefixes(binding, parents);
2314
+ for (const prefix of prefixes) {
2315
+ fields.set(prefix, binding.value);
2316
+ }
2317
+ if (binding.value.type === 'ObjectNode') {
2318
+ for (const child of binding.value.bindings) {
2319
+ collectEnvelopeFields(fields, child, prefixes);
2320
+ }
2321
+ return;
2322
+ }
2323
+ if (binding.value.type === 'ListNode') {
2324
+ for (let index = 0; index < binding.value.elements.length; index++) {
2325
+ const element = binding.value.elements[index];
2326
+ for (const prefix of prefixes) {
2327
+ fields.set(`${prefix}[${index}]`, element);
2328
+ }
2329
+ if (element.type === 'ObjectNode') {
2330
+ for (const child of element.bindings) {
2331
+ collectEnvelopeFields(fields, child, prefixes.map((prefix) => `${prefix}[${index}]`));
2332
+ }
2333
+ }
2334
+ }
2335
+ }
2336
+ }
2337
+ function formatEnvelopePrefixes(binding, parents) {
2338
+ const base = parents.length > 0 ? parents.map((parent) => `${parent}.${binding.key}`) : [binding.key];
2339
+ if (!binding.datatype?.name) {
2340
+ return base;
2341
+ }
2342
+ const typed = parents.length > 0
2343
+ ? parents.map((parent) => `${parent}.${binding.key}:${binding.datatype.name}`)
2344
+ : [`${binding.key}:${binding.datatype.name}`];
2345
+ return [...new Set([...base, ...typed])];
2346
+ }
2347
+ function readEnvelopeField(fields, key, diagnostics) {
2348
+ const value = fields.get(key);
2349
+ if (!value)
2350
+ return null;
2351
+ const literal = readLiteralString(value);
2352
+ if (literal === null) {
2353
+ diagnostics.errors.push({
2354
+ level: 'error',
2355
+ code: 'ENVELOPE_FIELD_TYPE',
2356
+ message: `${key} must be a literal string value`,
2357
+ });
2358
+ return null;
2359
+ }
2360
+ return literal;
2361
+ }
2362
+ function readEnvelopeFieldAny(fields, keys, diagnostics) {
2363
+ for (const key of keys) {
2364
+ const value = readEnvelopeField(fields, key, diagnostics);
2365
+ if (value !== null)
2366
+ return value;
2367
+ }
2368
+ return null;
2369
+ }
2370
+ function readLiteralString(value) {
2371
+ switch (value.type) {
2372
+ case 'TypedValue':
2373
+ return readLiteralString(value.value);
2374
+ case 'StringLiteral':
2375
+ case 'SeparatorLiteral':
2376
+ case 'DateLiteral':
2377
+ case 'DateTimeLiteral':
2378
+ case 'EncodingLiteral':
2379
+ case 'HexLiteral':
2380
+ case 'RadixLiteral':
2381
+ case 'InfinityLiteral':
2382
+ case 'NaNLiteral':
2383
+ case 'NullLiteral':
2384
+ return String(value.raw);
2385
+ case 'NumberLiteral':
2386
+ case 'BooleanLiteral':
2387
+ case 'ToggleLiteral':
2388
+ case 'CloneReference':
2389
+ case 'PointerReference':
2390
+ case 'ObjectNode':
2391
+ case 'ListNode':
2392
+ default:
2393
+ return null;
2394
+ }
2395
+ }
2396
+ function normalizeHash(value) {
2397
+ return value.trim().replace(/^#/, '').toLowerCase();
2398
+ }
2399
+ function normalizeHashAlgorithm(algorithm, diagnostics, strict, field) {
2400
+ const normalized = algorithm.trim().toLowerCase();
2401
+ if (normalized === 'sha-256' || normalized === 'sha256')
2402
+ return 'sha-256';
2403
+ if (normalized === 'sha-512' || normalized === 'sha512')
2404
+ return 'sha-512';
2405
+ const message = `${field} must be sha-256 or sha-512 (received "${algorithm}")`;
2406
+ if (strict) {
2407
+ diagnostics.errors.push({ level: 'error', code: 'ENVELOPE_HASH_ALG_UNSUPPORTED', message });
2408
+ }
2409
+ else {
2410
+ diagnostics.warnings.push({ level: 'warning', code: 'ENVELOPE_HASH_ALG_UNSUPPORTED', message });
2411
+ }
2412
+ return null;
2413
+ }
2414
+ function pushModeDiagnostic(diagnostics, strict, code, message) {
2415
+ if (strict) {
2416
+ diagnostics.errors.push({ level: 'error', code, message });
2417
+ }
2418
+ else {
2419
+ diagnostics.warnings.push({ level: 'warning', code, message });
2420
+ }
2421
+ }
2422
+ function outputEnvelopeDiagnostics(diagnostics, label) {
2423
+ for (const diagnostic of diagnostics) {
2424
+ const code = diagnostic.code ?? 'ENVELOPE';
2425
+ const message = diagnostic.message.replace(/[\r\n]+/g, ' ');
2426
+ console.log(`${label} [${code}] ${message}`);
2427
+ }
2428
+ }
2429
+ function outputEnvelopeJson(errors, warnings, ok, verification, receipt) {
2430
+ const payload = {
2431
+ ok,
2432
+ errors: errors.map((diag) => ({
2433
+ code: diag.code,
2434
+ message: diag.message,
2435
+ })),
2436
+ warnings: warnings.map((diag) => ({
2437
+ code: diag.code,
2438
+ message: diag.message,
2439
+ })),
2440
+ ...(receipt ? { receipt } : {}),
2441
+ ...(verification ? { verification } : {}),
2442
+ };
2443
+ console.log(JSON.stringify(payload, null, 2));
2444
+ }
2445
+ function outputMarkdown(file, result, info) {
2446
+ const visibleEvents = result.events.filter(e => !e.key.startsWith('aeon:'));
2447
+ const annotations = info.sortAnnotations
2448
+ ? sortAnnotationRecords(result.annotations ?? [])
2449
+ : (result.annotations ?? []);
2450
+ if (info.annotationsOnly) {
2451
+ console.log('# AEON Annotations');
2452
+ console.log('');
2453
+ console.log(`- Count: ${annotations.length}`);
2454
+ if (annotations.length > 0) {
2455
+ console.log('');
2456
+ console.log('## Annotation Records');
2457
+ for (const annotation of annotations) {
2458
+ console.log(`- ${formatAnnotationLine(annotation)}`);
2459
+ }
2460
+ }
2461
+ return;
2462
+ }
2463
+ console.log('# AEON Inspect');
2464
+ if (info.recovery) {
2465
+ console.log('> WARNING: recovery mode enabled (tooling-only); output may be partial');
2466
+ }
2467
+ console.log('');
2468
+ console.log('## Summary');
2469
+ console.log(`- File: ${path.basename(file)}`);
2470
+ console.log(`- Version: ${info.version ?? '—'}`);
2471
+ console.log(`- Mode: ${info.mode}`);
2472
+ console.log(`- Profile: ${info.profile ?? '—'}`);
2473
+ console.log(`- Schema: ${info.schema ?? '—'}`);
2474
+ console.log(`- Recovery: ${info.recovery ? 'true' : 'false'}`);
2475
+ console.log(`- Events: ${visibleEvents.length}`);
2476
+ if (info.includeAnnotations) {
2477
+ console.log(`- Annotations: ${annotations.length}`);
2478
+ }
2479
+ console.log(`- Errors: ${result.errors.length}`);
2480
+ if (info.profile || info.schema || Object.keys(info.schemas).length > 0) {
2481
+ console.log('');
2482
+ console.log('## Declared Contracts');
2483
+ if (info.profile)
2484
+ console.log(`- Profile: ${info.profile}`);
2485
+ if (info.schema)
2486
+ console.log(`- Schema: ${info.schema}`);
2487
+ for (const [context, value] of Object.entries(info.schemas)) {
2488
+ console.log(`- Schema Context (${context}): ${value}`);
2489
+ }
2490
+ }
2491
+ if (result.errors.length > 0) {
2492
+ console.log('');
2493
+ console.log('## Errors');
2494
+ for (const error of result.errors) {
2495
+ console.log(`- ${formatErrorLine(error)}`);
2496
+ }
2497
+ }
2498
+ if (visibleEvents.length > 0) {
2499
+ console.log('');
2500
+ console.log('## Assignment Events');
2501
+ for (const event of visibleEvents) {
2502
+ console.log(`- ${formatEventLine(event)}`);
2503
+ }
2504
+ }
2505
+ const refs = findReferences(visibleEvents);
2506
+ if (refs.length > 0) {
2507
+ console.log('');
2508
+ console.log('## References');
2509
+ for (const ref of refs) {
2510
+ console.log(`- ${ref}`);
2511
+ }
2512
+ }
2513
+ if (info.includeAnnotations && annotations.length > 0) {
2514
+ console.log('');
2515
+ console.log('## Annotation Records');
2516
+ for (const annotation of annotations) {
2517
+ console.log(`- ${formatAnnotationLine(annotation)}`);
2518
+ }
2519
+ }
2520
+ }
2521
+ function sortAnnotationRecords(records) {
2522
+ return [...records]
2523
+ .map((record, index) => ({ record, index }))
2524
+ .sort((left, right) => {
2525
+ const byStart = left.record.span.start.offset - right.record.span.start.offset;
2526
+ if (byStart !== 0)
2527
+ return byStart;
2528
+ const byEnd = left.record.span.end.offset - right.record.span.end.offset;
2529
+ if (byEnd !== 0)
2530
+ return byEnd;
2531
+ const byKind = left.record.kind.localeCompare(right.record.kind);
2532
+ if (byKind !== 0)
2533
+ return byKind;
2534
+ const byForm = left.record.form.localeCompare(right.record.form);
2535
+ if (byForm !== 0)
2536
+ return byForm;
2537
+ const byRaw = left.record.raw.localeCompare(right.record.raw);
2538
+ if (byRaw !== 0)
2539
+ return byRaw;
2540
+ return left.index - right.index;
2541
+ })
2542
+ .map((entry) => entry.record);
2543
+ }
2544
+ function formatAnnotationLine(annotation) {
2545
+ const target = (() => {
2546
+ if (annotation.target.kind === 'path') {
2547
+ return annotation.target.path;
2548
+ }
2549
+ if (annotation.target.kind === 'span') {
2550
+ return `span(${formatSpan(annotation.target.span)})`;
2551
+ }
2552
+ return `unbound(${annotation.target.reason})`;
2553
+ })();
2554
+ const subtype = annotation.subtype ? `/${annotation.subtype}` : '';
2555
+ return `${annotation.kind}${subtype} ${annotation.form} -> ${target} raw=${JSON.stringify(annotation.raw)}`;
2556
+ }
2557
+ function formatSpan(span) {
2558
+ if (!span || typeof span !== 'object')
2559
+ return '?:?-?:?';
2560
+ const start = span.start;
2561
+ const end = span.end;
2562
+ if (!start || !end || typeof start !== 'object' || typeof end !== 'object')
2563
+ return '?:?-?:?';
2564
+ const sl = start.line;
2565
+ const sc = start.column;
2566
+ const el = end.line;
2567
+ const ec = end.column;
2568
+ if ([sl, sc, el, ec].some(v => typeof v !== 'number'))
2569
+ return '?:?-?:?';
2570
+ return `${sl}:${sc}-${el}:${ec}`;
2571
+ }
2572
+ function formatErrorLine(error) {
2573
+ const code = error.code ?? 'UNKNOWN';
2574
+ const errPath = getErrorPath(error) ?? '$';
2575
+ const span = formatSpan(error.span);
2576
+ const message = String(error.message).replace(/[\r\n]+/g, ' ');
2577
+ const phaseLabel = getPhaseLabel(error);
2578
+ const prefix = phaseLabel ? `${phaseLabel}: ` : '';
2579
+ return `${prefix}${message} [${code}] path=${errPath} span=${span}`;
2580
+ }
2581
+ function phaseNumberLabel(phase) {
2582
+ switch (phase) {
2583
+ case 0:
2584
+ return 'Input Validation';
2585
+ case 5:
2586
+ return 'Profile Compilation';
2587
+ case 6:
2588
+ return 'Schema Validation';
2589
+ case 7:
2590
+ return 'Reference Resolution';
2591
+ case 8:
2592
+ return 'Finalization';
2593
+ default:
2594
+ return undefined;
2595
+ }
2596
+ }
2597
+ function inferPhaseLabelFromCode(code) {
2598
+ switch (code) {
2599
+ case 'INPUT_SIZE_EXCEEDED':
2600
+ return 'Input Validation';
2601
+ case 'UNEXPECTED_CHARACTER':
2602
+ case 'UNTERMINATED_BLOCK_COMMENT':
2603
+ case 'UNTERMINATED_STRING':
2604
+ case 'UNTERMINATED_TRIMTICK':
2605
+ return 'Lexical Analysis';
2606
+ case 'SYNTAX_ERROR':
2607
+ case 'INVALID_DATE':
2608
+ case 'INVALID_TIME':
2609
+ case 'INVALID_DATETIME':
2610
+ case 'INVALID_SEPARATOR_CHAR':
2611
+ case 'SEPARATOR_DEPTH_EXCEEDED':
2612
+ case 'GENERIC_DEPTH_EXCEEDED':
2613
+ return 'Parsing';
2614
+ case 'HEADER_CONFLICT':
2615
+ case 'DUPLICATE_KEY':
2616
+ case 'DUPLICATE_CANONICAL_PATH':
2617
+ case 'DATATYPE_LITERAL_MISMATCH':
2618
+ return 'Core Validation';
2619
+ case 'MISSING_REFERENCE_TARGET':
2620
+ case 'FORWARD_REFERENCE':
2621
+ case 'SELF_REFERENCE':
2622
+ case 'ATTRIBUTE_DEPTH_EXCEEDED':
2623
+ return 'Reference Validation';
2624
+ case 'UNTYPED_TOGGLE_LITERAL':
2625
+ case 'UNTYPED_VALUE_IN_STRICT_MODE':
2626
+ case 'CUSTOM_TOGGLE_ALIAS_NOT_ALLOWED':
2627
+ case 'CUSTOM_DATATYPE_NOT_ALLOWED':
2628
+ case 'INVALID_NODE_HEAD_DATATYPE':
2629
+ return 'Mode Enforcement';
2630
+ case 'PROFILE_NOT_FOUND':
2631
+ case 'PROFILE_PROCESSORS_SKIPPED':
2632
+ return 'Profile Compilation';
2633
+ case 'TYPE_GUARD_FAILED':
2634
+ return 'Finalization';
2635
+ default:
2636
+ return code?.startsWith('FINALIZE_') ? 'Finalization' : undefined;
2637
+ }
2638
+ }
2639
+ function getPhaseLabel(error) {
2640
+ const phase = typeof error.phase === 'number' ? error.phase : undefined;
2641
+ return phaseNumberLabel(phase) ?? inferPhaseLabelFromCode(error.code);
2642
+ }
2643
+ function formatEventLine(event) {
2644
+ const p = formatPath(event.path);
2645
+ const t = event.datatype ? ` :${event.datatype}` : '';
2646
+ return `${p}${t} = ${renderValue(event.value)}`;
2647
+ }
2648
+ function renderValue(value) {
2649
+ const type = String(value.type);
2650
+ switch (type) {
2651
+ case 'TypedValue': {
2652
+ const datatype = value.datatype && typeof value.datatype === 'object' ? value.datatype.name : null;
2653
+ const inner = value.value && typeof value.value === 'object' ? renderValue(value.value) : '';
2654
+ return `:${typeof datatype === 'string' ? datatype : 'unknown'} = ${inner}`;
2655
+ }
2656
+ case 'StringLiteral':
2657
+ return JSON.stringify(String(value.value ?? ''));
2658
+ case 'InfinityLiteral':
2659
+ case 'NaNLiteral':
2660
+ return String(value.raw ?? value.value ?? '');
2661
+ case 'NullLiteral':
2662
+ return String(value.raw ?? '');
2663
+ case 'NumberLiteral':
2664
+ return String(value.raw ?? value.value ?? '');
2665
+ case 'BooleanLiteral':
2666
+ return String(value.raw ?? value.value ?? '');
2667
+ case 'ToggleLiteral':
2668
+ return String(value.raw ?? value.value ?? '');
2669
+ case 'HexLiteral':
2670
+ case 'RadixLiteral':
2671
+ case 'EncodingLiteral':
2672
+ case 'SeparatorLiteral':
2673
+ case 'DateLiteral':
2674
+ case 'DateTimeLiteral':
2675
+ return String(value.raw ?? value.value ?? '');
2676
+ case 'CloneReference':
2677
+ return `~${Array.isArray(value.path) ? value.path.join('.') : ''}`;
2678
+ case 'PointerReference':
2679
+ return `~>${Array.isArray(value.path) ? value.path.join('.') : ''}`;
2680
+ case 'ObjectNode': {
2681
+ const bindings = Array.isArray(value.bindings) ? value.bindings : [];
2682
+ const rendered = bindings
2683
+ .map(b => renderBindingInline(b))
2684
+ .filter(s => s.length > 0)
2685
+ .join(', ');
2686
+ return `{ ${rendered} }`;
2687
+ }
2688
+ case 'ListNode': {
2689
+ const elements = Array.isArray(value.elements) ? value.elements : [];
2690
+ const rendered = elements
2691
+ .map(e => renderValue(e))
2692
+ .join(', ');
2693
+ return `[ ${rendered} ]`;
2694
+ }
2695
+ default:
2696
+ return type;
2697
+ }
2698
+ }
2699
+ function renderBindingInline(binding) {
2700
+ const key = typeof binding.key === 'string' ? binding.key : '';
2701
+ const datatype = binding.datatype && typeof binding.datatype === 'object' ? binding.datatype.name : null;
2702
+ const value = binding.value && typeof binding.value === 'object' ? binding.value : null;
2703
+ if (!key || !value)
2704
+ return '';
2705
+ const dt = typeof datatype === 'string' ? `:${datatype}` : '';
2706
+ return `${key}${dt} = ${renderValue(value)}`;
2707
+ }
2708
+ function getErrorPath(error) {
2709
+ const candidate = error.path;
2710
+ if (typeof candidate === 'string') {
2711
+ return candidate;
2712
+ }
2713
+ if (candidate && typeof candidate === 'object' && 'segments' in candidate) {
2714
+ return formatPath(candidate);
2715
+ }
2716
+ return undefined;
2717
+ }
2718
+ function findReferences(events) {
2719
+ const refs = [];
2720
+ for (const event of events) {
2721
+ const value = event.value;
2722
+ if (value.type === 'CloneReference') {
2723
+ refs.push(`${formatPath(event.path)} = ~${value.path.join('.')}`);
2724
+ }
2725
+ else if (value.type === 'PointerReference') {
2726
+ refs.push(`${formatPath(event.path)} = ~>${value.path.join('.')}`);
2727
+ }
2728
+ }
2729
+ return refs;
2730
+ }
2731
+ //# sourceMappingURL=main.js.map