@githolon/dsl 0.46.0 → 0.47.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/dart/lib/src/errors.dart +250 -0
- package/dart/lib/src/test_harness.dart +182 -175
- package/dart/test/errors_test.dart +77 -0
- package/dart/test/test_harness_test.dart +85 -57
- package/package.json +1 -1
- package/src/codegen_dart_client.ts +15 -0
- package/src/codegen_proof.ts +123 -8
- package/src/codegen_ts.ts +28 -3
- package/src/compat_manifest.ts +20 -0
- package/src/compile_package_main.ts +174 -16
- package/src/framework/framework_aggregates.ts +18 -0
- package/src/read_projection_composition.ts +9 -9
- package/src/read_projection_namespace.ts +4 -5
- package/src/usd.ts +3 -3
|
@@ -108,7 +108,7 @@ import {
|
|
|
108
108
|
layeredRootUsda,
|
|
109
109
|
type LayeredModuleInput,
|
|
110
110
|
} from "./usd_layers.js";
|
|
111
|
-
import { domainManifest } from "./manifest.js";
|
|
111
|
+
import { domainManifest, canonicalJson } from "./manifest.js";
|
|
112
112
|
import type { WireSchema } from "./wire.js";
|
|
113
113
|
import {
|
|
114
114
|
deriveStableIdContinuity,
|
|
@@ -121,6 +121,7 @@ import { generateTsClient, tsClientFactoryName } from "./codegen_ts.js";
|
|
|
121
121
|
import { generateOpenApiFragment, mergeOpenApiFragments } from "./codegen_openapi.js";
|
|
122
122
|
import { domainInterface, type DomainInterface } from "./shape_compat.js";
|
|
123
123
|
import { generateTsProof, proofLegsIndex } from "./codegen_proof.js";
|
|
124
|
+
import type { NomosCompat } from "./compat_manifest.js";
|
|
124
125
|
|
|
125
126
|
const DSL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
126
127
|
|
|
@@ -187,7 +188,19 @@ interface PackageConfig {
|
|
|
187
188
|
* `package:nomos_client/...` import is rewritten to a relative one) — so a Flutter app
|
|
188
189
|
* can vendor the directory as-is, no pub deps.
|
|
189
190
|
*/
|
|
190
|
-
readonly dart?:
|
|
191
|
+
readonly dart?:
|
|
192
|
+
| boolean
|
|
193
|
+
| {
|
|
194
|
+
readonly out?: string;
|
|
195
|
+
readonly support?: DartSupportImport;
|
|
196
|
+
/**
|
|
197
|
+
* Basename (no `.dart`) of the generated PACKAGE BARREL — the stable public facade emitted at
|
|
198
|
+
* `<out>/<barrel>.dart` over the `src/generated/` files. Set it to the pub package name (so
|
|
199
|
+
* `package:co2_nomos_domains/co2_nomos_domains.dart` resolves); defaults to the snake_cased
|
|
200
|
+
* config `name`.
|
|
201
|
+
*/
|
|
202
|
+
readonly barrel?: string;
|
|
203
|
+
};
|
|
191
204
|
/**
|
|
192
205
|
* OPT-IN layered USD emission (also `--layered` on the CLI). When true, ALSO emit
|
|
193
206
|
* `build/layers/<domain>--<module>.usda` (+ `.layer.json`) — one REAL USD layer per
|
|
@@ -372,9 +385,22 @@ const DART_SUPPORT_SRC = [
|
|
|
372
385
|
* (a real `nomos_client` pub dep) keeps the full barrel. */
|
|
373
386
|
const VENDORED_BARREL_EXCLUDES = ["src/schema_validation.dart", "src/home.dart"] as const;
|
|
374
387
|
|
|
375
|
-
|
|
388
|
+
/** The generated-owned subdirectory, relative to `dartOut` (the pub package's `lib/`). WIPED and
|
|
389
|
+
* rewritten on every compile — nothing hand-written may live in it. The stable facade (the package
|
|
390
|
+
* barrel) stays at `dartOut/<barrel>.dart`, so a prune/regenerate can never orphan app imports. */
|
|
391
|
+
const DART_GENERATED_SUBDIR = path.join("src", "generated");
|
|
392
|
+
|
|
393
|
+
/** Remove the PRE-barrel flat layout's generated files from `dartOut` root (they now live under
|
|
394
|
+
* `src/generated/`): the vendored support set + the per-domain files. Targeted by exact known
|
|
395
|
+
* name — `dartOut` is typically the tenant package's `lib/`, so never sweep directories. */
|
|
396
|
+
function removeLegacyFlatDartLayout(dartOut: string, moduleNames: readonly string[]): void {
|
|
376
397
|
rmSync(path.join(dartOut, "nomos_client.dart"), { force: true });
|
|
377
|
-
rmSync(path.join(dartOut, "
|
|
398
|
+
rmSync(path.join(dartOut, "nomos_framework.dart"), { force: true });
|
|
399
|
+
for (const f of DART_SUPPORT_SRC) rmSync(path.join(dartOut, "src", f), { force: true });
|
|
400
|
+
for (const name of moduleNames) {
|
|
401
|
+
rmSync(path.join(dartOut, `${name}.dart`), { force: true });
|
|
402
|
+
rmSync(path.join(dartOut, `${name}.client.dart`), { force: true });
|
|
403
|
+
}
|
|
378
404
|
}
|
|
379
405
|
|
|
380
406
|
/**
|
|
@@ -382,6 +408,10 @@ function removeVendoredDartSupport(dartOut: string): void {
|
|
|
382
408
|
* `<name>.dart` per domain (the SAME `generateDartDomain` path the co2 tenant's
|
|
383
409
|
* `emit_dart.ts` drives) + the vendored `nomos_client.dart` barrel and its `src/`
|
|
384
410
|
* support files, with the generated files' `package:` import rewritten relative.
|
|
411
|
+
* Everything lands under `dartOut/src/generated/` (generated-owned, wiped per
|
|
412
|
+
* compile) behind ONE generated package barrel `dartOut/<barrelName>.dart` — the
|
|
413
|
+
* analyzer-clean plug-and-play entrypoint (zero `show`/`hide`; framework-shared
|
|
414
|
+
* symbols dedup into `nomos_framework.dart`, so no export is ambiguous).
|
|
385
415
|
*/
|
|
386
416
|
function emitDart(
|
|
387
417
|
modules: readonly DomainModule[],
|
|
@@ -389,25 +419,29 @@ function emitDart(
|
|
|
389
419
|
domainHash: string,
|
|
390
420
|
packageInterface: Record<string, DomainInterface> = {},
|
|
391
421
|
supportImport: DartSupportImport = "vendored",
|
|
422
|
+
barrelName = "nomos_domains",
|
|
423
|
+
compat?: NomosCompat,
|
|
392
424
|
): string[] {
|
|
393
|
-
|
|
425
|
+
const genDir = path.join(dartOut, DART_GENERATED_SUBDIR);
|
|
426
|
+
removeLegacyFlatDartLayout(dartOut, modules.map((m) => m.name));
|
|
427
|
+
rmSync(genDir, { recursive: true, force: true });
|
|
428
|
+
mkdirSync(genDir, { recursive: true });
|
|
394
429
|
|
|
395
430
|
// 1. optional vendored framework support (barrel + src/), copied byte-for-byte except
|
|
396
431
|
// the barrel's schema_validation export (see DART_SUPPORT_SRC).
|
|
397
432
|
if (supportImport === "vendored") {
|
|
398
|
-
mkdirSync(path.join(
|
|
433
|
+
mkdirSync(path.join(genDir, "src"), { recursive: true });
|
|
399
434
|
const libDir = path.join(DSL_DIR, "dart", "lib");
|
|
400
435
|
const barrel = readFileSync(path.join(libDir, "nomos_client.dart"), "utf8")
|
|
401
436
|
.split("\n")
|
|
402
437
|
.filter((line) => !VENDORED_BARREL_EXCLUDES.some((x) => line.includes(x)))
|
|
403
438
|
.join("\n");
|
|
404
|
-
writeFileSync(path.join(
|
|
439
|
+
writeFileSync(path.join(genDir, "nomos_client.dart"), barrel, "utf8");
|
|
405
440
|
for (const f of DART_SUPPORT_SRC) {
|
|
406
|
-
copyFileSync(path.join(libDir, "src", f), path.join(
|
|
441
|
+
copyFileSync(path.join(libDir, "src", f), path.join(genDir, "src", f));
|
|
407
442
|
}
|
|
408
|
-
} else {
|
|
409
|
-
removeVendoredDartSupport(dartOut);
|
|
410
443
|
}
|
|
444
|
+
// (package-support mode vendors nothing — the genDir wipe above already cleared any prior set)
|
|
411
445
|
|
|
412
446
|
// 2. FRAMEWORK-SHARED symbol dedup (Dart-codegen hygiene). Framework-owned symbols fold into MANY
|
|
413
447
|
// domains, so their generated Dart would be DEFINED once per domain — a package barrel re-exporting
|
|
@@ -468,7 +502,7 @@ function emitDart(
|
|
|
468
502
|
`generated Dart for '${fileBase}' does not carry the expected support import (${DART_PACKAGE_IMPORT}) — codegen_dart header drifted; fix the rewrite in compile_package_main.ts`,
|
|
469
503
|
);
|
|
470
504
|
}
|
|
471
|
-
const p = path.join(
|
|
505
|
+
const p = path.join(genDir, `${fileBase}.dart`);
|
|
472
506
|
writeFileSync(
|
|
473
507
|
p,
|
|
474
508
|
supportImport === "vendored" ? src.replace(DART_PACKAGE_IMPORT, DART_RELATIVE_IMPORT) : src,
|
|
@@ -533,13 +567,39 @@ function emitDart(
|
|
|
533
567
|
const clientSrc = generateDartBridgeClient(modForDart, {
|
|
534
568
|
domainHash,
|
|
535
569
|
interface: packageInterface[modForDart.domain ?? modForDart.name],
|
|
570
|
+
...(compat !== undefined ? { compat } : {}),
|
|
536
571
|
supportImport,
|
|
537
572
|
...(domainFrameworkImport !== undefined ? { frameworkImportUri: domainFrameworkImport } : {}),
|
|
538
573
|
});
|
|
539
|
-
const cp = path.join(
|
|
574
|
+
const cp = path.join(genDir, `${mod.name}.client.dart`);
|
|
540
575
|
writeFileSync(cp, clientSrc, "utf8");
|
|
541
576
|
written.push(cp);
|
|
542
577
|
}
|
|
578
|
+
|
|
579
|
+
// 3. THE PACKAGE BARREL — the stable public facade (`dartOut/<barrelName>.dart`), generated-owned
|
|
580
|
+
// and overwritten every compile. ONE import gives an app the support runtime + the shared
|
|
581
|
+
// framework surface + every domain's types and client, with ZERO `show`/`hide` (the framework
|
|
582
|
+
// dedup above makes the exports collision-free by construction; re-exporting the same library
|
|
583
|
+
// through two paths — e.g. a client's own `nomos_framework.dart` re-export — is not ambiguous).
|
|
584
|
+
const genRel = DART_GENERATED_SUBDIR.split(path.sep).join("/");
|
|
585
|
+
const barrelLines = [
|
|
586
|
+
"// GENERATED by nomos-compile — the package's public entrypoint. DO NOT EDIT.",
|
|
587
|
+
"// Regenerated on every compile; generated implementation lives under src/generated/.",
|
|
588
|
+
"library;",
|
|
589
|
+
"",
|
|
590
|
+
supportImport === "vendored"
|
|
591
|
+
? `export '${genRel}/nomos_client.dart';`
|
|
592
|
+
: `export 'package:nomos_client/nomos_client.dart';`,
|
|
593
|
+
...(hasFrameworkSymbols ? [`export '${genRel}/${FRAMEWORK_FILE}.dart';`] : []),
|
|
594
|
+
...modules.flatMap((m) => [
|
|
595
|
+
`export '${genRel}/${m.name}.dart';`,
|
|
596
|
+
`export '${genRel}/${m.name}.client.dart';`,
|
|
597
|
+
]),
|
|
598
|
+
"",
|
|
599
|
+
];
|
|
600
|
+
const barrelPath = path.join(dartOut, `${barrelName}.dart`);
|
|
601
|
+
writeFileSync(barrelPath, barrelLines.join("\n"), "utf8");
|
|
602
|
+
written.push(barrelPath);
|
|
543
603
|
return written;
|
|
544
604
|
}
|
|
545
605
|
|
|
@@ -548,6 +608,9 @@ function emitDart(
|
|
|
548
608
|
async function main(): Promise<void> {
|
|
549
609
|
const argv = process.argv.slice(2);
|
|
550
610
|
const layeredFlag = argv.includes("--layered");
|
|
611
|
+
// §"Shipped (1–4)" follow-up: `githolon compile --analyze` (or NOMOS_ANALYZE=1) runs
|
|
612
|
+
// `dart analyze` on the generated Dart package after emission and fails the compile on red.
|
|
613
|
+
const analyzeFlag = argv.includes("--analyze") || process.env.NOMOS_ANALYZE === "1";
|
|
551
614
|
const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.package.mjs";
|
|
552
615
|
const configPath = path.resolve(process.cwd(), configArg);
|
|
553
616
|
if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
|
|
@@ -1480,6 +1543,34 @@ async function main(): Promise<void> {
|
|
|
1480
1543
|
// compute the identity to surface its per-domain hash in the summary below.
|
|
1481
1544
|
const identity = buildIdentity(domainModules);
|
|
1482
1545
|
|
|
1546
|
+
// ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (§5, architecture/plug_and_play_dx.md).
|
|
1547
|
+
// A DIAGNOSIS surface, NEVER a gate: the shape-compat arc (#72) established that clients
|
|
1548
|
+
// fit workspaces by STRUCTURAL INTERFACE (stable-id subtyping), not by law-hash pin. This
|
|
1549
|
+
// block is baked into both generated clients (TS + Dart) and mirrored into the deploy JSON
|
|
1550
|
+
// so a connect() can classify silent / shape-compat / incompatible. FRONTEND-ONLY: it is
|
|
1551
|
+
// emitted like the dart codegen hints — it does NOT enter the USD IR / package bytes /
|
|
1552
|
+
// domainHash (the package `text` above is already hashed and does not move).
|
|
1553
|
+
const dslVersion = ((): string => {
|
|
1554
|
+
try {
|
|
1555
|
+
return JSON.parse(readFileSync(path.join(DSL_DIR, "package.json"), "utf8")).version ?? "unknown";
|
|
1556
|
+
} catch {
|
|
1557
|
+
return "unknown";
|
|
1558
|
+
}
|
|
1559
|
+
})();
|
|
1560
|
+
const compatDomains: NomosCompat["domains"] = {};
|
|
1561
|
+
for (const m of domainModules) {
|
|
1562
|
+
const key = m.domain ?? m.name;
|
|
1563
|
+
const identityHash = identity.hashes[key];
|
|
1564
|
+
compatDomains[key] = identityHash === undefined ? {} : { identityHash };
|
|
1565
|
+
}
|
|
1566
|
+
const nomosCompat: NomosCompat = {
|
|
1567
|
+
packageName: cfg.name,
|
|
1568
|
+
lawHash: domainHash,
|
|
1569
|
+
interfaceHash: sha256HexUtf8(canonicalJson(packageInterface)),
|
|
1570
|
+
domains: compatDomains,
|
|
1571
|
+
generator: { dsl: dslVersion, schema: 1 },
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1483
1574
|
// ── 3b. the OpenAPI spec — THE PACKAGE'S SELF-DESCRIPTION (the host's runtime routing table;
|
|
1484
1575
|
// architecture/githolon_host_contract.md "The Self-Describing API"). Each domain emits its fragment
|
|
1485
1576
|
// from its OWN zod (z.toJSONSchema); the package's spec is the law-blind merge. Deploy STAGES it like
|
|
@@ -1524,6 +1615,9 @@ async function main(): Promise<void> {
|
|
|
1524
1615
|
// THE SELF-DESCRIBING API: this package's OpenAPI spec (law-derived). The worker stages it on the
|
|
1525
1616
|
// manifest overlay; a read serves the workspace's merged spec; the host builds its router from it.
|
|
1526
1617
|
openapi,
|
|
1618
|
+
// THE CLIENT/LAW COMPAT MANIFEST (§5): a DIAGNOSIS surface (law hash + interface hash + generator
|
|
1619
|
+
// provenance) the connect-time classifier reads. Frontend-only — the package bytes above ignore it.
|
|
1620
|
+
nomosCompat,
|
|
1527
1621
|
};
|
|
1528
1622
|
const deployPath = path.join(outDir, `${cfg.name}.deploy.json`);
|
|
1529
1623
|
writeFileSync(deployPath, JSON.stringify(deployBody) + "\n", "utf8");
|
|
@@ -1535,6 +1629,7 @@ async function main(): Promise<void> {
|
|
|
1535
1629
|
let proofPath: string | undefined;
|
|
1536
1630
|
let proofSkip: string | undefined;
|
|
1537
1631
|
let proofDomains: string[] = [];
|
|
1632
|
+
let birthWarnings: string[] = [];
|
|
1538
1633
|
try {
|
|
1539
1634
|
const proofSrc = generateTsProof(domainModules, { packageName: cfg.name, domainHash });
|
|
1540
1635
|
proofPath = path.join(outDir, `${cfg.name}.proof.mts`);
|
|
@@ -1547,10 +1642,20 @@ async function main(): Promise<void> {
|
|
|
1547
1642
|
if (legsIndex !== null) {
|
|
1548
1643
|
writeFileSync(path.join(outDir, `${cfg.name}.proof-legs.json`), JSON.stringify(legsIndex, null, 2) + "\n", "utf8");
|
|
1549
1644
|
proofDomains = Object.keys(legsIndex.domains);
|
|
1645
|
+
birthWarnings = Object.values(legsIndex.domains).flatMap((l) => l.birthDiagnostics ?? []);
|
|
1550
1646
|
}
|
|
1551
1647
|
} catch (e) {
|
|
1552
1648
|
proofSkip = (e as Error).message;
|
|
1553
1649
|
}
|
|
1650
|
+
// BIRTHS DX: a `.births()` directive that can't be (or can only shakily be) proven says WHY here —
|
|
1651
|
+
// never a silent skip, never an opaque runtime `born=[]` first contact. When the .mts generation
|
|
1652
|
+
// itself failed, still derive the per-domain diagnostics (best-effort).
|
|
1653
|
+
if (proofSkip !== undefined && birthWarnings.length === 0) {
|
|
1654
|
+
try {
|
|
1655
|
+
const idx = proofLegsIndex(domainModules);
|
|
1656
|
+
if (idx !== null) birthWarnings = Object.values(idx.domains).flatMap((l) => l.birthDiagnostics ?? []);
|
|
1657
|
+
} catch { /* diagnostics are best-effort */ }
|
|
1658
|
+
}
|
|
1554
1659
|
|
|
1555
1660
|
const summaryTxt = [
|
|
1556
1661
|
`${cfg.name} — compiled Nomos domain package`,
|
|
@@ -1573,7 +1678,7 @@ async function main(): Promise<void> {
|
|
|
1573
1678
|
// ── 5. the typed TS client (#10 — the web sibling of codegen_dart), with the
|
|
1574
1679
|
// deployed law's content hash baked in (emitted AFTER the package is hashed) ──
|
|
1575
1680
|
const clientPath = path.join(outDir, `${cfg.name}.client.ts`);
|
|
1576
|
-
writeFileSync(clientPath, generateTsClient(domainModules, { packageName: cfg.name, domainHash, interface: packageInterface }), "utf8");
|
|
1681
|
+
writeFileSync(clientPath, generateTsClient(domainModules, { packageName: cfg.name, domainHash, interface: packageInterface, compat: nomosCompat }), "utf8");
|
|
1577
1682
|
|
|
1578
1683
|
// ── 6. the typed Dart frontend (opt-in; the Flutter sibling of step 5, emitted
|
|
1579
1684
|
// after the package hash like the TS client) ──
|
|
@@ -1584,10 +1689,11 @@ async function main(): Promise<void> {
|
|
|
1584
1689
|
(typeof dartCfg !== "object" ||
|
|
1585
1690
|
dartCfg === null ||
|
|
1586
1691
|
(dartCfg.out !== undefined && typeof dartCfg.out !== "string") ||
|
|
1587
|
-
(dartCfg.support !== undefined && dartCfg.support !== "vendored" && dartCfg.support !== "package")
|
|
1692
|
+
(dartCfg.support !== undefined && dartCfg.support !== "vendored" && dartCfg.support !== "package") ||
|
|
1693
|
+
(dartCfg.barrel !== undefined && !/^[a-z][a-z0-9_]*$/.test(String(dartCfg.barrel))))
|
|
1588
1694
|
) {
|
|
1589
1695
|
fail(
|
|
1590
|
-
`config key 'dart' must be false, true, or { out?: "relative/dir", support?: "vendored" | "package" }; got ${JSON.stringify(dartCfg)}`,
|
|
1696
|
+
`config key 'dart' must be false, true, or { out?: "relative/dir", support?: "vendored" | "package", barrel?: "snake_case_name" }; got ${JSON.stringify(dartCfg)}`,
|
|
1591
1697
|
);
|
|
1592
1698
|
}
|
|
1593
1699
|
const dartSupportImport: DartSupportImport =
|
|
@@ -1599,7 +1705,16 @@ async function main(): Promise<void> {
|
|
|
1599
1705
|
cfgDir,
|
|
1600
1706
|
typeof dartCfg === "object" && dartCfg.out !== undefined ? dartCfg.out : path.join(outDir, "dart"),
|
|
1601
1707
|
);
|
|
1602
|
-
|
|
1708
|
+
// The barrel basename: configured, else the snake_cased package name (a valid Dart library
|
|
1709
|
+
// file name — pub packages import `package:<name>/<name>.dart`).
|
|
1710
|
+
const defaultBarrel = cfg.name
|
|
1711
|
+
.toLowerCase()
|
|
1712
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
1713
|
+
.replace(/^_+|_+$/g, "")
|
|
1714
|
+
.replace(/^(?=[0-9])/, "n");
|
|
1715
|
+
const dartBarrel =
|
|
1716
|
+
typeof dartCfg === "object" && dartCfg.barrel !== undefined ? dartCfg.barrel : defaultBarrel;
|
|
1717
|
+
dartFiles = emitDart(domainModules, dartOut, domainHash, packageInterface, dartSupportImport, dartBarrel, nomosCompat);
|
|
1603
1718
|
}
|
|
1604
1719
|
|
|
1605
1720
|
// ── summary ──
|
|
@@ -1645,6 +1760,9 @@ async function main(): Promise<void> {
|
|
|
1645
1760
|
} else {
|
|
1646
1761
|
console.log(` proof SKIPPED — ${proofSkip}`);
|
|
1647
1762
|
}
|
|
1763
|
+
for (const w of birthWarnings) {
|
|
1764
|
+
console.log(` ⚠ births ${w}`);
|
|
1765
|
+
}
|
|
1648
1766
|
if (dartOut !== undefined) {
|
|
1649
1767
|
const supportText =
|
|
1650
1768
|
dartSupportImport === "vendored" ? "vendored nomos_client support" : "package nomos_client imports";
|
|
@@ -1666,6 +1784,46 @@ async function main(): Promise<void> {
|
|
|
1666
1784
|
} else {
|
|
1667
1785
|
console.log(`prove it: npm run e2e # offline write -> sync -> admission -> cloud query, live`);
|
|
1668
1786
|
}
|
|
1787
|
+
|
|
1788
|
+
if (analyzeFlag) runDartAnalyze(dartOut);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
/**
|
|
1792
|
+
* `--analyze` (or NOMOS_ANALYZE=1): run `dart analyze` on the generated Dart package so a red
|
|
1793
|
+
* analyze fails the compile loudly. NEVER changes any emitted bytes — a pure post-emission check.
|
|
1794
|
+
* - No Dart out (dart config off) or no `pubspec.yaml` at the out dir / one level up → warn + skip
|
|
1795
|
+
* (the generated sources need a pub package around them to analyze).
|
|
1796
|
+
* - `dart` missing from PATH → warn + skip.
|
|
1797
|
+
* - Analyzer red → print its output and exit non-zero.
|
|
1798
|
+
*/
|
|
1799
|
+
function runDartAnalyze(dartOut: string | undefined): void {
|
|
1800
|
+
if (dartOut === undefined) {
|
|
1801
|
+
console.warn(` ⚠ analyze skipped — no Dart output (set 'dart' in the config to emit + analyze)`);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
// The pub package root: the out dir, else one level up (a common layout is
|
|
1805
|
+
// `<pkg>/lib/generated` with the pubspec at `<pkg>/`, but out is usually `<pkg>` itself).
|
|
1806
|
+
const pkgRoot = [dartOut, path.dirname(dartOut)].find((d) => existsSync(path.join(d, "pubspec.yaml")));
|
|
1807
|
+
if (pkgRoot === undefined) {
|
|
1808
|
+
console.warn(
|
|
1809
|
+
` ⚠ analyze skipped — no pubspec.yaml at ${path.relative(process.cwd(), dartOut)} or its parent; ` +
|
|
1810
|
+
`\`dart analyze\` needs a pub package (add a pubspec.yaml around the generated Dart)`,
|
|
1811
|
+
);
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
try {
|
|
1815
|
+
execFileSync("dart", ["analyze", pkgRoot], { stdio: "inherit" });
|
|
1816
|
+
console.log(` analyze dart analyze GREEN (${path.relative(process.cwd(), pkgRoot)})`);
|
|
1817
|
+
} catch (e) {
|
|
1818
|
+
const err = e as NodeJS.ErrnoException;
|
|
1819
|
+
if (err.code === "ENOENT") {
|
|
1820
|
+
console.warn(` ⚠ analyze skipped — \`dart\` is not on PATH (install the Dart SDK to analyze)`);
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
// A non-zero exit is analyzer RED — execFileSync already streamed the output (stdio: inherit).
|
|
1824
|
+
console.error(` ✗ analyze dart analyze FAILED (${path.relative(process.cwd(), pkgRoot)}) — see output above`);
|
|
1825
|
+
process.exit(1);
|
|
1826
|
+
}
|
|
1669
1827
|
}
|
|
1670
1828
|
|
|
1671
1829
|
await main();
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
import type { AggregateHandle } from "../aggregate.js";
|
|
33
33
|
import type { Directive } from "../directive.js";
|
|
34
34
|
import type { QueryDecl } from "../query.js";
|
|
35
|
+
import { nomosSourceFrontierEnsure } from "../attested.js";
|
|
36
|
+
import * as birthMeter from "../birth_meter.js";
|
|
37
|
+
import * as birthMeterEnsure from "../birth_meter_ensure.js";
|
|
35
38
|
import * as disclosure from "./disclosure.js";
|
|
36
39
|
import * as domainLifecycle from "./domain_lifecycle.js";
|
|
37
40
|
import * as encryptionIdentity from "./encryption_identity.js";
|
|
@@ -47,6 +50,12 @@ import * as workspaces from "./workspaces.js";
|
|
|
47
50
|
// Every framework module that DECLARES aggregate / directive / query singletons. Namespace imports so
|
|
48
51
|
// the registry is derived from the real exports (no hand-kept id list to drift).
|
|
49
52
|
const FRAMEWORK_NAMESPACES: ReadonlyArray<Record<string, unknown>> = [
|
|
53
|
+
// MERGE-SEAM-INJECTED singletons (`composeDomainModule` folds these into every `.births()`
|
|
54
|
+
// domain WITHOUT a tenant re-export — `withBirthMeterExports` + BIRTH_METER_AGGREGATES/QUERIES).
|
|
55
|
+
// Unbranded, a 2-birthing-domain package defined `NomosBirthRecordEnsurePayload` (+ the meter
|
|
56
|
+
// read models/accessors) once PER domain — the exact ambiguous_export co2 hit on its barrel.
|
|
57
|
+
birthMeter,
|
|
58
|
+
birthMeterEnsure,
|
|
50
59
|
disclosure,
|
|
51
60
|
domainLifecycle,
|
|
52
61
|
encryptionIdentity,
|
|
@@ -114,6 +123,15 @@ for (const ns of FRAMEWORK_NAMESPACES) {
|
|
|
114
123
|
}
|
|
115
124
|
}
|
|
116
125
|
|
|
126
|
+
// THE ATTESTED-READS INJECTED DIRECTIVE — branded INDIVIDUALLY, not via its namespace: its payload
|
|
127
|
+
// class (`NomosSourceFrontierEnsurePayload`, zero-field) is domain-agnostic and must dedup like the
|
|
128
|
+
// birth-meter's, but the `NomosSourceFrontier` AGGREGATE stays UNBRANDED deliberately — it is
|
|
129
|
+
// NAMESPACED per owning domain at law (it is NOT in `GLOBAL_READ_PROJECTION_AGGREGATE_IDS`), so its
|
|
130
|
+
// per-domain Dart read surface is domain-specific and must stay in each domain's file. Branding the
|
|
131
|
+
// whole namespace would have flipped its law-plane namespacing via the drift-guard pin.
|
|
132
|
+
(nomosSourceFrontierEnsure as { framework?: true }).framework = true;
|
|
133
|
+
directiveIds.add(nomosSourceFrontierEnsure.id);
|
|
134
|
+
|
|
117
135
|
export const FRAMEWORK_AGGREGATE_IDS: ReadonlySet<string> = aggregateIds;
|
|
118
136
|
export const FRAMEWORK_DIRECTIVE_IDS: ReadonlySet<string> = directiveIds;
|
|
119
137
|
export const FRAMEWORK_QUERY_IDS: ReadonlySet<string> = queryIds;
|
|
@@ -6,20 +6,20 @@
|
|
|
6
6
|
// sibling of `relation_composition.ts` (the scalpel).
|
|
7
7
|
//
|
|
8
8
|
// THE MODEL. The kernel's installed-read-manifest accumulator
|
|
9
|
-
// (`wasm_git_holon.rs::ReadProjectionAccumulator`) UNIONS
|
|
10
|
-
// `nomosReadProjection`
|
|
9
|
+
// (`wasm_git_holon.rs::ReadProjectionAccumulator`) UNIONS the CURRENT installed
|
|
10
|
+
// `nomosReadProjection` fragments BY KEY — `aggregateFieldKinds` by aggregate TYPE,
|
|
11
11
|
// `queries`/`counts`/`spatials`/`sums`/`mins`/`maxes`/`orderedReads` by declared `id`,
|
|
12
12
|
// `deriveds`/`combineds` by `"<of>\0<id>"` — and (correctly, fail-closed) REFUSES a
|
|
13
|
-
// key declared twice with DIVERGENT shapes: "divergent read projection
|
|
14
|
-
// for key <k>".
|
|
13
|
+
// CURRENT key declared twice with DIVERGENT shapes: "divergent read projection
|
|
14
|
+
// declaration for key <k>". Superseded eras never feed the current read manifest.
|
|
15
15
|
//
|
|
16
16
|
// Read-projection NAMESPACING (`read_projection_namespace.ts`) makes a NON-GLOBAL
|
|
17
17
|
// aggregate's keys domain-qualified, so tenant domains never share a key. What CAN
|
|
18
18
|
// still share a key inside one package: GLOBAL framework aggregates declared by ≥2
|
|
19
19
|
// domains (byte-identical by construction — same framework version — but a version
|
|
20
20
|
// skew diverges), and same-named declared reads across domains. This guard turns the
|
|
21
|
-
// runtime
|
|
22
|
-
// (the kernel dedupes them), divergent ones REFUSE with the key and each domain's
|
|
21
|
+
// current-runtime conflict into a precise COMPILE refusal: identical shared declarations
|
|
22
|
+
// PASS (the kernel dedupes them), divergent ones REFUSE with the key and each domain's
|
|
23
23
|
// shape. Validation only — a conflict-free package is returned untouched (era rule).
|
|
24
24
|
|
|
25
25
|
import type { UsdLayer } from "./usd.js";
|
|
@@ -38,9 +38,9 @@ interface ReadProjectionConflict {
|
|
|
38
38
|
function scalpelMessage(conflicts: readonly ReadProjectionConflict[]): string {
|
|
39
39
|
const lines: string[] = [
|
|
40
40
|
`Refusing to compose the package: shared read-projection key(s) carry DIVERGENT declarations ` +
|
|
41
|
-
`across domains — the
|
|
42
|
-
`SERVE time ("divergent read projection declaration for key …")
|
|
43
|
-
`read
|
|
41
|
+
`across domains — the current installed-read-manifest accumulator refuses exactly this at ` +
|
|
42
|
+
`SERVE time ("divergent read projection declaration for key …"). Superseded eras are ignored, ` +
|
|
43
|
+
`but genuinely current law must have one coherent read shape. Fail-closed compile refusal.`,
|
|
44
44
|
``,
|
|
45
45
|
];
|
|
46
46
|
for (const c of conflicts) {
|
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
// relation-type namespacing (`relation_schema.ts::qualifyRelationType`).
|
|
7
7
|
//
|
|
8
8
|
// THE DISEASE. The kernel's installed-read-manifest accumulator
|
|
9
|
-
// (`wasm_git_holon.rs::insert_read_projection_value`)
|
|
10
|
-
// `nomosReadProjection` fragment
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// same-named aggregate therefore WEDGE the workspace's read plane at serve time.
|
|
9
|
+
// (`wasm_git_holon.rs::insert_read_projection_value`) merges every current install's
|
|
10
|
+
// `nomosReadProjection` fragment by key. Superseded eras are ignored, so old read
|
|
11
|
+
// declarations cannot wedge a workspace, but two independent current domain keys that
|
|
12
|
+
// each declare a same-named aggregate are still an ambiguous read surface.
|
|
14
13
|
//
|
|
15
14
|
// THE CURE (the maintainer's rule: "Namespaces are a good idea. Use them."): a
|
|
16
15
|
// NON-GLOBAL aggregate's TYPE NAME is QUALIFIED by its owning domain key at compile —
|
package/src/usd.ts
CHANGED
|
@@ -288,9 +288,9 @@ export interface UsdLayer {
|
|
|
288
288
|
* THE READ-PROJECTION NAMESPACE ERA KEY (read-projection namespacing — the read-layer mirror of
|
|
289
289
|
* `nomosRelationNamespace`). Present IFF this layer qualified ≥1 (non-global) AGGREGATE TYPE by its
|
|
290
290
|
* owning domain key (`Entry` → `guestbook__Entry`), so two domains / two installs that each declare
|
|
291
|
-
* the same bare aggregate name no longer collide at the kernel's installed-read-manifest accumulator
|
|
292
|
-
*
|
|
293
|
-
*
|
|
291
|
+
* the same bare aggregate name no longer collide at the kernel's installed-read-manifest accumulator.
|
|
292
|
+
* Runtime evolution ignores superseded eras; qualification keeps independent domains distinct.
|
|
293
|
+
* `types` is the SORTED set of qualified type
|
|
294
294
|
* names this layer produced; `delimiter` is the same ratified colon-free `"__"`. OMITTED for a layer
|
|
295
295
|
* whose aggregates are all GLOBAL (the explicit framework allowlist / `{global:true}`) — the marker
|
|
296
296
|
* is a witness; the qualification itself is baked into the layer's keys.
|