@githolon/dsl 0.102.5 → 0.103.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 (131) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +7 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5504 -1236
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +196 -89
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +73 -33
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1858 -590
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +29 -69
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +58 -29
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +140 -6
  110. package/src/stable_ids.ts +1 -1
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. package/src/usd_layers.ts +0 -1776
package/src/fields.ts CHANGED
@@ -142,6 +142,74 @@ export interface EvidenceRefValue {
142
142
  storageClass: "git" | "external";
143
143
  }
144
144
 
145
+ /** One portable address inside Nomos custody. This is identity and routing, never authority. */
146
+ export interface NomosRefValue {
147
+ readonly workspace: string;
148
+ readonly type: string;
149
+ readonly id: string;
150
+ }
151
+
152
+ /** An immutable version pin plus the stable series whose law names the current version. */
153
+ export interface VersionedNomosRefValue {
154
+ readonly pinned: NomosRefValue;
155
+ readonly series: NomosRefValue;
156
+ }
157
+
158
+ /** A SHA-256 digest encoded as exactly 64 lowercase hexadecimal characters.
159
+ *
160
+ * This is nominal at author/client compile time and remains a plain string on the
161
+ * wire. Construct values through {@link sha256Digest}; never cast a string into it.
162
+ */
163
+ export type Sha256Digest = string & { readonly __nomosSha256Digest: unique symbol };
164
+
165
+ /** Codegen marker for the semantic SHA-256 string type. The marker is deliberately
166
+ * non-enumerable: Zod remains the runtime/admission schema, while emitters recover
167
+ * the stronger client type without adding a parallel manifest representation. */
168
+ export const NOMOS_SHA256_DIGEST_BRAND: unique symbol = Symbol.for("nomos.sha256Digest");
169
+
170
+ const SHA256_DIGEST_RE = /^[0-9a-f]{64}$/;
171
+
172
+ /** Declare and validate a lowercase hexadecimal SHA-256 digest payload value. */
173
+ export function sha256Digest(): z.ZodType<Sha256Digest> {
174
+ const schema = z.string().regex(SHA256_DIGEST_RE, "expected a lowercase hexadecimal SHA-256 digest");
175
+ Object.defineProperty(schema, NOMOS_SHA256_DIGEST_BRAND, { value: true, enumerable: false });
176
+ return schema as unknown as z.ZodType<Sha256Digest>;
177
+ }
178
+
179
+ /** Recover the SHA-256 semantic marker through omission/null wrappers. */
180
+ export function isSha256DigestSchema(schema: z.ZodTypeAny): boolean {
181
+ let current: z.ZodTypeAny | undefined = schema;
182
+ for (let hops = 0; current !== undefined && hops < 16; hops++) {
183
+ if ((current as unknown as Record<symbol, unknown>)[NOMOS_SHA256_DIGEST_BRAND] === true) {
184
+ return true;
185
+ }
186
+ const raw = current as unknown as {
187
+ _def?: { type?: unknown; innerType?: z.ZodTypeAny };
188
+ def?: { type?: unknown; innerType?: z.ZodTypeAny };
189
+ };
190
+ const def = raw._def ?? raw.def;
191
+ const kind = def?.type;
192
+ const name = (current as unknown as { _def?: { typeName?: string } })._def?.typeName;
193
+ if (kind !== "optional" && kind !== "default" && kind !== "nullable" &&
194
+ name !== "ZodOptional" && name !== "ZodDefault" && name !== "ZodNullable") {
195
+ return false;
196
+ }
197
+ current = def?.innerType;
198
+ }
199
+ return false;
200
+ }
201
+
202
+ export const nomosRefValueSchema: z.ZodType<NomosRefValue> = z.object({
203
+ workspace: z.string().min(1),
204
+ type: z.string().min(1),
205
+ id: z.string().min(1),
206
+ }).strict();
207
+
208
+ export const versionedNomosRefValueSchema: z.ZodType<VersionedNomosRefValue> = z.object({
209
+ pinned: nomosRefValueSchema,
210
+ series: nomosRefValueSchema,
211
+ }).strict();
212
+
145
213
  /** The Zod schema for an authored `EvidenceRefValue` — the single source of truth. */
146
214
  export const evidenceRefSchema: z.ZodType<EvidenceRefValue> = z.object({
147
215
  hash: z.string(),
@@ -174,6 +242,8 @@ export type ValueObjectFieldDescriptor =
174
242
  | {
175
243
  readonly type: "string";
176
244
  readonly brand?: string;
245
+ /** A framework semantic string whose generated client type validates its canonical spelling. */
246
+ readonly digest?: "sha256";
177
247
  readonly idOf?: string;
178
248
  /** Present only for an `idOf(recordContract(...))` target. Aggregate is the default. */
179
249
  readonly idOfContract?: true;
@@ -285,6 +355,9 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
285
355
  * absent the clear lane is ungated (and `law_check` WARNs if sibling writes ARE gated). Compose/codegen
286
356
  * metadata — the REAL law is the generated directive's `requiresRelationGate` (hashed there, not here). */
287
357
  readonly clearRequires?: string;
358
+ /** Static Zanzibar object paired with `clearRequires`. Non-workspace clear lanes must state this so
359
+ * generated directives cannot silently fall back to the wrong authorization namespace. */
360
+ readonly clearRequiresObject?: string;
288
361
  /** For `t.ref` cross-workspace edges: the foreign workspace id, if any. */
289
362
  readonly refWorkspace?: string;
290
363
  /**
@@ -306,6 +379,21 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
306
379
  * the target's concrete implementation. */
307
380
  readonly targetIsContract?: boolean;
308
381
  };
382
+ /** First-class atomic custody address. Kept in the reference kind so query/read routing remains a ref. */
383
+ readonly nomosRef?: {
384
+ readonly targetAggregateId: string;
385
+ readonly targetIsContract?: boolean;
386
+ };
387
+ /** Immutable publication pin + stable series, with the current pointer selected by compiled law. */
388
+ readonly versionedForeignRef?: {
389
+ readonly targetAggregateId: string;
390
+ readonly targetIsContract?: boolean;
391
+ readonly seriesAggregateId: string;
392
+ readonly seriesIsContract?: boolean;
393
+ readonly currentField: string;
394
+ /** Owning domain for cross-domain target, series and current-field law references. */
395
+ readonly targetDomain?: string;
396
+ };
309
397
  /** For `t.ref` (the target) AND `t.hasMany` (the child type): the related aggregate's wire id. */
310
398
  readonly refAggregateId?: string;
311
399
  /** For `t.hasMany(Child).via(backref)`: the back-reference field NAME on the child. Virtual. */
@@ -399,6 +487,8 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
399
487
  * Flag named `isMeta` — the `meta()` METHOD sets it, mirroring `encrypted()`/`optional()`.
400
488
  */
401
489
  readonly isMeta?: true;
490
+ /** ZD6 — this encrypted field carries a blind-index equality token (the `blindIndexed()` METHOD sets it). */
491
+ readonly isBlindIndexed?: true;
402
492
  /**
403
493
  * THE INTEGRITY/CONFIDENTIALITY FACET (security_model.md — the endorse model). Set by the
404
494
  * `authorityField`/`attributionField`/`standingField`/`secretField`/`publicField` wrappers (or the
@@ -418,6 +508,15 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
418
508
  withFacet<Fa extends FieldFacet>(facet: Fa): Field<T, K> & { readonly facet: Fa };
419
509
  /** Mark this field's value ENCRYPTED (sets {@link Field.isEncrypted}). Chainable + non-destructive. */
420
510
  encrypted(): Field<T, K>;
511
+ /**
512
+ * ZD6 BLIND INDEX (encrypted_merge_model.md — the CipherSweet pattern): opt THIS encrypted field
513
+ * into EDGE-SERVED EQUALITY lookups. The sealing client attaches a scope-key-derived HMAC token
514
+ * (`bidx`) beside the ciphertext; key holders compute the same token query-side; the operator can
515
+ * observe equality/frequency among the opted-in rows but can never invert a token. Requires
516
+ * `.encrypted()` (aggregate() refuses otherwise) — a DELIBERATE, DECLARED equality leak, rotated
517
+ * with the scope epoch. Sets {@link Field.isBlindIndexed}.
518
+ */
519
+ blindIndexed(): Field<T, K>;
421
520
  /** Keep AUTHOR PROVENANCE beside this field's folded value (sets {@link Field.isAttributed}) —
422
521
  * `<field>Provenance` accessors are generated on the read models. Lww-class scalars only. */
423
522
  attributed(): Field<T, K>;
@@ -441,7 +540,7 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
441
540
  withNull(semantic: NullSemantic, reason?: string): Field<T, K>;
442
541
  /** INTERNAL (`t.clearable` calls this): mark this field EXPLICIT-CLEARABLE (sets {@link Field.isClearable}
443
542
  * + optional {@link Field.clearRequires}). Threaded through merge/optional like every other flag. */
444
- clearable(requires?: string): Field<T, K> & { readonly isClearable: true };
543
+ clearable(requires?: string, object?: string): Field<T, K> & { readonly isClearable: true };
445
544
  /** Declare how values authored by an earlier field contract become this field's current
446
545
  * business value. With no function, the compiler proves identity widening. With a function,
447
546
  * the source must be finite (enum/boolean/literal union), so Nomos can exhaustively compile
@@ -458,6 +557,14 @@ function makeField<T, K extends FieldKind = FieldKind>(
458
557
  extra: {
459
558
  refWorkspace?: string;
460
559
  foreignRef?: { workspaceField: string; targetAggregateId: string; targetDomain?: string; targetIsContract?: boolean };
560
+ nomosRef?: { targetAggregateId: string; targetIsContract?: boolean };
561
+ versionedForeignRef?: {
562
+ targetAggregateId: string;
563
+ targetIsContract?: boolean;
564
+ seriesAggregateId: string;
565
+ seriesIsContract?: boolean;
566
+ currentField: string;
567
+ };
461
568
  refAggregateId?: string;
462
569
  /** LAZY target resolution — `t.ref(() => Agg)` / `t.hasMany(() => Agg)`. Breaks a BIDIRECTIONAL
463
570
  * declaration cycle (a Parent's `hasMany(Child)` ⇄ a Child's `ref(Parent)`): the thunk is stored and
@@ -473,6 +580,7 @@ function makeField<T, K extends FieldKind = FieldKind>(
473
580
  valueObject?: ValueObjectFieldMeta;
474
581
  mapValueVo?: ValueObjectFieldMeta;
475
582
  encrypted?: true;
583
+ blindIndexed?: true;
476
584
  attributed?: true;
477
585
  meta?: true;
478
586
  facet?: FieldFacet;
@@ -480,6 +588,7 @@ function makeField<T, K extends FieldKind = FieldKind>(
480
588
  nullReason?: string;
481
589
  isClearable?: true;
482
590
  clearRequires?: string;
591
+ clearRequiresObject?: string;
483
592
  evolutions?: readonly FieldEvolutionDeclaration[];
484
593
  } = {},
485
594
  ): Field<T, K> {
@@ -493,12 +602,15 @@ function makeField<T, K extends FieldKind = FieldKind>(
493
602
  ...(extra.refWorkspace !== undefined ? { refWorkspace: extra.refWorkspace } : {}),
494
603
  ...(extra.refAggregateId !== undefined ? { refAggregateId: extra.refAggregateId } : {}),
495
604
  ...(extra.foreignRef !== undefined ? { foreignRef: extra.foreignRef } : {}),
605
+ ...(extra.nomosRef !== undefined ? { nomosRef: extra.nomosRef } : {}),
606
+ ...(extra.versionedForeignRef !== undefined ? { versionedForeignRef: extra.versionedForeignRef } : {}),
496
607
  ...(extra.viaField !== undefined ? { viaField: extra.viaField } : {}),
497
608
  ...(extra.mapValueKind !== undefined ? { mapValueKind: extra.mapValueKind } : {}),
498
609
  ...(extra.jsonShape !== undefined ? { jsonShape: extra.jsonShape } : {}),
499
610
  ...(extra.valueObject !== undefined ? { valueObject: extra.valueObject } : {}),
500
611
  ...(extra.mapValueVo !== undefined ? { mapValueVo: extra.mapValueVo } : {}),
501
612
  ...(extra.encrypted ? { isEncrypted: true as const } : {}),
613
+ ...(extra.blindIndexed ? { isBlindIndexed: true as const } : {}),
502
614
  ...(extra.attributed ? { isAttributed: true as const } : {}),
503
615
  ...(extra.meta ? { isMeta: true as const } : {}),
504
616
  ...(extra.fromBirth ? { isFromBirth: true as const } : {}),
@@ -506,6 +618,7 @@ function makeField<T, K extends FieldKind = FieldKind>(
506
618
  ...(extra.nullReason !== undefined ? { nullReason: extra.nullReason } : {}),
507
619
  ...(extra.isClearable ? { isClearable: true as const } : {}),
508
620
  ...(extra.clearRequires !== undefined ? { clearRequires: extra.clearRequires } : {}),
621
+ ...(extra.clearRequiresObject !== undefined ? { clearRequiresObject: extra.clearRequiresObject } : {}),
509
622
  ...(extra.evolutions !== undefined && extra.evolutions.length > 0
510
623
  ? { evolutions: extra.evolutions }
511
624
  : {}),
@@ -549,11 +662,12 @@ function makeField<T, K extends FieldKind = FieldKind>(
549
662
  ...(reason !== undefined ? { nullReason: reason } : {}),
550
663
  });
551
664
  },
552
- clearable(requires?: string): Field<T, K> & { readonly isClearable: true } {
665
+ clearable(requires?: string, object?: string): Field<T, K> & { readonly isClearable: true } {
553
666
  return makeField<T, K>(kind, zod, driver, {
554
667
  ...extra,
555
668
  isClearable: true,
556
669
  ...(requires !== undefined ? { clearRequires: requires } : {}),
670
+ ...(object !== undefined ? { clearRequiresObject: object } : {}),
557
671
  }) as Field<T, K> & { readonly isClearable: true };
558
672
  },
559
673
  evolvesFrom<From>(source: Field<From>, project?: (value: From) => T): Field<T, K> {
@@ -600,6 +714,11 @@ function makeField<T, K extends FieldKind = FieldKind>(
600
714
  // `nomosEncryption` era key, built by aggregate()/manifest, is what carries the fact.
601
715
  return makeField<T, K>(kind, zod, driver, { ...extra, encrypted: true });
602
716
  },
717
+ blindIndexed(): Field<T, K> {
718
+ // ZD6 — carried through merge/optional like encrypted(); aggregate() enforces the
719
+ // encrypted-only rule (a plaintext field needs no blind index — index it directly).
720
+ return makeField<T, K>(kind, zod, driver, { ...extra, blindIndexed: true });
721
+ },
603
722
  attributed(): Field<T, K> {
604
723
  // Carry the flag through merge/optional (extra is threaded), so `.merge(Lww).attributed()`
605
724
  // and `.attributed().merge(Lww)` agree. aggregate() validates Lww-class eligibility and
@@ -771,6 +890,11 @@ export const t = {
771
890
  }
772
891
  return makeField("string", schema, FirstWriterWins);
773
892
  },
893
+ /** A SHA-256 digest with one canonical wire spelling: 64 lowercase hex characters.
894
+ * Generated clients surface [Sha256Digest], not a bare string. */
895
+ sha256Digest(): Field<Sha256Digest, "string"> {
896
+ return makeField<Sha256Digest, "string">("string", sha256Digest(), FirstWriterWins);
897
+ },
774
898
  int(rules: IntRules = {}): Field<number, "int"> {
775
899
  for (const [name, value] of [["minimum", rules.minimum], ["maximum", rules.maximum]] as const) {
776
900
  if (value !== undefined && !Number.isSafeInteger(value)) {
@@ -904,10 +1028,13 @@ export const t = {
904
1028
  */
905
1029
  clearable<T, K extends FieldKind>(
906
1030
  field: Field<T, K>,
907
- opts?: { requires?: string },
1031
+ opts?: { requires?: string; object?: string },
908
1032
  ): Field<T, K> & { readonly isClearable: true } {
909
1033
  assertNullEligible(field, "clearable");
910
- return field.clearable(opts?.requires);
1034
+ if (opts?.object !== undefined && opts.requires === undefined) {
1035
+ throw new Error("t.clearable(..., { object }) also requires an authorization relation");
1036
+ }
1037
+ return field.clearable(opts?.requires, opts?.object);
911
1038
  },
912
1039
  /**
913
1040
  * `t.nullable(field, { reason })` — null IS a real domain value on this field (`architecture/
@@ -977,6 +1104,58 @@ export const t = {
977
1104
  },
978
1105
  });
979
1106
  },
1107
+ /** A first-class, atomic portable custody address. Unlike legacy `foreignRef`, there is no sibling
1108
+ * workspace field which can tear under merge; workspace, aggregate type and id are one Lww value. */
1109
+ nomosRef(
1110
+ target: AggregateHandle<string, Record<string, Field>> | ContractHandle,
1111
+ ): Field<NomosRefValue, "ref"> {
1112
+ const isContract = (target as { __isContract?: unknown }).__isContract === true;
1113
+ return makeField<NomosRefValue, "ref">("ref", nomosRefValueSchema, FirstWriterWins, {
1114
+ refAggregateId: target.id,
1115
+ nomosRef: {
1116
+ targetAggregateId: target.id,
1117
+ ...(isContract ? { targetIsContract: true } : {}),
1118
+ },
1119
+ });
1120
+ },
1121
+ /** A version pin whose freshness is checked against one law-declared current pointer on a stable series.
1122
+ * The declaration fails immediately if `series.current` is not an atomic ref to `target`. */
1123
+ versionedForeignRef(opts: {
1124
+ target: AggregateHandle<string, Record<string, Field>> | ContractHandle;
1125
+ series: AggregateHandle<string, Record<string, Field>> | ContractHandle;
1126
+ current: string;
1127
+ domain?: string;
1128
+ }): Field<VersionedNomosRefValue, "ref"> {
1129
+ const current = (opts.series.fields as Record<string, Field>)[opts.current];
1130
+ if (current === undefined) {
1131
+ throw new Error(
1132
+ `t.versionedForeignRef: series '${opts.series.id}' has no current field '${opts.current}'`,
1133
+ );
1134
+ }
1135
+ if (current.nomosRef?.targetAggregateId !== opts.target.id) {
1136
+ throw new Error(
1137
+ `t.versionedForeignRef: '${opts.series.id}.${opts.current}' must be ` +
1138
+ `t.nomosRef(${opts.target.id}); it is the O(1) current pointer used for freshness`,
1139
+ );
1140
+ }
1141
+ const targetIsContract = (opts.target as { __isContract?: unknown }).__isContract === true;
1142
+ const seriesIsContract = (opts.series as { __isContract?: unknown }).__isContract === true;
1143
+ return makeField<VersionedNomosRefValue, "ref">(
1144
+ "ref",
1145
+ versionedNomosRefValueSchema,
1146
+ FirstWriterWins,
1147
+ {
1148
+ versionedForeignRef: {
1149
+ targetAggregateId: opts.target.id,
1150
+ ...(targetIsContract ? { targetIsContract: true } : {}),
1151
+ seriesAggregateId: opts.series.id,
1152
+ ...(seriesIsContract ? { seriesIsContract: true } : {}),
1153
+ currentField: opts.current,
1154
+ ...(opts.domain === undefined ? {} : { targetDomain: opts.domain }),
1155
+ },
1156
+ },
1157
+ );
1158
+ },
980
1159
  /**
981
1160
  * A 1:N INVERSE relation — the read side of a `t.ref` on the child (`aggregate_lifecycle_and_relations.md`).
982
1161
  * `t.hasMany(Child).via("parent")` on a Parent declares "the Children whose `parent` ref points here".
package/src/flow_gate.ts CHANGED
@@ -15,7 +15,11 @@
15
15
  // Integrity (who may WRITE) is gated natively at admission by `.requires(relation)`;
16
16
  // the integrity-flow dual + rule 4 (secret-correlated refusal) + `declassify` + the
17
17
  // `nomosFlowGate` era key are the next slices.
18
- import type { CanonicalManifest } from "./manifest.js";
18
+ import type { DomainModule } from "./codegen_dart.js";
19
+ import { finishCount } from "./count.js";
20
+ import { existsAsCount } from "./exists.js";
21
+ import { finishExtremum } from "./extremum.js";
22
+ import { finishSum } from "./sum.js";
19
23
  import type { CanonicalPred } from "./predicate.js";
20
24
 
21
25
  /** A read projection, read structurally — every read kind (query / count / sum / extremum /
@@ -32,6 +36,26 @@ type Projection = {
32
36
  readonly where?: CanonicalPred;
33
37
  };
34
38
 
39
+ /** The finite facts the information-flow proof consumes. A canonical manifest satisfies this
40
+ * structurally for legacy callers; the production compiler derives it straight from typed declarations. */
41
+ export interface FlowLaw {
42
+ readonly aggregates: readonly {
43
+ readonly id: string;
44
+ readonly encryptedFields?: readonly string[];
45
+ readonly requiresRead?: string;
46
+ }[];
47
+ readonly directives: readonly {
48
+ readonly id: string;
49
+ readonly capturedReads?: readonly { readonly queryId: string; readonly returns: string }[];
50
+ }[];
51
+ readonly queries?: readonly Projection[];
52
+ readonly counts?: readonly Projection[];
53
+ readonly sums?: readonly Projection[];
54
+ readonly mins?: readonly Projection[];
55
+ readonly maxes?: readonly Projection[];
56
+ readonly orderedReads?: readonly Projection[];
57
+ }
58
+
35
59
  /** The raw module bits rule 4 needs that the canonical manifest strips (the executable
36
60
  * invariant `.reads` + the declassify waivers). Read structurally — no coupling to DomainModule. */
37
61
  type FlowModule = {
@@ -82,7 +106,7 @@ function predFields(p: CanonicalPred | undefined): string[] {
82
106
  * {@link FlowGateError} on the first violation (a typed compile error). Pure over
83
107
  * manifest IR — no side effects, no AST.
84
108
  */
85
- export function assertFlowGate(m: CanonicalManifest, mod?: FlowModule): void {
109
+ export function assertFlowGate(m: FlowLaw, mod?: FlowModule): void {
86
110
  // ── RULE 1 — NO ENCRYPT-THEN-INDEX ──────────────────────────────────────────
87
111
  // A field that is .encrypted() (operator-blind ciphertext) MUST NOT appear in any
88
112
  // read's group-by / key / value / where over its own type: grouping/indexing/
@@ -233,3 +257,52 @@ export function assertFlowGate(m: CanonicalManifest, mod?: FlowModule): void {
233
257
  }
234
258
  }
235
259
  }
260
+
261
+ /**
262
+ * Production compiler entry: prove information flow directly from the live typed declarations.
263
+ * This deliberately does not build, serialize or hash the retired canonical JSON manifest.
264
+ */
265
+ export function assertFlowGateModule(mod: DomainModule): void {
266
+ const counts = [
267
+ ...(mod.counts ?? []).map(finishCount),
268
+ ...(mod.exists_ ?? []).map(existsAsCount),
269
+ ];
270
+ const facts: FlowLaw = {
271
+ aggregates: mod.aggregates.map((aggregate) => ({
272
+ id: aggregate.id,
273
+ ...(aggregate.encryptedFields === undefined ? {} : { encryptedFields: aggregate.encryptedFields }),
274
+ ...(aggregate.visibility === undefined ? {} : { requiresRead: aggregate.visibility.requires }),
275
+ })),
276
+ directives: mod.directives.map((directive) => ({
277
+ id: directive.id,
278
+ ...((directive.declaredQueryReads ?? []).length === 0
279
+ ? {}
280
+ : {
281
+ capturedReads: (directive.declaredQueryReads ?? []).map((read) => ({
282
+ queryId: read.id,
283
+ returns: read.returns,
284
+ })),
285
+ }),
286
+ })),
287
+ ...(mod.queries === undefined ? {} : { queries: mod.queries }),
288
+ ...(counts.length === 0 ? {} : { counts }),
289
+ ...(mod.sums === undefined
290
+ ? {}
291
+ : {
292
+ sums: mod.sums.map((raw) => {
293
+ const sum = finishSum(raw);
294
+ return {
295
+ id: sum.id,
296
+ of: sum.of,
297
+ valueField: sum.sumField,
298
+ ...(sum.by === undefined ? {} : { by: sum.by }),
299
+ ...(sum.where === undefined ? {} : { where: sum.where }),
300
+ };
301
+ }),
302
+ }),
303
+ ...(mod.mins === undefined ? {} : { mins: mod.mins.map(finishExtremum) }),
304
+ ...(mod.maxes === undefined ? {} : { maxes: mod.maxes.map(finishExtremum) }),
305
+ ...(mod.orderedReads === undefined ? {} : { orderedReads: mod.orderedReads }),
306
+ };
307
+ assertFlowGate(facts, mod);
308
+ }
@@ -38,6 +38,7 @@ import {
38
38
  seedBirthAttestation,
39
39
  seedReceivingGroup,
40
40
  seedReceivingGroupPolicy,
41
+ sharedByObject,
41
42
  sharedByWorkspace,
42
43
  } from "./home.js";
43
44
  import { RelationTuple, relationTuplesByObject } from "./relations.js";
@@ -86,6 +87,7 @@ export const __nomos_SharedWithMe = aggregate(SharedWithMe);
86
87
  export const __nomos_recordShare = directive(recordShare);
87
88
  export const __nomos_pruneShare = directive(pruneShare);
88
89
  export const __nomos_sharedByWorkspace = query(sharedByWorkspace);
90
+ export const __nomos_sharedByObject = query(sharedByObject);
89
91
 
90
92
  // Warrant, provenance, signer seating, recovery and verifier state.
91
93
  export const __nomos_GenesisWarrant = aggregate(GenesisWarrant);
@@ -13,7 +13,7 @@ import { Lww } from "../drivers.js";
13
13
  import { set, withMarker } from "../ops.js";
14
14
  import { query } from "../query.js";
15
15
  import { GENESIS_WARRANT_ID } from "./genesis.js";
16
- import { refAs, workspaceInvariant } from "./workspace_invariant.js";
16
+ import { readRef, readSet, refAs, workspaceInvariant } from "./workspace_invariant.js";
17
17
 
18
18
  const epochMs = z.number().int().nonnegative();
19
19
 
@@ -135,7 +135,7 @@ export const seedAuthProvider = directive("seedAuthProvider")
135
135
 
136
136
  export const authProviderSeedBeforeWarrant = workspaceInvariant("AuthProviderSeedBeforeWarrant")
137
137
  .on("seedAuthProvider")
138
- .reads(() => [refAs("warrant", "GenesisWarrant", GENESIS_WARRANT_ID)])
138
+ .reads(readSet(readRef("warrant", "GenesisWarrant").at(GENESIS_WARRANT_ID)))
139
139
  .assert(({ warrant }) => {
140
140
  // Missing invariant refs are represented as an empty snapshot by the executor, not necessarily null.
141
141
  const principal = (warrant as { principal?: unknown } | null | undefined)?.principal;
@@ -27,7 +27,7 @@ import { z } from "zod";
27
27
  import { instance } from "../aggregate.js";
28
28
  import { directive } from "../directive.js";
29
29
  import { set, type PlannedOp } from "../ops.js";
30
- import { refAs, workspaceInvariant } from "./workspace_invariant.js";
30
+ import { readRef, readSet, refAs, workspaceInvariant } from "./workspace_invariant.js";
31
31
  import {
32
32
  BirthPolicy,
33
33
  BirthGrant,
@@ -149,9 +149,7 @@ export const reclaimChildAsAdmin = directive("reclaimChildAsAdmin")
149
149
  /** Reclaim is two-phase; a child must first be explicitly withdrawn. */
150
150
  export const reclaimChildRequiresRetired = workspaceInvariant("ReclaimChildRequiresRetired")
151
151
  .on("reclaimChild")
152
- .reads((ctx) => [
153
- refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
154
- ])
152
+ .reads(readSet(readRef("record", NomosChildLifecycle).pre().fromPayload("child", { prefix: "nomos-child-lifecycle:" })))
155
153
  .assert(({ record }, ctx) =>
156
154
  (record as { status?: string; principal?: string } | undefined)?.status === "retired"
157
155
  && sameLifecyclePrincipal((record as { principal?: string }).principal, ctx?.principal)
@@ -162,9 +160,7 @@ export const reclaimChildRequiresRetired = workspaceInvariant("ReclaimChildRequi
162
160
  /** Operator recovery has the same two-phase law, without depending on the broken creator credential. */
163
161
  export const reclaimChildAsAdminRequiresRetired = workspaceInvariant("ReclaimChildAsAdminRequiresRetired")
164
162
  .on("reclaimChildAsAdmin")
165
- .reads((ctx) => [
166
- refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
167
- ])
163
+ .reads(readSet(readRef("record", NomosChildLifecycle).pre().fromPayload("child", { prefix: "nomos-child-lifecycle:" })))
168
164
  .assert(({ record }) =>
169
165
  (record as { status?: string } | undefined)?.status === "retired"
170
166
  ? { accept: true }
@@ -174,9 +170,7 @@ export const reclaimChildAsAdminRequiresRetired = workspaceInvariant("ReclaimChi
174
170
  /** Reclaimed is permanent: retirement cannot move a child back out of the terminal state. */
175
171
  export const retireChildNotReclaimed = workspaceInvariant("RetireChildNotReclaimed")
176
172
  .on("retireChild")
177
- .reads((ctx) => [
178
- refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
179
- ])
173
+ .reads(readSet(readRef("record", NomosChildLifecycle).pre().fromPayload("child", { prefix: "nomos-child-lifecycle:" })))
180
174
  .assert(({ record }) =>
181
175
  (record as { status?: string } | undefined)?.status === "reclaimed"
182
176
  ? { reject: "reclaimed-child-is-terminal" }
@@ -186,9 +180,7 @@ export const retireChildNotReclaimed = workspaceInvariant("RetireChildNotReclaim
186
180
  /** Reclaimed is terminal for operator recovery too. */
187
181
  export const retireChildAsAdminNotReclaimed = workspaceInvariant("RetireChildAsAdminNotReclaimed")
188
182
  .on("retireChildAsAdmin")
189
- .reads((ctx) => [
190
- refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
191
- ])
183
+ .reads(readSet(readRef("record", NomosChildLifecycle).pre().fromPayload("child", { prefix: "nomos-child-lifecycle:" })))
192
184
  .assert(({ record }) =>
193
185
  (record as { status?: string } | undefined)?.status === "reclaimed"
194
186
  ? { reject: "reclaimed-child-is-terminal" }
@@ -198,9 +190,7 @@ export const retireChildAsAdminNotReclaimed = workspaceInvariant("RetireChildAsA
198
190
  /** A lifecycle cannot mint authority for arbitrary custody: retirement must name its exact birth witness. */
199
191
  export const retireChildRequiresBirth = workspaceInvariant("RetireChildRequiresBirth")
200
192
  .on("retireChild")
201
- .reads((ctx) => [
202
- refAs("birth", "NomosBirthRecord", "pre:" + birthRecordId(String(ctx.intent.child ?? ""))),
203
- ])
193
+ .reads(readSet(readRef("birth", NomosBirthRecord).pre().fromPayload("child", { prefix: "nomos-birth-record:" })))
204
194
  .assert(({ birth }, ctx) => {
205
195
  const record = birth as { child?: string; principal?: string } | undefined;
206
196
  const intent = ctx?.intent as { child?: string; principal?: string } | undefined;
@@ -214,9 +204,7 @@ export const retireChildRequiresBirth = workspaceInvariant("RetireChildRequiresB
214
204
  /** Admin recovery may act for the recorded creator, but still cannot invent or retarget custody. */
215
205
  export const retireChildAsAdminRequiresBirth = workspaceInvariant("RetireChildAsAdminRequiresBirth")
216
206
  .on("retireChildAsAdmin")
217
- .reads((ctx) => [
218
- refAs("birth", "NomosBirthRecord", "pre:" + birthRecordId(String(ctx.intent.child ?? ""))),
219
- ])
207
+ .reads(readSet(readRef("birth", NomosBirthRecord).pre().fromPayload("child", { prefix: "nomos-birth-record:" })))
220
208
  .assert(({ birth }, ctx) => {
221
209
  const record = birth as { child?: string; principal?: string } | undefined;
222
210
  const intent = ctx?.intent as { child?: string; principal?: string } | undefined;
@@ -16,7 +16,5 @@
16
16
 
17
17
  export {
18
18
  DomainInstallation,
19
- PolicyBundle,
20
19
  installDomain,
21
- policyAggregateId,
22
20
  } from "./domain_lifecycle.js";
@@ -22,8 +22,16 @@ const correctionRecord = aggregate(BUSINESS_CORRECTION_AGGREGATE, {
22
22
  correctedAt: t.string().merge(Lww),
23
23
  }).public();
24
24
 
25
+ export const businessCorrectionRecord = {
26
+ ...correctionRecord,
27
+ businessBirthInternal: true as const,
28
+ };
29
+
25
30
  const correction = directive(BUSINESS_CORRECTION_DIRECTIVE)
26
- .ensures(correctionRecord)
31
+ // The directive's typed aggregate handle is followed by composeDomainModule. It must be the same
32
+ // framework-private singleton that is exported below; pointing at the unbranded construction
33
+ // intermediate lets that handle overwrite the branded one during typed target closure.
34
+ .ensures(businessCorrectionRecord)
27
35
  .payload(z.object({
28
36
  recordId: z.string().min(1),
29
37
  target: z.string().min(1),
@@ -45,10 +53,6 @@ const correction = directive(BUSINESS_CORRECTION_DIRECTIVE)
45
53
  ];
46
54
  });
47
55
 
48
- export const businessCorrectionRecord = {
49
- ...correctionRecord,
50
- businessBirthInternal: true as const,
51
- };
52
56
  export const recordBusinessCorrection = {
53
57
  ...correction,
54
58
  businessBirthInternal: true as const,