@githolon/dsl 0.102.1 → 0.102.3
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/CHANGELOG.md +9 -0
- package/dart/lib/src/dev_cloud.dart +61 -18
- package/dart/lib/src/runner/node-entry.mjs +27 -6
- package/dart/lib/src/runner/offline-install.mjs +80 -18
- package/dart/lib/src/test_harness.dart +874 -554
- package/dart/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +14 -6
- package/src/application_composition.ts +117 -2
- package/src/business_birth.ts +11 -9
- package/src/codegen_dart_application.ts +42 -31
- package/src/codegen_dart_client.ts +9 -5
- package/src/codegen_dart_scenario.ts +68 -9
- package/src/compile_package_main.ts +168 -42
- package/src/framework/home.ts +7 -1
- package/src/law_check.ts +13 -5
- package/src/usd_layers.ts +68 -51
- package/src/workspace_sharing.ts +5 -3
|
@@ -262,8 +262,9 @@ interface PackageConfig {
|
|
|
262
262
|
* • `test` — optional; generated business scenarios for widget/integration tests.
|
|
263
263
|
* The layering line: UI code depends on `types`; composition/runtime code on `client`;
|
|
264
264
|
* generated TYPES may flow to the UI, generated CLIENTS may not.
|
|
265
|
-
* Each package's `pubspec.yaml`
|
|
266
|
-
*
|
|
265
|
+
* Each package's `pubspec.yaml` keeps tenant-owned versioning/publishing metadata and custom
|
|
266
|
+
* dependencies; Nomos-owned framework dependency floors are reconciled on every compile.
|
|
267
|
+
* Barrels + `lib/src/generated/` are generated-owned and rewritten every compile.
|
|
267
268
|
* Requires `support: "package"` (vendored mode contradicts pub-package layering).
|
|
268
269
|
*/
|
|
269
270
|
readonly packages?: {
|
|
@@ -1298,12 +1299,27 @@ function emitDart(
|
|
|
1298
1299
|
// ── the LAYERED-PACKAGES Dart emit (dart.packages: blast-radius isolation) ────────
|
|
1299
1300
|
|
|
1300
1301
|
/** The pure-core support import generated TYPES files carry in layered-packages mode. */
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
]
|
|
1302
|
+
const dartTypesImportFor = (generatedSource: string): string => {
|
|
1303
|
+
const lines = [
|
|
1304
|
+
`import 'package:nomos_types/nomos_types.dart';`,
|
|
1305
|
+
...(generatedSource.includes("NmsInternalNomosRefs.")
|
|
1306
|
+
? [`import 'package:nomos_types/src/nomos_ref.dart' show NmsInternalNomosRefs;`]
|
|
1307
|
+
: []),
|
|
1308
|
+
...(generatedSource.includes("NmsInternalForeignRefs.")
|
|
1309
|
+
? [`import 'package:nomos_types/src/foreign_ref.dart' show NmsInternalForeignRefs;`]
|
|
1310
|
+
: []),
|
|
1311
|
+
];
|
|
1312
|
+
const invitationHelpers = [
|
|
1313
|
+
...(generatedSource.includes("NmsInternalInvitations.") ? ["NmsInternalInvitations"] : []),
|
|
1314
|
+
...(generatedSource.includes("NmsInternalRecipients.") ? ["NmsInternalRecipients"] : []),
|
|
1315
|
+
];
|
|
1316
|
+
if (invitationHelpers.length > 0) {
|
|
1317
|
+
lines.push(
|
|
1318
|
+
`import 'package:nomos_types/src/invitation.dart' show ${invitationHelpers.join(", ")};`,
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
return lines.join("\n");
|
|
1322
|
+
};
|
|
1307
1323
|
/** Version constraints the scaffolded pubspecs pin — move together with the Dart packages' releases.
|
|
1308
1324
|
* Caret on 0.x is EXACT-MINOR (`^0.45.0` = `<0.46.0`), so these must track the CURRENT release, not
|
|
1309
1325
|
* just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
|
|
@@ -1313,8 +1329,8 @@ const NOMOS_TYPES_DEP = "^0.8.0";
|
|
|
1313
1329
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1314
1330
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1315
1331
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1316
|
-
const NOMOS_CLIENT_DEP = "^0.64.
|
|
1317
|
-
const NOMOS_FLUTTER_DEP = "^0.63.
|
|
1332
|
+
const NOMOS_CLIENT_DEP = "^0.64.1";
|
|
1333
|
+
const NOMOS_FLUTTER_DEP = "^0.63.1";
|
|
1318
1334
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1319
1335
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1320
1336
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -1690,13 +1706,59 @@ function cursedNameHints(declared: ReadonlySet<string>, names: Readonly<Record<s
|
|
|
1690
1706
|
.sort((a, b) => b.length - a.length);
|
|
1691
1707
|
}
|
|
1692
1708
|
|
|
1693
|
-
/** Scaffold a pubspec
|
|
1694
|
-
*
|
|
1695
|
-
|
|
1709
|
+
/** Scaffold a pubspec when missing, otherwise reconcile only the dependencies owned by Nomos.
|
|
1710
|
+
* Tenant versioning, publishing metadata and application dependencies remain untouched, while a
|
|
1711
|
+
* regenerated client can never silently retain an obsolete framework floor. */
|
|
1712
|
+
function reconcilePubspec(
|
|
1713
|
+
pkgDir: string,
|
|
1714
|
+
yaml: string,
|
|
1715
|
+
managedDependencies: Readonly<Record<string, string>> = {},
|
|
1716
|
+
): boolean {
|
|
1696
1717
|
const p = path.join(pkgDir, "pubspec.yaml");
|
|
1697
|
-
if (existsSync(p))
|
|
1698
|
-
|
|
1699
|
-
|
|
1718
|
+
if (!existsSync(p)) {
|
|
1719
|
+
mkdirSync(pkgDir, { recursive: true });
|
|
1720
|
+
writeFileSync(p, yaml, "utf8");
|
|
1721
|
+
return true;
|
|
1722
|
+
}
|
|
1723
|
+
if (Object.keys(managedDependencies).length === 0) return false;
|
|
1724
|
+
|
|
1725
|
+
const source = readFileSync(p, "utf8");
|
|
1726
|
+
const trailingNewline = source.endsWith("\n");
|
|
1727
|
+
const lines = source.split(/\r?\n/);
|
|
1728
|
+
if (trailingNewline) lines.pop();
|
|
1729
|
+
let dependenciesStart = lines.findIndex((line) => /^dependencies:\s*(?:#.*)?$/.test(line));
|
|
1730
|
+
if (dependenciesStart < 0) {
|
|
1731
|
+
const before = lines.findIndex((line) => /^dev_dependencies:\s*(?:#.*)?$/.test(line));
|
|
1732
|
+
dependenciesStart = before < 0 ? lines.length : before;
|
|
1733
|
+
lines.splice(dependenciesStart, 0, "dependencies:");
|
|
1734
|
+
}
|
|
1735
|
+
let dependenciesEnd = dependenciesStart + 1;
|
|
1736
|
+
while (dependenciesEnd < lines.length) {
|
|
1737
|
+
const line = lines[dependenciesEnd]!;
|
|
1738
|
+
if (line.trim() !== "" && !/^\s/.test(line) && !/^\s*#/.test(line)) break;
|
|
1739
|
+
dependenciesEnd += 1;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
for (const [name, constraint] of Object.entries(managedDependencies)) {
|
|
1743
|
+
const prefix = ` ${name}:`;
|
|
1744
|
+
const existing = lines.findIndex(
|
|
1745
|
+
(line, index) => index > dependenciesStart && index < dependenciesEnd && line.startsWith(prefix),
|
|
1746
|
+
);
|
|
1747
|
+
const replacement = ` ${name}: ${constraint}`;
|
|
1748
|
+
if (existing < 0) {
|
|
1749
|
+
lines.splice(dependenciesEnd, 0, replacement);
|
|
1750
|
+
dependenciesEnd += 1;
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
let nestedEnd = existing + 1;
|
|
1754
|
+
while (nestedEnd < dependenciesEnd && /^(?: {4,}|\t)/.test(lines[nestedEnd]!)) nestedEnd += 1;
|
|
1755
|
+
lines.splice(existing, nestedEnd - existing, replacement);
|
|
1756
|
+
dependenciesEnd -= nestedEnd - existing - 1;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
const next = `${lines.join("\n")}${trailingNewline ? "\n" : ""}`;
|
|
1760
|
+
if (next === source) return false;
|
|
1761
|
+
writeFileSync(p, next, "utf8");
|
|
1700
1762
|
return true;
|
|
1701
1763
|
}
|
|
1702
1764
|
|
|
@@ -1705,7 +1767,8 @@ function scaffoldPubspec(pkgDir: string, yaml: string): boolean {
|
|
|
1705
1767
|
* `<types>/` (pure data, depends only on `nomos_types`), `<client>/` (effectful runtime clients,
|
|
1706
1768
|
* depends on the types package + `nomos_client`), and optionally `<test>/` (the headless-harness
|
|
1707
1769
|
* surface). Generated code lives under each package's `lib/src/generated/` (wiped per compile)
|
|
1708
|
-
* behind a generated-owned barrel `lib/<pkg>.dart`;
|
|
1770
|
+
* behind a generated-owned barrel `lib/<pkg>.dart`; pubspec framework floors are compiler-owned while
|
|
1771
|
+
* tenant versioning, publishing metadata and application dependencies survive regeneration.
|
|
1709
1772
|
* TYPES-FILE PURITY is enforced fail-closed: a generated types file may import ONLY the
|
|
1710
1773
|
* `nomos_types` barrel and the relative `nomos_framework.dart` — any runtime import is a compile
|
|
1711
1774
|
* failure here, not a review comment downstream.
|
|
@@ -1760,7 +1823,7 @@ function emitDartPackages(
|
|
|
1760
1823
|
if (!src.includes(DART_PACKAGE_IMPORT)) {
|
|
1761
1824
|
fail(`generated Dart for '${fileBase}' does not carry the expected support import — codegen_dart header drifted`);
|
|
1762
1825
|
}
|
|
1763
|
-
const pure = src.replace(DART_PACKAGE_IMPORT,
|
|
1826
|
+
const pure = src.replace(DART_PACKAGE_IMPORT, dartTypesImportFor(src));
|
|
1764
1827
|
// PURITY, fail-closed: only the nomos_types barrel, the framework file, and SIBLING generated type files
|
|
1765
1828
|
// (cross-domain foreignRef targets) may be imported. Runtime/bridge surfaces (nomos_client) stay out.
|
|
1766
1829
|
for (const line of pure.split("\n")) {
|
|
@@ -1954,7 +2017,7 @@ function emitDartPackages(
|
|
|
1954
2017
|
const typesBarrelPath = path.join(pkgDir(packages.types), "lib", `${packages.types}.dart`);
|
|
1955
2018
|
writeFileSync(typesBarrelPath, typesBarrel.join("\n"), "utf8");
|
|
1956
2019
|
written.push(typesBarrelPath);
|
|
1957
|
-
|
|
2020
|
+
reconcilePubspec(
|
|
1958
2021
|
pkgDir(packages.types),
|
|
1959
2022
|
[
|
|
1960
2023
|
`name: ${packages.types}`,
|
|
@@ -1967,6 +2030,7 @@ function emitDartPackages(
|
|
|
1967
2030
|
` nomos_types: ${NOMOS_TYPES_DEP}`,
|
|
1968
2031
|
``,
|
|
1969
2032
|
].join("\n"),
|
|
2033
|
+
{ nomos_types: NOMOS_TYPES_DEP },
|
|
1970
2034
|
);
|
|
1971
2035
|
|
|
1972
2036
|
// ── the CLIENT package — effectful runtime, depends on types + nomos_client ──
|
|
@@ -2110,7 +2174,7 @@ function emitDartPackages(
|
|
|
2110
2174
|
const clientBarrelPath = path.join(pkgDir(packages.client), "lib", `${packages.client}.dart`);
|
|
2111
2175
|
writeFileSync(clientBarrelPath, clientBarrel.join("\n"), "utf8");
|
|
2112
2176
|
written.push(clientBarrelPath);
|
|
2113
|
-
|
|
2177
|
+
reconcilePubspec(
|
|
2114
2178
|
pkgDir(packages.client),
|
|
2115
2179
|
[
|
|
2116
2180
|
`name: ${packages.client}`,
|
|
@@ -2129,6 +2193,7 @@ function emitDartPackages(
|
|
|
2129
2193
|
` path: ../${packages.types} # co-emitted sibling; replace with a hosted constraint when publishing`,
|
|
2130
2194
|
``,
|
|
2131
2195
|
].join("\n"),
|
|
2196
|
+
{ nomos_client: NOMOS_CLIENT_DEP, nomos_flutter: NOMOS_FLUTTER_DEP },
|
|
2132
2197
|
);
|
|
2133
2198
|
|
|
2134
2199
|
// ── the TEST package (optional) — generated business scenarios, test-only dependency ──
|
|
@@ -2195,7 +2260,7 @@ function emitDartPackages(
|
|
|
2195
2260
|
const testBarrelPath = path.join(pkgDir(packages.test), "lib", `${packages.test}.dart`);
|
|
2196
2261
|
writeFileSync(testBarrelPath, testBarrel.join("\n"), "utf8");
|
|
2197
2262
|
written.push(testBarrelPath);
|
|
2198
|
-
|
|
2263
|
+
reconcilePubspec(
|
|
2199
2264
|
pkgDir(packages.test),
|
|
2200
2265
|
[
|
|
2201
2266
|
`name: ${packages.test}`,
|
|
@@ -2217,6 +2282,7 @@ function emitDartPackages(
|
|
|
2217
2282
|
` - lib/${genRel}/${embeddedDeployName}`,
|
|
2218
2283
|
``,
|
|
2219
2284
|
].join("\n"),
|
|
2285
|
+
{ nomos_client: NOMOS_CLIENT_DEP },
|
|
2220
2286
|
);
|
|
2221
2287
|
}
|
|
2222
2288
|
|
|
@@ -2270,7 +2336,7 @@ function emitDartPackages(
|
|
|
2270
2336
|
const compatBarrelPath = path.join(pkgDir(compatName), "lib", `${compatName}.dart`);
|
|
2271
2337
|
writeFileSync(compatBarrelPath, compatBarrel.join("\n"), "utf8");
|
|
2272
2338
|
written.push(compatBarrelPath);
|
|
2273
|
-
|
|
2339
|
+
reconcilePubspec(
|
|
2274
2340
|
pkgDir(compatName),
|
|
2275
2341
|
[
|
|
2276
2342
|
`name: ${compatName}`,
|
|
@@ -2341,6 +2407,8 @@ async function main(): Promise<void> {
|
|
|
2341
2407
|
`${configValue.application} — compiled Nomos application composition`,
|
|
2342
2408
|
`stage: ${emitted.outputPath}`,
|
|
2343
2409
|
`assets: ${emitted.referencedAssets.length} local content-addressed reference(s)`,
|
|
2410
|
+
`deployment: ${emitted.deploymentPackages.filter((p) => p.placement === "reference").length} serving reference package(s), ` +
|
|
2411
|
+
`${emitted.deploymentPackages.filter((p) => p.placement === "payload").length} deferred payload package(s) — derived from the stage`,
|
|
2344
2412
|
`runtime configuration: USD (the authoring .mjs does not travel)`,
|
|
2345
2413
|
].join("\n"));
|
|
2346
2414
|
return;
|
|
@@ -2897,6 +2965,44 @@ async function main(): Promise<void> {
|
|
|
2897
2965
|
] as const) {
|
|
2898
2966
|
const canonical = (composed as unknown as Record<string, unknown>)[field];
|
|
2899
2967
|
if (!Array.isArray(canonical)) continue;
|
|
2968
|
+
// A package-level language may be declared in a different source module from the
|
|
2969
|
+
// aggregate its generated read indexes (workspaceSharing is the canonical case).
|
|
2970
|
+
// Single-module composition then discovers the read in the language module even though
|
|
2971
|
+
// that layer cannot type it. Move every targeted declaration beside its aggregate schema
|
|
2972
|
+
// before filling genuinely missing package opinions. This changes no composed law: one
|
|
2973
|
+
// declaration id still contributes once; it merely gives every authored USD layer a
|
|
2974
|
+
// self-contained, valid read projection.
|
|
2975
|
+
for (const value of canonical) {
|
|
2976
|
+
if (value === null || typeof value !== "object") continue;
|
|
2977
|
+
const id = (value as { id?: unknown }).id;
|
|
2978
|
+
if (typeof id !== "string") continue;
|
|
2979
|
+
const declarationTarget = field === "queries"
|
|
2980
|
+
? (value as { returns?: unknown }).returns
|
|
2981
|
+
: (["counts", "spatials", "sums", "mins", "maxes", "deriveds", "combineds"] as const)
|
|
2982
|
+
.includes(field as never)
|
|
2983
|
+
? (value as { of?: unknown }).of
|
|
2984
|
+
: undefined;
|
|
2985
|
+
if (typeof declarationTarget !== "string") continue;
|
|
2986
|
+
const ownerIndex = inputs.findIndex((input) =>
|
|
2987
|
+
input.module.aggregates.some((aggregate) => aggregate.id === declarationTarget));
|
|
2988
|
+
if (ownerIndex < 0) continue;
|
|
2989
|
+
for (const [inputIndex, input] of inputs.entries()) {
|
|
2990
|
+
if (inputIndex === ownerIndex) continue;
|
|
2991
|
+
const module = input.module as DomainModule & Record<string, unknown>;
|
|
2992
|
+
const existing = module[field];
|
|
2993
|
+
if (!Array.isArray(existing)) continue;
|
|
2994
|
+
(module as Record<string, unknown>)[field] = existing.filter((candidate) =>
|
|
2995
|
+
candidate === null || typeof candidate !== "object" ||
|
|
2996
|
+
(candidate as { id?: unknown }).id !== id);
|
|
2997
|
+
}
|
|
2998
|
+
const owner = inputs[ownerIndex]!.module as DomainModule & Record<string, unknown>;
|
|
2999
|
+
const ownerValues = Array.isArray(owner[field]) ? owner[field] as unknown[] : [];
|
|
3000
|
+
if (!ownerValues.some((candidate) =>
|
|
3001
|
+
candidate !== null && typeof candidate === "object" &&
|
|
3002
|
+
(candidate as { id?: unknown }).id === id)) {
|
|
3003
|
+
(owner as Record<string, unknown>)[field] = [...ownerValues, value];
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
2900
3006
|
const present = new Set(inputs.flatMap((input) => {
|
|
2901
3007
|
const values = (input.module as unknown as Record<string, unknown>)[field];
|
|
2902
3008
|
return Array.isArray(values)
|
|
@@ -2956,7 +3062,11 @@ async function main(): Promise<void> {
|
|
|
2956
3062
|
? (JSON.parse(readFileSync(lockPath, "utf8")) as StableIdLock)
|
|
2957
3063
|
: undefined;
|
|
2958
3064
|
const inferredRenames: { domain: string; renames: InferredRename[] }[] = [];
|
|
2959
|
-
|
|
3065
|
+
// The lock is project-wide, not package-local. Several package configs commonly live beside one
|
|
3066
|
+
// application stage; compiling one must not erase the stable identity of domains owned by its siblings.
|
|
3067
|
+
// Current package domains are replaced below, while absent domains remain only as continuity metadata
|
|
3068
|
+
// and never leak into this package's interface, law, clients or deployment.
|
|
3069
|
+
const nextLock: StableIdLock = { v: 1, domains: { ...(priorLock?.domains ?? {}) } };
|
|
2960
3070
|
for (const mod of domainModules) {
|
|
2961
3071
|
const identityName = mod.name;
|
|
2962
3072
|
// The CURRENT canonical aggregate schemas (a pre-continuity manifest pass —
|
|
@@ -3052,7 +3162,9 @@ async function main(): Promise<void> {
|
|
|
3052
3162
|
};
|
|
3053
3163
|
}
|
|
3054
3164
|
// Persist the lockfile (commit it with the law: it is how a rename keeps its
|
|
3055
|
-
// identity across clean clones).
|
|
3165
|
+
// identity across clean clones). Existing project-domain order is retained, so once sibling
|
|
3166
|
+
// packages are registered, recompiling them in any order rewrites the same bytes without a
|
|
3167
|
+
// giant formatting-only diff in an established lock.
|
|
3056
3168
|
writeFileSync(lockPath, JSON.stringify(nextLock, null, 2) + "\n", "utf8");
|
|
3057
3169
|
|
|
3058
3170
|
// ── THE STRUCTURAL INTERFACE (#72) — directives (payload shapes) + aggregates (read
|
|
@@ -3434,24 +3546,21 @@ async function main(): Promise<void> {
|
|
|
3434
3546
|
for (const ld of layeredDomains) {
|
|
3435
3547
|
const authored = emitLayeredDomain(ld.key, ld.inputs, { applyBusinessCorrectionLayer: false });
|
|
3436
3548
|
const canonicalLayer = canonicalLayers.get(`/Nomos/${ld.key}`);
|
|
3437
|
-
//
|
|
3438
|
-
//
|
|
3439
|
-
//
|
|
3440
|
-
//
|
|
3441
|
-
//
|
|
3442
|
-
//
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
const
|
|
3446
|
-
if (!needsPackagePrelude) return emitted;
|
|
3549
|
+
// Authored layers carry structural declarations (aggregate/directive/query prims).
|
|
3550
|
+
// Domain-level `nomos:*` values are the compiler's closure over the complete package
|
|
3551
|
+
// (relation namespaces, read projections, invariant gates, contracts and language
|
|
3552
|
+
// consequences), so placing partial versions on source layers lets weaker generated
|
|
3553
|
+
// facts show through. Strip those derived attributes from source layers and append one
|
|
3554
|
+
// explicit strongest closure layer. This is ordinary OpenUSD composition: source
|
|
3555
|
+
// opinions remain inspectable and the compiler-owned overlay is the one canonical law.
|
|
3556
|
+
const structuralLayers = authored.map((emitted, index) => {
|
|
3557
|
+
const { path: layerPath, prims, queries, deriveds, combineds } = emitted.layer;
|
|
3447
3558
|
const layer: UsdLayer = {
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
...(
|
|
3453
|
-
? { nomosContracts: canonicalLayer.nomosContracts }
|
|
3454
|
-
: {}),
|
|
3559
|
+
path: layerPath,
|
|
3560
|
+
prims,
|
|
3561
|
+
...(queries !== undefined ? { queries } : {}),
|
|
3562
|
+
...(deriveds !== undefined ? { deriveds } : {}),
|
|
3563
|
+
...(combineds !== undefined ? { combineds } : {}),
|
|
3455
3564
|
};
|
|
3456
3565
|
return {
|
|
3457
3566
|
...emitted,
|
|
@@ -3459,6 +3568,20 @@ async function main(): Promise<void> {
|
|
|
3459
3568
|
usda: usdaLayerText(ld.key, emitted.name, layer, ld.inputs[index]!.module, new Set()),
|
|
3460
3569
|
};
|
|
3461
3570
|
});
|
|
3571
|
+
const closureLayers = canonicalLayer === undefined
|
|
3572
|
+
? []
|
|
3573
|
+
: (() => {
|
|
3574
|
+
const layer: UsdLayer = canonicalLayer;
|
|
3575
|
+
const name = "__nomos_package_closure";
|
|
3576
|
+
const canonicalModule = domainModules.find((module) =>
|
|
3577
|
+
(module.domain ?? module.name) === ld.key);
|
|
3578
|
+
return [{
|
|
3579
|
+
name,
|
|
3580
|
+
layer,
|
|
3581
|
+
usda: usdaLayerText(ld.key, name, layer, canonicalModule, new Set()),
|
|
3582
|
+
}];
|
|
3583
|
+
})();
|
|
3584
|
+
const emittedLayers = [...structuralLayers, ...closureLayers];
|
|
3462
3585
|
for (const emitted of emittedLayers) {
|
|
3463
3586
|
const base = `${safe(ld.key)}--${safe(emitted.name)}`;
|
|
3464
3587
|
pendingWrites.push({ path: path.join(layersDir, `${base}.usda`), text: emitted.usda });
|
|
@@ -3485,7 +3608,7 @@ async function main(): Promise<void> {
|
|
|
3485
3608
|
}
|
|
3486
3609
|
for (const w of pendingWrites) writeFileSync(w.path, w.text, "utf8");
|
|
3487
3610
|
layeredRootPath = path.join(outDir, `${cfg.name}.layered.usda`);
|
|
3488
|
-
writeFileSync(layeredRootPath, layeredRootUsda(relPaths
|
|
3611
|
+
writeFileSync(layeredRootPath, layeredRootUsda(relPaths), "utf8");
|
|
3489
3612
|
layeredFileCount = relPaths.length;
|
|
3490
3613
|
|
|
3491
3614
|
const usdcat = resolveUsdcat();
|
|
@@ -3634,6 +3757,9 @@ async function main(): Promise<void> {
|
|
|
3634
3757
|
// agent who simply cats the build output.
|
|
3635
3758
|
const summary = {
|
|
3636
3759
|
package: cfg.name,
|
|
3760
|
+
// Binds the deploy body to the published package asset consumed by an application stage. This is the
|
|
3761
|
+
// content identity of the composed source layers; domainHash below is the runtime package-law hash.
|
|
3762
|
+
sourceIdentity: composedUsdIdentity!,
|
|
3637
3763
|
domainHash,
|
|
3638
3764
|
domains: presentationDomainModules.map((m) => ({
|
|
3639
3765
|
domain: m.domain ?? m.name,
|
package/src/framework/home.ts
CHANGED
|
@@ -81,7 +81,11 @@ export const ReceivingGroup = aggregate("NomosInternalReceivingGroup7c1f5e", {
|
|
|
81
81
|
workspaceName: t.string().merge(Lww),
|
|
82
82
|
/** ISO-8601, caller-stamped (determinism — no clock in the plan). */
|
|
83
83
|
bornAt: t.string().merge(Lww),
|
|
84
|
-
|
|
84
|
+
// The group's genesis seats its controller in OWNED_READ_RELATION below. Declaring that same capability
|
|
85
|
+
// here makes the law's coarse pack gate recognise the seat, so a newly enrolled device can clone its own
|
|
86
|
+
// receiving group before any tenant records or tenant-authored grants exist. This remains ordinary,
|
|
87
|
+
// replayable Zanzibar state: revocation removes the seat; there is no kernel-level founder bypass.
|
|
88
|
+
}, { cap: 1 }).readableBy(OWNED_READ_RELATION);
|
|
85
89
|
|
|
86
90
|
/**
|
|
87
91
|
* The framework-owned device lifecycle policy for one receiving group. This is deliberately data, not a
|
|
@@ -177,6 +181,7 @@ export const seedReceivingGroup = directive("nomosInternalSeedReceivingGroup7c1f
|
|
|
177
181
|
export const receivingGroupNameBindsPrincipal = workspaceInvariant("ReceivingGroupNameBindsPrincipal")
|
|
178
182
|
.on("nomosInternalSeedReceivingGroup7c1f5e")
|
|
179
183
|
.reads(() => [refAs("group", "NomosInternalReceivingGroup7c1f5e", RECEIVING_GROUP_ID)])
|
|
184
|
+
.declassify("the receiving-group address is a public hash of its stable principal; refusing a mismatch reveals no private field")
|
|
180
185
|
.assert(({ group }) => {
|
|
181
186
|
const g = (group ?? {}) as { principal?: string; workspaceName?: string };
|
|
182
187
|
const expected = RECEIVING_GROUP_NAME_PREFIX
|
|
@@ -196,6 +201,7 @@ const receivingGroupPolicyRefs = () => [
|
|
|
196
201
|
export const receivingGroupRejectsKeylessAdditionalDevice = workspaceInvariant("ReceivingGroupRejectsKeylessAdditionalDevice")
|
|
197
202
|
.on("enrollFirstDevice")
|
|
198
203
|
.reads(receivingGroupPolicyRefs)
|
|
204
|
+
.declassify("device enrollment deliberately reveals that this receiving group has already completed first-device bootstrap")
|
|
199
205
|
.assert(({ group }) => typeof group?.principal !== "string" || group.principal.length === 0
|
|
200
206
|
? { accept: true }
|
|
201
207
|
: { reject: "receiving-group-additional-device-requires-current-device-or-guardian" });
|
package/src/law_check.ts
CHANGED
|
@@ -409,6 +409,14 @@ function unreadPayloadFindings(domain: string, dir: Directive<unknown>, probe: P
|
|
|
409
409
|
// the gate object from a named payload id field (per-record authz). Excluding it keeps a real
|
|
410
410
|
// gate-consumed field from being flagged dead (the todo-pro `shareTodo.listId` case).
|
|
411
411
|
const gateConsumed = new Set<string>();
|
|
412
|
+
// Compiler-owned law may deliberately consume proof material only in an invariant. The
|
|
413
|
+
// plan must not persist a signature merely to satisfy this lint, and the tenant must not
|
|
414
|
+
// receive a warning about private security machinery. This is internal lowering metadata,
|
|
415
|
+
// not an authored escape hatch.
|
|
416
|
+
for (const field of (dir as { nomosInvariantConsumedPayloadFields?: readonly string[] })
|
|
417
|
+
.nomosInvariantConsumedPayloadFields ?? []) {
|
|
418
|
+
gateConsumed.add(field);
|
|
419
|
+
}
|
|
412
420
|
const objectFromField = (dir as { requiresRelationGate?: { objectFrom?: { idField?: string } } })
|
|
413
421
|
.requiresRelationGate?.objectFrom?.idField;
|
|
414
422
|
if (typeof objectFromField === "string") gateConsumed.add(objectFromField);
|
|
@@ -721,7 +729,7 @@ function dottedQueryKeyFindings(mod: DomainModule, aggById: ReadonlyMap<string,
|
|
|
721
729
|
// admits before a null is lawful (t.optional / t.clearable / t.nullable / t.json({null})). These
|
|
722
730
|
// twins pre-fire the three refusals the gate would raise: (a) a payload field that ADMITS null with
|
|
723
731
|
// no null-admitting receiver; (b) a null-admitting field used where a null is structurally illegal
|
|
724
|
-
// (authority gate,
|
|
732
|
+
// (authority gate, spatial index, read key); (c) a plan op writing literal null into a
|
|
725
733
|
// field that never declared null a meaning. All PAYLOAD-INDEPENDENT (pure declarations) ⇒ REFUSE.
|
|
726
734
|
|
|
727
735
|
/** The verbatim null-semantics guidance (`null_semantics.md`) — the `fix` on twin N-a/N-c. */
|
|
@@ -835,7 +843,10 @@ function nullBanFindings(mod: DomainModule, aggById: ReadonlyMap<string, Aggrega
|
|
|
835
843
|
});
|
|
836
844
|
};
|
|
837
845
|
|
|
838
|
-
// Facet gates (authority/standing)
|
|
846
|
+
// Facet gates (authority/standing) — a null in these breaks the very thing they decide. A plain
|
|
847
|
+
// typed ref is an association, not structural identity: clearing `asset.spaceId`, for example,
|
|
848
|
+
// means the asset is no longer in a space. If a ref participates in a query/group/spatial key the
|
|
849
|
+
// corresponding structural check below still refuses it.
|
|
839
850
|
for (const agg of mod.aggregates) {
|
|
840
851
|
for (const [name, f0] of Object.entries(agg.fields)) {
|
|
841
852
|
const f = f0 as Field;
|
|
@@ -843,9 +854,6 @@ function nullBanFindings(mod: DomainModule, aggById: ReadonlyMap<string, Aggrega
|
|
|
843
854
|
if (f.facet === "authority" || f.facet === "standing") {
|
|
844
855
|
ban(agg.id, name, "an authority/standing gate", "drop the null semantic — an authority sink must always resolve to a subject.");
|
|
845
856
|
}
|
|
846
|
-
if (f.kind === "ref") {
|
|
847
|
-
ban(agg.id, name, "an identity reference", "an identity ref must always point somewhere — model absence with a separate presence flag, not a null ref.");
|
|
848
|
-
}
|
|
849
857
|
}
|
|
850
858
|
}
|
|
851
859
|
// Read keys / group-bys / spatial indexes — a null key row is a silent dead index.
|