@githolon/dsl 0.113.4 → 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 (103) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/compile_package.mjs +23 -3
  3. package/compile_proof.d.mts +7 -0
  4. package/compile_proof.mjs +38 -3
  5. package/dart/CHANGELOG.md +60 -0
  6. package/dart/lib/src/application_framework_calls.dart +16 -6
  7. package/dart/lib/src/console_enrolment.dart +7 -0
  8. package/dart/lib/src/console_http_io.dart +22 -15
  9. package/dart/lib/src/dev_cloud.dart +62 -0
  10. package/dart/lib/src/generated_call.dart +14 -3
  11. package/dart/lib/src/nms_generated_runtime.dart +7 -0
  12. package/dart/lib/src/node_runner_transport.dart +77 -8
  13. package/dart/lib/src/nomos_trace.dart +107 -0
  14. package/dart/lib/src/operation_receipts.dart +74 -7
  15. package/dart/lib/src/reception/dart_reception.dart +174 -17
  16. package/dart/lib/src/rows_watch.dart +390 -0
  17. package/dart/lib/src/runner/node-entry.mjs +7678 -2860
  18. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  19. package/dart/lib/src/sync_status_projection.dart +94 -1
  20. package/dart/lib/src/sync_status_reducer.dart +5 -2
  21. package/dart/lib/src/test_harness.dart +107 -41
  22. package/dart/lib/src/testing/test_peer.dart +107 -99
  23. package/dart/lib/src/testing/test_peer_host.dart +305 -0
  24. package/dart/lib/src/typed_watch_registry.dart +26 -3
  25. package/dart/pubspec.lock +1 -1
  26. package/dart/pubspec.yaml +2 -2
  27. package/dart/test/background_model_adoption_test.dart +1 -0
  28. package/dart/test/console_enrolment_test.dart +47 -0
  29. package/dart/test/custodian_warming_test.dart +105 -0
  30. package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
  31. package/dart/test/kernel_author_after_read_test.dart +6 -3
  32. package/dart/test/kernel_fence_test.dart +4 -1
  33. package/dart/test/model_adoption_test.dart +66 -0
  34. package/dart/test/nomos_trace_test.dart +91 -0
  35. package/dart/test/own_offer_watch_order_test.dart +90 -0
  36. package/dart/test/receiving_group_open_test.dart +4 -8
  37. package/dart/test/replica_readiness_test.dart +14 -4
  38. package/dart/test/rows_watch_test.dart +331 -0
  39. package/dart/test/runtime_projections_test.dart +41 -0
  40. package/dart/test/test_peer_host_selection_test.dart +49 -0
  41. package/dart_types/CHANGELOG.md +39 -0
  42. package/dart_types/lib/src/approved_model.dart +100 -6
  43. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +1063 -3
  44. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +89 -0
  45. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
  46. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +2272 -130
  47. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
  48. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +118 -2
  49. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
  50. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
  51. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  52. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
  53. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  54. package/dart_types/pubspec.yaml +1 -1
  55. package/package.json +6 -2
  56. package/src/aggregate.ts +93 -0
  57. package/src/anonymous_value_interning.ts +205 -0
  58. package/src/baseline.ts +27 -0
  59. package/src/baseline_export.ts +432 -0
  60. package/src/baseline_import.ts +312 -0
  61. package/src/baseline_usda.ts +186 -0
  62. package/src/birth_key_rule.ts +472 -0
  63. package/src/births.ts +207 -24
  64. package/src/build_provenance.ts +17 -2
  65. package/src/business_birth.ts +50 -4
  66. package/src/check_package_main.ts +50 -6
  67. package/src/codegen_call_proto.ts +5 -18
  68. package/src/codegen_dart.ts +95 -3
  69. package/src/codegen_dart_client.ts +73 -15
  70. package/src/codegen_dart_facade.ts +58 -35
  71. package/src/codegen_dart_framework_calls.ts +32 -10
  72. package/src/codegen_dart_scenario.ts +7 -0
  73. package/src/codegen_proof.ts +5 -2
  74. package/src/codegen_result_proto.ts +8 -6
  75. package/src/codegen_result_usda.ts +4 -0
  76. package/src/codegen_ts.ts +135 -34
  77. package/src/compatibility_graph.ts +41 -4
  78. package/src/compatibility_usda.ts +11 -1
  79. package/src/compile_package_main.ts +272 -105
  80. package/src/composed_wire_identity.ts +24 -0
  81. package/src/directive.ts +6 -0
  82. package/src/domain_requirements.ts +75 -0
  83. package/src/engine_entry.ts +4 -48
  84. package/src/engine_javascript.ts +12 -6
  85. package/src/fields.ts +109 -0
  86. package/src/identity_usda.ts +77 -51
  87. package/src/index.ts +25 -1
  88. package/src/intent.ts +5 -0
  89. package/src/law_graph.ts +142 -1
  90. package/src/law_graph_usda.ts +25 -141
  91. package/src/law_layer_prims.ts +199 -0
  92. package/src/law_unit_selection.ts +45 -0
  93. package/src/law_unit_set_identity.ts +44 -0
  94. package/src/law_version.ts +107 -0
  95. package/src/package_config.ts +44 -0
  96. package/src/package_dependencies.ts +8 -0
  97. package/src/query/compile.ts +0 -0
  98. package/src/release_inspection.ts +24 -0
  99. package/src/release_publication.ts +298 -0
  100. package/src/value_object.ts +6 -3
  101. package/src/wire.ts +2 -0
  102. package/src/wire_paths.ts +17 -0
  103. package/src/workspace_routing.ts +1 -1
@@ -1,4 +1,5 @@
1
1
  import { admitsNull, isGitObjectIdSchema, isSha256DigestSchema, type Field } from "./fields.js";
2
+ import { brandedIdMetaOf } from "./value_object.js";
2
3
  import type {
3
4
  LawAnonymousValueFieldNode,
4
5
  LawAnonymousValueTypeNode,
@@ -125,12 +126,13 @@ function authoredFieldValue(
125
126
  return `foreignRef<${target}>(${holon}, ${JSON.stringify(field.foreignRef.targetAggregateId)}, ${selected}, ${foreignWorkspace})`;
126
127
  }
127
128
  switch (field.kind) {
128
- case "string":
129
- return isSha256DigestSchema(field.zod)
130
- ? `sha256Digest(${selected})`
131
- : isGitObjectIdSchema(field.zod)
132
- ? `gitObjectId(${selected})`
133
- : selected;
129
+ case "string": {
130
+ if (isSha256DigestSchema(field.zod)) return `sha256Digest(${selected})`;
131
+ if (isGitObjectIdSchema(field.zod)) return `gitObjectId(${selected})`;
132
+ // A typed id field (`t.brandedId(Name)` / `t.uuid()`): the wire string IS the id (nominal only).
133
+ const brand = brandedIdMetaOf(field.zod as never);
134
+ return brand === undefined ? selected : `(${selected} as ${brand})`;
135
+ }
134
136
  case "position": return selected;
135
137
  case "ref":
136
138
  return field.foreignRef === undefined &&
@@ -15,6 +15,7 @@ import {
15
15
  zodKind,
16
16
  zodObjectShape,
17
17
  } from "./zod_introspection.js";
18
+ import { brandedIdMetaOf } from "./value_object.js";
18
19
 
19
20
  export interface TsResultField {
20
21
  readonly name: string;
@@ -391,6 +392,9 @@ const decode = (field: Field, expression: string): string => {
391
392
  ? `sha256Digest(_nmsUsdaStringValue(${expression}))`
392
393
  : isGitObjectIdSchema(field.zod)
393
394
  ? `gitObjectId(_nmsUsdaStringValue(${expression}))`
395
+ : brandedIdMetaOf(field.zod as z.ZodTypeAny) !== undefined
396
+ // A typed id field: the wire string IS the id; the brand is nominal only.
397
+ ? `(_nmsUsdaStringValue(${expression}) as ${brandedIdMetaOf(field.zod as z.ZodTypeAny)})`
394
398
  : `_nmsUsdaStringValue(${expression})`;
395
399
  case "position":
396
400
  return `_nmsUsdaStringValue(${expression})`;
package/src/codegen_ts.ts CHANGED
@@ -35,6 +35,7 @@ import { refusalContracts, tsRefusalDecoder, tsRefusalDecoders } from "./codegen
35
35
  * Like every emitter here: NO FALLBACK. An unsupported zod shape throws at compile
36
36
  * time, never degrades to `any` silently.
37
37
  */
38
+ import { assertDomainRequirements, type DomainUnitRequirement } from "./domain_requirements.js";
38
39
  import type { z } from "zod";
39
40
  import { generateTsWorkspaceAccessRead } from "./codegen_workspace_access.js";
40
41
 
@@ -45,6 +46,7 @@ import {
45
46
  OWNED_PREMINTED_RECORDS_PAYLOAD_FIELD,
46
47
  type AggregateHandle,
47
48
  } from "./aggregate.js";
49
+ import { BUSINESS_BIRTH_KEY_PAYLOAD_FIELD } from "./births.js";
48
50
  import {
49
51
  isGitObjectIdSchema,
50
52
  isSha256DigestSchema,
@@ -86,6 +88,7 @@ import {
86
88
  collectValueObjectDescriptors,
87
89
  idOfMetaOf,
88
90
  idOfTargetKindOf,
91
+ brandedIdMetaOf,
89
92
  refOfMetaOf,
90
93
  versionedRefOfMetaOf,
91
94
  voMetaOf,
@@ -230,6 +233,13 @@ export const tsApplicationName = (packageName: string): string =>
230
233
  export const tsHashConstName = (packageName: string): string =>
231
234
  `${upperSnake(packageName)}_DOMAIN_HASH`;
232
235
 
236
+ /** `tool-library` → `TOOL_LIBRARY_DOMAIN_REQUIREMENTS` — the per-domain unit requirements const (CAP-666).
237
+ * Module-private: the sealed Reception exports only the receiver, the definition, the value helpers and
238
+ * the `<domain>Client` factories, so the table reaches tenants as `<Name>ApplicationDefinition
239
+ * .compatibility.domainRequirements`, mirroring Dart's `frameworkCalls.domainRequirements`. */
240
+ export const tsDomainRequirementsConstName = (packageName: string): string =>
241
+ `${upperSnake(packageName)}_DOMAIN_REQUIREMENTS`;
242
+
233
243
  /** `tool-library` → `TOOL_LIBRARY_INTERFACE` — the baked structural-interface const (#72). */
234
244
  export const interfaceConstName = (packageName: string): string =>
235
245
  `${upperSnake(packageName)}_COMPATIBILITY_MATRIX_USDA`;
@@ -431,6 +441,18 @@ function mintLazyTypeName(reg: LazyTypeRegistry, path: string): string {
431
441
  * (never `JsonValue`) — see {@link LazyTypeRegistry}. `reg` is absent only for standalone callers
432
442
  * (e.g. a query return) that can't host a named decl; a recursive lazy there is the one place the
433
443
  * `JsonValue` safety valve applies (query returns are read models, never recursive in practice). */
444
+ /** BRANDED-ID ALIASES REACHED WHILE GENERATING ONE CLIENT. {@link generateTsClient} opens the sink and
445
+ * emits one `export type <Brand> = string & { readonly __brand }` per name it collected, so every
446
+ * `brandedId(...)` / `t.brandedId(...)` / `t.uuid()` value is typed by its brand in payloads AND read models.
447
+ * A standalone caller with no open sink keeps the plain `string` (it has nowhere to declare an alias). */
448
+ let brandedIdSink: Set<string> | undefined;
449
+
450
+ function brandedIdTsType(brand: string): string {
451
+ if (brandedIdSink === undefined) return "string";
452
+ brandedIdSink.add(brand);
453
+ return brand;
454
+ }
455
+
434
456
  function tsTypeOfZod(
435
457
  zt: z.ZodTypeAny,
436
458
  indent: string,
@@ -468,6 +490,8 @@ function tsTypeOfZod(
468
490
  : aggregateType;
469
491
  return `NomosRef<${target}>`;
470
492
  }
493
+ const brand = brandedIdMetaOf(zt);
494
+ if (brand !== undefined && zodKind(zt) === "string") return brandedIdTsType(brand);
471
495
  // A DECLARED VALUE OBJECT keeps its NAME (vo_first_class.md): the payload
472
496
  // references the same exported interface the read model uses — one name, both directions —
473
497
  // instead of an anonymous inline literal type. Checked on the BARE object only: optional/
@@ -882,12 +906,15 @@ export function tsTypeOfField(field: Field): string {
882
906
 
883
907
  function tsBaseTypeOfField(field: Field): string {
884
908
  switch (field.kind) {
885
- case "string":
886
- return isSha256DigestSchema(field.zod as z.ZodTypeAny)
887
- ? "Sha256Digest"
888
- : isGitObjectIdSchema(field.zod as z.ZodTypeAny)
889
- ? "GitObjectId"
890
- : "string";
909
+ case "string": {
910
+ const schema = field.zod as z.ZodTypeAny;
911
+ if (isSha256DigestSchema(schema)) return "Sha256Digest";
912
+ if (isGitObjectIdSchema(schema)) return "GitObjectId";
913
+ // TYPED IDS on aggregate fields (`t.brandedId(Name)` / `t.uuid()`): the read model types them exactly as
914
+ // a payload of the same schema — never a bare string.
915
+ const brand = brandedIdMetaOf(schema);
916
+ return brand !== undefined ? brandedIdTsType(brand) : "string";
917
+ }
891
918
  case "int":
892
919
  return "number";
893
920
  // STRUCTURAL CRDT scalars (read-collapsed): a counter reads as its net number, a position as its
@@ -1128,8 +1155,13 @@ function aggregatesOf(mod: Record<string, unknown>): AggregateHandle[] {
1128
1155
  export interface TsClientOptions {
1129
1156
  /** The package name (`nomos.project.ts` `name`) — used for the hash constant. */
1130
1157
  readonly packageName: string;
1131
- /** Content identity of the built typed USDA closure (`.law.usdz`). */
1158
+ /** THE LAW this client is generated against: the unit set identity of `build/<name>.units` (CAP-773,
1159
+ * law_unit_set_identity.ts). Never the package closure, and never sent to the kernel with an offer. */
1132
1160
  readonly domainHash: string;
1161
+ /** THE PER-DOMAIN REQUIREMENTS (CAP-666): per application domain key, its unit hash and its generated
1162
+ * requirement identity. Carried by the definition's `compatibility.domainRequirements` only when present
1163
+ * (the era rule). */
1164
+ readonly domainRequirements?: readonly DomainUnitRequirement[];
1133
1165
  /** Sibling lossless USDZ transport carried by the generated client when its signed-in workspace
1134
1166
  * still serves the predecessor model. The archive is never canonical state and is not embedded. */
1135
1167
  readonly lawAssetName?: string;
@@ -1420,6 +1452,20 @@ function zodUsesGitObjectId(
1420
1452
  export function generateTsClient(
1421
1453
  modules: readonly DomainModule[],
1422
1454
  opts: TsClientOptions
1455
+ ): string {
1456
+ const outerSink = brandedIdSink;
1457
+ brandedIdSink = new Set<string>();
1458
+ try {
1459
+ return generateTsClientBody(modules, opts, brandedIdSink);
1460
+ } finally {
1461
+ brandedIdSink = outerSink;
1462
+ }
1463
+ }
1464
+
1465
+ function generateTsClientBody(
1466
+ modules: readonly DomainModule[],
1467
+ opts: TsClientOptions,
1468
+ brandSink: Set<string>,
1423
1469
  ): string {
1424
1470
  if (opts.callUsda === undefined) {
1425
1471
  throw new Error(
@@ -1450,6 +1496,8 @@ export function generateTsClient(
1450
1496
  )
1451
1497
  );
1452
1498
  const topologyModules = modules;
1499
+ // Branded-id aliases already declared (by the value-object block); the rest are declared at the end.
1500
+ const emittedBrands = new Set<string>();
1453
1501
  // A public business birth can target a deliberately hidden root aggregate. Its private binder still
1454
1502
  // needs a nominal type, but unrelated framework records must remain absent even from generated source.
1455
1503
  const publicBusinessAggregateIds = new Set(
@@ -1588,6 +1636,8 @@ export function generateTsClient(
1588
1636
  opts.interface as unknown as Record<string, DomainInterface>
1589
1637
  );
1590
1638
  const hashConst = tsHashConstName(opts.packageName);
1639
+ const domainRequirements = [...(opts.domainRequirements ?? [])].sort((a, b) => a.domainKey < b.domainKey ? -1 : a.domainKey > b.domainKey ? 1 : 0);
1640
+ assertDomainRequirements(domainRequirements);
1591
1641
  // THE E2E-SEALING MAP (the sealing client): non-null iff any field is `.encrypted()`. When null,
1592
1642
  // NOTHING below emits — the generated client is byte-identical to a pre-sealing compile.
1593
1643
  const encMap = buildEncryptionMap(
@@ -2211,9 +2261,24 @@ export function generateTsClient(
2211
2261
  ]
2212
2262
  : []),
2213
2263
  ``,
2214
- `/** Content identity of the installed typed USDA closure (transported as \`.law.usdz\`). */`,
2264
+ `/** The law this client was generated against: its unit set identity (\`build/<name>.units.sha256\`). Each`,
2265
+ ` * workspace serves a closure its own kernel composes from those units; compatibility is the kernel's verdict. */`,
2215
2266
  `const ${hashConst} = ${JSON.stringify(opts.domainHash)};`,
2216
2267
  ``,
2268
+ ...(domainRequirements.length === 0
2269
+ ? []
2270
+ : [
2271
+ `/** THE PER-DOMAIN REQUIREMENTS (CAP-666) this client was generated against: for each application domain,`,
2272
+ ` * the hash of its law unit (\`domain:<key>\` of the unit set) and the sha256 of its generated compatibility`,
2273
+ ` * requirement. A workspace serving exactly that unit for the key is current for this build whatever else`,
2274
+ ` * its unit set carries. Identities the adoption inventory and the support policy name; never a verdict. */`,
2275
+ `const ${tsDomainRequirementsConstName(opts.packageName)} = Object.freeze({`,
2276
+ ...domainRequirements.map((requirement) =>
2277
+ ` ${JSON.stringify(requirement.domainKey)}: Object.freeze({ domainKey: ${JSON.stringify(requirement.domainKey)}, `
2278
+ + `unitSha256: ${JSON.stringify(requirement.unitSha256)}, requirementSha256: ${JSON.stringify(requirement.requirementSha256)} }),`),
2279
+ `} as const);`,
2280
+ ``,
2281
+ ]),
2217
2282
  ...(clientInterface !== undefined
2218
2283
  ? [
2219
2284
  `/** Canonical compiler-authored USDA compatibility matrix. The application carries`,
@@ -2579,7 +2644,7 @@ export function generateTsClient(
2579
2644
  `export interface NomosHolonBinding { readonly workspace: string }`,
2580
2645
  `interface NomosHolon {`,
2581
2646
  ` /** Carry one compiler-generated nominal USDA call. Routing identity and bytes cannot be paired independently. */`,
2582
- ` offerCall(call: NmsGeneratedCall, opts?: { domainHash?: string; actor?: string; returnCreated?: boolean; __nomosConflictSnapshots?: readonly { aggregateType: string; aggregateId: string; field: string; targetSchema: string; token: string }[] }): Promise<string | { head: string; created?: { type: string; id: string }[]; affected?: { type: string; id: string }[]; workspace?: string; born?: unknown; birthOutcomes?: unknown }>;`,
2647
+ ` offerCall(call: NmsGeneratedCall, opts?: { actor?: string; returnCreated?: boolean; __nomosConflictSnapshots?: readonly { aggregateType: string; aggregateId: string; field: string; targetSchema: string; token: string }[] }): Promise<string | { head: string; created?: { type: string; id: string }[]; affected?: { type: string; id: string }[]; workspace?: string; born?: unknown; birthOutcomes?: unknown }>;`,
2583
2648
  ...(sourceTrustPresent
2584
2649
  ? [
2585
2650
  ` /** FRAMEWORK-INTERNAL: inspect a sibling's immutable custody identity before authoring`,
@@ -2590,7 +2655,7 @@ export function generateTsClient(
2590
2655
  ...(hasCorrections
2591
2656
  ? [
2592
2657
  ` /** Framework-private seam used by generated \`corrections\`; apps never supply an intent id. */`,
2593
- ` correctMostRecent?(opts: { kind: "undo" | "redo"; domain: string; actions: string[]; reason: string; domainHash?: string; actor?: string }): Promise<NomosCorrectionOutcome>;`,
2658
+ ` correctMostRecent?(opts: { kind: "undo" | "redo"; domain: string; actions: string[]; reason: string; actor?: string }): Promise<NomosCorrectionOutcome>;`,
2594
2659
  ]
2595
2660
  : []),
2596
2661
  ...(hasBirths
@@ -2598,7 +2663,7 @@ export function generateTsClient(
2598
2663
  ` /** BIRTH A CHILD WORKSPACE (a signed CROSS-WORKSPACE governance offer to \`parent\`) — OPTIONAL:`,
2599
2664
  ` * @githolon/runtime's connect() attaches it. The generated \`.births()\` methods call this, binding`,
2600
2665
  ` * the declaring domain/directiveId/law hash so a frontend dev never hand-writes those raw strings. */`,
2601
- ` createWorkspace?(opts: { domain: string; directiveId: string; domainHash?: string; parent: string; actor: string; authorSecret?: string; payload?: unknown; name?: string; authToken?: string }): Promise<{ ok?: boolean; status?: number; body?: unknown }>;`,
2666
+ ` createWorkspace?(opts: { domain: string; directiveId: string; parent: string; actor: string; authorSecret?: string; payload?: unknown; name?: string; authToken?: string }): Promise<{ ok?: boolean; status?: number; body?: unknown }>;`,
2602
2667
  ]
2603
2668
  : []),
2604
2669
  ` /** The workspace this holon is bound to — the \`workspace\` of every [NomosRef] this client composes. */`,
@@ -2769,7 +2834,7 @@ export function generateTsClient(
2769
2834
  ? [
2770
2835
  `/** Framework-private merge lane used only by generated conflict capabilities. The snapshot`,
2771
2836
  ` * proves the exact observation set being merged; application code never supplies it. */`,
2772
- `type __NomosConflictOffer = (call: NmsGeneratedCall, opts: { domainHash?: string; actor?: string; returnCreated: true; __nomosConflictSnapshots: readonly { aggregateType: string; aggregateId: string; field: string; targetSchema: string; token: string }[] }) => ReturnType<NomosHolon["offerCall"]>;`,
2837
+ `type __NomosConflictOffer = (call: NmsGeneratedCall, opts: { actor?: string; returnCreated: true; __nomosConflictSnapshots: readonly { aggregateType: string; aggregateId: string; field: string; targetSchema: string; token: string }[] }) => ReturnType<NomosHolon["offerCall"]>;`,
2773
2838
  ``,
2774
2839
  ]
2775
2840
  : []),
@@ -3001,8 +3066,8 @@ export function generateTsClient(
3001
3066
  `}`,
3002
3067
  ``,
3003
3068
  `/** Build the owner ceremony for one domain (thin: all mechanism lives here; the factory just wires it). */`,
3004
- `function _ownerEncryption(holon: NomosHolon, cfg: { domain: string; domainHash?: string; actor?: string; scopes: readonly string[] }): NomosEncryption {`,
3005
- ` const { domain, domainHash, actor, scopes } = cfg;`,
3069
+ `function _ownerEncryption(holon: NomosHolon, cfg: { domain: string; actor?: string; scopes: readonly string[] }): NomosEncryption {`,
3070
+ ` const { domain, actor, scopes } = cfg;`,
3006
3071
  ` const defScope = (scope?: string): string => {`,
3007
3072
  ` if (scope) return scope;`,
3008
3073
  ` if (scopes.length === 1) return scopes[0]!;`,
@@ -3076,7 +3141,7 @@ export function generateTsClient(
3076
3141
  ` wraps.push({ recipientPrincipal: principal, recipientPubKey: pub, hpkeEpk: w.hpkeEpk, ct: w.ct });`,
3077
3142
  ` }`,
3078
3143
  ` const epoch = (await currentEpoch(scope)) + 1; // forward-only: strictly increments`,
3079
- ' const head = await holon.offerDirective(domain, "advanceKeyEpoch", { scope, epoch, openedAt: opts.openedAt ?? new Date().toISOString(), wraps }, { domainHash, actor });',
3144
+ ' const head = await holon.offerDirective(domain, "advanceKeyEpoch", { scope, epoch, openedAt: opts.openedAt ?? new Date().toISOString(), wraps }, { actor });',
3080
3145
  ` return { head, epoch, roster: members };`,
3081
3146
  ` };`,
3082
3147
  ` return {`,
@@ -3084,12 +3149,12 @@ export function generateTsClient(
3084
3149
  ` roster: (scope) => readRoster(defScope(scope)),`,
3085
3150
  ` async grantAndWrap(args) {`,
3086
3151
  ` const scope = defScope(args.scope);`,
3087
- ` await holon.offerDirective(domain, args.grantDirective, args.grantPayload, { domainHash, actor });`,
3152
+ ` await holon.offerDirective(domain, args.grantDirective, args.grantPayload, { actor });`,
3088
3153
  ` return advance(scope, { add: { principal: args.principal, publicKey: args.publicKey }, scopeKey: args.scopeKey, memberKeys: args.memberKeys, expectRoster: args.expectRoster, openedAt: args.openedAt });`,
3089
3154
  ` },`,
3090
3155
  ` async revokeAndRotate(args) {`,
3091
3156
  ` const scope = defScope(args.scope);`,
3092
- ` await holon.offerDirective(domain, args.revokeDirective, args.revokePayload, { domainHash, actor });`,
3157
+ ` await holon.offerDirective(domain, args.revokeDirective, args.revokePayload, { actor });`,
3093
3158
  ` return advance(scope, { remove: [args.principal], scopeKey: args.scopeKey, memberKeys: args.memberKeys, expectRoster: args.expectRoster, openedAt: args.openedAt });`,
3094
3159
  ` },`,
3095
3160
  ` async rotate(args) {`,
@@ -3727,6 +3792,7 @@ export function generateTsClient(
3727
3792
  for (const [, d] of voByName)
3728
3793
  for (const b of voBrandNames(d)) brands.add(b);
3729
3794
  for (const b of [...brands].sort()) {
3795
+ emittedBrands.add(b);
3730
3796
  out.push(
3731
3797
  `/** Branded id \`${b}\` — a nominal string (\`brandedId("${b}")\`); wire-identical to a string. */`
3732
3798
  );
@@ -4668,7 +4734,7 @@ export function generateTsClient(
4668
4734
  domain
4669
4735
  )}, actions: ${JSON.stringify(
4670
4736
  correctionActions.map((d) => d.id)
4671
- )}, reason, domainHash, actor: actor ?? holon.principal });`,
4737
+ )}, reason, actor: actor ?? holon.principal });`,
4672
4738
  ` },`,
4673
4739
  ` redo: async (reason: string): Promise<NomosCorrectionOutcome> => {`,
4674
4740
  ` if (!reason.trim()) throw new Error("corrections.redo() needs a reason");`,
@@ -4677,7 +4743,7 @@ export function generateTsClient(
4677
4743
  domain
4678
4744
  )}, actions: ${JSON.stringify(
4679
4745
  correctionActions.map((d) => d.id)
4680
- )}, reason, domainHash, actor: actor ?? holon.principal });`,
4746
+ )}, reason, actor: actor ?? holon.principal });`,
4681
4747
  ` },`,
4682
4748
  ` },`
4683
4749
  );
@@ -4695,7 +4761,7 @@ export function generateTsClient(
4695
4761
  publicPayloadSchema: z.ZodTypeAny;
4696
4762
  rootId: string;
4697
4763
  namespace: string;
4698
- keyField: string;
4764
+ keyField?: string;
4699
4765
  };
4700
4766
  }
4701
4767
  ).businessBirth;
@@ -4924,17 +4990,29 @@ export function generateTsClient(
4924
4990
  businessBirth === undefined || encFields === undefined
4925
4991
  ? undefined
4926
4992
  : Object.values(encFields).find((field) => field.owned === true);
4993
+ // A minted birth (the default) has no business key, but an owned birth seals to the child's address
4994
+ // before dispatch: reserve the key from the kernel's own minter and hand it to the law (CAP-804).
4995
+ const mintedOwnedBirth =
4996
+ ownedBirthDescriptor !== undefined && businessBirth!.keyField === undefined;
4927
4997
  const ownedBirthPrelude =
4928
4998
  ownedBirthDescriptor === undefined
4929
4999
  ? ""
4930
5000
  : ` if (!holon.__nomosOwnedBirthScopeKey) throw new Error("this holon predates automatic .owned() birth custody — upgrade @githolon/runtime");\n` +
5001
+ (mintedOwnedBirth
5002
+ ? ` if (!holon.mint) throw new Error("this holon predates minted birth keys — upgrade @githolon/runtime");\n` +
5003
+ ` const _birthKey = await holon.mint(${JSON.stringify(
5004
+ businessBirth!.aggregateId
5005
+ )});\n`
5006
+ : "") +
4931
5007
  ` const _ownedBirthKey = await holon.__nomosOwnedBirthScopeKey({ scope: ${JSON.stringify(
4932
5008
  ownedBirthDescriptor.scope
4933
5009
  )}, namespace: ${JSON.stringify(
4934
5010
  businessBirth!.namespace
4935
- )}, key: ${inputPayload}[${JSON.stringify(
4936
- businessBirth!.keyField
4937
- )}], record: { type: ${JSON.stringify(
5011
+ )}, key: ${
5012
+ mintedOwnedBirth
5013
+ ? "_birthKey"
5014
+ : `${inputPayload}[${JSON.stringify(businessBirth!.keyField)}]`
5015
+ }, record: { type: ${JSON.stringify(
4938
5016
  ownedRuntimeType
4939
5017
  )}, id: ${JSON.stringify(businessBirth!.rootId)} } });`;
4940
5018
  const paramSig = `${argumentName}: ${payloadName}`;
@@ -5044,7 +5122,7 @@ export function generateTsClient(
5044
5122
  )}: _sourceGenesis,`,
5045
5123
  `${indent} ${JSON.stringify(sourceTrust.keyHashesField)}: [],`,
5046
5124
  `${indent}} satisfies ${payloadType};`,
5047
- `${indent}await _commit(holon.offerCall(${encoder}(_sourceTrustCall, holon.workspace), { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
5125
+ `${indent}await _commit(holon.offerCall(${encoder}(_sourceTrustCall, holon.workspace), { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
5048
5126
  ...(satisfactionQuery === undefined ? [] : [" }"]),
5049
5127
  ];
5050
5128
  })();
@@ -5266,6 +5344,10 @@ export function generateTsClient(
5266
5344
  type: `${payloadName} & { readonly principal: string; readonly bornAt: string${ownedSeedFields} }`,
5267
5345
  });
5268
5346
  }
5347
+ // A minted birth's law admits a key the client reserved ahead of dispatch (CAP-804).
5348
+ if (businessBirth !== undefined && businessBirth.keyField === undefined) {
5349
+ callExtraFields.set(BUSINESS_BIRTH_KEY_PAYLOAD_FIELD, { type: "string", optional: true });
5350
+ }
5269
5351
  // The public payload remains purely application-authored. The private encoder
5270
5352
  // type additionally admits the custody facts injected by the generated client
5271
5353
  // after it obtains them from the framework-owned key ceremony.
@@ -5418,9 +5500,13 @@ export function generateTsClient(
5418
5500
  ownedBirthDescriptor !== undefined
5419
5501
  ? `, [_OWNED_KEY_PAYLOAD_FIELD]: _ownedBirthKey.ownedKeySubject`
5420
5502
  : ""
5503
+ }${
5504
+ ownedBirthDescriptor !== undefined && businessBirth.keyField === undefined
5505
+ ? `, ${JSON.stringify(BUSINESS_BIRTH_KEY_PAYLOAD_FIELD)}: _birthKey`
5506
+ : ""
5421
5507
  } }`
5422
5508
  )};`,
5423
- ` const result = await holon.offerCall(_call, { domainHash, actor: principal, returnCreated: true });`,
5509
+ ` const result = await holon.offerCall(_call, { actor: principal, returnCreated: true });`,
5424
5510
  ` const born = typeof result === "object" && result !== null && Array.isArray(result.born) ? result.born[0] : undefined;`,
5425
5511
  ` if (typeof born !== "object" || born === null || typeof born.workspace !== "string" || !born.workspace) throw new Error("birth() committed without returning the born workspace");`,
5426
5512
  ...(ownedBirthDescriptor !== undefined
@@ -5453,7 +5539,7 @@ export function generateTsClient(
5453
5539
  ...(gateGuard ? [gateGuard.replace(/\n$/, "")] : []),
5454
5540
  ` return _commit(holon.offerCall(${encodeCall(
5455
5541
  birthPayload
5456
- )}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
5542
+ )}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
5457
5543
  ` },`
5458
5544
  );
5459
5545
  continue;
@@ -5488,10 +5574,10 @@ export function generateTsClient(
5488
5574
  const mintedCall = hasBusinessResult
5489
5575
  ? `_businessResult<${resultName}>(holon.workspace, ${resultSpec}, holon.offerCall(${encodeCall(
5490
5576
  body
5491
- )}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`
5577
+ )}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`
5492
5578
  : `_commit(holon.offerCall(${encodeCall(
5493
5579
  body
5494
- )}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`;
5580
+ )}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`;
5495
5581
  methods.push(
5496
5582
  ` /** Author \`${domain}/${d.id}\`: the generated client MINTS \`${mint.field}\` internally`,
5497
5583
  ` * — ids are not application input — ${
@@ -5541,10 +5627,10 @@ export function generateTsClient(
5541
5627
  const commitCall = hasBusinessResult
5542
5628
  ? `_businessResult<${resultName}>(holon.workspace, ${resultSpec}, holon.offerCall(${encodeCall(
5543
5629
  offerPayload
5544
- )}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`
5630
+ )}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`
5545
5631
  : `_commit(holon.offerCall(${encodeCall(
5546
5632
  offerPayload
5547
- )}, { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`;
5633
+ )}, { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>)`;
5548
5634
  const outcomeDoc =
5549
5635
  d.marker === "creates"
5550
5636
  ? `Author \`${domain}/${d.id}\`: the LAW mints the ${
@@ -6181,7 +6267,7 @@ export function generateTsClient(
6181
6267
  ` merge: (reconcile: (versions: readonly SurfacedObservation<${valueType}>[]) => ${valueType}, rest?: ${restType}): Promise<void> => {`,
6182
6268
  ` const merged = reconcile(versions);`,
6183
6269
  ` const resolution = ${resolutionLiteral} satisfies ${payloadName};`,
6184
- ` return _commit((holon.offerCall as __NomosConflictOffer)(${resolverCallEncoder}(resolution, holon.workspace), { domainHash, actor, returnCreated: true, __nomosConflictSnapshots: [{ aggregateType: ${JSON.stringify(
6270
+ ` return _commit((holon.offerCall as __NomosConflictOffer)(${resolverCallEncoder}(resolution, holon.workspace), { actor, returnCreated: true, __nomosConflictSnapshots: [{ aggregateType: ${JSON.stringify(
6185
6271
  runtimeAggregateType
6186
6272
  )}, aggregateId, field: ${JSON.stringify(
6187
6273
  b.field
@@ -6617,7 +6703,7 @@ export function generateTsClient(
6617
6703
  ` const payload = await holon.__nomosPrepareOwnedRecordAction({ action: ${JSON.stringify(
6618
6704
  descriptor.action
6619
6705
  )}, scope: ${JSON.stringify(ownedScope)}, record, peer });`,
6620
- ` return _commit(holon.offerCall(${descriptor.functionName}(payload, holon.workspace), { domainHash, actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
6706
+ ` return _commit(holon.offerCall(${descriptor.functionName}(payload, holon.workspace), { actor, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
6621
6707
  ` }`,
6622
6708
  ]),
6623
6709
  ` default: throw new Error(\`${"${action}()"} has no generated canonical offer encoder\`);`,
@@ -6666,7 +6752,7 @@ export function generateTsClient(
6666
6752
  ` const payload = await holon.__nomosPrepareOwnedDeviceApproval({ request, scope: ${JSON.stringify(
6667
6753
  ownedScope
6668
6754
  )} });`,
6669
- ` return _commit(holon.offerCall(${ownedDeviceApprovalCallEncoder!}(payload, holon.workspace), { domainHash, actor: principal, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
6755
+ ` return _commit(holon.offerCall(${ownedDeviceApprovalCallEncoder!}(payload, holon.workspace), { actor: principal, returnCreated: true }) as Promise<_NomosRawWriteResult>);`,
6670
6756
  ` },`,
6671
6757
  ` },`,
6672
6758
  ]
@@ -6677,7 +6763,7 @@ export function generateTsClient(
6677
6763
  ` /** Owner policy: grant, revoke and rotate under the signed-in identity. */`,
6678
6764
  ` encryption: _ownerEncryption(holon, { domain: ${JSON.stringify(
6679
6765
  domain
6680
- )}, domainHash, actor, scopes: ${JSON.stringify(
6766
+ )}, actor, scopes: ${JSON.stringify(
6681
6767
  domScopes
6682
6768
  )} }) as NomosEncryption,`,
6683
6769
  ]
@@ -7074,6 +7160,7 @@ export function generateTsClient(
7074
7160
  ` compatibility: {`,
7075
7161
  ` lawIdentity: sha256Digest(${hashConst}),`,
7076
7162
  ` matrixUsda: ${clientInterface === undefined ? "new Uint8Array()" : interfaceConstName(opts.packageName)},`,
7163
+ ...(domainRequirements.length === 0 ? [] : [` domainRequirements: ${tsDomainRequirementsConstName(opts.packageName)},`]),
7077
7164
  ` readyCommands: [${rootBirths
7078
7165
  .map(
7079
7166
  (birth) =>
@@ -7091,6 +7178,20 @@ export function generateTsClient(
7091
7178
  );
7092
7179
  }
7093
7180
 
7181
+ // TYPED-ID ALIASES reached by payloads and read models (beyond those the value objects declared above).
7182
+ // TS hoists type aliases, so declaring them last is fine; sorted for byte-determinism.
7183
+ const remainingBrands = [...brandSink].filter((brand) => !emittedBrands.has(brand)).sort();
7184
+ if (remainingBrands.length > 0) {
7185
+ out.push(`// ── typed ids (brandedId / t.brandedId / t.uuid) — nominal strings, wire-identical to a string ──`);
7186
+ for (const b of remainingBrands) {
7187
+ out.push(
7188
+ `/** Branded id \`${b}\` — a nominal string (\`brandedId("${b}")\`); wire-identical to a string. */`,
7189
+ `export type ${b} = string & { readonly __brand: "${b}" };`,
7190
+ ``
7191
+ );
7192
+ }
7193
+ }
7194
+
7094
7195
  // RECURSIVE PAYLOAD TYPES (`z.lazy`): emit each minted named alias once. Sorted by name for
7095
7196
  // byte-determinism; TS hoists type aliases so referencing them above is fine.
7096
7197
  if (lazyReg.decls.size > 0) {
@@ -18,6 +18,7 @@ import type { Field } from "./fields.js";
18
18
  import type { GeometryKind } from "./value_object.js";
19
19
  import { isBusinessIntent } from "./intent.js";
20
20
  import { isFrameworkAggregate } from "./framework/framework_aggregates.js";
21
+ import { composedWireIdentity } from "./composed_wire_identity.js";
21
22
  import type {
22
23
  LawAnonymousValueTypeNode,
23
24
  LawAggregateNode,
@@ -42,6 +43,21 @@ export interface PayloadFieldShape {
42
43
  readonly type?: string;
43
44
  }
44
45
 
46
+ /**
47
+ * One member of a nested record value. Two laws can agree on every nested name and kind while
48
+ * their generated codecs disagree on the Protobuf field numbers (the 1 October 2026
49
+ * `EstateAsset.canvasRequirements{}.isRequired` incident), so the member carries the allocation
50
+ * the generated codec actually encodes and decodes it under, plus any retained aliases.
51
+ */
52
+ export interface CompatibilityRecordField {
53
+ readonly required: boolean;
54
+ readonly type: string;
55
+ /** Canonical Protobuf allocation the generated codec uses for this member. */
56
+ readonly wireKey?: string;
57
+ /** Previously shipped allocations this codec still decodes during an overlap window. */
58
+ readonly wireAliases?: readonly string[];
59
+ }
60
+
45
61
  /**
46
62
  * The compatibility vocabulary for domain values. This is a graph rather than
47
63
  * an inline tree so recursive values and shared nested value objects remain
@@ -68,9 +84,7 @@ export type CompatibilityValueShape =
68
84
  | { readonly kind: "map"; readonly value: string }
69
85
  | {
70
86
  readonly kind: "record";
71
- readonly fields: Readonly<
72
- Record<string, { readonly required: boolean; readonly type: string }>
73
- >;
87
+ readonly fields: Readonly<Record<string, CompatibilityRecordField>>;
74
88
  }
75
89
  | { readonly kind: "sum"; readonly variants: readonly string[] }
76
90
  | { readonly kind: "recursive"; readonly body: string };
@@ -352,10 +366,31 @@ export function domainInterface(
352
366
  * workspace offers are now two projections of the same graph/USDA, rather than
353
367
  * independently interpreting DomainModule and hoping the shapes coincide.
354
368
  */
355
- export function domainInterfaceFromLawGraph(graph: LawGraph): DomainInterface {
369
+ export function domainInterfaceFromLawGraph(
370
+ graph: LawGraph,
371
+ /** The composed law layer's path index. Nested members of a record that OpenUSD composition
372
+ * shares with another contract take the canonical prim's allocation, exactly as the generated
373
+ * codecs do through `composedWireIdentity`; without the index each member's own identity is
374
+ * its allocation. */
375
+ paths: ReadonlyMap<string, string> = new Map()
376
+ ): DomainInterface {
356
377
  const nodes = new Map<string, LawGraph["nodes"][number]>(
357
378
  graph.nodes.map((node) => [node.entity.id, node])
358
379
  );
380
+ // The nested allocation is the one fact the generated Dart/TS codecs and installed law must
381
+ // agree on. It is derived here from the same identity the codecs derive it from, never from
382
+ // the member's name.
383
+ const nestedWire = (entity: {
384
+ readonly id: string;
385
+ readonly sid: string;
386
+ readonly wireKey?: string;
387
+ readonly wireAliases?: readonly string[];
388
+ }): Pick<CompatibilityRecordField, "wireKey" | "wireAliases"> => ({
389
+ wireKey: composedWireIdentity(paths, entity),
390
+ ...(entity.wireAliases === undefined || entity.wireAliases.length === 0
391
+ ? {}
392
+ : { wireAliases: entity.wireAliases }),
393
+ });
359
394
  const sorted = <T>(
360
395
  entries: readonly (readonly [string, T])[]
361
396
  ): Record<string, T> =>
@@ -587,6 +622,7 @@ export function domainInterfaceFromLawGraph(graph: LawGraph): DomainInterface {
587
622
  {
588
623
  required: field.required,
589
624
  type: resolvedTypeId(field.value.id),
625
+ ...nestedWire(field.entity),
590
626
  },
591
627
  ] as const,
592
628
  ];
@@ -653,6 +689,7 @@ export function domainInterfaceFromLawGraph(graph: LawGraph): DomainInterface {
653
689
  {
654
690
  required: field.required,
655
691
  type: resolvedTypeId(field.value.id),
692
+ ...nestedWire(field.entity),
656
693
  },
657
694
  ] as const,
658
695
  ];
@@ -60,10 +60,14 @@ function canonicalCompatibilityTypes(
60
60
  value = `sum(${type.variants.map(ref).sort().map(fingerprintAtom).join("")})`;
61
61
  break;
62
62
  case "record":
63
+ // Two records with identical names and kinds but different nested allocations are
64
+ // different contracts; interning them as one would erase the very evidence the kernel
65
+ // judges, so the allocation and its aliases are part of the fingerprint.
63
66
  value = `record(${Object.entries(type.fields)
64
67
  .sort(([left], [right]) => left.localeCompare(right))
65
68
  .map(([name, field]) =>
66
- `${fingerprintAtom(name)}${field.required ? "1" : "0"}${fingerprintAtom(ref(field.type))}`)
69
+ `${fingerprintAtom(name)}${field.required ? "1" : "0"}${fingerprintAtom(ref(field.type))}` +
70
+ `${fingerprintAtom(field.wireKey ?? "")}${[...(field.wireAliases ?? [])].sort().map(fingerprintAtom).join("")}`)
67
71
  .join("")})`;
68
72
  break;
69
73
  }
@@ -146,6 +150,12 @@ function emitType(lines: string[], type: CompatibilityValueShape, typeRef: (id:
146
150
  ` def NomosCompatibilityValueField "${numbered("f", index)}"`,
147
151
  " {",
148
152
  ` custom uniform string nomos:name = ${usdaString(name)}`,
153
+ ...(field.wireKey === undefined ? [] : [
154
+ ` custom uniform string nomos:wireKey = ${usdaString(field.wireKey)}`,
155
+ ]),
156
+ ...(field.wireAliases === undefined || field.wireAliases.length === 0 ? [] : [
157
+ ` custom uniform string[] nomos:wireAliases = [${field.wireAliases.map(usdaString).join(", ")}]`,
158
+ ]),
149
159
  ` custom uniform bool nomos:required = ${field.required}`,
150
160
  ` custom rel nomos:type = <${typeRef(field.type)}>`,
151
161
  " }",