@githolon/dsl 0.2.0 → 0.2.2
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/package.json +3 -1
- package/src/codegen_proof.ts +430 -0
- package/src/codegen_ts.ts +8 -6
- package/src/compile_package_main.ts +289 -10
- package/src/engine_entry.ts +236 -83
- package/src/usd_layers.ts +722 -0
- package/src/usd_state.ts +505 -0
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
* identityHashes}`: the EXACT body `POST /v1/workspaces/:ws/domains` accepts as
|
|
38
38
|
* `application/json` on Nomos Cloud (package + per-workspace manifest overlay in
|
|
39
39
|
* one deploy).
|
|
40
|
+
* * `<name>.proof.mts` — the GENERATED PROOF (codegen_proof.ts): a
|
|
41
|
+
* runnable, domain-shaped live e2e synthesized from the law's own directives /
|
|
42
|
+
* queries / counts. Run it with `githolon proof`. Never fatal: an unsampleable
|
|
43
|
+
* domain still compiles, the skip names its remedy.
|
|
40
44
|
*
|
|
41
45
|
* The engine entry is GENERATED (imports + one `registerEngine` call — the machinery
|
|
42
46
|
* lives in `@githolon/dsl/engine-entry`), bundled with the SAME deterministic esbuild
|
|
@@ -60,16 +64,27 @@ import type { DerivedDecl } from "./derived.js";
|
|
|
60
64
|
import type { CombinedDecl } from "./combined.js";
|
|
61
65
|
import type { ImpureCapabilityDecl } from "./codegen_provider_dart.js";
|
|
62
66
|
import {
|
|
67
|
+
aggregatesOf,
|
|
63
68
|
buildIdentity,
|
|
64
69
|
buildReadManifest,
|
|
65
70
|
composeDomainModule,
|
|
71
|
+
directivesOf,
|
|
66
72
|
emitUsdJsonForModules,
|
|
67
73
|
packageUsda,
|
|
68
74
|
sha256HexUtf8,
|
|
69
75
|
writeIdentity,
|
|
70
76
|
type Mod,
|
|
71
77
|
} from "./build_package.js";
|
|
78
|
+
import { collectEngineRouting } from "./engine_entry.js";
|
|
79
|
+
import {
|
|
80
|
+
emitLayeredDomain,
|
|
81
|
+
flattenLayeredUsd,
|
|
82
|
+
layeredRootUsda,
|
|
83
|
+
type LayeredModuleInput,
|
|
84
|
+
} from "./usd_layers.js";
|
|
85
|
+
import type { UsdLayer } from "./usd.js";
|
|
72
86
|
import { generateTsClient, tsClientFactoryName } from "./codegen_ts.js";
|
|
87
|
+
import { generateTsProof } from "./codegen_proof.js";
|
|
73
88
|
|
|
74
89
|
const DSL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
75
90
|
|
|
@@ -106,6 +121,17 @@ interface PackageConfig {
|
|
|
106
121
|
* can vendor the directory as-is, no pub deps.
|
|
107
122
|
*/
|
|
108
123
|
readonly dart?: boolean | { readonly out?: string };
|
|
124
|
+
/**
|
|
125
|
+
* OPT-IN layered USD emission (also `--layered` on the CLI). When true, ALSO emit
|
|
126
|
+
* `build/layers/<domain>--<module>.usda` (+ `.layer.json`) — one REAL USD layer per
|
|
127
|
+
* authored MODULE — and `build/<name>.layered.usda`, a root whose subLayers stack
|
|
128
|
+
* them (USD strongest-first == Nomos module order reversed). The flattened
|
|
129
|
+
* `.package.usda` emission and the domainHash DO NOT MOVE; the compile asserts
|
|
130
|
+
* fail-closed that the layer stack flattens BYTE-IDENTICALLY to the canonical
|
|
131
|
+
* composed IR (the monoid homomorphism — see `usd_layers.ts` /
|
|
132
|
+
* `docs/usd_layered_composition.md`).
|
|
133
|
+
*/
|
|
134
|
+
readonly layered?: boolean;
|
|
109
135
|
}
|
|
110
136
|
|
|
111
137
|
function fail(msg: string): never {
|
|
@@ -278,7 +304,9 @@ function emitDart(modules: readonly DomainModule[], dartOut: string): string[] {
|
|
|
278
304
|
// ── main ──────────────────────────────────────────────────────────────────────────
|
|
279
305
|
|
|
280
306
|
async function main(): Promise<void> {
|
|
281
|
-
const
|
|
307
|
+
const argv = process.argv.slice(2);
|
|
308
|
+
const layeredFlag = argv.includes("--layered");
|
|
309
|
+
const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.package.mjs";
|
|
282
310
|
const configPath = path.resolve(process.cwd(), configArg);
|
|
283
311
|
if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
|
|
284
312
|
const cfgDir = path.dirname(configPath);
|
|
@@ -299,9 +327,21 @@ async function main(): Promise<void> {
|
|
|
299
327
|
mkdirSync(outDir, { recursive: true });
|
|
300
328
|
|
|
301
329
|
// ── import + compose each domain ──
|
|
330
|
+
// LAZY PER-DOMAIN BOOT (#34): the generated entry registers each domain's modules
|
|
331
|
+
// as `() => require("…")` THUNKS — esbuild lazy-wraps every required module
|
|
332
|
+
// (`__esm` init at the require call, not at bundle top level), so a fresh
|
|
333
|
+
// per-plan sandbox boots ONLY the domain the dispatched intent touches. The
|
|
334
|
+
// routing table for type/relation-keyed dispatches is computed below by the
|
|
335
|
+
// SAME scans the engine registries run (`collectEngineRouting` — one machinery).
|
|
336
|
+
// `NOMOS_LUMP_BOOT=eager` keeps the pre-#34 whole-law top-level boot (static
|
|
337
|
+
// imports, no routing) — the equivalence baseline the #34 suite compares against.
|
|
338
|
+
const eagerLump = process.env.NOMOS_LUMP_BOOT === "eager";
|
|
302
339
|
const entryImports: string[] = [];
|
|
303
340
|
const entryDomains: string[] = [];
|
|
341
|
+
const routingInput: Record<string, Mod[]> = {};
|
|
304
342
|
const domainModules: DomainModule[] = [];
|
|
343
|
+
const layered = layeredFlag || cfg.layered === true;
|
|
344
|
+
const layeredDomains: { key: string; inputs: LayeredModuleInput[] }[] = [];
|
|
305
345
|
let importSeq = 0;
|
|
306
346
|
|
|
307
347
|
for (const d of cfg.domains) {
|
|
@@ -309,17 +349,22 @@ async function main(): Promise<void> {
|
|
|
309
349
|
fail(`each domain needs { key, modules: [path, ...] }; got ${JSON.stringify(d)}`);
|
|
310
350
|
}
|
|
311
351
|
const mods: Mod[] = [];
|
|
312
|
-
const
|
|
352
|
+
const sourceExprs: string[] = [];
|
|
313
353
|
for (const rel of d.modules) {
|
|
314
354
|
const abs = path.resolve(cfgDir, rel);
|
|
315
355
|
if (!existsSync(abs)) fail(`domain '${d.key}': module not found: ${abs}`);
|
|
316
356
|
const m = (await import(pathToFileURL(abs).href)) as Mod;
|
|
317
357
|
mods.push(m);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
358
|
+
if (eagerLump) {
|
|
359
|
+
const v = `m${importSeq++}`;
|
|
360
|
+
sourceExprs.push(v);
|
|
361
|
+
entryImports.push(`import * as ${v} from ${JSON.stringify(abs)};`);
|
|
362
|
+
} else {
|
|
363
|
+
sourceExprs.push(`() => require(${JSON.stringify(abs)})`);
|
|
364
|
+
}
|
|
321
365
|
}
|
|
322
|
-
entryDomains.push(` ${JSON.stringify(d.key)}: [${
|
|
366
|
+
entryDomains.push(` ${JSON.stringify(d.key)}: [${sourceExprs.join(", ")}],`);
|
|
367
|
+
routingInput[d.key] = mods;
|
|
323
368
|
|
|
324
369
|
let merged: Mod = {};
|
|
325
370
|
for (const m of mods) merged = { ...merged, ...m };
|
|
@@ -370,6 +415,51 @@ async function main(): Promise<void> {
|
|
|
370
415
|
...(spatials.length > 0 ? { spatials } : {}),
|
|
371
416
|
}),
|
|
372
417
|
);
|
|
418
|
+
|
|
419
|
+
// ── opt-in: ONE USD LAYER PER MODULE (Φ applied per module, not once over the
|
|
420
|
+
// composed fold) — the layered emission `usd_layers.ts` proves flattens back
|
|
421
|
+
// to the EXACT canonical IR (asserted below, fail-closed). Per-module decls
|
|
422
|
+
// are discovered the same way as above, on each module's OWN exports; config
|
|
423
|
+
// export-names resolve on whichever module actually exports them; config
|
|
424
|
+
// `extraAggregates` ride the FIRST (weakest) layer, prelude-style. Counts /
|
|
425
|
+
// sums / spatials never enter the USD IR, so they have no layer placement.
|
|
426
|
+
if (layered) {
|
|
427
|
+
const usedStems = new Set<string>();
|
|
428
|
+
const inputs: LayeredModuleInput[] = mods.map((m, i) => {
|
|
429
|
+
let stem = path.basename(d.modules[i]!).replace(/\.[^.]*$/, "");
|
|
430
|
+
for (let n = 2; usedStems.has(stem); n++) stem = `${stem}-${n}`;
|
|
431
|
+
usedStems.add(stem);
|
|
432
|
+
|
|
433
|
+
const presentNames = (names: readonly string[] | undefined) =>
|
|
434
|
+
(names ?? []).filter((n) => (m as Record<string, unknown>)[n] !== undefined);
|
|
435
|
+
const layerQueries = [
|
|
436
|
+
...discover<QueryDecl>(m, isQueryDecl),
|
|
437
|
+
...resolveNamed<QueryDecl>(m, presentNames(d.queries), "queries"),
|
|
438
|
+
];
|
|
439
|
+
const layerDeriveds = [
|
|
440
|
+
...discover<DerivedDecl>(m, isDerivedDecl),
|
|
441
|
+
...resolveNamed<DerivedDecl>(m, presentNames(d.deriveds), "deriveds"),
|
|
442
|
+
];
|
|
443
|
+
const layerCombineds = [
|
|
444
|
+
...discover<CombinedDecl>(m, isCombinedDecl),
|
|
445
|
+
...resolveNamed<CombinedDecl>(m, presentNames(d.combineds), "combineds"),
|
|
446
|
+
];
|
|
447
|
+
const byId = new Map<string, AggregateHandle>();
|
|
448
|
+
for (const a of [...aggregatesOf(m), ...(i === 0 ? extraAggregates : [])]) byId.set(a.id, a);
|
|
449
|
+
|
|
450
|
+
const module: DomainModule = {
|
|
451
|
+
name: d.name ?? d.key,
|
|
452
|
+
domain: d.key,
|
|
453
|
+
aggregates: [...byId.values()],
|
|
454
|
+
directives: directivesOf(m),
|
|
455
|
+
...(layerQueries.length > 0 ? { queries: layerQueries } : {}),
|
|
456
|
+
...(layerDeriveds.length > 0 ? { deriveds: layerDeriveds } : {}),
|
|
457
|
+
...(layerCombineds.length > 0 ? { combineds: layerCombineds } : {}),
|
|
458
|
+
};
|
|
459
|
+
return { name: stem, module };
|
|
460
|
+
});
|
|
461
|
+
layeredDomains.push({ key: d.key, inputs });
|
|
462
|
+
}
|
|
373
463
|
}
|
|
374
464
|
|
|
375
465
|
// ── reports ──
|
|
@@ -379,14 +469,76 @@ async function main(): Promise<void> {
|
|
|
379
469
|
if (!rel || !exportName) fail(`report '${reportId}' must be "modulePath#exportName"; got '${ref}'`);
|
|
380
470
|
const abs = path.resolve(cfgDir, rel);
|
|
381
471
|
if (!existsSync(abs)) fail(`report '${reportId}': module not found: ${abs}`);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
472
|
+
if (eagerLump) {
|
|
473
|
+
const v = `r${importSeq++}`;
|
|
474
|
+
entryImports.push(`import * as ${v} from ${JSON.stringify(abs)};`);
|
|
475
|
+
entryReports.push(` ${JSON.stringify(reportId)}: ${v}.${exportName},`);
|
|
476
|
+
} else {
|
|
477
|
+
// A lazy report factory: the module's `__esm` init runs on first render call.
|
|
478
|
+
entryReports.push(
|
|
479
|
+
` ${JSON.stringify(reportId)}: (actor: string) => (require(${JSON.stringify(abs)}) as any).${exportName}(actor),`,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
385
482
|
}
|
|
386
483
|
|
|
387
484
|
// ── 1. generate + bundle the engine entry ──
|
|
485
|
+
// The routing table is computed by the SAME duck-type scans the engine registries
|
|
486
|
+
// run, over the SAME imported modules (collectEngineRouting — one machinery, so a
|
|
487
|
+
// law whose invariant declarations are inconsistent refuses to COMPILE, and the
|
|
488
|
+
// table can never disagree with what a full force would register).
|
|
489
|
+
const routing = collectEngineRouting(routingInput);
|
|
490
|
+
// AMBIENT-SLOT SEEDING (lazy boot only): zod v4 writes
|
|
491
|
+
// `globalThis.__zod_global{Config,Registry}` at MODULE INIT. The sealed sandbox
|
|
492
|
+
// freezes globalThis after the lump's top level — an eager lump's zod init ran
|
|
493
|
+
// pre-freeze, but a lazy lump would init zod inside the dispatch, post-freeze,
|
|
494
|
+
// and the frozen global refuses the new slots. So the entry eagerly imports the
|
|
495
|
+
// TWO tiny zod core modules that own those writes (resolved to the same files
|
|
496
|
+
// the domain modules' `zod` import reaches — same esbuild module instances);
|
|
497
|
+
// the heavy classic schema graph stays deferred. The frozen-sandbox boot probe
|
|
498
|
+
// below fail-closes the compile if any OTHER module writes globals at lazy init.
|
|
499
|
+
// Namespace imports + a `seeds` call argument (NOT bare side-effect imports):
|
|
500
|
+
// zod ships `sideEffects: false`, so esbuild would tree-shake a bare import away;
|
|
501
|
+
// passing the namespaces into the registerEngine CALL forces their init at the
|
|
502
|
+
// entry's top level — pre-freeze, exactly where the eager lump ran them.
|
|
503
|
+
const zodSeedImports: string[] = [];
|
|
504
|
+
const zodSeedNames: string[] = [];
|
|
505
|
+
if (!eagerLump) {
|
|
506
|
+
const zodPkgJson = [cfgDir, DSL_DIR]
|
|
507
|
+
.map((dir) => {
|
|
508
|
+
try {
|
|
509
|
+
return createRequire(pathToFileURL(path.join(dir, "noop.js"))).resolve("zod/package.json");
|
|
510
|
+
} catch {
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
})
|
|
514
|
+
.find((p): p is string => p !== undefined);
|
|
515
|
+
const zodCoreFiles = zodPkgJson
|
|
516
|
+
? ["core.js", "registries.js"].map((f) => path.join(path.dirname(zodPkgJson), "v4", "core", f))
|
|
517
|
+
: [];
|
|
518
|
+
const seedFiles =
|
|
519
|
+
zodCoreFiles.length > 0 && zodCoreFiles.every((f) => existsSync(f))
|
|
520
|
+
? zodCoreFiles
|
|
521
|
+
: zodPkgJson !== undefined
|
|
522
|
+
? // Unknown zod layout — seed by initializing the whole library at top
|
|
523
|
+
// level (loses some laziness, never correctness).
|
|
524
|
+
["zod"]
|
|
525
|
+
: [];
|
|
526
|
+
for (const f of seedFiles) {
|
|
527
|
+
const v = `__seed${zodSeedNames.length}`;
|
|
528
|
+
zodSeedImports.push(`import * as ${v} from ${JSON.stringify(f)};`);
|
|
529
|
+
zodSeedNames.push(v);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
388
532
|
const entrySource = [
|
|
389
533
|
`// AUTO-GENERATED by nomos-compile from ${path.basename(configPath)} — do not edit.`,
|
|
534
|
+
...(eagerLump
|
|
535
|
+
? []
|
|
536
|
+
: [
|
|
537
|
+
`// LAZY PER-DOMAIN BOOT (#34): each domain registers as require-thunks; esbuild`,
|
|
538
|
+
`// defers every module's init to its first dispatch touch inside the fresh sandbox.`,
|
|
539
|
+
`declare function require(id: string): Record<string, unknown>;`,
|
|
540
|
+
...zodSeedImports,
|
|
541
|
+
]),
|
|
390
542
|
...entryImports,
|
|
391
543
|
`import { registerEngine } from "@githolon/dsl/engine-entry";`,
|
|
392
544
|
``,
|
|
@@ -395,6 +547,8 @@ async function main(): Promise<void> {
|
|
|
395
547
|
...entryDomains,
|
|
396
548
|
` },`,
|
|
397
549
|
...(entryReports.length > 0 ? [` reports: {`, ...entryReports, ` },`] : []),
|
|
550
|
+
...(eagerLump ? [] : [` routing: ${JSON.stringify(routing)},`]),
|
|
551
|
+
...(zodSeedNames.length > 0 ? [` seeds: [${zodSeedNames.join(", ")}],`] : []),
|
|
398
552
|
`});`,
|
|
399
553
|
``,
|
|
400
554
|
].join("\n");
|
|
@@ -431,6 +585,57 @@ async function main(): Promise<void> {
|
|
|
431
585
|
fail(`bundled engine entry does not assign globalThis.plan — refusing to package`);
|
|
432
586
|
}
|
|
433
587
|
|
|
588
|
+
// ── 1b. THE FROZEN-SANDBOX BOOT PROBE (lazy boot only, fail-closed) ──
|
|
589
|
+
// The sealed engine freezes globalThis after the lump's top level; a lazy
|
|
590
|
+
// domain's module init runs LATER, inside the dispatch. Any module that writes
|
|
591
|
+
// an ambient global at init (the zod slots are pre-seeded above; this guards
|
|
592
|
+
// everything else) would deterministically throw at FIRST DISPATCH in
|
|
593
|
+
// production. Reproduce the sandbox order here — eval the real bundle in a
|
|
594
|
+
// fresh frozen context, then force every domain — and refuse to package on any
|
|
595
|
+
// failure that is not the expected unknown-directive refusal.
|
|
596
|
+
if (!eagerLump) {
|
|
597
|
+
// A CHILD process: the probe must freeze a REAL global object (a `vm` context
|
|
598
|
+
// global refuses Object.freeze), and the lump must run STRICT (the engine
|
|
599
|
+
// evals it with the STRICT flag — sloppy mode would silently no-op the very
|
|
600
|
+
// frozen-global writes the probe exists to catch).
|
|
601
|
+
const lumpPath = path.join(outDir, ".nomos_lump.js");
|
|
602
|
+
writeFileSync(lumpPath, javascript, "utf8");
|
|
603
|
+
const probePath = path.join(outDir, ".nomos_boot_probe.cjs");
|
|
604
|
+
writeFileSync(
|
|
605
|
+
probePath,
|
|
606
|
+
[
|
|
607
|
+
`// AUTO-GENERATED by nomos-compile — the frozen-sandbox lazy-boot probe.`,
|
|
608
|
+
`const fs = require("node:fs");`,
|
|
609
|
+
`const lump = fs.readFileSync(process.argv[2], "utf8");`,
|
|
610
|
+
`const domains = JSON.parse(process.argv[3]);`,
|
|
611
|
+
`(0, eval)('"use strict";\\n' + lump); // indirect eval → real global scope, strict like the engine`,
|
|
612
|
+
`Object.freeze(globalThis); // the sealed sandbox freezes before dispatch`,
|
|
613
|
+
`for (const key of domains) {`,
|
|
614
|
+
` let msg;`,
|
|
615
|
+
` try { globalThis.plan({ intent: { domain: key, directiveId: "__nomos_boot_probe__" } }); msg = "unexpected-success"; }`,
|
|
616
|
+
` catch (e) { msg = String((e && e.message) || e); }`,
|
|
617
|
+
` if (!/no directive registered/.test(msg)) { console.error(key + "\\u0000" + msg); process.exit(3); }`,
|
|
618
|
+
`}`,
|
|
619
|
+
``,
|
|
620
|
+
].join("\n"),
|
|
621
|
+
"utf8",
|
|
622
|
+
);
|
|
623
|
+
try {
|
|
624
|
+
execFileSync(process.execPath, [probePath, lumpPath, JSON.stringify(cfg.domains.map((d) => d.key))], {
|
|
625
|
+
encoding: "utf8",
|
|
626
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
627
|
+
});
|
|
628
|
+
} catch (e) {
|
|
629
|
+
const stderr = String((e as { stderr?: string }).stderr ?? "").trim();
|
|
630
|
+
const [domainKey, msg] = stderr.includes("\u0000") ? stderr.split("\u0000") : ["?", stderr];
|
|
631
|
+
fail(
|
|
632
|
+
`domain '${domainKey}' failed its frozen-sandbox lazy-boot probe — a module ` +
|
|
633
|
+
`initializer touches ambient globals after the freeze (the engine would ` +
|
|
634
|
+
`refuse its first dispatch): ${msg}`,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
434
639
|
// ── 2. USD IR + the package envelope ──
|
|
435
640
|
const usdJson = emitUsdJsonForModules(domainModules);
|
|
436
641
|
const text = packageUsda(usdJson, javascript);
|
|
@@ -438,6 +643,48 @@ async function main(): Promise<void> {
|
|
|
438
643
|
writeFileSync(pkgPath, text, "utf8");
|
|
439
644
|
const domainHash = sha256HexUtf8(text);
|
|
440
645
|
|
|
646
|
+
// ── 2b. opt-in LAYERED emission — additive artifacts ONLY (the flattened package
|
|
647
|
+
// above and its domainHash are already written and do not move). The
|
|
648
|
+
// homomorphism is ASSERTED fail-closed on every layered compile: the
|
|
649
|
+
// per-module layer stack must flatten BYTE-IDENTICALLY to the canonical
|
|
650
|
+
// composed IR embedded in the package, or nothing layered is emitted.
|
|
651
|
+
let layeredRootPath: string | undefined;
|
|
652
|
+
let layeredFileCount = 0;
|
|
653
|
+
if (layered) {
|
|
654
|
+
const safe = (s: string) => s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
655
|
+
const layersDir = path.join(outDir, "layers");
|
|
656
|
+
mkdirSync(layersDir, { recursive: true });
|
|
657
|
+
const allLayers: UsdLayer[] = [];
|
|
658
|
+
const relPaths: string[] = [];
|
|
659
|
+
const pendingWrites: { path: string; text: string }[] = [];
|
|
660
|
+
for (const ld of layeredDomains) {
|
|
661
|
+
for (const emitted of emitLayeredDomain(ld.key, ld.inputs)) {
|
|
662
|
+
const base = `${safe(ld.key)}--${safe(emitted.name)}`;
|
|
663
|
+
pendingWrites.push({ path: path.join(layersDir, `${base}.usda`), text: emitted.usda });
|
|
664
|
+
pendingWrites.push({
|
|
665
|
+
path: path.join(layersDir, `${base}.layer.json`),
|
|
666
|
+
text: JSON.stringify(emitted.layer) + "\n",
|
|
667
|
+
});
|
|
668
|
+
allLayers.push(emitted.layer);
|
|
669
|
+
relPaths.push(`layers/${base}.usda`);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const flatJson = JSON.stringify(flattenLayeredUsd(allLayers));
|
|
673
|
+
if (flatJson !== usdJson) {
|
|
674
|
+
fail(
|
|
675
|
+
`layered emission does not flatten to the canonical composed IR — the module fold and ` +
|
|
676
|
+
`the layer-stack fold DISAGREE for this package (usually a re-declared directive ` +
|
|
677
|
+
`silently dropping an omit-when-empty reads/emits boundary, or a re-declared ` +
|
|
678
|
+
`aggregate dropping a field: USD shows the weaker opinion through; the module fold ` +
|
|
679
|
+
`does not). Refusing to emit the layered artifact (the flattened package is unaffected).`,
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
for (const w of pendingWrites) writeFileSync(w.path, w.text, "utf8");
|
|
683
|
+
layeredRootPath = path.join(outDir, `${cfg.name}.layered.usda`);
|
|
684
|
+
writeFileSync(layeredRootPath, layeredRootUsda(relPaths), "utf8");
|
|
685
|
+
layeredFileCount = relPaths.length;
|
|
686
|
+
}
|
|
687
|
+
|
|
441
688
|
// ── 3. the READ manifest + IDENTITY artifacts ──
|
|
442
689
|
const readManifest = buildReadManifest(domainModules);
|
|
443
690
|
const readPath = path.join(outDir, "domain_manifests.json");
|
|
@@ -472,6 +719,21 @@ async function main(): Promise<void> {
|
|
|
472
719
|
};
|
|
473
720
|
const deployPath = path.join(outDir, `${cfg.name}.deploy.json`);
|
|
474
721
|
writeFileSync(deployPath, JSON.stringify(deployBody) + "\n", "utf8");
|
|
722
|
+
|
|
723
|
+
// ── 4b. the GENERATED PROOF — a runnable, domain-shaped e2e synthesized from the
|
|
724
|
+
// law itself (codegen_proof.ts): reshape the law, recompile, the proof
|
|
725
|
+
// reshapes itself — no test rewriting. NEVER FATAL: a domain the
|
|
726
|
+
// synthesizer can't sample yet still compiles — the skip names its remedy.
|
|
727
|
+
let proofPath: string | undefined;
|
|
728
|
+
let proofSkip: string | undefined;
|
|
729
|
+
try {
|
|
730
|
+
const proofSrc = generateTsProof(domainModules, { packageName: cfg.name, domainHash });
|
|
731
|
+
proofPath = path.join(outDir, `${cfg.name}.proof.mts`);
|
|
732
|
+
writeFileSync(proofPath, proofSrc, "utf8");
|
|
733
|
+
} catch (e) {
|
|
734
|
+
proofSkip = (e as Error).message;
|
|
735
|
+
}
|
|
736
|
+
|
|
475
737
|
const summaryTxt = [
|
|
476
738
|
`${cfg.name} — compiled Nomos domain package`,
|
|
477
739
|
`law (domainHash): ${domainHash} # sha256(${cfg.name}.package.usda) — content-addressed`,
|
|
@@ -483,6 +745,9 @@ async function main(): Promise<void> {
|
|
|
483
745
|
]),
|
|
484
746
|
`deploy: POST ${cfg.name}.deploy.json to /v1/workspaces/<ws>/domains (or: githolon deploy <ws>)`,
|
|
485
747
|
`typed client: ${cfg.name}.client.ts — ${tsClientFactoryName(domainModules[0]?.domain ?? domainModules[0]?.name ?? cfg.name)}(holon), law hash baked in`,
|
|
748
|
+
proofPath !== undefined
|
|
749
|
+
? `generated proof: ${cfg.name}.proof.mts — run: githolon proof (live; ends ALL GREEN or names the jam)`
|
|
750
|
+
: `generated proof: SKIPPED — ${proofSkip}`,
|
|
486
751
|
``,
|
|
487
752
|
].join("\n");
|
|
488
753
|
writeFileSync(path.join(outDir, `${cfg.name}.summary.txt`), summaryTxt, "utf8");
|
|
@@ -513,11 +778,21 @@ async function main(): Promise<void> {
|
|
|
513
778
|
console.log(`nomos-compile: ${cfg.name}`);
|
|
514
779
|
console.log(` package ${rel(pkgPath)} (${text.length} bytes)`);
|
|
515
780
|
console.log(` domainHash ${domainHash}`);
|
|
781
|
+
if (layeredRootPath !== undefined) {
|
|
782
|
+
console.log(
|
|
783
|
+
` layered ${rel(layeredRootPath)} (${layeredFileCount} module layer(s) in build/layers/ — flatten-verified byte-identical to the canonical IR)`,
|
|
784
|
+
);
|
|
785
|
+
}
|
|
516
786
|
console.log(` read ${rel(readPath)} (${Object.keys(readManifest.aggregateFieldKinds).length} aggregate(s), ${readManifest.queries.length} quer(y/ies), ${readManifest.counts.length} count(s))`);
|
|
517
787
|
console.log(` identity ${rel(manifestsPath)} (${Object.keys(identity.manifests).length} domain(s)${identity.excluded.length ? `, ${identity.excluded.length} EXCLUDED (palette gap)` : ""})`);
|
|
518
788
|
for (const [dom, h] of Object.entries(identity.hashes)) console.log(` ${dom.padEnd(20)} ${h}`);
|
|
519
789
|
for (const ex of identity.excluded) console.log(` EXCLUDED ${ex.domain}: ${ex.reason}`);
|
|
520
790
|
console.log(` client ${rel(clientPath)} (typed TS client — payloads, read models, query accessors)`);
|
|
791
|
+
if (proofPath !== undefined) {
|
|
792
|
+
console.log(` proof ${rel(proofPath)} (a runnable e2e GENERATED from your law — run: githolon proof)`);
|
|
793
|
+
} else {
|
|
794
|
+
console.log(` proof SKIPPED — ${proofSkip}`);
|
|
795
|
+
}
|
|
521
796
|
if (dartOut !== undefined) {
|
|
522
797
|
console.log(` dart ${rel(dartOut)}${path.sep} (${dartFiles.length} domain file(s) + vendored nomos_dsl support — typed Dart for Flutter)`);
|
|
523
798
|
}
|
|
@@ -527,7 +802,11 @@ async function main(): Promise<void> {
|
|
|
527
802
|
console.log(` githolon ws create <ws> && githolon deploy <ws> # the secret is saved + sent for you`);
|
|
528
803
|
console.log(` # raw lane: curl -X POST -H 'content-type: application/json' -H 'Authorization: Bearer <workspaceSecret>' \\`);
|
|
529
804
|
console.log(` # --data-binary @${rel(deployPath)} https://nomos.captainapp.co.uk/v1/workspaces/<ws>/domains`);
|
|
530
|
-
|
|
805
|
+
if (proofPath !== undefined) {
|
|
806
|
+
console.log(`prove it: npx githolon proof # GENERATED from your law — offline write -> sync -> admission -> cloud reads, live`);
|
|
807
|
+
} else {
|
|
808
|
+
console.log(`prove it: npm run e2e # offline write -> sync -> admission -> cloud query, live`);
|
|
809
|
+
}
|
|
531
810
|
}
|
|
532
811
|
|
|
533
812
|
await main();
|