@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
@@ -699,6 +699,8 @@ export interface AggFieldSpec {
699
699
  dartType: string;
700
700
  /** A canonical semantic string wrapper whose wire representation remains String. */
701
701
  semanticString?: "sha256" | "git-object-id";
702
+ /** A typed id (`t.brandedId(Name)` / `t.uuid()`): the zero-cost extension type wrapping the wire String. */
703
+ brandedId?: string;
702
704
  enumValues?: string[];
703
705
  /** For an own-id string field represented as `NomosRef<TAggregate>`. */
704
706
  valueClass?: string;
@@ -923,6 +925,9 @@ function readKeyParamSpec(
923
925
  if (fieldSpec.semanticString === "git-object-id") {
924
926
  return { dartType: "GitObjectId", valueExpr: `${paramName}.value` };
925
927
  }
928
+ if (fieldSpec.brandedId !== undefined) {
929
+ return { dartType: fieldSpec.brandedId, valueExpr: `${paramName}.value` };
930
+ }
926
931
  return { dartType: "String", valueExpr: paramName };
927
932
  }
928
933
 
@@ -1032,7 +1037,9 @@ function aggFieldSpec(
1032
1037
  // field schema marked them `.optional()`.
1033
1038
  const optional = isCollectionKind(field.kind) ? false : field.isOptional;
1034
1039
  switch (field.kind) {
1035
- case "string":
1040
+ case "string": {
1041
+ // A TYPED ID field (`t.brandedId(Name)` / `t.uuid()`) reads as its extension type, never a String.
1042
+ const brand = valueClass === undefined ? brandedIdMetaOf(field.zod as z.ZodTypeAny) : undefined;
1036
1043
  return {
1037
1044
  name,
1038
1045
  wireName,
@@ -1042,9 +1049,10 @@ function aggFieldSpec(
1042
1049
  ? "Sha256Digest"
1043
1050
  : isGitObjectIdSchema(field.zod as z.ZodTypeAny)
1044
1051
  ? "GitObjectId"
1045
- : "String"
1052
+ : brand ?? "String"
1046
1053
  : `NomosRef<${valueClass}>`,
1047
1054
  optional,
1055
+ ...(brand !== undefined ? { brandedId: brand } : {}),
1048
1056
  ...(valueClass === undefined && isSha256DigestSchema(field.zod as z.ZodTypeAny)
1049
1057
  ? { semanticString: "sha256" as const }
1050
1058
  : {}),
@@ -1055,6 +1063,7 @@ function aggFieldSpec(
1055
1063
  ? { valueClass, valueAggregateType }
1056
1064
  : {}),
1057
1065
  };
1066
+ }
1058
1067
  case "json":
1059
1068
  // A NAMED VALUE OBJECT (`ValueObject.field()`) types as its generated Dart class
1060
1069
  // (typed decode via `.fromProjected`, the EvidenceRef pattern). Otherwise
@@ -3172,6 +3181,11 @@ function parseField(s: AggFieldSpec, aggregate: string): string {
3172
3181
  ? `() { final v = ${reader}; return v == null ? null : GitObjectId(v); }()`
3173
3182
  : `GitObjectId(${reader})`;
3174
3183
  }
3184
+ if (s.brandedId !== undefined) {
3185
+ return opt
3186
+ ? `() { final v = ${reader}; return v == null ? null : ${s.brandedId}(v); }()`
3187
+ : `${s.brandedId}(${reader})`;
3188
+ }
3175
3189
  return reader;
3176
3190
  }
3177
3191
  case "json":
@@ -3260,7 +3274,9 @@ function slotDecodeCallback(s: AggFieldSpec, mode: "aggregate" | "readModel"): s
3260
3274
  ? `(v) => Sha256Digest(v as String)`
3261
3275
  : s.semanticString === "git-object-id"
3262
3276
  ? `(v) => GitObjectId(v as String)`
3263
- : `(v) => v as String`;
3277
+ : s.brandedId !== undefined
3278
+ ? `(v) => ${s.brandedId}(v as String)`
3279
+ : `(v) => v as String`;
3264
3280
  case "json":
3265
3281
  if (s.voClass !== undefined) {
3266
3282
  return `(v) => ${s.voClass}.fromProjected((v as Map).cast<String, Object?>())`;
@@ -3413,6 +3429,11 @@ function parseReadModelField(s: AggFieldSpec, aggregate: string): string {
3413
3429
  ? `() { final v = ${reader}; return v == null ? null : GitObjectId(v); }()`
3414
3430
  : `GitObjectId(${reader})`;
3415
3431
  }
3432
+ if (s.brandedId !== undefined) {
3433
+ return opt
3434
+ ? `() { final v = ${reader}; return v == null ? null : ${s.brandedId}(v); }()`
3435
+ : `${s.brandedId}(${reader})`;
3436
+ }
3416
3437
  return reader;
3417
3438
  }
3418
3439
  case "json":
@@ -4993,6 +5014,65 @@ export interface GenerateDartDomainOptions {
4993
5014
  * because it was present in the composed law.
4994
5015
  */
4995
5016
  readonly additionalValueObjects?: readonly ValueObjectDescriptor[];
5017
+ /** Branded-id extension types another file (the package's `nomos_framework.dart`) declares because ≥2
5018
+ * domains use them; this domain references them via {@link frameworkImportUri}. */
5019
+ readonly skipBrandNames?: ReadonlySet<string>;
5020
+ /** Branded-id extension types this file declares for the package (the framework file's shared set). */
5021
+ readonly extraBrandNames?: readonly string[];
5022
+ }
5023
+
5024
+ /**
5025
+ * Every branded-id name (`brandedId(...)`, `t.brandedId(...)`, `t.uuid()`) whose Dart extension type this
5026
+ * domain's generated types reference: typed-id aggregate fields, top-level payload fields of its public
5027
+ * directives (business births render their public payload), and value-object fields. The package counts
5028
+ * these per domain so a brand used by two domains is declared ONCE, in the framework file.
5029
+ */
5030
+ export function dartBrandNamesForModule(mod: DomainModule): Set<string> {
5031
+ const brands = new Set<string>();
5032
+ const publicAggregates = mod.aggregates.filter(
5033
+ (a) => (a as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
5034
+ );
5035
+ for (const agg of publicAggregates) {
5036
+ for (const f of Object.values(agg.fields as Record<string, Field>)) {
5037
+ if (f.kind !== "string") continue;
5038
+ const b = brandedIdMetaOf(f.zod as z.ZodTypeAny);
5039
+ if (b !== undefined) brands.add(b);
5040
+ }
5041
+ }
5042
+ const directives = (mod.applicationIntents ?? mod.directives)
5043
+ .filter((d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true)
5044
+ .map((d) => {
5045
+ const birth = (d as { businessBirth?: { publicPayloadSchema?: z.ZodTypeAny } }).businessBirth;
5046
+ return birth?.publicPayloadSchema === undefined ? d : { ...d, payloadSchema: birth.publicPayloadSchema };
5047
+ });
5048
+ for (const dir of directives) {
5049
+ const schema = (dir as { payloadSchema?: unknown }).payloadSchema;
5050
+ if (!schema || typeof schema !== "object" || zodTypeName(schema as z.ZodTypeAny) !== "ZodObject") continue;
5051
+ const rawShape = (zodDef(schema as z.ZodTypeAny) as { shape?: Record<string, z.ZodTypeAny> | (() => Record<string, z.ZodTypeAny>) }).shape;
5052
+ const shp = typeof rawShape === "function" ? rawShape() : rawShape ?? {};
5053
+ for (let zt of Object.values(shp)) {
5054
+ for (let hops = 0; hops < 8 && (zodTypeName(zt) === "ZodOptional" || zodTypeName(zt) === "ZodDefault" || zodTypeName(zt) === "ZodArray"); hops++) {
5055
+ zt = zodTypeName(zt) === "ZodArray" ? zodArrayElement(zt) : zodInnerType(zt);
5056
+ }
5057
+ const b = brandedIdMetaOf(zt);
5058
+ if (b !== undefined) brands.add(b);
5059
+ }
5060
+ }
5061
+ const walk = (f: ValueObjectFieldDescriptor): void => {
5062
+ if (f.type === "string" && f.brand !== undefined && f.idOf === undefined) brands.add(f.brand);
5063
+ else if (f.type === "array") walk(f.items);
5064
+ else if (f.type === "map") walk(f.values);
5065
+ };
5066
+ for (const d of [
5067
+ ...collectValueObjectDescriptors(publicAggregates),
5068
+ ...collectPayloadValueObjectDescriptors(directives),
5069
+ ]) {
5070
+ for (const f of Object.values(d.fields ?? {})) walk(f);
5071
+ for (const arm of Object.values(d.union ?? {})) {
5072
+ if ("fields" in arm) for (const f of Object.values(arm.fields)) walk(f);
5073
+ }
5074
+ }
5075
+ return brands;
4996
5076
  }
4997
5077
 
4998
5078
  /** Generate the full Dart source for one domain module. */
@@ -5098,6 +5178,16 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
5098
5178
  else if (f.type === "array") walkUnitField(f.items);
5099
5179
  else if (f.type === "map") walkUnitField(f.values);
5100
5180
  };
5181
+ // TYPED-ID aggregate fields (`t.brandedId(Name)` / `t.uuid()`) declare their extension type with the
5182
+ // aggregate that owns them (skipping aggregates another file declares).
5183
+ for (const agg of mod.aggregates) {
5184
+ if (opts.skipAggregateIds?.has(agg.id) === true) continue;
5185
+ for (const f of Object.values(agg.fields as Record<string, Field>)) {
5186
+ if (f.kind !== "string") continue;
5187
+ const b = brandedIdMetaOf(f.zod as z.ZodTypeAny);
5188
+ if (b !== undefined) brandNames.add(b);
5189
+ }
5190
+ }
5101
5191
  for (const d of localValueObjects) {
5102
5192
  for (const f of Object.values(d.fields ?? {})) walkUnitField(f);
5103
5193
  for (const arm of Object.values(d.union ?? {})) {
@@ -5128,6 +5218,8 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
5128
5218
  } catch { /* an unsupported payload shape is reported by the payload emitter, not here */ }
5129
5219
  }
5130
5220
  }
5221
+ for (const b of opts.skipBrandNames ?? []) brandNames.delete(b);
5222
+ for (const b of opts.extraBrandNames ?? []) brandNames.add(b);
5131
5223
  const unitBlock = [...unitNames].sort().map((u) => [
5132
5224
  `/// UNIT \`${u}\` — a ZERO-COST wrapper over the wire's plain JSON number (Dart extension`,
5133
5225
  `/// type): passing a bare double (or another unit) where [${u}] is required is a COMPILE`,
@@ -1,3 +1,4 @@
1
+ import { BUSINESS_BIRTH_KEY_PAYLOAD_FIELD } from "./births.js";
1
2
  import { dartRefusalDecoders } from "./codegen_refusal.js";
2
3
  // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
3
4
  // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
@@ -99,7 +100,7 @@ import { publicClientInterface, type DomainInterface } from "./compatibility_gra
99
100
  import { emitApplicationCompatibilityUsda } from "./compatibility_usda.js";
100
101
  import { BUSINESS_CORRECTION_DIRECTIVE } from "./framework/business_corrections.js";
101
102
  import { deriveDirectiveRecordTarget } from "./workspace_routing.js";
102
- import { voMetaOf } from "./value_object.js";
103
+ import { brandedIdMetaOf, voMetaOf } from "./value_object.js";
103
104
  import {
104
105
  buildNomosDescribeModel,
105
106
  type NomosDescribeModel,
@@ -389,6 +390,11 @@ function dartKeyType(
389
390
  if (f?.kind === "string" && isSha256DigestSchema(f.zod as z.ZodTypeAny)) {
390
391
  return "Sha256Digest";
391
392
  }
393
+ // A typed-id key (`t.brandedId(Name)` / `t.uuid()`) is its extension type, never a bare String.
394
+ if (f?.kind === "string" && !key.includes(".")) {
395
+ const brand = brandedIdMetaOf(f.zod as z.ZodTypeAny);
396
+ if (brand !== undefined) return brand;
397
+ }
392
398
  if (f?.kind === "string" && isGitObjectIdSchema(f.zod as z.ZodTypeAny)) {
393
399
  return "GitObjectId";
394
400
  }
@@ -435,6 +441,13 @@ function dartKeyExpr(agg: AggregateHandle | undefined, key: string): string {
435
441
  ) {
436
442
  return `${param}.value`;
437
443
  }
444
+ if (
445
+ field?.kind === "string" &&
446
+ !key.includes(".") &&
447
+ brandedIdMetaOf(field.zod as z.ZodTypeAny) !== undefined
448
+ ) {
449
+ return `${param}.value`;
450
+ }
438
451
  if (field?.kind !== "ref") return param;
439
452
  return field.foreignRef !== undefined
440
453
  ? `NmsInternalNomosRefs.id(${param}.ref)`
@@ -491,6 +504,8 @@ function dartUsdaBasePresentValue(
491
504
  ? `Sha256Digest(${value})`
492
505
  : spec.semanticString === "git-object-id"
493
506
  ? `GitObjectId(${value})`
507
+ : spec.brandedId !== undefined
508
+ ? `${spec.brandedId}(${value})`
494
509
  : value;
495
510
  }
496
511
  case "ref": {
@@ -815,7 +830,7 @@ function emitDartUsdaResultDecoder(
815
830
  }
816
831
 
817
832
  export interface DartClientOptions {
818
- /** Content identity of the built typed USDA closure (`.law.usdz`). */
833
+ /** THE LAW the client is generated against: the unit set identity of `build/<name>.units` (CAP-773). */
819
834
  readonly domainHash: string;
820
835
  /** Flutter asset key for the compiler-authored USDZ transport. Generated
821
836
  * child openers use it to inherit an accepted parent model through the
@@ -1305,7 +1320,7 @@ export function generateDartDomainClient(
1305
1320
  const compilerInternal = sourceTrustViaIds.has(directive.id);
1306
1321
  const businessBirth = (
1307
1322
  directive as unknown as {
1308
- businessBirth?: { publicPayloadSchema: z.ZodTypeAny };
1323
+ businessBirth?: { publicPayloadSchema: z.ZodTypeAny; keyField?: string };
1309
1324
  }
1310
1325
  ).businessBirth;
1311
1326
  const specs = introspectPayload(
@@ -1360,6 +1375,15 @@ export function generateDartDomainClient(
1360
1375
  arguments_.push("principal", "bornAt");
1361
1376
  access.set("principal", { value: "nmsInternalBirthPrincipal" });
1362
1377
  access.set("bornAt", { value: "nmsInternalBornAt" });
1378
+ // A minted birth's law admits a key the client reserved ahead of dispatch (CAP-804).
1379
+ if (businessBirth.keyField === undefined) {
1380
+ extraParameters.push("String? nmsInternalBirthKey");
1381
+ arguments_.push("nmsInternalBirthKey");
1382
+ access.set(BUSINESS_BIRTH_KEY_PAYLOAD_FIELD, {
1383
+ value: "nmsInternalBirthKey!",
1384
+ present: "nmsInternalBirthKey != null",
1385
+ });
1386
+ }
1363
1387
  }
1364
1388
  for (const input of dartBusinessRecordInputs(directive, mod.aggregates)) {
1365
1389
  const origin = `NmsInternalRecordOrigins.require(payload.${input.argument})`;
@@ -1954,7 +1978,8 @@ export function generateDartDomainClient(
1954
1978
  domain: string;
1955
1979
  rootId: string;
1956
1980
  namespace: string;
1957
- keyField: string;
1981
+ keyField?: string;
1982
+ keyType?: { type: string; brand: string };
1958
1983
  seedDirectiveId?: string;
1959
1984
  genesisDirectiveIds?: readonly string[];
1960
1985
  publicPayloadSchema: z.ZodTypeAny;
@@ -2010,6 +2035,18 @@ export function generateDartDomainClient(
2010
2035
  const birthSpecsByName = new Map(
2011
2036
  birthSpecs.map((spec) => [spec.name, spec])
2012
2037
  );
2038
+ // A minted birth (the default) has no business key; an owned one seals to the child's address
2039
+ // before dispatch, so it reserves its key from the kernel's own minter and hands it to the law.
2040
+ const mintedBirth = businessBirth.keyField === undefined;
2041
+ const birthKeyPrelude = !mintedBirth
2042
+ ? []
2043
+ : ownedBirthDescriptor === undefined
2044
+ ? [` const String? nmsInternalBirthKey = null;`]
2045
+ : [
2046
+ ` final String? nmsInternalBirthKey = await session.mintId(${dartStr(
2047
+ businessBirth.aggregateId
2048
+ )});`,
2049
+ ];
2013
2050
  const ownedBirthPrelude =
2014
2051
  ownedBirthDescriptor === undefined
2015
2052
  ? []
@@ -2017,9 +2054,17 @@ export function generateDartDomainClient(
2017
2054
  ` final nmsInternalOwnedKey = await session.nomosOwnedBirthScopeKey(`,
2018
2055
  ` ${dartStr(ownedBirthDescriptor.scope)}, ${dartStr(
2019
2056
  businessBirth.namespace
2020
- )}, payload.${dartMemberName(businessBirth.keyField)}, ${dartStr(
2021
- birthRuntimeType!
2022
- )}, ${dartStr(businessBirth.rootId)});`,
2057
+ )}, ${
2058
+ mintedBirth
2059
+ ? "nmsInternalBirthKey!"
2060
+ : `payload.${dartMemberName(businessBirth.keyField!)}${
2061
+ businessBirth.keyType?.type === "brandedId" || businessBirth.keyType?.type === "uuid"
2062
+ ? ".value"
2063
+ : ""
2064
+ }`
2065
+ }, ${dartStr(birthRuntimeType!)}, ${dartStr(
2066
+ businessBirth.rootId
2067
+ )});`,
2023
2068
  ` final nmsInternalOwnedKeySubject = nmsInternalOwnedKey.ownedKeySubject;`,
2024
2069
  ` if (nmsInternalOwnedKeySubject == null || nmsInternalOwnedKeySubject.isEmpty) {`,
2025
2070
  ` throw StateError('Nomos did not return the framework-owned device identity for .owned() custody');`,
@@ -2148,6 +2193,7 @@ export function generateDartDomainClient(
2148
2193
  ),
2149
2194
  ]),
2150
2195
  ` final bornAt = DateTime.now().toUtc().toIso8601String();`,
2196
+ ...birthKeyPrelude,
2151
2197
  ...ownedBirthPrelude,
2152
2198
  ...birthSealPrelude,
2153
2199
  ...(birthEncryption === undefined
@@ -2282,19 +2328,30 @@ export function generateDartDomainClient(
2282
2328
  const qOwned = qScopes.some((scope) => scope.owned === true);
2283
2329
  const qPrivate = needsReadPrincipal(q.returns);
2284
2330
  const qScopesExpr = scopesExpr(qScopes);
2331
+ // CAP-731: a declared rows watch whose keys the client does not re-derive per read merges
2332
+ // kernel row deltas (O(changed) per local write) instead of re-decoding the whole result.
2333
+ // Ordered reads keep complete results: their order is the law's, not the id's.
2334
+ const rowsWatch = (keys: string) =>
2335
+ `session.nmsWatchRows(${queryCall}, decode: (result) => ${decodeRows}(result, _ws), idOf: (row) => row.ref.id${keys}${
2336
+ qPrivate ? ", principal: actor" : ""
2337
+ })`;
2285
2338
  const nominalWatch = qOwned
2286
2339
  ? `session.nmsWatch(${queryCall}${
2287
2340
  qPrivate ? ", principal: actor" : ""
2288
2341
  }).asyncMap((result) async => _readWithOwnedKeys(${qScopesExpr}, (keys) async => ${decodeRows}(await session.nmsQuery(${queryCall}, keys${
2289
2342
  qPrivate ? ", actor" : ""
2290
2343
  }), _ws), (row) => row.ref, initial: await ${decodeRows}(result, _ws)))`
2344
+ : ordered !== undefined
2345
+ ? qScopes.length
2346
+ ? `Stream.fromFuture(_readKeys(${qScopesExpr})).asyncExpand((keys) => session.nmsWatch(${queryCall}, keys: keys${
2347
+ qPrivate ? ", principal: actor" : ""
2348
+ }).asyncMap((result) => ${decodeRows}(result, _ws)))`
2349
+ : `session.nmsWatch(${queryCall}${
2350
+ qPrivate ? ", principal: actor" : ""
2351
+ }).asyncMap((result) => ${decodeRows}(result, _ws))`
2291
2352
  : qScopes.length
2292
- ? `Stream.fromFuture(_readKeys(${qScopesExpr})).asyncExpand((keys) => session.nmsWatch(${queryCall}, keys: keys${
2293
- qPrivate ? ", principal: actor" : ""
2294
- }).asyncMap((result) => ${decodeRows}(result, _ws)))`
2295
- : `session.nmsWatch(${queryCall}${
2296
- qPrivate ? ", principal: actor" : ""
2297
- }).asyncMap((result) => ${decodeRows}(result, _ws))`;
2353
+ ? `Stream.fromFuture(_readKeys(${qScopesExpr})).asyncExpand((keys) => ${rowsWatch(", keys: keys")})`
2354
+ : rowsWatch("");
2298
2355
  if (ordered !== undefined) {
2299
2356
  const resultType = ordered.limit === 1 ? `${model}?` : `List<${model}>`;
2300
2357
  const select = ordered.limit === 1 ? "rows.isEmpty ? null : rows.single" : "rows";
@@ -3530,7 +3587,7 @@ export function generateDartDomainClient(
3530
3587
  ` final latest = events.isEmpty ? null : events.first;`,
3531
3588
  ` items = [`,
3532
3589
  ` NomosDescribeItem(kind: 'evidence', id: 'workspace', label: 'Workspace', summary: _ws, details: {'workspace': _ws}),`,
3533
- ` NomosDescribeItem(kind: 'evidence', id: 'law', label: 'Generated law', summary: domainHash, details: {'domainHash': domainHash}),`,
3590
+ ` NomosDescribeItem(kind: 'evidence', id: 'law', label: 'Generated law (unit set)', summary: domainHash, details: {'lawUnitSet': domainHash}),`,
3534
3591
  ` NomosDescribeItem(kind: 'evidence', id: 'reader', label: 'Verified reader', summary: this.actor ?? 'anonymous', details: {'actor': this.actor}),`,
3535
3592
  ` if (latest != null) NomosDescribeItem(kind: 'evidence', id: latest.eventId, label: 'Latest verified moment', summary: latest.eventId,`,
3536
3593
  ` details: {'occurredAt': latest.occurredAt, 'performedBy': latest.performedBy, 'domain': latest.domain, 'kind': latest.kind}),`,
@@ -3630,7 +3687,8 @@ export function generateDartDomainClient(
3630
3687
  ? [`export '${opts.frameworkImportUri}';`]
3631
3688
  : []),
3632
3689
  ``,
3633
- `/// Content identity of the installed typed USDA closure (transported as \`.law.usdz\`).`,
3690
+ `/// The law this client was generated against: its unit set identity (\`build/<name>.units.sha256\`).`,
3691
+ `/// Each workspace serves a closure its own kernel composes from those units; never sent with an offer.`,
3634
3692
  `const String ${hashConst} = ${dartStr(opts.domainHash)};`,
3635
3693
  ``,
3636
3694
  ...(opts.emitActivity === true
@@ -5,6 +5,7 @@ import { refusalContracts, refusalType, refusalDecoder } from "./codegen_refusal
5
5
  // application contract: business inputs in, business ids/models out. It deliberately has no public
6
6
  // session/session/workspace/actor/sync surface.
7
7
  import type { z } from "zod";
8
+ import { brandedIdMetaOf } from "./value_object.js";
8
9
 
9
10
  import type { AggregateHandle } from "./aggregate.js";
10
11
  import type { Directive } from "./directive.js";
@@ -153,6 +154,11 @@ function keyType(
153
154
  ) {
154
155
  return "GitObjectId";
155
156
  }
157
+ // A typed-id key (`t.brandedId(Name)` / `t.uuid()`) is its extension type, never a bare String.
158
+ if (field?.kind === "string" && !key.includes(".")) {
159
+ const brand = brandedIdMetaOf(field.zod as z.ZodTypeAny);
160
+ if (brand !== undefined) return brand;
161
+ }
156
162
  return "String";
157
163
  }
158
164
 
@@ -381,7 +387,7 @@ export function generateDartApplication(
381
387
  const source = aggregate.fields[name]!;
382
388
  const value = source.kind === "enum" ? `${access}?.wire`
383
389
  : source.kind === "ref" ? `${access}?.id`
384
- : source.kind === "string" && (isSha256DigestSchema(source.zod) || isGitObjectIdSchema(source.zod)) ? `${access}?.value`
390
+ : source.kind === "string" && (isSha256DigestSchema(source.zod) || isGitObjectIdSchema(source.zod) || brandedIdMetaOf(source.zod as z.ZodTypeAny) !== undefined) ? `${access}?.value`
385
391
  : access;
386
392
  return ` ${member(name)}: ${value},`;
387
393
  }),
@@ -900,7 +906,9 @@ export function generateDartApplication(
900
906
  aggregateId: string;
901
907
  namespace: string;
902
908
  rootId: string;
903
- keyField: string;
909
+ keyMode?: "minted" | "field";
910
+ keyField?: string;
911
+ keyType?: { type: string; brand: string };
904
912
  };
905
913
  birthsChild?: boolean;
906
914
  }
@@ -993,39 +1001,53 @@ export function generateDartApplication(
993
1001
  ` return _nmsReception.fetchWorkspace(reference.workspace);`,
994
1002
  ` }`,
995
1003
  ``,
996
- ` /// Open the ${birth.aggregateId} whose ${birth.keyField} is [key].`,
997
- ` /// THE OFFICE NAMES THE WORKSPACE this key addresses: the derivation is law,`,
998
- ` /// and a generated copy of it would silently address a different workspace`,
999
- ` /// the moment either side moved.`,
1000
- ` Future<${targetApplication}> open(String key) async => _nmsOpenReference(`,
1001
- ` NmsInternalNomosRefs.create<${rootType}>(`,
1002
- ` workspace: await _nmsReception.keyedBirthWorkspace(`,
1003
- ` namespace: ${JSON.stringify(birth.namespace)}, key: key,`,
1004
- ` ),`,
1005
- ` type: ${JSON.stringify(birth.aggregateId)},`,
1006
- ` id: ${JSON.stringify(birth.rootId)},`,
1007
- ` ),`,
1008
- ` );`,
1009
- ``,
1010
- ` /// Fetch the ${birth.aggregateId} whose ${birth.keyField} is [key] onto this device now,`,
1011
- ` /// opening and reading nothing: its placement's follow mounts the provider's head in`,
1012
- ` /// the background. Completes once that follow exists (e.g. prefetching every workspace`,
1013
- ` /// a list names, all at once, so each is local before it is tapped).`,
1014
- ` Future<void> fetch(String key) async => _nmsReception.fetchWorkspace(`,
1015
- ` await _nmsReception.keyedBirthWorkspace(`,
1016
- ` namespace: ${JSON.stringify(birth.namespace)}, key: key,`,
1017
- ` ),`,
1018
- ` );`,
1019
- ``,
1020
- ` /// Kernel-owned progress updates while [open] prepares this workspace.`,
1021
- ` /// ONE LANE: the stream is the whole surface. A synchronous snapshot`,
1022
- ` /// would have to answer before the office has named the workspace, so`,
1023
- ` /// it could only ever answer null or hold a cached name of its own.`,
1024
- ` Stream<ApplicationWorkspaceOpenProgress> watchOpenProgress(String key) =>`,
1025
- ` Stream<String>.fromFuture(_nmsReception.keyedBirthWorkspace(`,
1026
- ` namespace: ${JSON.stringify(birth.namespace)}, key: key,`,
1027
- ` )).asyncExpand(_nmsReception.streams.workspaceOpenProgress);`,
1028
- ``,
1004
+ // Open/fetch BY KEY exist only for a birth keyed by a declared business fact. A minted birth
1005
+ // (the default) has no business key: its address is the NomosRef its birth returned.
1006
+ // The key param is the key field's TYPED ID (its extension type, declared with the birth payload in
1007
+ // this domain's types); `.value` is its wire string.
1008
+ ...(birth.keyField === undefined
1009
+ ? []
1010
+ : (() => {
1011
+ const typedKey = birth.keyType !== undefined &&
1012
+ (birth.keyType.type === "brandedId" || birth.keyType.type === "uuid");
1013
+ const keyParam = typedKey ? `${birth.keyType!.brand} key` : "String key";
1014
+ const keyWire = typedKey ? "key.value" : "key";
1015
+ return [
1016
+ ` /// Open the ${birth.aggregateId} whose ${birth.keyField} is [key].`,
1017
+ ` /// THE OFFICE NAMES THE WORKSPACE this key addresses: the derivation is law,`,
1018
+ ` /// and a generated copy of it would silently address a different workspace`,
1019
+ ` /// the moment either side moved.`,
1020
+ ` Future<${targetApplication}> open(${keyParam}) async => _nmsOpenReference(`,
1021
+ ` NmsInternalNomosRefs.create<${rootType}>(`,
1022
+ ` workspace: await _nmsReception.keyedBirthWorkspace(`,
1023
+ ` namespace: ${JSON.stringify(birth.namespace)}, key: ${keyWire},`,
1024
+ ` ),`,
1025
+ ` type: ${JSON.stringify(birth.aggregateId)},`,
1026
+ ` id: ${JSON.stringify(birth.rootId)},`,
1027
+ ` ),`,
1028
+ ` );`,
1029
+ ``,
1030
+ ` /// Fetch the ${birth.aggregateId} whose ${birth.keyField} is [key] onto this device now,`,
1031
+ ` /// opening and reading nothing: its placement's follow mounts the provider's head in`,
1032
+ ` /// the background. Completes once that follow exists (e.g. prefetching every workspace`,
1033
+ ` /// a list names, all at once, so each is local before it is tapped).`,
1034
+ ` Future<void> fetch(${keyParam}) async => _nmsReception.fetchWorkspace(`,
1035
+ ` await _nmsReception.keyedBirthWorkspace(`,
1036
+ ` namespace: ${JSON.stringify(birth.namespace)}, key: ${keyWire},`,
1037
+ ` ),`,
1038
+ ` );`,
1039
+ ``,
1040
+ ` /// Kernel-owned progress updates while [open] prepares this workspace.`,
1041
+ ` /// ONE LANE: the stream is the whole surface. A synchronous snapshot`,
1042
+ ` /// would have to answer before the office has named the workspace, so`,
1043
+ ` /// it could only ever answer null or hold a cached name of its own.`,
1044
+ ` Stream<ApplicationWorkspaceOpenProgress> watchOpenProgress(${keyParam}) =>`,
1045
+ ` Stream<String>.fromFuture(_nmsReception.keyedBirthWorkspace(`,
1046
+ ` namespace: ${JSON.stringify(birth.namespace)}, key: ${keyWire},`,
1047
+ ` )).asyncExpand(_nmsReception.streams.workspaceOpenProgress);`,
1048
+ ``,
1049
+ ];
1050
+ })()),
1029
1051
  ]);
1030
1052
  addCollectionMember(birth.aggregateId, "mine", [
1031
1053
  ` /// The ${birth.aggregateId} lives founded by the signed-in person.`,
@@ -1907,6 +1929,7 @@ export function generateDartApplication(
1907
1929
  `// AUTO-GENERATED by nomos-compile — the tenant application surface for "${domain}".`,
1908
1930
  `// Business facts in, business records out. Runtime lifecycle is deliberately absent.`,
1909
1931
  `export 'package:nomos_client/src/write_progress.dart' show NomosWrite, NomosWriteProgress, NomosWritePhase;`,
1932
+ `export 'package:nomos_client/src/rows_watch.dart' show NomosRows, NomosRowProjection, NomosRowsProjectionStream;`,
1910
1933
  ...((mod.workspaceInvariants ?? []).some(rule => rule.refusal) ? [
1911
1934
  `export 'package:nomos_types/nomos_types.dart' show AdmissionDecision, Admitted, DomainRefused, FrameworkRefused, UnsupportedRefusalContract, NomosDomainRefusalEvidence, KernelRefusalCode;`,
1912
1935
  ] : []),
@@ -1,3 +1,4 @@
1
+ import { assertDomainRequirements, type DomainUnitRequirement } from "./domain_requirements.js";
1
2
  import type { z } from "zod";
2
3
 
3
4
  import type { Directive } from "./directive.js";
@@ -173,15 +174,25 @@ export function dartFrameworkOperationDirectiveIds(
173
174
  export function generateDartApplicationFrameworkCalls(
174
175
  laws: readonly DartFrameworkCallLaw[],
175
176
  descriptor: {
176
- /** The compiled law package identity — the descriptor's own closure, `currentLaw[<application-home domain>]`. */
177
- readonly lawSha256: string;
177
+ /** THE LAW the application is compiled as: the unit set identity of `build/<name>.units` (CAP-773). */
178
+ readonly lawUnitSet: string;
179
+ /** The compiled package closure (`build/<name>.law.usdz`): the bytes a device stages. Not what a target serves. */
180
+ readonly lawPackageSha256: string;
181
+ /**
182
+ * THE PER-DOMAIN REQUIREMENTS (CAP-666): per application domain key, its unit hash and the identity of its
183
+ * generated compatibility requirement. Absent or empty emits an empty table (a package with no application
184
+ * interface); the getter itself is always emitted, since the runtime interface is exhaustive.
185
+ */
186
+ readonly domainRequirements?: readonly DomainUnitRequirement[];
178
187
  },
179
188
  ): string | undefined {
180
- if (!/^[0-9a-f]{64}$/.test(descriptor.lawSha256)) {
181
- throw new Error(
182
- `Dart framework call codegen: the application birth contract needs the compiled law package sha256 identity, not '${descriptor.lawSha256}'`,
183
- );
189
+ for (const [name, value] of [["law unit set", descriptor.lawUnitSet], ["package", descriptor.lawPackageSha256]] as const) {
190
+ if (!/^[0-9a-f]{64}$/.test(value)) {
191
+ throw new Error(`Dart framework call codegen: the application's ${name} identity must be 64 lowercase hex, not '${value}'`);
192
+ }
184
193
  }
194
+ const domainRequirements = [...(descriptor.domainRequirements ?? [])].sort((a, b) => a.domainKey < b.domainKey ? -1 : a.domainKey > b.domainKey ? 1 : 0);
195
+ assertDomainRequirements(domainRequirements);
185
196
  const frameworkEnumDartTypes = collectFixedFrameworkDartEnums().dartTypes;
186
197
  const fixedEmitted = FIXED_CALLS.flatMap((definition) => laws.flatMap((law) => {
187
198
  const declares = law.graph.nodes.some((node) =>
@@ -619,8 +630,8 @@ export function generateDartApplicationFrameworkCalls(
619
630
  `import 'dart:typed_data' show Uint8List;`,
620
631
  `import 'package:fixnum/fixnum.dart' show Int64;`,
621
632
  `import 'operation_payloads.pb.dart' as _nmsop;`,
622
- `import 'package:nomos_types/nomos_kernel_protocol.dart' show ApplicationBirthContract, BirthCertificate;`,
623
- `import 'package:nomos_types/nomos_types.dart' show NomosSlot, Sha256Digest;`,
633
+ `import 'package:nomos_types/nomos_kernel_protocol.dart' show BirthCertificate;`,
634
+ `import 'package:nomos_types/nomos_types.dart' show NomosDomainRequirement, NomosSlot, Sha256Digest;`,
624
635
  `import 'package:nomos_client/src/application_framework_calls.dart';`,
625
636
  `import 'package:nomos_client/src/generated_call.dart';`,
626
637
  ``,
@@ -646,8 +657,19 @@ export function generateDartApplicationFrameworkCalls(
646
657
  ` @override`,
647
658
  ` String get parentModelAuthoritySeatId => ${JSON.stringify(PARENT_MODEL_AUTHORITY_SEAT_ID)};`,
648
659
  ` @override`,
649
- ` ApplicationBirthContract get applicationBirthContract =>`,
650
- ` ApplicationBirthContract(lawSha256: ${JSON.stringify(descriptor.lawSha256)});`,
660
+ ` String get lawUnitSet => ${JSON.stringify(descriptor.lawUnitSet)};`,
661
+ ` @override`,
662
+ ` String get lawPackageSha256 => ${JSON.stringify(descriptor.lawPackageSha256)};`,
663
+ ` @override`,
664
+ ...(domainRequirements.length === 0
665
+ ? [` Map<String, NomosDomainRequirement> get domainRequirements => const {};`]
666
+ : [
667
+ ` Map<String, NomosDomainRequirement> get domainRequirements => const {`,
668
+ ...domainRequirements.map((requirement) =>
669
+ ` ${JSON.stringify(requirement.domainKey)}: NomosDomainRequirement(domainKey: ${JSON.stringify(requirement.domainKey)}, `
670
+ + `unitSha256: ${JSON.stringify(requirement.unitSha256)}, requirementSha256: ${JSON.stringify(requirement.requirementSha256)}),`),
671
+ ` };`,
672
+ ]),
651
673
  ``,
652
674
  ...methods,
653
675
  `}`,
@@ -123,6 +123,7 @@ export function generateDartScenario(
123
123
  `import 'package:nomos_client/src/reception/reception_session.dart' show NmsReceptionUser, NmsReceptionSessionView;`,
124
124
  `import 'package:nomos_client/src/nms_generated_runtime.dart' show NomosSession;`,
125
125
  `import 'package:nomos_client/src/nms_test_environment.dart' show NomosTestCloud, NomosTestHarness, NomosTestFrontier;`,
126
+ `import 'package:nomos_flutter/testing.dart' show registerNomosNativeTestPeerHost;`,
126
127
  ``,
127
128
  `// The office names the framework's identity anchor; a scenario never re-derives it.`,
128
129
  `Future<NmsReceptionSessionView> _nmsTestReception(`,
@@ -212,6 +213,9 @@ export function generateDartScenario(
212
213
  ` void Function(Future<void> Function())? registerTearDown,`,
213
214
  ` }) async {`,
214
215
  ` final person = signedInAs ?? ${testPerson}.named('test');`,
216
+ ` // Make the Apple native test peer host available; NOMOS_TEST_PEER_HOST=native selects it.`,
217
+ ` // Registering never selects it, so the default Node peer is unchanged.`,
218
+ ` registerNomosNativeTestPeerHost();`,
215
219
  ` final packageFiles = await _applicationPackages();`,
216
220
  ` final environment = await NomosTestHarness.ephemeral(`,
217
221
  ` lawUsdzPaths: packageFiles.map((file) => file.path).toList(growable: false),`,
@@ -244,6 +248,9 @@ export function generateDartScenario(
244
248
  ` if (serverEntry == null || serverEntry.isEmpty) {`,
245
249
  ` throw StateError('Connected scenarios require the Nomos test cloud. Run this test through githolon test.');`,
246
250
  ` }`,
251
+ ` // Make the Apple native test peer host available; NOMOS_TEST_PEER_HOST=native selects it.`,
252
+ ` // Registering never selects it, so the default Node peer is unchanged.`,
253
+ ` registerNomosNativeTestPeerHost();`,
247
254
  ` final applicationName = 'test-application-\${DateTime.now().microsecondsSinceEpoch}';`,
248
255
  ` final packageFiles = await _applicationPackages();`,
249
256
  ` final cloud = await NomosTestCloud.start(`,
@@ -32,6 +32,7 @@
32
32
  * Deterministic codegen: the emitted bytes are a pure function of the modules +
33
33
  * package name + domain hash — no timestamps or generation-time randomness.
34
34
  */
35
+ import { BUSINESS_BIRTH_KEY_PAYLOAD_FIELD } from "./births.js";
35
36
  import type { z } from "zod";
36
37
 
37
38
  import { BUSINESS_BIRTH_SEED_PAYLOAD_FIELD, type AggregateHandle } from "./aggregate.js";
@@ -2836,10 +2837,12 @@ function attachBusinessEncryption(
2836
2837
  const directive = (mod?.directives as Directive<unknown>[] | undefined)?.find(
2837
2838
  (candidate) => candidate.id === leg.birthDirectiveId,
2838
2839
  ) as (Directive<unknown> & {
2839
- businessBirth?: { namespace?: string; keyField?: string };
2840
+ businessBirth?: { namespace?: string; keyField?: string; keyMode?: string };
2840
2841
  }) | undefined;
2841
2842
  const namespace = directive?.businessBirth?.namespace;
2842
- const keyField = directive?.businessBirth?.keyField;
2843
+ // A minted birth's key rides the hidden reserved-key field (CAP-804).
2844
+ const keyField = directive?.businessBirth?.keyField
2845
+ ?? (directive?.businessBirth?.keyMode === "minted" ? BUSINESS_BIRTH_KEY_PAYLOAD_FIELD : undefined);
2843
2846
  const offered = packageInterface[leg.parentDomain];
2844
2847
  const offeredAggregate = offered === undefined
2845
2848
  ? undefined