@githolon/dsl 0.103.12 → 0.103.13

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 (43) hide show
  1. package/dart/CHANGELOG.md +5 -0
  2. package/dart/lib/src/runner/node-entry.mjs +234 -170
  3. package/dart/lib/src/runner/offline-install.mjs +87 -112
  4. package/dart/lib/src/webview_bridge.dart +11 -11
  5. package/dart/pubspec.yaml +1 -1
  6. package/dart/test/webview_bridge_test.dart +10 -7
  7. package/dart_types/CHANGELOG.md +1 -1
  8. package/dart_types/lib/nomos_types.dart +4 -4
  9. package/dart_types/lib/src/versioned_nomos_ref.dart +13 -11
  10. package/package.json +1 -1
  11. package/runtime/generated-application-host.js +424 -237
  12. package/src/aggregate.ts +3 -1
  13. package/src/build_package.ts +77 -6
  14. package/src/check_package_main.ts +39 -16
  15. package/src/codegen_dart.ts +185 -48
  16. package/src/codegen_dart_application.ts +82 -16
  17. package/src/codegen_dart_client.ts +38 -10
  18. package/src/codegen_dot.ts +3 -3
  19. package/src/codegen_proof.ts +24 -2
  20. package/src/codegen_ts.ts +32 -19
  21. package/src/compile_package_main.ts +68 -11
  22. package/src/derived.ts +17 -0
  23. package/src/directive.ts +65 -0
  24. package/src/engine_entry.ts +22 -2
  25. package/src/enum_type.ts +71 -0
  26. package/src/fields.ts +135 -5
  27. package/src/flow_gate.ts +2 -1
  28. package/src/framework/workspaces.ts +73 -0
  29. package/src/idof_lint.ts +5 -1
  30. package/src/index.ts +20 -0
  31. package/src/kernel_capabilities.ts +12 -1
  32. package/src/law_check.ts +107 -28
  33. package/src/law_graph.ts +151 -20
  34. package/src/law_graph_usda.ts +23 -0
  35. package/src/law_value_type.ts +10 -3
  36. package/src/lsp_server.ts +26 -16
  37. package/src/package_config.ts +112 -9
  38. package/src/package_feature_topology.ts +174 -0
  39. package/src/plan_source_slice.ts +6 -0
  40. package/src/report.ts +6 -0
  41. package/src/stable_ids_for_module.ts +1 -0
  42. package/src/value_object.ts +9 -2
  43. package/src/workspace_sharding.ts +4 -4
@@ -29,6 +29,7 @@
29
29
  * generated, so they stay reproducible from the domain definitions.
30
30
  */
31
31
  import { z } from "zod";
32
+ import { enumTypeNameOf } from "./enum_type.js";
32
33
  import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf, refOfMetaOf, versionedRefOfMetaOf, brandedIdMetaOf, NOMOS_UNIT_BRAND } from "./value_object.js";
33
34
  import { recursiveTypeBaseName, zodUnwrapLazy } from "./zod_lazy.js";
34
35
  import type { AggregateHandle } from "./aggregate.js";
@@ -46,7 +47,7 @@ import { finishSum, type AnySum } from "./sum.js";
46
47
  import { finishExtremum, type AnyExtremum } from "./extremum.js";
47
48
  import { finishExists, type AnyExists } from "./exists.js";
48
49
  import type { OrderedReadDecl } from "./ordered_read.js";
49
- import type { DerivedDecl } from "./derived.js";
50
+ import type { AnyDerivedDecl } from "./derived.js";
50
51
  import type { CombinedDecl } from "./combined.js";
51
52
  import { emitProviderSdk, type ImpureCapabilityDecl } from "./codegen_provider_dart.js";
52
53
  import type { WorkspaceTypeDecl } from "./workspace_type.js";
@@ -69,7 +70,10 @@ export function dartActionInputClassName(directive: AnyDirective): string {
69
70
  if (businessBirth?.aggregateId !== undefined && !isBusinessIntent(directive)) {
70
71
  return `Create${concreteClassName(businessBirth.aggregateId)}Input`;
71
72
  }
72
- return `${cap(directive.id)}${isBusinessIntent(directive) ? "Input" : "Payload"}`;
73
+ const suffix = isBusinessIntent(directive)
74
+ ? directive.targetBinding === undefined ? "Input" : "Facts"
75
+ : "Payload";
76
+ return `${cap(directive.id)}${suffix}`;
73
77
  }
74
78
 
75
79
  // ---- Zod payload introspection --------------------------------------------
@@ -564,7 +568,7 @@ function introspectField(
564
568
  return { name, dartType: "bool", toJson: "scalar" };
565
569
  case "ZodEnum": {
566
570
  const values = zodEnumValues(zt);
567
- const key = values.join("|");
571
+ const key = dartEnumLookupKey(zt);
568
572
  const dartType = enumDartTypes.get(key) ?? `${cap(name)}Enum`;
569
573
  return { name, dartType, toJson: "enum", enumValues: values };
570
574
  }
@@ -695,6 +699,8 @@ interface AggFieldSpec {
695
699
  * fields like a create's id/name).
696
700
  */
697
701
  optional: boolean;
702
+ /** Native read fields which should be preferred for age/status decisions over this raw clock. */
703
+ temporalEnumIds?: readonly string[];
698
704
  /**
699
705
  * For a `map`-kind field: the inner VALUE field's kind (`field.mapValueKind`). A
700
706
  * `"json"`-valued map (`t.map(t.json())`) is surfaced as `Map<String, Object?>` (each
@@ -1117,8 +1123,9 @@ function aggFieldSpec(
1117
1123
  return { name, wireName, kind: field.kind, dartType: "bool", optional };
1118
1124
  case "enum": {
1119
1125
  // Pull the enum values out of the Zod schema (unwrapping `.optional()`).
1120
- const values = zodEnumValues(unwrapZod(field.zod));
1121
- const key = values.join("|");
1126
+ const schema = unwrapZod(field.zod);
1127
+ const values = zodEnumValues(schema);
1128
+ const key = dartEnumLookupKey(schema);
1122
1129
  const dartType = enumDartTypes.get(key) ?? `${dartTypeNameFromField(wireName)}Enum`;
1123
1130
  return { name, wireName, kind: field.kind, dartType, enumValues: values, optional };
1124
1131
  }
@@ -1202,14 +1209,17 @@ function aggregateFieldSpecs(
1202
1209
  : f.isFromBirth === true
1203
1210
  ? { ...base, optional: false }
1204
1211
  : { ...base, optional: true };
1212
+ const temporalSpec: AggFieldSpec = (f.temporalEnums?.length ?? 0) > 0
1213
+ ? { ...baseSpec, temporalEnumIds: f.temporalEnums!.map((read) => read.id) }
1214
+ : baseSpec;
1205
1215
  // SURFACED MERGE CONFLICTS (architecture/surfaced_merge_conflict.md): a `.merge(Surfaced(...))`
1206
1216
  // field types `SurfacedValue<T>?` and decodes via `SurfacedValue.decode`. ALWAYS nullable —
1207
1217
  // even `.fromBirth()` (the wrapper's pending arm has no non-null plain-scalar reading).
1208
1218
  // aggregate() already v1-scoped the driver to Lww-class scalar kinds; excludes nullAdmits.
1209
1219
  const surfacedSpec: AggFieldSpec =
1210
1220
  (f.driver as { kind?: string } | undefined)?.kind === "Surfaced"
1211
- ? { ...baseSpec, optional: true, surfaced: true }
1212
- : baseSpec;
1221
+ ? { ...temporalSpec, optional: true, surfaced: true }
1222
+ : temporalSpec;
1213
1223
  // NULL SEMANTICS (null_semantics.md): a null-admitting scalar carries the tri-state slot.
1214
1224
  const nmeta = nullMetaOf(f);
1215
1225
  const spec: AggFieldSpec = admitsNull(f)
@@ -1290,12 +1300,14 @@ function dartNameStem(aggregateType: string): string {
1290
1300
  }
1291
1301
 
1292
1302
  /** Collect all enum value-sets across aggregates + directives, name each ONCE. */
1293
- function collectEnums(
1303
+ export function collectDartEnums(
1294
1304
  aggregates: AggregateHandle[],
1295
1305
  directives: AnyDirective[],
1296
1306
  domainKey: string,
1307
+ valueObjects: readonly ValueObjectDescriptor[] = [],
1308
+ deriveds: readonly AnyDerivedDecl[] = [],
1297
1309
  ): { dartTypes: Map<string, string>; decls: string[] } {
1298
- const dartTypes = new Map<string, string>(); // "a|b|c" -> DartEnumName
1310
+ const dartTypes = new Map<string, string>(); // structural values or declared nominal identity -> DartEnumName
1299
1311
  const usedNames = new Set<string>(); // every emitted enum name (collision guard)
1300
1312
  const decls: string[] = [];
1301
1313
  // Generated enum types are DOMAIN-QUALIFIED (`EstateStatusEnum`,
@@ -1310,10 +1322,20 @@ function collectEnums(
1310
1322
  // shared across owners keeps the FIRST owner's name (one identical type, aggregates register
1311
1323
  // before payloads so the aggregate's name wins). Numeric suffixes survive only as the
1312
1324
  // last-resort collision guard — with owner-qualified bases they should never fire.
1313
- const register = (preferredName: string, values: readonly string[], owner = "") => {
1314
- const key = values.join("|");
1325
+ const register = (
1326
+ preferredName: string,
1327
+ values: readonly string[],
1328
+ owner = "",
1329
+ declaredName?: string,
1330
+ ) => {
1331
+ // Anonymous enums remain structural: the same wire vocabulary can share one
1332
+ // generated Dart type. A declared enumType is nominal: its exported name is
1333
+ // part of its identity, so two business concepts never collapse merely because
1334
+ // their current wire values happen to be equal.
1335
+ const key = dartEnumLookupKey(values, declaredName);
1315
1336
  if (dartTypes.has(key)) return;
1316
- const base = `${domainPrefix}${dartTypeNameFromField(owner)}${dartTypeNameFromField(preferredName)}`;
1337
+ const base = declaredName ??
1338
+ `${domainPrefix}${dartTypeNameFromField(owner)}${dartTypeNameFromField(preferredName)}`;
1317
1339
  let name = base;
1318
1340
  let n = 1;
1319
1341
  while (usedNames.has(name)) {
@@ -1336,29 +1358,83 @@ function collectEnums(
1336
1358
  `}`,
1337
1359
  );
1338
1360
  };
1339
- for (const agg of aggregates) {
1340
- for (const [name, field] of Object.entries(
1341
- agg.fields as Record<string, Field>,
1342
- )) {
1343
- if (field.kind === "enum") {
1344
- // Unwrap `.optional()` so an optional enum field still registers its options.
1345
- register(name, zodEnumValues(unwrapZod(field.zod)), agg.id);
1361
+ // Named enum declarations win before anonymous occurrence-derived names, irrespective of source
1362
+ // or aggregate ordering. Reorganising files therefore cannot rename a declared public type.
1363
+ for (const descriptor of valueObjects) {
1364
+ for (const field of voEnumFields(descriptor)) {
1365
+ if (field.declaredName !== undefined) {
1366
+ register(field.fieldName, field.values, field.owner ?? descriptor.name, field.declaredName);
1346
1367
  }
1347
1368
  }
1348
1369
  }
1349
- for (const d of directives) {
1350
- if (zodTypeName(d.payloadSchema as z.ZodTypeAny) !== "ZodObject") continue;
1351
- const shape = zodObjectShape(d.payloadSchema as z.ZodTypeAny);
1352
- for (const [name, zt] of Object.entries(shape)) {
1353
- const inner = unwrapZod(zt as z.ZodTypeAny);
1354
- if (zodTypeName(inner) === "ZodEnum") {
1355
- register(name, zodEnumValues(inner), (d as { aggregateId?: string }).aggregateId ?? d.id);
1370
+ for (const namedPass of [true, false]) {
1371
+ for (const agg of aggregates) {
1372
+ for (const [name, field] of Object.entries(
1373
+ agg.fields as Record<string, Field>,
1374
+ )) {
1375
+ if (field.kind === "enum") {
1376
+ const schema = unwrapZod(field.zod);
1377
+ const declaredName = enumTypeNameOf(schema);
1378
+ if ((declaredName !== undefined) !== namedPass) continue;
1379
+ register(name, zodEnumValues(schema), agg.id, declaredName);
1380
+ }
1381
+ }
1382
+ }
1383
+ for (const d of directives) {
1384
+ if (zodTypeName(d.payloadSchema as z.ZodTypeAny) !== "ZodObject") continue;
1385
+ const shape = zodObjectShape(d.payloadSchema as z.ZodTypeAny);
1386
+ for (const [name, zt] of Object.entries(shape)) {
1387
+ const inner = unwrapZod(zt as z.ZodTypeAny);
1388
+ if (zodTypeName(inner) === "ZodEnum") {
1389
+ const declaredName = enumTypeNameOf(inner);
1390
+ if ((declaredName !== undefined) !== namedPass) continue;
1391
+ register(
1392
+ name,
1393
+ zodEnumValues(inner),
1394
+ (d as { aggregateId?: string }).aggregateId ?? d.id,
1395
+ declaredName,
1396
+ );
1397
+ }
1356
1398
  }
1357
1399
  }
1358
1400
  }
1401
+ for (const declaration of deriveds) {
1402
+ if ("kind" in declaration && declaration.kind === "temporalEnum") {
1403
+ register(
1404
+ declaration.id,
1405
+ zodEnumValues(declaration.returns),
1406
+ declaration.of,
1407
+ );
1408
+ }
1409
+ }
1359
1410
  return { dartTypes, decls };
1360
1411
  }
1361
1412
 
1413
+ /** Resolve an enum field to the exact nominal Dart type emitted for its value set. */
1414
+ export function dartEnumTypeForField(
1415
+ field: Field | undefined,
1416
+ dartTypes: ReadonlyMap<string, string>,
1417
+ ): string | undefined {
1418
+ if (field?.kind !== "enum") return undefined;
1419
+ return dartTypes.get(dartEnumLookupKey(unwrapZod(field.zod)));
1420
+ }
1421
+
1422
+ function dartEnumLookupKey(
1423
+ schemaOrValues: z.ZodTypeAny | readonly string[],
1424
+ declaredName?: string,
1425
+ ): string {
1426
+ if (Array.isArray(schemaOrValues)) {
1427
+ return declaredName === undefined
1428
+ ? `values:${schemaOrValues.join("|")}`
1429
+ : `declared:${declaredName}`;
1430
+ }
1431
+ const schema = unwrapZod(schemaOrValues as z.ZodTypeAny);
1432
+ const name = enumTypeNameOf(schema);
1433
+ return name === undefined
1434
+ ? `values:${zodEnumValues(schema).join("|")}`
1435
+ : `declared:${name}`;
1436
+ }
1437
+
1362
1438
  /** Dart reserved words that cannot be bare identifiers (enum members / fields). A
1363
1439
  * value like `"new"` (feedback status) collides, so it is suffixed (`new_`). */
1364
1440
  const DART_RESERVED = new Set([
@@ -1584,10 +1660,17 @@ function emitAggregate(
1584
1660
  : []),
1585
1661
  ...specs.flatMap((s) => {
1586
1662
  const doc = s.nullReason !== undefined ? [` /// null semantic: ${s.nullReason}`] : [];
1663
+ const temporalGuidance = s.temporalEnumIds === undefined
1664
+ ? []
1665
+ : [
1666
+ ` /// Raw captured timestamp. Prefer ${s.temporalEnumIds.map((id) => `[${id}]`).join(" or ")} for age-based business/UI decisions.`,
1667
+ ` @Deprecated('Use ${s.temporalEnumIds.join(" or ")} for age/freshness decisions; this raw timestamp is for display and audit.')`,
1668
+ ];
1587
1669
  // SURFACED MERGE CONFLICTS: the field reads as the plain scalar, or [SurfacedPending]
1588
1670
  // while genuinely concurrent writes await a business merge (resolve = an ordinary next write).
1589
1671
  if (s.surfaced === true) {
1590
1672
  return [
1673
+ ...temporalGuidance,
1591
1674
  ` /// Surfaced merge conflicts: [SurfacedResolved] steady state, [SurfacedPending] under conflict.`,
1592
1675
  ` final SurfacedValue<${s.dartType}>? ${s.name};`,
1593
1676
  ];
@@ -1808,9 +1891,9 @@ const geometryFactoryNameDart: Record<string, string> = {
1808
1891
  * IDENTITY = THE VO FIELD, not the value set: two identical value-sets in DIFFERENT
1809
1892
  * VOs stay separate types (VO names are package-unique, so so are these).
1810
1893
  */
1811
- function voEnumName(voName: string, fieldName: string): string {
1894
+ function voEnumName(voName: string, fieldName: string, declaredName?: string): string {
1812
1895
  // SEMANTIC NAME (co2 feedback): `<VoName><Field>` — `SiteLocationSource`, not `…SourceEnum`.
1813
- return `${voName}${dartTypeNameFromField(fieldName)}`;
1896
+ return declaredName ?? `${voName}${dartTypeNameFromField(fieldName)}`;
1814
1897
  }
1815
1898
 
1816
1899
  /** The tolerant-decode SENTINEL case name — dodge a literal enum value that would
@@ -1825,8 +1908,8 @@ function voEnumSentinel(values: readonly string[]): string {
1825
1908
  * a new value added by a later law must NOT crash an old client): `fromWire` maps an
1826
1909
  * unrecognized wire string to the [sentinel] case instead of throwing (the deliberate
1827
1910
  * divergence from the strict aggregate-enum `fromWire`; VO data crosses law versions). */
1828
- function emitVoEnum(voName: string, fieldName: string, values: readonly string[]): string {
1829
- const name = voEnumName(voName, fieldName);
1911
+ function emitVoEnum(voName: string, fieldName: string, values: readonly string[], declaredName?: string): string {
1912
+ const name = voEnumName(voName, fieldName, declaredName);
1830
1913
  const sentinel = voEnumSentinel(values);
1831
1914
  const variants = values.map((v) => ` ${dartIdent(v)}('${v}')`).join(",\n");
1832
1915
  return [
@@ -1852,10 +1935,25 @@ function emitVoEnum(voName: string, fieldName: string, values: readonly string[]
1852
1935
 
1853
1936
  /** Every (fieldName, values) enum pair a VO descriptor carries (arrays recursed —
1854
1937
  * an array-of-enum field's element enum is named by the FIELD). */
1855
- function voEnumFields(d: ValueObjectDescriptor): { fieldName: string; values: readonly string[]; owner?: string }[] {
1856
- const out: { fieldName: string; values: readonly string[]; owner?: string }[] = [];
1938
+ function voEnumFields(d: ValueObjectDescriptor): {
1939
+ fieldName: string;
1940
+ values: readonly string[];
1941
+ owner?: string;
1942
+ declaredName?: string;
1943
+ }[] {
1944
+ const out: {
1945
+ fieldName: string;
1946
+ values: readonly string[];
1947
+ owner?: string;
1948
+ declaredName?: string;
1949
+ }[] = [];
1857
1950
  const walk = (fieldName: string, f: ValueObjectFieldDescriptor, owner?: string): void => {
1858
- if (f.type === "enum") out.push({ fieldName, values: f.values, ...(owner !== undefined ? { owner } : {}) });
1951
+ if (f.type === "enum") out.push({
1952
+ fieldName,
1953
+ values: f.values,
1954
+ ...(owner !== undefined ? { owner } : {}),
1955
+ ...(f.name === undefined ? {} : { declaredName: f.name }),
1956
+ });
1859
1957
  else if (f.type === "array") walk(fieldName, f.items, owner);
1860
1958
  else if (f.type === "map") walk(fieldName, f.values, owner);
1861
1959
  };
@@ -1891,7 +1989,7 @@ function dartTypeOfVoField(f: ValueObjectFieldDescriptor, voName: string, fieldN
1891
1989
  return "bool";
1892
1990
  case "enum":
1893
1991
  // A REAL generated Dart enum (`emitVoEnum`), named by the owning VO field.
1894
- return voEnumName(voName, fieldName);
1992
+ return voEnumName(voName, fieldName, f.name);
1895
1993
  case "literal":
1896
1994
  return "String"; // surfaced as a constant getter, not a ctor param
1897
1995
  case "array":
@@ -1923,7 +2021,7 @@ function voFromExpr(
1923
2021
  return f.brand !== undefined ? `${f.brand}(${expr} as String)` : `${expr} as String`;
1924
2022
  case "enum":
1925
2023
  // TOLERANT typed decode — an unknown wire value becomes the sentinel case.
1926
- return `${voEnumName(voName, fieldName)}.fromWire(${expr} as String)`;
2024
+ return `${voEnumName(voName, fieldName, f.name)}.fromWire(${expr} as String)`;
1927
2025
  case "number":
1928
2026
  if (f.unit !== undefined) return `${f.unit}((${expr} as num).toDouble())`;
1929
2027
  return f.int === true ? `(${expr} as num).toInt()` : `(${expr} as num).toDouble()`;
@@ -3316,7 +3414,7 @@ function parseReadModelField(s: AggFieldSpec, aggregate: string): string {
3316
3414
  function emitReadModel(
3317
3415
  agg: AggregateHandle,
3318
3416
  enumDartTypes: Map<string, string>,
3319
- deriveds: DerivedDecl[] = [],
3417
+ deriveds: AnyDerivedDecl[] = [],
3320
3418
  combineds: CombinedDecl[] = [],
3321
3419
  refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
3322
3420
  ownIdFields: ReadonlySet<string> = new Set(),
@@ -3347,21 +3445,32 @@ function emitReadModel(
3347
3445
  ` final NomosRef<${concreteClassName(agg.id)}> ref;`,
3348
3446
  ...specs.flatMap((s) => {
3349
3447
  const doc = s.nullReason !== undefined ? [` /// null semantic: ${s.nullReason}`] : [];
3448
+ const temporalGuidance = s.temporalEnumIds === undefined
3449
+ ? []
3450
+ : [
3451
+ ` /// Raw captured timestamp. Prefer ${s.temporalEnumIds.map((id) => `[${id}]`).join(" or ")} for age-based business/UI decisions.`,
3452
+ ` @Deprecated('Use ${s.temporalEnumIds.join(" or ")} for age/freshness decisions; this raw timestamp is for display and audit.')`,
3453
+ ];
3350
3454
  // SURFACED MERGE CONFLICTS: pending observations surface typed; resolve = ordinary next write.
3351
3455
  if (s.surfaced === true) {
3352
3456
  return [
3457
+ ...temporalGuidance,
3353
3458
  ` /// Surfaced merge conflicts: [SurfacedResolved] steady state, [SurfacedPending] under conflict.`,
3354
3459
  ` final SurfacedValue<${readModelDartType(s)}>? ${s.name};`,
3355
3460
  ];
3356
3461
  }
3357
3462
  return s.nullAdmits === true
3358
- ? [...doc, ` final NomosSlot<${stripNullable(readModelDartType(s))}> ${s.name};`]
3359
- : [` final ${readModelDartType(s)}${readModelNullableSuffix(s)} ${s.name};`];
3463
+ ? [...doc, ...temporalGuidance, ` final NomosSlot<${stripNullable(readModelDartType(s))}> ${s.name};`]
3464
+ : [...temporalGuidance, ` final ${readModelDartType(s)}${readModelNullableSuffix(s)} ${s.name};`];
3465
+ }),
3466
+ ...deriveds.map((d) => {
3467
+ if ("kind" in d && d.kind === "temporalEnum") {
3468
+ const values = zodEnumValues(d.returns);
3469
+ const dartType = enumDartTypes.get(dartEnumLookupKey(d.returns)) ?? `${dartTypeNameFromField(d.id)}Enum`;
3470
+ return ` /// Native temporal enum read field evaluated at the captured read clock.\n final ${dartType} ${d.id};`;
3471
+ }
3472
+ return ` /// Engine-projected DERIVED read field (pure fn of the folded fields; NOT kernel state).\n final ${projectionReturnDartType(d.returns)} ${d.id};`;
3360
3473
  }),
3361
- ...deriveds.map(
3362
- (d) =>
3363
- ` /// Engine-projected DERIVED read field (pure fn of the folded fields; NOT kernel state).\n final ${projectionReturnDartType(d.returns)} ${d.id};`,
3364
- ),
3365
3474
  ...combineds.map(
3366
3475
  (c) =>
3367
3476
  ` /// Engine-projected COMBINED read field (JOIN across aggregates; NOT kernel state).\n final ${projectionReturnDartType(c.returns)} ${c.id};`,
@@ -3380,7 +3489,11 @@ function emitReadModel(
3380
3489
  : ` required this.${s.name},`,
3381
3490
  ),
3382
3491
  // Derived/combined fields are always nullable (null until the engine pass runs).
3383
- ...deriveds.map((d) => ` this.${d.id},`),
3492
+ ...deriveds.map((d) =>
3493
+ "kind" in d && d.kind === "temporalEnum"
3494
+ ? ` required this.${d.id},`
3495
+ : ` this.${d.id},`
3496
+ ),
3384
3497
  ...combineds.map((c) => ` this.${c.id},`),
3385
3498
  ].join("\n");
3386
3499
 
@@ -3418,7 +3531,14 @@ function emitReadModel(
3418
3531
  : ` ${s.name}: ${parseReadModelField(s, wireType)},`,
3419
3532
  ),
3420
3533
  // Derived/combined fields decode from the engine-produced bare JSON leaf.
3421
- ...deriveds.map((d) => ` ${d.id}: ${parseProjectionReturnField(d.id, d.returns)},`),
3534
+ ...deriveds.map((d) => {
3535
+ if ("kind" in d && d.kind === "temporalEnum") {
3536
+ const values = zodEnumValues(d.returns);
3537
+ const dartType = enumDartTypes.get(dartEnumLookupKey(d.returns)) ?? `${dartTypeNameFromField(d.id)}Enum`;
3538
+ return ` ${d.id}: ${dartType}.fromWire(readStr(data, '${agg.id}', '${d.id}')),`;
3539
+ }
3540
+ return ` ${d.id}: ${parseProjectionReturnField(d.id, d.returns)},`;
3541
+ }),
3422
3542
  ...combineds.map((c) => ` ${c.id}: ${parseProjectionReturnField(c.id, c.returns)},`),
3423
3543
  ].join("\n");
3424
3544
 
@@ -3470,10 +3590,15 @@ function emitDirective(
3470
3590
  const foreignReferenceInputs = dartForeignReferenceInputs(d, aggregates);
3471
3591
  const foreignReferenceIds = new Set(foreignReferenceInputs.map((input) => input.idField));
3472
3592
  const principalField = d.businessPrincipalField;
3593
+ const targetBinding = isBusinessIntent(d) ? d.targetBinding : undefined;
3594
+ const targetType = targetBinding === undefined
3595
+ ? undefined
3596
+ : `NomosRef<${concreteClassName(targetBinding.aggregateId)}>`;
3473
3597
  const derivedFields = new Set([
3474
3598
  ...recordInputs.flatMap((input) => [input.idField, input.workspaceField]),
3475
3599
  ...foreignReferenceInputs.map((input) => input.workspaceField),
3476
3600
  ...(principalField === undefined ? [] : [principalField]),
3601
+ ...(targetBinding === undefined ? [] : [targetBinding.field]),
3477
3602
  ]);
3478
3603
  const publicSpecs = specs.filter((spec) => !derivedFields.has(spec.name));
3479
3604
  // Optional first-class relationships are OPTIONAL on the wire but REQUIRED
@@ -3483,6 +3608,9 @@ function emitDirective(
3483
3608
  const isOptionalReferenceChoice = (spec: PayloadFieldSpec): boolean =>
3484
3609
  spec.optional && spec.toJson === "ref";
3485
3610
  const internalPayloadArguments = [
3611
+ ...(targetBinding === undefined
3612
+ ? []
3613
+ : [`required ${targetType} nmsInternalTarget`]),
3486
3614
  ...(principalField === undefined ? [] : ["required String nmsInternalPrincipal"]),
3487
3615
  ...(recordInputs.length === 0
3488
3616
  ? []
@@ -3541,6 +3669,9 @@ function emitDirective(
3541
3669
  s.nullAdmits !== true
3542
3670
  )
3543
3671
  .map((s) => ` '${s.name}': ${toJsonExpr(s, s.name)},`);
3672
+ const targetEntry = targetBinding === undefined
3673
+ ? []
3674
+ : [` '${targetBinding.field}': NmsInternalNomosRefs.id(nmsInternalTarget),`];
3544
3675
  const optionalLines = specs
3545
3676
  .filter((s) =>
3546
3677
  !derivedFields.has(s.name) &&
@@ -3631,6 +3762,7 @@ function emitDirective(
3631
3762
  ` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) {`,
3632
3763
  ` final data = <String, Object?>{`,
3633
3764
  ...requiredEntries,
3765
+ ...targetEntry,
3634
3766
  ...requiredForeignReferenceEntries,
3635
3767
  ...optionalLines,
3636
3768
  ...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
@@ -3649,6 +3781,7 @@ function emitDirective(
3649
3781
  : [
3650
3782
  ` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) => <String, Object?>{`,
3651
3783
  ...requiredEntries,
3784
+ ...targetEntry,
3652
3785
  ...requiredForeignReferenceEntries,
3653
3786
  ...optionalLines,
3654
3787
  ...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
@@ -4662,7 +4795,7 @@ export interface DomainModule {
4662
4795
  * read engine (`nomos_readmodel`) stores each derived value as an ordinary projected
4663
4796
  * field, so a reader decodes it like any other field.
4664
4797
  */
4665
- deriveds?: DerivedDecl[];
4798
+ deriveds?: AnyDerivedDecl[];
4666
4799
  /**
4667
4800
  * The domain's ENGINE-PROJECTED COMBINED read fields (read-engine: combined read
4668
4801
  * fields) — the JOIN analogue of {@link deriveds}. Each is a PURE fn of ONE owner
@@ -4881,7 +5014,9 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
4881
5014
  ? [
4882
5015
  emitVoHelpers(),
4883
5016
  ...localValueObjects.flatMap((d) => [
4884
- ...voEnumFields(d).map((e) => emitVoEnum(e.owner ?? d.name, e.fieldName, e.values)),
5017
+ ...voEnumFields(d)
5018
+ .filter((e) => e.declaredName === undefined)
5019
+ .map((e) => emitVoEnum(e.owner ?? d.name, e.fieldName, e.values)),
4885
5020
  emitValueObjectClass(d),
4886
5021
  ]),
4887
5022
  ]
@@ -4906,10 +5041,12 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
4906
5041
  const localQueries = (mod.queries ?? []).filter((q) => !skipQueryIds.has(q.id));
4907
5042
  // Enums are collected from the LOCALLY-emitted symbols only — a framework directive/aggregate's
4908
5043
  // enums ride the shared `nomos_framework.dart`, so a domain that skips them emits no dangling enum.
4909
- const { dartTypes, decls } = collectEnums(
5044
+ const { dartTypes, decls } = collectDartEnums(
4910
5045
  localAggregates,
4911
5046
  localDirectives,
4912
5047
  domainKey,
5048
+ localValueObjects,
5049
+ mod.deriveds ?? [],
4913
5050
  );
4914
5051
  const refIdClassForAggregate = refIdClassResolver(mod);
4915
5052
  const packageAggregateDomains = opts.packageAggregateDomains ?? {};
@@ -4965,7 +5102,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
4965
5102
  // `…ReadModel` ALSO carries the engine-projected derived fields its type declares (e.g.
4966
5103
  // `SupportSessionReadModel.isTerminal`), decoded like a normal projected field from the
4967
5104
  // row's `data` (the Rust read engine stores it there as a json-leaf).
4968
- const derivedsByType = new Map<string, DerivedDecl[]>();
5105
+ const derivedsByType = new Map<string, AnyDerivedDecl[]>();
4969
5106
  for (const d of mod.deriveds ?? []) {
4970
5107
  const list = derivedsByType.get(d.of) ?? [];
4971
5108
  list.push(d);