@githolon/dsl 0.114.0 → 0.115.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.
Files changed (67) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/dart/CHANGELOG.md +40 -0
  3. package/dart/lib/src/application_framework_calls.dart +7 -1
  4. package/dart/lib/src/console_enrolment.dart +7 -0
  5. package/dart/lib/src/console_http_io.dart +22 -15
  6. package/dart/lib/src/dev_cloud.dart +62 -0
  7. package/dart/lib/src/operation_receipts.dart +74 -7
  8. package/dart/lib/src/reception/dart_reception.dart +26 -6
  9. package/dart/lib/src/runner/node-entry.mjs +7129 -4424
  10. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/console_enrolment_test.dart +47 -0
  14. package/dart/test/model_adoption_test.dart +49 -0
  15. package/dart/test/runtime_projections_test.dart +41 -0
  16. package/dart_types/CHANGELOG.md +24 -0
  17. package/dart_types/lib/src/approved_model.dart +78 -1
  18. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +528 -2
  19. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +24 -0
  20. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +1897 -112
  21. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +98 -0
  22. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  23. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  24. package/dart_types/pubspec.yaml +1 -1
  25. package/package.json +4 -2
  26. package/src/aggregate.ts +93 -0
  27. package/src/anonymous_value_interning.ts +205 -0
  28. package/src/baseline.ts +27 -0
  29. package/src/baseline_export.ts +432 -0
  30. package/src/baseline_import.ts +312 -0
  31. package/src/baseline_usda.ts +186 -0
  32. package/src/birth_key_rule.ts +472 -0
  33. package/src/births.ts +207 -24
  34. package/src/build_provenance.ts +10 -0
  35. package/src/business_birth.ts +50 -4
  36. package/src/check_package_main.ts +50 -6
  37. package/src/codegen_call_proto.ts +5 -18
  38. package/src/codegen_dart.ts +95 -3
  39. package/src/codegen_dart_client.ts +52 -6
  40. package/src/codegen_dart_facade.ts +57 -35
  41. package/src/codegen_dart_framework_calls.ts +20 -1
  42. package/src/codegen_proof.ts +5 -2
  43. package/src/codegen_result_proto.ts +8 -6
  44. package/src/codegen_result_usda.ts +4 -0
  45. package/src/codegen_ts.ts +109 -10
  46. package/src/compatibility_graph.ts +41 -4
  47. package/src/compatibility_usda.ts +11 -1
  48. package/src/compile_package_main.ts +214 -51
  49. package/src/composed_wire_identity.ts +24 -0
  50. package/src/directive.ts +6 -0
  51. package/src/domain_requirements.ts +75 -0
  52. package/src/engine_javascript.ts +12 -6
  53. package/src/fields.ts +109 -0
  54. package/src/identity_usda.ts +77 -51
  55. package/src/index.ts +25 -1
  56. package/src/intent.ts +5 -0
  57. package/src/law_graph.ts +142 -1
  58. package/src/law_graph_usda.ts +25 -141
  59. package/src/law_layer_prims.ts +199 -0
  60. package/src/law_unit_selection.ts +45 -0
  61. package/src/law_version.ts +107 -0
  62. package/src/package_config.ts +44 -0
  63. package/src/package_dependencies.ts +8 -0
  64. package/src/value_object.ts +6 -3
  65. package/src/wire.ts +2 -0
  66. package/src/wire_paths.ts +17 -0
  67. package/src/workspace_routing.ts +1 -1
@@ -1,3 +1,4 @@
1
+ import { collectPackageSources, findBirthKeyWrites, formatBirthKeyWriteWarnings } from "./birth_key_rule.js";
1
2
  import type { Directive } from "./directive.js";
2
3
  // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
3
4
  // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
@@ -46,6 +47,7 @@ import type { Directive } from "./directive.js";
46
47
  import { execFile, execFileSync } from "node:child_process";
47
48
  import { promisify } from "node:util";
48
49
  import { checkOperationContracts, deriveInterfaceOffer } from "./operation_contract_check.js";
50
+ import { lawVersionClaim, type LawVersionClaim } from "./law_version.js";
49
51
  import { compilerIntermediateCacheRoot, compilerSourceName, compilerSourceRoot } from "./compiler_cache.js";
50
52
  import {
51
53
  APPLICATION_CONTRACT_MODULE,
@@ -80,6 +82,7 @@ import ts from "typescript";
80
82
  import type { AggregateHandle } from "./aggregate.js";
81
83
  import {
82
84
  generateDartDomain,
85
+ dartBrandNamesForModule,
83
86
  generateDartContracts,
84
87
  generateDartActivityRegistry,
85
88
  collectActivityDescriptors,
@@ -97,6 +100,8 @@ import {
97
100
  } from "./evolution_files.js";
98
101
  import { assertEvolutionModuleExports } from "./evolution.js";
99
102
  import { readHistoricalIdentityFrontier } from "./identity_frontier.js";
103
+ import { parseBaselineUsda, type ReleasedBaseline } from "./baseline_usda.js";
104
+ import { BaselineRefusal, assertBaselineRetained, importBaseline, verifyBaselinePin } from "./baseline_import.js";
100
105
  import {
101
106
  dartBridgeClientOperationDirectiveIds,
102
107
  generateDartDomainClient,
@@ -266,6 +271,7 @@ import {
266
271
  splitLawUnits,
267
272
  } from "./law_units.js";
268
273
  import { lawUnitSetIdentityWith } from "./law_unit_set_identity.js";
274
+ import { domainRequirementsOf, type DomainUnitRequirement } from "./domain_requirements.js";
269
275
  import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
270
276
  import {
271
277
  currentEvolveLaw,
@@ -624,6 +630,7 @@ const compareLogicalPath = (left: string, right: string): number =>
624
630
  * inventories the USDZ/Git tree and re-hashes every logically named layer. */
625
631
  function emitLawClosure(
626
632
  packageName: string,
633
+ claimedVersion: LawVersionClaim | undefined,
627
634
  exportedDomains: readonly string[],
628
635
  runtimeDependencies: readonly RuntimePackageDependency[],
629
636
  domainAssets: readonly EmittedLawClosureAsset[],
@@ -670,6 +677,7 @@ function emitLawClosure(
670
677
  "package.usda",
671
678
  packageContractUsda({
672
679
  packageName,
680
+ ...(claimedVersion === undefined ? {} : { claimedVersion }),
673
681
  exportedDomains,
674
682
  runtimeDependencies,
675
683
  })
@@ -1265,6 +1273,7 @@ function closeComposedBusinessBirthPresentation(
1265
1273
  aggregateId?: unknown;
1266
1274
  rootId?: unknown;
1267
1275
  namespace?: unknown;
1276
+ keyMode?: unknown;
1268
1277
  keyField?: unknown;
1269
1278
  };
1270
1279
  type BirthDirective = DomainModule["directives"][number] & {
@@ -1278,14 +1287,15 @@ function closeComposedBusinessBirthPresentation(
1278
1287
  typeof birth.aggregateId !== "string" ||
1279
1288
  typeof birth.rootId !== "string" ||
1280
1289
  typeof birth.namespace !== "string" ||
1281
- typeof birth.keyField !== "string"
1290
+ (typeof birth.keyField !== "string" && birth.keyMode !== "minted")
1282
1291
  )
1283
1292
  return undefined;
1284
1293
  return [
1285
1294
  birth.aggregateId,
1286
1295
  birth.rootId,
1287
1296
  birth.namespace,
1288
- birth.keyField,
1297
+ // A minted birth has no key field; its identity here is the minted mode itself.
1298
+ typeof birth.keyField === "string" ? birth.keyField : "\u0001minted",
1289
1299
  ].join("\u0000");
1290
1300
  };
1291
1301
 
@@ -1335,6 +1345,8 @@ interface DartSharedSymbols {
1335
1345
  readonly frameworkDirIds: ReadonlySet<string>;
1336
1346
  readonly frameworkQueryIds: ReadonlySet<string>;
1337
1347
  readonly sharedVoNames: ReadonlySet<string>;
1348
+ /** Branded-id extension types used by ≥2 domains — declared ONCE in the framework file. */
1349
+ readonly sharedBrandNames: ReadonlySet<string>;
1338
1350
  readonly hasFrameworkSymbols: boolean;
1339
1351
  /** The synthetic module whose emit IS `nomos_framework.dart` — undefined when nothing is shared. */
1340
1352
  readonly frameworkMod: DomainModule | undefined;
@@ -1496,11 +1508,23 @@ function collectDartSharedSymbols(
1496
1508
  const sharedVos = [...sharedVoNames]
1497
1509
  .sort()
1498
1510
  .map((name) => voDescriptorByName.get(name)!);
1511
+ // TYPED IDS: a branded id used by two domains (a parent's business-birth payload and the child's key
1512
+ // field, typically) would otherwise declare one extension type per file and make the barrel ambiguous.
1513
+ const brandDomains = new Map<string, number>();
1514
+ for (const mod of modules) {
1515
+ for (const brand of dartBrandNamesForModule(mod)) {
1516
+ brandDomains.set(brand, (brandDomains.get(brand) ?? 0) + 1);
1517
+ }
1518
+ }
1519
+ const sharedBrandNames: ReadonlySet<string> = new Set(
1520
+ [...brandDomains.entries()].filter(([, n]) => n > 1).map(([name]) => name).sort()
1521
+ );
1499
1522
  const hasFrameworkSymbols =
1500
1523
  frameworkAggs.length > 0 ||
1501
1524
  frameworkDirs.length > 0 ||
1502
1525
  frameworkQueries.length > 0 ||
1503
- sharedVos.length > 0;
1526
+ sharedVos.length > 0 ||
1527
+ sharedBrandNames.size > 0;
1504
1528
  const frameworkMod: DomainModule | undefined = hasFrameworkSymbols
1505
1529
  ? {
1506
1530
  name: FRAMEWORK_FILE,
@@ -1518,6 +1542,7 @@ function collectDartSharedSymbols(
1518
1542
  frameworkDirIds: new Set(frameworkDirById.keys()),
1519
1543
  frameworkQueryIds: new Set(frameworkQueryById.keys()),
1520
1544
  sharedVoNames,
1545
+ sharedBrandNames,
1521
1546
  hasFrameworkSymbols,
1522
1547
  frameworkMod,
1523
1548
  };
@@ -1535,6 +1560,7 @@ function domainDedupOpts(
1535
1560
  readonly skipDirectiveIds: Set<string>;
1536
1561
  readonly skipQueryIds: Set<string>;
1537
1562
  readonly skipValueObjectNames: Set<string>;
1563
+ readonly skipBrandNames: Set<string>;
1538
1564
  };
1539
1565
  readonly foldsFramework: boolean;
1540
1566
  } {
@@ -1562,11 +1588,17 @@ function domainDedupOpts(
1562
1588
  .filter((d) => shared.sharedVoNames.has(d.name))
1563
1589
  .map((d) => d.name)
1564
1590
  );
1591
+ const skipBrandNames = new Set(
1592
+ [...dartBrandNamesForModule(modForDart)].filter((name) =>
1593
+ shared.sharedBrandNames.has(name)
1594
+ )
1595
+ );
1565
1596
  const foldsFramework =
1566
1597
  skipAggregateIds.size > 0 ||
1567
1598
  skipDirectiveIds.size > 0 ||
1568
1599
  skipQueryIds.size > 0 ||
1569
- skipValueObjectNames.size > 0;
1600
+ skipValueObjectNames.size > 0 ||
1601
+ skipBrandNames.size > 0;
1570
1602
  return {
1571
1603
  modForDart,
1572
1604
  skips: {
@@ -1574,6 +1606,7 @@ function domainDedupOpts(
1574
1606
  skipDirectiveIds,
1575
1607
  skipQueryIds,
1576
1608
  skipValueObjectNames,
1609
+ skipBrandNames,
1577
1610
  },
1578
1611
  foldsFramework,
1579
1612
  };
@@ -2146,6 +2179,8 @@ function emitDartOperationProto(
2146
2179
  interface CompiledLawIdentities {
2147
2180
  readonly unitSet: string;
2148
2181
  readonly packageSha256: string;
2182
+ /** One requirement per application domain (CAP-666): its unit hash and its generated requirement identity. */
2183
+ readonly domainRequirements: readonly DomainUnitRequirement[];
2149
2184
  }
2150
2185
 
2151
2186
  function emitDart(
@@ -2227,7 +2262,7 @@ function emitDart(
2227
2262
  ? []
2228
2263
  : [{ module, graph, paths }];
2229
2264
  }),
2230
- { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256 }
2265
+ { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256, domainRequirements: law.domainRequirements }
2231
2266
  );
2232
2267
  if (applicationFrameworkCallsSource !== undefined) {
2233
2268
  const applicationFrameworkCallsPath = path.join(
@@ -2249,6 +2284,7 @@ function emitDart(
2249
2284
  FRAMEWORK_FILE,
2250
2285
  generateDartDomain(shared.frameworkMod, {
2251
2286
  additionalValueObjects: shared.frameworkMod.valueObjects ?? [],
2287
+ extraBrandNames: [...shared.sharedBrandNames],
2252
2288
  })
2253
2289
  );
2254
2290
  }
@@ -2542,12 +2578,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
2542
2578
  * just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
2543
2579
  // 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
2544
2580
  // the pendingConflicts read rows.
2545
- const NOMOS_TYPES_DEP = "^0.13.0";
2581
+ const NOMOS_TYPES_DEP = "^0.14.0";
2546
2582
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
2547
2583
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
2548
2584
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
2549
- const NOMOS_CLIENT_DEP = "^0.68.0";
2550
- const NOMOS_FLUTTER_DEP = "^0.67.0";
2585
+ const NOMOS_CLIENT_DEP = "^0.69.0";
2586
+ const NOMOS_FLUTTER_DEP = "^0.68.0";
2551
2587
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
2552
2588
  // package pubspecs — a release without this bump fails the suite, so tenants never
2553
2589
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -3390,6 +3426,7 @@ function emitDartPackages(
3390
3426
  FRAMEWORK_FILE,
3391
3427
  generateDartDomain(shared.frameworkMod, {
3392
3428
  additionalValueObjects: shared.frameworkMod.valueObjects ?? [],
3429
+ extraBrandNames: [...shared.sharedBrandNames],
3393
3430
  })
3394
3431
  );
3395
3432
  }
@@ -3680,7 +3717,7 @@ function emitDartPackages(
3680
3717
  ? []
3681
3718
  : [{ module, graph, paths }];
3682
3719
  }),
3683
- { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256 }
3720
+ { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256, domainRequirements: law.domainRequirements }
3684
3721
  );
3685
3722
  const workspaceAccessReadImports = new Map<string, string>();
3686
3723
  for (const module of operationProtoModules) {
@@ -4205,6 +4242,7 @@ async function main(): Promise<void> {
4205
4242
  "--preflight",
4206
4243
  "--inspect-sqlite",
4207
4244
  "--inspect-law",
4245
+ "--publishable",
4208
4246
  ]);
4209
4247
  for (const flag of argv.filter((arg) => arg.startsWith("--"))) {
4210
4248
  if (supportedFlags.has(flag)) continue;
@@ -4240,6 +4278,9 @@ async function main(): Promise<void> {
4240
4278
  // `dart analyze` on the generated Dart package after emission and fails the compile on red.
4241
4279
  const analyzeFlag =
4242
4280
  argv.includes("--analyze") || process.env.NOMOS_ANALYZE === "1";
4281
+ // A PUBLISHABLE compile (architecture/domain_release_lifecycle.md, step 1) must start from the
4282
+ // baseline exported from released custody: it refuses without a pinned `nomos.baseline.usda`.
4283
+ const publishableFlag = argv.includes("--publishable");
4243
4284
  const configArg = argv.find((a) => !a.startsWith("--")) ?? "nomos.project.ts";
4244
4285
  const configPath = path.resolve(process.cwd(), configArg);
4245
4286
  if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
@@ -4324,6 +4365,39 @@ async function main(): Promise<void> {
4324
4365
  "nomos.project.ts no longer accepts 'layered': every law closure is layered, with no alternate compile lane"
4325
4366
  );
4326
4367
  }
4368
+ // ── THE RELEASED BASELINE (CAP-660) ──────────────────────────────────────────────────────────
4369
+ // `nomos.project.ts` pins the baseline by content address. Verify the bytes are the pinned ones
4370
+ // and read it now, before any identity is touched; the import and the retention proof follow.
4371
+ const baselineRefused = (error: unknown): never => {
4372
+ if (error instanceof BaselineRefusal) fail(`[${error.code}] ${error.message}`);
4373
+ throw error;
4374
+ };
4375
+ let releasedBaseline: ReleasedBaseline | undefined;
4376
+ let releasedBaselineSha256: string | undefined;
4377
+ if (cfg.baseline === undefined) {
4378
+ if (publishableFlag) {
4379
+ fail(
4380
+ "[baseline-required] a publishable compile must name the released baseline it extends: " +
4381
+ `run \`githolon baseline export --units build/${cfg.name}.units\` (or --release <id>) against the release ` +
4382
+ "you are extending, commit nomos.baseline.usda, and pin it in nomos.project.ts as " +
4383
+ "baseline: { artifact: \"./nomos.baseline.usda\", sha256: \"<printed sha256>\" }"
4384
+ );
4385
+ }
4386
+ } else {
4387
+ const baselinePath = path.resolve(cfgDir, cfg.baseline.artifact);
4388
+ if (!existsSync(baselinePath)) {
4389
+ fail(`[baseline-unreadable] pinned baseline not found: ${baselinePath}`);
4390
+ }
4391
+ const baselineText = readFileSync(baselinePath, "utf8");
4392
+ try {
4393
+ verifyBaselinePin(baselineText, cfg.baseline);
4394
+ releasedBaseline = parseBaselineUsda(baselineText);
4395
+ } catch (error) {
4396
+ if (error instanceof BaselineRefusal) baselineRefused(error);
4397
+ fail(`[baseline-unreadable] ${cfg.baseline.artifact}: ${(error as Error).message}`);
4398
+ }
4399
+ releasedBaselineSha256 = cfg.baseline.sha256;
4400
+ }
4327
4401
  let sourceTopologyDiagnostics: ReturnType<
4328
4402
  typeof assertPackageFeatureTopology
4329
4403
  > = [];
@@ -4395,6 +4469,7 @@ async function main(): Promise<void> {
4395
4469
  if (cfg.rootPart !== undefined) {
4396
4470
  applicationBirthPolicyDomains.push(APPLICATION_HOME_DOMAIN);
4397
4471
  }
4472
+ const authoredDomainModules: Mod[] = [];
4398
4473
  for (const domain of cfg.domains) {
4399
4474
  let authored: Mod = {};
4400
4475
  for (const rel of domain.modules) {
@@ -4420,10 +4495,32 @@ async function main(): Promise<void> {
4420
4495
  }
4421
4496
  }
4422
4497
  const expanded = expandBusinessBirthExports(authored, domain.key);
4498
+ authoredDomainModules.push(authored);
4423
4499
  if (directivesOf(expanded).some((dir) => dir.birthsChild === true)) {
4424
4500
  applicationBirthPolicyDomains.push(domain.key);
4425
4501
  }
4426
4502
  }
4503
+ // A workspace name is fixed at birth, and the framework enforces it at the kernel: every field-keyed
4504
+ // birth's key is write-once (aggregate.ts BIRTH_KEY_WRITE_ONCE_CODE). Statically WARN at each source line
4505
+ // that writes a key outside the birth in a way that rule will refuse, or that keys another instance
4506
+ // (CAP-804; architecture/choosing_a_birth_key.md). Advisory only — the runtime rule is the guarantee.
4507
+ {
4508
+ const birthKeyWarnings = findBirthKeyWrites(
4509
+ authoredDomainModules,
4510
+ collectPackageSources(
4511
+ cfg.domains.flatMap((domain) => domain.modules.map((rel) => path.resolve(cfgDir, rel))),
4512
+ cfgDir
4513
+ ),
4514
+ { root: cfgDir }
4515
+ );
4516
+ if (birthKeyWarnings.length > 0) {
4517
+ console.warn(
4518
+ `nomos-compile: ${birthKeyWarnings.length} birth-key write warning(s)`
4519
+ );
4520
+ for (const line of formatBirthKeyWriteWarnings(birthKeyWarnings))
4521
+ console.warn(line);
4522
+ }
4523
+ }
4427
4524
  stage("domain discovery imports");
4428
4525
  if (
4429
4526
  cfg.application?.homeGenesis !== undefined &&
@@ -5617,12 +5714,36 @@ async function main(): Promise<void> {
5617
5714
  ? parseIdentityUsda(readFileSync(lockPath, "utf8"))
5618
5715
  : undefined;
5619
5716
  const historicalFrontier = readHistoricalIdentityFrontier(lockPath);
5717
+ // Outside Git custody (a clean export, a CI checkout without history) the released baseline IS
5718
+ // the evolution frontier: it carries every released aggregate and field by sid, so a removal
5719
+ // without a declared retirement still refuses. It carries no value contracts, so it never
5720
+ // produces a false in-place refusal.
5620
5721
  const historicalLock: CompilerIdentityModel | undefined =
5621
5722
  historicalFrontier === undefined
5622
- ? undefined
5723
+ ? releasedBaseline?.continuity
5623
5724
  : parseIdentityUsda(historicalFrontier.source);
5624
5725
  const inferredRenames: { domain: string; renames: InferredRename[] }[] = [];
5625
5726
  let nextIdentity = identityRegistryFromModel(priorLock);
5727
+ if (releasedBaseline !== undefined) {
5728
+ // Verify the ledger against released custody and import what it lacks, so a clean checkout
5729
+ // allocates the released sids and wire keys rather than fresh ones, and a diverged branch
5730
+ // refuses with the coordinate named (baseline_import.ts).
5731
+ try {
5732
+ const imported = importBaseline(
5733
+ nextIdentity,
5734
+ releasedBaseline,
5735
+ cfg.domains.flatMap((domain) => [domain.key, ...(domain.name === undefined ? [] : [domain.name])])
5736
+ );
5737
+ nextIdentity = imported.registry;
5738
+ console.log(
5739
+ `baseline: ${releasedBaseline.release === "none" ? "unreleased units" : `release ${releasedBaseline.release}`} ` +
5740
+ `(law ${releasedBaseline.unitSet}) — ${imported.verified} verified, ${imported.imported.length} identit${imported.imported.length === 1 ? "y" : "ies"} ` +
5741
+ `and ${imported.allocated.length} wire allocation(s) imported${imported.relabelled.length === 0 ? "" : `, ${imported.relabelled.length} released rename(s) carried`}`
5742
+ );
5743
+ } catch (error) {
5744
+ baselineRefused(error);
5745
+ }
5746
+ }
5626
5747
  const typedLawGraphs = new Map<string, LawGraph>();
5627
5748
  // The lock is project-wide, not package-local. Several package configs commonly live beside one
5628
5749
  // application stage; compiling one must not erase the stable identity of domains owned by its siblings.
@@ -5832,6 +5953,18 @@ async function main(): Promise<void> {
5832
5953
  return;
5833
5954
  }
5834
5955
 
5956
+ // ── BASELINE RETENTION PROOF (CAP-660) ───────────────────────────────────────────
5957
+ // Every released identity still resolves to its released sid and every released wire key is still
5958
+ // the canonical allocation. Reconciliation never rewrites an allocation, so this holds by
5959
+ // construction; proving it here keeps "the candidate extends the release" a checked fact.
5960
+ if (releasedBaseline !== undefined) {
5961
+ try {
5962
+ assertBaselineRetained(nextIdentity, releasedBaseline);
5963
+ } catch (error) {
5964
+ baselineRefused(error);
5965
+ }
5966
+ }
5967
+
5835
5968
  // ── ORDINARY-COMPILE EVOLUTION GATE ──────────────────────────────────────────────
5836
5969
  // Agents reliably run `compile`; they do not reliably discover an optional migration command.
5837
5970
  // Compare the candidate against the committed identity frontier here, after stable-id
@@ -5910,6 +6043,44 @@ async function main(): Promise<void> {
5910
6043
  // giant formatting-only diff in an established lock.
5911
6044
  writeFileSync(lockPath, serializeIdentityUsda(nextLock), "utf8");
5912
6045
 
6046
+ // USDA is rendered directly from the typed graph. The final closure writer below publishes these
6047
+ // exact texts under their logical paths; no parallel layer directory or JSON reconstruction exists.
6048
+ const v3Closure = [...typedLawGraphs.values()];
6049
+ const v3DomainAssets: EmittedLawClosureAsset[] = [];
6050
+ const v3DomainRoots: string[] = [];
6051
+ const v3LawPathByNode = new Map<LawGraph["nodes"][number], string>();
6052
+ const v3CompiledGraphs = new Map<LawGraph, ReturnType<typeof compileSparseLawGraphUsda>>();
6053
+ const typedDomainIdentityByName = new Map<string, string>();
6054
+ const typedLawPathsByName = new Map<string, ReadonlyMap<string, string>>();
6055
+ for (const [domain, graph] of typedLawGraphs) {
6056
+ const safeDomain = domain.replace(/[^A-Za-z0-9._-]/g, "_");
6057
+ const logicalPath = `domains/${safeDomain}.usda`;
6058
+ const compiledLayer = compileSparseLawGraphUsda(
6059
+ graph,
6060
+ logicalPath,
6061
+ v3Closure
6062
+ );
6063
+ v3CompiledGraphs.set(graph, compiledLayer);
6064
+ const layerText = compiledLayer.text;
6065
+ const sha256 = sha256HexUtf8(layerText);
6066
+ v3DomainAssets.push({
6067
+ logicalPath,
6068
+ sha256,
6069
+ utf8: layerText,
6070
+ });
6071
+ const domainRoot = compiledLayer.paths.get(graph.domain.entity.id);
6072
+ if (domainRoot === undefined) {
6073
+ fail(`domain '${domain}' did not emit its canonical OpenUSD root path`);
6074
+ }
6075
+ v3DomainRoots.push(domainRoot);
6076
+ for (const node of graph.nodes) {
6077
+ const lawPath = compiledLayer.paths.get(node.entity.id);
6078
+ if (lawPath !== undefined) v3LawPathByNode.set(node, lawPath);
6079
+ }
6080
+ typedDomainIdentityByName.set(domain, sha256);
6081
+ typedLawPathsByName.set(domain, compiledLayer.paths);
6082
+ }
6083
+
5913
6084
  // ── THE STRUCTURAL INTERFACE (#72) — directives (payload shapes) + aggregates (read
5914
6085
  // shapes, sid-keyed), keyed by DOMAIN. The SAME object is (a) baked into the generated
5915
6086
  // clients as their requirement and (b) staged on the deploy as the workspace's offer, so
@@ -5924,7 +6095,12 @@ async function main(): Promise<void> {
5924
6095
  `compiler surface invariant: domain '${key}' has no typed USDA graph`
5925
6096
  );
5926
6097
  }
5927
- const offeredInterface = domainInterfaceFromLawGraph(graph);
6098
+ // Nested record members carry the allocation the generated codecs encode them under, which
6099
+ // follows the composed layer's canonical prim (CAP-681).
6100
+ const offeredInterface = domainInterfaceFromLawGraph(
6101
+ graph,
6102
+ typedLawPathsByName.get(mod.name)
6103
+ );
5928
6104
  const di = publicClientInterface([mod], { [key]: offeredInterface })[key]!;
5929
6105
  const graphRoutes = graph.nodes
5930
6106
  .filter((node) => node.kind === "intent" && node.public)
@@ -6017,44 +6193,6 @@ async function main(): Promise<void> {
6017
6193
  }
6018
6194
  }
6019
6195
 
6020
- // USDA is rendered directly from the typed graph. The final closure writer below publishes these
6021
- // exact texts under their logical paths; no parallel layer directory or JSON reconstruction exists.
6022
- const v3Closure = [...typedLawGraphs.values()];
6023
- const v3DomainAssets: EmittedLawClosureAsset[] = [];
6024
- const v3DomainRoots: string[] = [];
6025
- const v3LawPathByNode = new Map<LawGraph["nodes"][number], string>();
6026
- const v3CompiledGraphs = new Map<LawGraph, ReturnType<typeof compileSparseLawGraphUsda>>();
6027
- const typedDomainIdentityByName = new Map<string, string>();
6028
- const typedLawPathsByName = new Map<string, ReadonlyMap<string, string>>();
6029
- for (const [domain, graph] of typedLawGraphs) {
6030
- const safeDomain = domain.replace(/[^A-Za-z0-9._-]/g, "_");
6031
- const logicalPath = `domains/${safeDomain}.usda`;
6032
- const compiledLayer = compileSparseLawGraphUsda(
6033
- graph,
6034
- logicalPath,
6035
- v3Closure
6036
- );
6037
- v3CompiledGraphs.set(graph, compiledLayer);
6038
- const layerText = compiledLayer.text;
6039
- const sha256 = sha256HexUtf8(layerText);
6040
- v3DomainAssets.push({
6041
- logicalPath,
6042
- sha256,
6043
- utf8: layerText,
6044
- });
6045
- const domainRoot = compiledLayer.paths.get(graph.domain.entity.id);
6046
- if (domainRoot === undefined) {
6047
- fail(`domain '${domain}' did not emit its canonical OpenUSD root path`);
6048
- }
6049
- v3DomainRoots.push(domainRoot);
6050
- for (const node of graph.nodes) {
6051
- const lawPath = compiledLayer.paths.get(node.entity.id);
6052
- if (lawPath !== undefined) v3LawPathByNode.set(node, lawPath);
6053
- }
6054
- typedDomainIdentityByName.set(domain, sha256);
6055
- typedLawPathsByName.set(domain, compiledLayer.paths);
6056
- }
6057
-
6058
6196
  // Index the transient graph once. Both projections follow the same nominal edges, including
6059
6197
  // transitive cross-domain dependencies. Per-command selection also follows reverse guard edges;
6060
6198
  // the retained package command projection remains the compatibility consumer until the runtime
@@ -7729,6 +7867,7 @@ async function main(): Promise<void> {
7729
7867
  };
7730
7868
  let lawClosure = emitLawClosure(
7731
7869
  cfg.name,
7870
+ cfg.version === undefined ? undefined : lawVersionClaim(cfg.version),
7732
7871
  domainModules.map((module) => module.domain ?? module.name),
7733
7872
  runtimeDependencies,
7734
7873
  v3DomainAssets,
@@ -7890,6 +8029,7 @@ async function main(): Promise<void> {
7890
8029
  };
7891
8030
  lawClosure = emitLawClosure(
7892
8031
  cfg.name,
8032
+ cfg.version === undefined ? undefined : lawVersionClaim(cfg.version),
7893
8033
  domainModules.map((module) => module.domain ?? module.name),
7894
8034
  runtimeDependencies,
7895
8035
  v3DomainAssets,
@@ -7941,6 +8081,10 @@ async function main(): Promise<void> {
7941
8081
  // THE LAW IDENTITY (CAP-773): the unit set identity of the units below, the kernel's own definition
7942
8082
  // (law_unit_set_identity.ts). Releases, generated clients and qualification name the law by it.
7943
8083
  let unitSetIdentity: string;
8084
+ // THE PER-DOMAIN REQUIREMENTS (CAP-666): each application domain's unit hash beside the identity of its own
8085
+ // compatibility requirement, derived from the units just split and the interfaces just emitted. Written into
8086
+ // no law or package (the era rule); the generated clients, the approved model and the inventory carry them.
8087
+ let domainRequirements: readonly DomainUnitRequirement[];
7944
8088
  {
7945
8089
  const lawUnits = (() => {
7946
8090
  try {
@@ -7968,6 +8112,7 @@ async function main(): Promise<void> {
7968
8112
  );
7969
8113
  // The law identity on its own line, for publishers and gates that need it and nothing else.
7970
8114
  writeFileSync(path.join(outDir, `${cfg.name}.units.sha256`), `${unitSetIdentity}\n`, "utf8");
8115
+ domainRequirements = domainRequirementsOf(lawUnits, packageInterface, sha256HexUtf8);
7971
8116
  }
7972
8117
  const layeredRootPath = path.join(
7973
8118
  lawClosureAssetsPath,
@@ -8163,6 +8308,7 @@ async function main(): Promise<void> {
8163
8308
  const summaryTxt = [
8164
8309
  `${cfg.name} — compiled Nomos domain package`,
8165
8310
  `law (unit set): ${unitSetIdentity} # THE law identity: ${cfg.name}.units/, as releases, clients and qualification name it (${cfg.name}.units.sha256)`,
8311
+ `claimed version: ${cfg.version ?? "none"} # orders versions of law '${cfg.name}' (nomos.project.ts version); covered by the unit set identity`,
8166
8312
  `package closure: ${domainHash} # package — not what targets serve: this compiler's closure of ${cfg.name}.law.usdz (${cfg.name}.law.sha256)`,
8167
8313
  `served law: githolon domains status <ws> # each target's kernel composes the units into its own closure (it depends on that kernel's composer)`,
8168
8314
  `law transport: ${path.basename(
@@ -8186,6 +8332,11 @@ async function main(): Promise<void> {
8186
8332
  d.counts.join(", ") || "—"
8187
8333
  } sums: ${d.sums.join(", ") || "—"}`,
8188
8334
  ]),
8335
+ ...(releasedBaseline === undefined
8336
+ ? [`baseline: NONE # not a publishable build; githolon compile --publishable needs a pinned nomos.baseline.usda`]
8337
+ : [
8338
+ `baseline: ${releasedBaselineSha256} # extends ${releasedBaseline.release === "none" ? "unreleased units" : `release ${releasedBaseline.release}`}, law ${releasedBaseline.unitSet} (${releasedBaseline.source})`,
8339
+ ]),
8189
8340
  `deploy: githolon deploy <ws> --file ${cfg.name}.law.usdz`,
8190
8341
  packageAssetPath !== undefined
8191
8342
  ? `package asset: ${path.basename(
@@ -8242,6 +8393,16 @@ async function main(): Promise<void> {
8242
8393
  id: "https://github.com/Captain-App/nomos2",
8243
8394
  version: dslVersion,
8244
8395
  },
8396
+ ...(releasedBaseline === undefined || releasedBaselineSha256 === undefined
8397
+ ? {}
8398
+ : {
8399
+ baseline: {
8400
+ sha256: releasedBaselineSha256,
8401
+ source: releasedBaseline.source,
8402
+ release: releasedBaseline.release,
8403
+ unitSet: releasedBaseline.unitSet,
8404
+ },
8405
+ }),
8245
8406
  });
8246
8407
  writeFileSync(
8247
8408
  path.join(outDir, `${cfg.name}.provenance.json`),
@@ -8271,6 +8432,7 @@ async function main(): Promise<void> {
8271
8432
  packageName: cfg.name,
8272
8433
  // THE LAW the generated clients name: the unit set (CAP-773), never the package closure.
8273
8434
  domainHash: unitSetIdentity,
8435
+ domainRequirements,
8274
8436
  lawAssetName: `${cfg.name}.law.usdz`,
8275
8437
  interface: packageInterface,
8276
8438
  kernelContracts,
@@ -8860,7 +9022,7 @@ async function main(): Promise<void> {
8860
9022
  const r = emitDartPackages(
8861
9023
  presentationDomainModules,
8862
9024
  dartOut,
8863
- { unitSet: unitSetIdentity, packageSha256: domainHash },
9025
+ { unitSet: unitSetIdentity, packageSha256: domainHash, domainRequirements },
8864
9026
  typedLawGraphs,
8865
9027
  typedLawPathsByName,
8866
9028
  packageInterface,
@@ -8888,7 +9050,7 @@ async function main(): Promise<void> {
8888
9050
  dartFiles = emitDart(
8889
9051
  presentationDomainModules,
8890
9052
  dartOut,
8891
- { unitSet: unitSetIdentity, packageSha256: domainHash },
9053
+ { unitSet: unitSetIdentity, packageSha256: domainHash, domainRequirements },
8892
9054
  typedLawGraphs,
8893
9055
  typedLawPathsByName,
8894
9056
  packageInterface,
@@ -9272,6 +9434,7 @@ async function main(): Promise<void> {
9272
9434
  const rel = (p: string) => path.relative(process.cwd(), p);
9273
9435
  console.log(`nomos-compile: ${cfg.name}`);
9274
9436
  console.log(` law unit set ${unitSetIdentity} (${rel(path.join(outDir, `${cfg.name}.units`))})`);
9437
+ if (cfg.version !== undefined) console.log(` version ${cfg.name}@${cfg.version} (claimed; covered by the unit set identity)`);
9275
9438
  console.log(
9276
9439
  ` package ${domainHash} — package — not what targets serve (${rel(lawUsdzPath)}, ${lawUsdz.byteLength} transport bytes)`
9277
9440
  );
@@ -0,0 +1,24 @@
1
+ // NOMOS — The wire identity of an operation field under OpenUSD composition.
2
+ //
3
+ // Node-free on purpose: the compatibility graph reaches this from the law bundle (the release law's
4
+ // plans bundle for a neutral platform), where `codegen_call_proto.ts` cannot live (it mints stable
5
+ // ids through `node:crypto`). Both the generated codecs and the compatibility requirement derive a
6
+ // nested member's allocation here, so the two can never disagree (CAP-681).
7
+
8
+ /**
9
+ * OpenUSD composition may share structurally identical anonymous records between
10
+ * operation contracts. `compileSparseLawGraphUsda` records that sharing by
11
+ * redirecting each duplicate field path to the canonical field prim. The wire
12
+ * identity must follow that composed path too; using the duplicate graph-local
13
+ * SID makes generated clients speak a different Protobuf schema from the law the
14
+ * kernel actually installs.
15
+ */
16
+ export function composedWireIdentity(
17
+ paths: ReadonlyMap<string, string>,
18
+ entity: { readonly id: string; readonly sid: string; readonly wireKey?: string },
19
+ ): string {
20
+ if (entity.wireKey !== undefined) return entity.wireKey;
21
+ const path = paths.get(entity.id);
22
+ const canonical = path?.match(/\/(?:Fields\/f_|Variants\/v_)([0-9a-f]{16})$/i)?.[1];
23
+ return canonical ?? entity.sid;
24
+ }
package/src/directive.ts CHANGED
@@ -536,6 +536,12 @@ export interface Directive<P = unknown> extends AuthenticatedMember {
536
536
  * `attestationAuthorizedGate`/`.attestationAuthorized()` naming convention; the declaration is the noun.)
537
537
  */
538
538
  readonly birthsChild?: boolean;
539
+ /** THE TYPED BIRTH KEY of a business birth (`birth(Aggregate, spec)`): the key's typed-id family, brand and
540
+ * the name's namespace. Lowered to the `nomos:birthKeyType` / `nomos:birthKeyBrand` /
541
+ * `nomos:birthKeyNamespace` era keys on the birthing intent; the kernel then requires every carried recipe
542
+ * of this intent to carry `nomos:birthKey`, checks its shape, and re-derives the child's name from it.
543
+ * Absent on every other directive (and on all historical law), so those chains replay unchanged. */
544
+ readonly birthKey?: import("./births.js").BirthKeyDeclaration;
539
545
  /** Additional ordinary offers which the born workspace's typed law requires in genesis.
540
546
  * This is compiler/runtime metadata attached only to a birthing directive. It contains no
541
547
  * payload or fallback value: the kernel checks that the re-derived recipe invokes each named