@githolon/dsl 0.100.5 → 0.101.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 (97) hide show
  1. package/dart/CHANGELOG.md +12 -0
  2. package/dart/lib/src/dev_cloud.dart +46 -15
  3. package/dart/lib/src/test_harness.dart +220 -71
  4. package/dart/lib/src/webview_bridge.dart +640 -126
  5. package/dart/pubspec.lock +1 -1
  6. package/dart/pubspec.yaml +2 -2
  7. package/dart/test/acquire_session_credentials_test.dart +1 -1
  8. package/dart/test/adv_realm_interference_test.dart +1 -1
  9. package/dart/test/bundle_version_pairing_test.dart +11 -22
  10. package/dart/test/cluster_attested_test.dart +1 -1
  11. package/dart/test/cluster_births_test.dart +1 -1
  12. package/dart/test/cluster_device_keyed_acceptance_test.dart +1 -1
  13. package/dart/test/cluster_local_autosync_test.dart +1 -1
  14. package/dart/test/cluster_scale_test.dart +1 -1
  15. package/dart/test/co2_product_gauntlet_test.dart +228 -0
  16. package/dart/test/dlq_session_surface_test.dart +1 -1
  17. package/dart/test/errors_test.dart +63 -12
  18. package/dart/test/first_app_gauntlet_test.dart +132 -87
  19. package/dart/test/local_sync_inert_test.dart +10 -5
  20. package/dart/test/parent_signer_recovery_test.dart +1 -1
  21. package/dart/test/sealing_client_test.dart +1 -1
  22. package/dart/test/session_bound_test.dart +1 -1
  23. package/dart/test/test_harness_test.dart +2 -2
  24. package/dart/test/vo_read_typing_test.dart +1 -1
  25. package/dart/test/watch_after_offer_stall_test.dart +1 -1
  26. package/dart/test/webview_bridge_test.dart +274 -0
  27. package/dart_types/CHANGELOG.md +6 -0
  28. package/dart_types/lib/src/errors.dart +136 -30
  29. package/dart_types/lib/src/surfaced.dart +48 -2
  30. package/dart_types/lib/src/wire.dart +3 -1
  31. package/dart_types/pubspec.yaml +1 -1
  32. package/dart_types/test/surfaced_test.dart +19 -0
  33. package/package.json +1 -1
  34. package/src/activity.ts +43 -4
  35. package/src/aggregate.ts +132 -22
  36. package/src/attested_core.ts +1 -1
  37. package/src/authoring.ts +119 -6
  38. package/src/birth_meter.ts +4 -4
  39. package/src/birth_meter_ensure.ts +165 -29
  40. package/src/births.ts +53 -22
  41. package/src/build_package.ts +96 -20
  42. package/src/business_birth.ts +79 -16
  43. package/src/codegen_dart.ts +267 -37
  44. package/src/codegen_dart_client.ts +930 -87
  45. package/src/codegen_openapi.ts +28 -5
  46. package/src/codegen_proof.ts +107 -11
  47. package/src/codegen_ts.ts +961 -135
  48. package/src/compile_package_main.ts +172 -53
  49. package/src/conflicts.ts +252 -0
  50. package/src/describe_model.ts +448 -0
  51. package/src/directive.ts +5 -38
  52. package/src/drivers.ts +12 -4
  53. package/src/engine_entry.ts +4 -0
  54. package/src/evolve_preview.ts +72 -11
  55. package/src/fields.ts +201 -55
  56. package/src/framework/application_home.ts +103 -0
  57. package/src/framework/application_home_domain.ts +2 -0
  58. package/src/framework/attested_receipts.ts +2 -2
  59. package/src/framework/auth_provider_law.ts +51 -24
  60. package/src/framework/business_corrections.ts +170 -0
  61. package/src/framework/disclosure.ts +1 -1
  62. package/src/framework/domain_lifecycle.ts +250 -16
  63. package/src/framework/encryption_identity.ts +1 -1
  64. package/src/framework/encryption_scope.ts +21 -13
  65. package/src/framework/era_certification.ts +1 -1
  66. package/src/framework/framework_aggregates.ts +2 -0
  67. package/src/framework/genesis.ts +1 -1
  68. package/src/framework/home.ts +22 -9
  69. package/src/framework/identity.ts +5 -5
  70. package/src/framework/impure_capability.ts +2 -2
  71. package/src/framework/owned_custody.ts +561 -0
  72. package/src/framework/platform_admin.ts +1 -1
  73. package/src/framework/relations.ts +9 -1
  74. package/src/framework/repair.ts +1 -1
  75. package/src/framework/signing_identity.ts +2 -2
  76. package/src/framework/source_roster.ts +1 -1
  77. package/src/framework/workspaces.ts +17 -10
  78. package/src/generated_client_names.ts +39 -0
  79. package/src/index.ts +20 -15
  80. package/src/kernel_capabilities.ts +25 -1
  81. package/src/law_check.ts +4 -1
  82. package/src/manifest.ts +356 -63
  83. package/src/read_projection_manifest.ts +8 -0
  84. package/src/read_projection_namespace.ts +4 -0
  85. package/src/relation.ts +1 -1
  86. package/src/shape_compat.ts +61 -5
  87. package/src/stable_ids.ts +38 -6
  88. package/src/stable_ids_types.ts +30 -0
  89. package/src/surfaced_lint.ts +0 -0
  90. package/src/usd.ts +62 -11
  91. package/src/usd_layers.ts +54 -3
  92. package/src/value_object.ts +23 -19
  93. package/src/wire.ts +1 -0
  94. package/src/wire_encode.ts +48 -4
  95. package/src/workspace_routing.ts +57 -0
  96. package/src/workspace_sharding.ts +9 -9
  97. package/src/certified_read.ts +0 -313
@@ -83,7 +83,7 @@ export const NomosBirthRecord = aggregate("NomosBirthRecord", {
83
83
  childGenesisHash: t.string().merge(Lww),
84
84
  /** Caller-stamped birth time from the payload (`bornAt`, else `requestedAt`) — the timestamp doctrine. */
85
85
  bornAt: t.string().merge(Lww),
86
- });
86
+ }).public();
87
87
 
88
88
  /**
89
89
  * NomosChildLifecycle — additive lifecycle for a child witnessed by NomosBirthRecord. It is deliberately
@@ -98,7 +98,7 @@ export const NomosChildLifecycle = aggregate("NomosChildLifecycle", {
98
98
  retiredAt: t.int().merge(Lww),
99
99
  reclaimedBy: t.string().merge(Lww),
100
100
  reclaimedAt: t.int().merge(Lww),
101
- });
101
+ }).public();
102
102
 
103
103
  /** BirthPolicy — the workspace's birth-allowance singleton (id `birth-policy:workspace`), the
104
104
  * `CloudPolicy` shape one level down: the default max live births per subject in THIS workspace. */
@@ -110,7 +110,7 @@ export const BirthPolicy = aggregate("BirthPolicy", {
110
110
  setBy: t.string().merge(Lww),
111
111
  /** Caller-stamped (ISO or epoch-ms string) — the timestamp doctrine. */
112
112
  setAt: t.string().merge(Lww),
113
- });
113
+ }).public();
114
114
 
115
115
  /** BirthGrant — a per-subject allowance override (id `birth-grant:<principal>`), the `QuotaGrant`
116
116
  * shape: the LATEST grant (max grantedAt) for a subject is the allowance in force. */
@@ -123,7 +123,7 @@ export const BirthGrant = aggregate("BirthGrant", {
123
123
  grantedBy: t.string().merge(Lww),
124
124
  /** epoch-ms — the recency tiebreak (latest grant wins), caller-stamped. */
125
125
  grantedAt: t.int().merge(Lww),
126
- });
126
+ }).public();
127
127
 
128
128
  // ── the meter's declared queries (the #58 captured-read recipes) ────────────────────────────────────
129
129
 
@@ -42,6 +42,50 @@ import {
42
42
  retireChildRequiresBirth,
43
43
  retireChildNotReclaimed,
44
44
  } from "./framework/birth_meter.js";
45
+ import { RelationTuple, relationTupleInstanceId, relationTuplesByObject } from "./framework/relations.js";
46
+ import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/genesis.js";
47
+ import {
48
+ BIRTH_ATTESTATION_ID,
49
+ BirthAttestation,
50
+ birthAttestation,
51
+ seedBirthAttestation,
52
+ } from "./framework/home.js";
53
+ import { requireParentAttestedBirth } from "./births.js";
54
+ import {
55
+ SignerDelegation,
56
+ enrollDelegation,
57
+ enrollSigner,
58
+ identityRelations,
59
+ signerDelegationByUser,
60
+ signerObject,
61
+ } from "./framework/signing_identity.js";
62
+ import {
63
+ AuthProvider,
64
+ authProvider,
65
+ authProviderSeedBeforeWarrant,
66
+ seedAuthProvider,
67
+ setAuthProvider,
68
+ } from "./framework/auth_provider_law.js";
69
+ import { refAs, workspaceInvariant } from "./framework/workspace_invariant.js";
70
+
71
+ /** Lightweight copy of the framework's founding-signer guard, kept here to avoid importing the whole
72
+ * governance module (whose unrelated captured-read laws would otherwise survive into every child lump). */
73
+ const bornFounderSignerWriteOnce = workspaceInvariant("FounderSignerWriteOnce")
74
+ .on("enrollSigner")
75
+ .reads((ctx) => [
76
+ refAs(
77
+ "tuple",
78
+ "RelationTuple",
79
+ "pre:" + relationTupleInstanceId(signerObject(String((ctx.intent ?? {}).principal ?? "")), "signer"),
80
+ ),
81
+ ])
82
+ .assert(({ tuple }, ctx) => {
83
+ const subjects = (tuple as { subjects?: Record<string, unknown> })?.subjects ?? {};
84
+ const want = "key:" + String((ctx?.intent ?? {}).keyHash ?? "");
85
+ return Object.keys(subjects).every((key) => key === want)
86
+ ? { accept: true }
87
+ : { reject: "founding-signer-already-enrolled" };
88
+ });
45
89
 
46
90
  /**
47
91
  * The sanctioning declaration — an `ensures(NomosBirthRecord)` directive whose plan emits ZERO ops.
@@ -70,36 +114,128 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
70
114
  (v as { birthsChild?: boolean }).birthsChild === true &&
71
115
  typeof (v as Directive<unknown>).plan === "function",
72
116
  );
73
- if (!declaresBirth) return mod;
117
+ // A law that accepts the kernel-injected birth attestation is itself a possible CHILD even if it never
118
+ // births anything. Such a law must also accept the framework-owned founding signer step, otherwise the
119
+ // child can replay but can never sign its first write or birth a descendant without tenant recipe work.
120
+ const acceptsBirth = Object.values(mod).some(
121
+ (v) =>
122
+ v !== null &&
123
+ typeof v === "object" &&
124
+ (v as { id?: unknown }).id === "seedBirthAttestation" &&
125
+ typeof (v as Directive<unknown>).plan === "function",
126
+ );
127
+ if (!declaresBirth && !acceptsBirth) return mod;
74
128
  const out: Record<string, unknown> = { ...mod };
75
- // Business birth hides framework maintenance declarations from the generated application facade while
76
- // retaining them in certified law for the operator's structural offer discovery. A non-business
77
- // `.births()` law keeps its existing explicit generated surface.
78
- const businessInternal = Object.values(mod).some(
79
- (v) => v !== null && typeof v === "object" && (v as { businessBirth?: unknown }).businessBirth !== undefined,
129
+ const internal = <T extends object>(v: T): T => ({ ...v, businessBirthInternal: true }) as T;
130
+ const hasOwn = (key: string): boolean => Object.prototype.hasOwnProperty.call(out, key);
131
+ const isGeneratedInternal = (value: unknown): boolean =>
132
+ value !== null &&
133
+ typeof value === "object" &&
134
+ (value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
135
+ const reservedNameError = (key: string): Error => new Error(
136
+ `Nomos internal export name '${key}' is reserved inside a birth-capable law, but the tenant module ` +
137
+ "already exports a different concept there. Nomos will not silently overwrite or reroute it; rename " +
138
+ "the tenant export.",
80
139
  );
81
- const presented = <T extends object>(v: T): T =>
82
- businessInternal ? ({ ...v, businessBirthInternal: true } as T) : v;
83
- if (!("nomosBirthRecordEnsure" in out)) out["nomosBirthRecordEnsure"] = presented(nomosBirthRecordEnsure);
84
- if (!("NomosBirthRecord" in out)) out["NomosBirthRecord"] = presented(NomosBirthRecord);
85
- if (!("NomosChildLifecycle" in out)) out["NomosChildLifecycle"] = presented(NomosChildLifecycle);
86
- if (!("BirthPolicy" in out)) out["BirthPolicy"] = presented(BirthPolicy);
87
- if (!("BirthGrant" in out)) out["BirthGrant"] = presented(BirthGrant);
88
- if (!("retireChild" in out)) out["retireChild"] = presented(retireChild);
89
- if (!("reclaimChild" in out)) out["reclaimChild"] = presented(reclaimChild);
90
- if (!("retireChildAsAdmin" in out)) out["retireChildAsAdmin"] = presented(retireChildAsAdmin);
91
- if (!("reclaimChildAsAdmin" in out)) out["reclaimChildAsAdmin"] = presented(reclaimChildAsAdmin);
92
- if (!("reclaimChildRequiresRetired" in out)) out["reclaimChildRequiresRetired"] = reclaimChildRequiresRetired;
93
- if (!("reclaimChildAsAdminRequiresRetired" in out)) {
94
- out["reclaimChildAsAdminRequiresRetired"] = reclaimChildAsAdminRequiresRetired;
95
- }
96
- if (!("retireChildRequiresBirth" in out)) out["retireChildRequiresBirth"] = retireChildRequiresBirth;
97
- if (!("retireChildNotReclaimed" in out)) out["retireChildNotReclaimed"] = retireChildNotReclaimed;
98
- if (!("retireChildAsAdminRequiresBirth" in out)) {
99
- out["retireChildAsAdminRequiresBirth"] = retireChildAsAdminRequiresBirth;
100
- }
101
- if (!("retireChildAsAdminNotReclaimed" in out)) {
102
- out["retireChildAsAdminNotReclaimed"] = retireChildAsAdminNotReclaimed;
103
- }
140
+ /** Reserve a framework-only JS export slot as well as its stable law id. The keys are deliberately obscure,
141
+ * but obscurity is collision avoidance, not collision handling: an occupied slot is always a loud refusal. */
142
+ const reserve = <T>(key: string, canonical: T): void => {
143
+ if (!hasOwn(key)) {
144
+ out[key] = canonical;
145
+ return;
146
+ }
147
+ const existing = out[key];
148
+ if (existing !== canonical && !isGeneratedInternal(existing)) throw reservedNameError(key);
149
+ };
150
+ /**
151
+ * Framework ownership follows the stable law id, not the tenant's export key. Older laws commonly
152
+ * re-exported `enrollSigner`/`seedGenesis` under their public names; merely adding a second hidden
153
+ * `__nomos_*` binding leaves BOTH declarations discoverable and lets codegen expose the old one. Mark
154
+ * every matching binding internal, and add the canonical declaration only when the law carried none.
155
+ */
156
+ const internalize = <T extends { readonly id: string }>(key: string, canonical: T): void => {
157
+ if (hasOwn(key)) {
158
+ const existing = out[key];
159
+ const sameGeneratedId =
160
+ isGeneratedInternal(existing) &&
161
+ (existing as { id?: unknown }).id === canonical.id;
162
+ if (existing !== canonical && !sameGeneratedId) throw reservedNameError(key);
163
+ }
164
+ let found = false;
165
+ for (const [exportName, value] of Object.entries(out)) {
166
+ if (
167
+ value !== null &&
168
+ typeof value === "object" &&
169
+ (value as { id?: unknown }).id === canonical.id
170
+ ) {
171
+ const isCanonicalSingleton = value === canonical;
172
+ const isNomosGeneratedBinding =
173
+ (value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
174
+ if (!isCanonicalSingleton && !isNomosGeneratedBinding) {
175
+ throw new Error(
176
+ `Nomos framework id '${canonical.id}' is reserved inside a birth-capable law, but export ` +
177
+ `'${exportName}' declares a different tenant-owned concept with that id. Nomos will not ` +
178
+ `silently reinterpret it as framework machinery; rename the tenant concept.`,
179
+ );
180
+ }
181
+ out[exportName] = internal(value);
182
+ found = true;
183
+ }
184
+ }
185
+ if (!found) out[key] = internal(canonical);
186
+ };
187
+ // THE BORN-WORKSPACE CORE: the kernel appends enrollSigner -> attestation -> warrant to every valid birth.
188
+ // Carry the law needed to judge that internal step in every possible child, plus the write-once guard that
189
+ // keeps the founding lane a genesis primitive. These declarations are certified but never tenant-facing.
190
+ internalize("__nomos_RelationTuple", RelationTuple);
191
+ reserve("__nomos_identityRelations", identityRelations);
192
+ internalize("__nomos_enrollSigner", enrollSigner);
193
+ internalize("__nomos_relationTuplesByObject", relationTuplesByObject);
194
+ internalize("__nomos_GenesisWarrant", GenesisWarrant);
195
+ internalize("__nomos_seedGenesis", seedGenesis);
196
+ internalize("__nomos_BirthAttestation", BirthAttestation);
197
+ internalize("__nomos_seedBirthAttestation", seedBirthAttestation);
198
+ internalize("__nomos_birthAttestation", birthAttestation);
199
+ internalize("__nomos_AuthProvider", AuthProvider);
200
+ internalize("__nomos_seedAuthProvider", seedAuthProvider);
201
+ reserve("__nomos_authProviderSeedBeforeWarrant", authProviderSeedBeforeWarrant);
202
+ internalize("__nomos_setAuthProvider", setAuthProvider);
203
+ internalize("__nomos_authProvider", authProvider);
204
+ reserve("__nomos_founderSignerWriteOnce", bornFounderSignerWriteOnce);
205
+ if (!declaresBirth) return out as M;
206
+ // Birth lifecycle is Nomos machinery for every birth-capable law. It remains certified/routable law,
207
+ // but no generated tenant API gets a second maintenance lane beside business `birth(...)`.
208
+ internalize("__nomos_nomosBirthRecordEnsure", nomosBirthRecordEnsure);
209
+ internalize("__nomos_NomosBirthRecord", NomosBirthRecord);
210
+ internalize("__nomos_NomosChildLifecycle", NomosChildLifecycle);
211
+ internalize("__nomos_BirthPolicy", BirthPolicy);
212
+ internalize("__nomos_BirthGrant", BirthGrant);
213
+ internalize("__nomos_retireChild", retireChild);
214
+ internalize("__nomos_reclaimChild", reclaimChild);
215
+ internalize("__nomos_retireChildAsAdmin", retireChildAsAdmin);
216
+ internalize("__nomos_reclaimChildAsAdmin", reclaimChildAsAdmin);
217
+ reserve("__nomos_reclaimChildRequiresRetired", reclaimChildRequiresRetired);
218
+ reserve("__nomos_reclaimChildAsAdminRequiresRetired", reclaimChildAsAdminRequiresRetired);
219
+ reserve("__nomos_retireChildRequiresBirth", retireChildRequiresBirth);
220
+ reserve("__nomos_retireChildNotReclaimed", retireChildNotReclaimed);
221
+ reserve("__nomos_retireChildAsAdminRequiresBirth", retireChildAsAdminRequiresBirth);
222
+ reserve("__nomos_retireChildAsAdminNotReclaimed", retireChildAsAdminNotReclaimed);
223
+ // ONE BIRTH LANE ALSO MEANS ONE PRIVATE SIGNING LANE. The client completes a `.births()` offer's signer
224
+ // enrollment, delegation and cert retry internally; therefore every birth-declaring parent law must carry
225
+ // the declarations that make those framework writes lawful. Previously only the newer `businessBirth`
226
+ // helper composed them, while an ordinary `.births()` directive forced the harness/app to understand
227
+ // `enrollDelegation` (or failed as an unverifiable black box). Auto-compose the exact same framework
228
+ // declarations for ALL births and mark them presentation-internal: they remain certified/routable law,
229
+ // but never appear in TS/Dart/OpenAPI tenant surfaces.
230
+ internalize("__nomos_SignerDelegation", SignerDelegation);
231
+ internalize("__nomos_enrollDelegation", enrollDelegation);
232
+ internalize("__nomos_signerDelegationByUser", signerDelegationByUser);
233
+ reserve("__nomos_parentAttestedBirth", internal({
234
+ ...requireParentAttestedBirth,
235
+ ownerObject: GENESIS_WARRANT_ID,
236
+ ownerField: "principal",
237
+ certObject: BIRTH_ATTESTATION_ID,
238
+ certField: "certChain",
239
+ }));
104
240
  return out as M;
105
241
  }
package/src/births.ts CHANGED
@@ -3,10 +3,10 @@
3
3
  // wasm32-wasip1 artifact {kernel · projection · embedded
4
4
  // QuickJS engine}. If a file isn't this / hosting this / authoring for this / proving this — it's gone.
5
5
 
6
- import type { z } from "zod";
6
+ import { z } from "zod";
7
7
  import type { AggregateHandle } from "./aggregate.js";
8
8
  import type { AggregateRef } from "./authoring.js";
9
- import type { Field } from "./fields.js";
9
+ import type { Field, FieldValue } from "./fields.js";
10
10
 
11
11
  /**
12
12
  * THE BIRTH EFFECT — birth is an EXPLICIT, DECLARED kernel primitive (G3, the unix model).
@@ -75,19 +75,29 @@ export interface BusinessBirthDecl<
75
75
  readonly payloadSchema: z.ZodObject<z.ZodRawShape>;
76
76
  /** A stable business key the caller already knows (an estate reference, account number, etc.). */
77
77
  readonly key: (payload: P) => string;
78
- /** Author the born thing's initial business facts. Nomos supplies the durable identity and lifecycle. */
78
+ /** The declared business fact used by {@link key}; retained for generated clients to pre-address custody. */
79
+ readonly keyField: string;
80
+ /** Framework-derived initial authoring. Tenant code marks aggregate fields `.fromBirth()` instead. */
79
81
  readonly plan: (root: AggregateRef<F>, payload: P) => unknown;
80
82
  /** Optional business noun used only to make the durable address readable. Defaults to the aggregate id. */
81
83
  readonly namespace?: string;
82
84
  }
83
85
 
84
- export interface BusinessBirthSpec<
85
- P extends Record<string, unknown>,
86
- F extends Record<string, Field>,
87
- > {
88
- readonly payload: z.ZodType<P> & z.ZodObject<z.ZodRawShape>;
89
- readonly key: (payload: P) => string;
90
- readonly plan: (root: AggregateRef<F>, payload: P) => unknown;
86
+ export type BirthFactKeys<F extends Record<string, Field>> = {
87
+ [K in keyof F]-?: F[K] extends { readonly isFromBirth: true } ? K : never;
88
+ }[keyof F] & string;
89
+
90
+ export type BirthStringFactKeys<F extends Record<string, Field>> = {
91
+ [K in BirthFactKeys<F>]: FieldValue<F[K]> extends string ? K : never;
92
+ }[BirthFactKeys<F>] & string;
93
+
94
+ export type BusinessBirthPayload<F extends Record<string, Field>> = {
95
+ readonly [K in BirthFactKeys<F>]: FieldValue<F[K]>;
96
+ };
97
+
98
+ export interface BusinessBirthSpec<F extends Record<string, Field>> {
99
+ /** One declared birth fact that gives this business life its stable address. */
100
+ readonly key: BirthStringFactKeys<F>;
91
101
  readonly namespace?: string;
92
102
  }
93
103
 
@@ -95,30 +105,51 @@ export interface BusinessBirthSpec<
95
105
  * declares `birth(Aggregate, spec)` and leaves the recipe, identity, and recovery machinery to Nomos. */
96
106
  export function birth(recipe: BirthRecipe): void;
97
107
  /** Declare that one business aggregate is given its own durable life. */
98
- export function birth<P extends Record<string, unknown>, F extends Record<string, Field>>(
108
+ export function birth<F extends Record<string, Field>>(
99
109
  aggregate: AggregateHandle<string, F>,
100
- spec: BusinessBirthSpec<P, F>,
101
- ): BusinessBirthDecl<P, F>;
102
- export function birth<P extends Record<string, unknown>, F extends Record<string, Field>>(
110
+ spec: BusinessBirthSpec<F>,
111
+ ): BusinessBirthDecl<BusinessBirthPayload<F>, F>;
112
+ export function birth<F extends Record<string, Field>>(
103
113
  recipeOrAggregate: BirthRecipe | AggregateHandle<string, F>,
104
- spec?: BusinessBirthSpec<P, F>,
105
- ): void | BusinessBirthDecl<P, F> {
114
+ spec?: BusinessBirthSpec<F>,
115
+ ): void | BusinessBirthDecl<BusinessBirthPayload<F>, F> {
106
116
  if (spec !== undefined) {
107
117
  const aggregate = recipeOrAggregate as AggregateHandle<string, F>;
108
118
  if (!aggregate || aggregate.__isAggregateHandle !== true) {
109
119
  throw new Error("birth(Aggregate, spec): the first argument must be an aggregate handle");
110
120
  }
111
- if (!spec.payload || typeof (spec.payload as { safeParse?: unknown }).safeParse !== "function") {
112
- throw new Error("birth(Aggregate, spec): spec.payload must be a z.object(...) schema");
121
+ const internalFields = new Set(aggregate.internalFields ?? []);
122
+ const facts = Object.entries(aggregate.fields as Record<string, Field>).filter(
123
+ ([name, field]) => field.isFromBirth === true && !internalFields.has(name),
124
+ );
125
+ if (facts.length === 0) {
126
+ throw new Error(
127
+ `birth(${aggregate.id}): no birth facts are declared — mark every required initial business field with .fromBirth()`,
128
+ );
129
+ }
130
+ const keyField = (aggregate.fields as Record<string, Field>)[spec.key];
131
+ if (keyField === undefined || keyField.isFromBirth !== true) {
132
+ throw new Error(`birth(${aggregate.id}): key '${String(spec.key)}' must be a declared .fromBirth() field`);
133
+ }
134
+ if (keyField.kind !== "string" && keyField.kind !== "enum" && keyField.kind !== "ref") {
135
+ throw new Error(
136
+ `birth(${aggregate.id}): key '${String(spec.key)}' must be a string-like business fact; got ${keyField.kind}`,
137
+ );
113
138
  }
139
+ const payloadSchema = z.object(Object.fromEntries(facts.map(([name, field]) => [name, field.zod])));
140
+ type Payload = BusinessBirthPayload<F>;
114
141
  return {
115
142
  __isBusinessBirth: true,
116
143
  aggregate,
117
- payloadSchema: spec.payload,
118
- key: spec.key,
119
- plan: spec.plan,
144
+ payloadSchema,
145
+ key: (payload: Payload) => String(payload[spec.key]),
146
+ keyField: spec.key,
147
+ plan: (root: AggregateRef<F>, payload: Payload) => {
148
+ const writable = root as unknown as { set(field: string, value: unknown): unknown };
149
+ for (const [name] of facts) writable.set(name, payload[name as keyof Payload]);
150
+ },
120
151
  ...(spec.namespace !== undefined ? { namespace: spec.namespace } : {}),
121
- };
152
+ } as BusinessBirthDecl<Payload, F>;
122
153
  }
123
154
  footprint.push(recipeOrAggregate as BirthRecipe);
124
155
  }
@@ -69,18 +69,26 @@ import {
69
69
  type TrustSourceDecl,
70
70
  type TrustPolicyDecl,
71
71
  } from "./attested.js";
72
- import type { RelationTypeDecl } from "./relation_schema.js";
72
+ import { isRelationTypeDecl, type RelationTypeDecl } from "./relation_schema.js";
73
73
  import { domainHash, emitManifestBytes } from "./manifest.js";
74
74
  import { collectValueObjectDescriptors } from "./value_object.js";
75
75
  import { emitUsd } from "./usd.js";
76
76
  import { expandCapabilityExports } from "./capability_exports.js";
77
77
  import { RelationTuple, relationTuplesByObject } from "./framework/relations.js";
78
+ import { signerDelegationByUser } from "./framework/signing_identity.js";
78
79
  import { AuthProvider, setAuthProvider, authProvider } from "./framework/workspaces.js";
79
80
  import { parentAttestedBirthMarker } from "./births.js";
80
81
  import { discoverUnsanctionedEnsures, sanctioningEnsureDirective } from "./secondary_ensure.js";
81
82
  import { assertIdOfLint } from "./idof_lint.js";
82
83
  import { assertSurfacedResolvers } from "./surfaced_lint.js";
84
+ import {
85
+ applyConflictPoliciesToAggregates,
86
+ applyConflictPolicyResolvers,
87
+ conflictPoliciesOf,
88
+ } from "./conflicts.js";
83
89
  import { expandBusinessBirthExports } from "./business_birth.js";
90
+ import { withOwnedCustodyExports } from "./framework/owned_custody.js";
91
+ import { withBusinessCorrectionExports } from "./framework/business_corrections.js";
84
92
  import {
85
93
  reclaimChildAsAdminRequiresRetired,
86
94
  reclaimChildRequiresRetired,
@@ -125,7 +133,10 @@ export function contractsOf(mod: Mod): import("./contract.js").ContractHandle[]
125
133
 
126
134
  /** Collect every exported `Directive` ({id, plan, aggregateId} duck-type). */
127
135
  export function directivesOf(mod: Mod): Directive<any>[] {
128
- const out: Directive<any>[] = [];
136
+ // One wire directive id is one law declaration. Merge seams may retain both an old tenant re-export and
137
+ // Nomos's presentation-internal wrapper under different JS keys; later wins so the framework wrapper
138
+ // controls codegen without duplicating the hashed/routable law.
139
+ const byId = new Map<string, Directive<any>>();
129
140
  for (const v of Object.values(mod)) {
130
141
  if (
131
142
  v &&
@@ -134,10 +145,10 @@ export function directivesOf(mod: Mod): Directive<any>[] {
134
145
  typeof (v as { plan?: unknown }).plan === "function" &&
135
146
  typeof (v as { aggregateId?: unknown }).aggregateId === "string"
136
147
  ) {
137
- out.push(v as Directive<any>);
148
+ byId.set((v as { id: string }).id, v as Directive<any>);
138
149
  }
139
150
  }
140
- return out;
151
+ return [...byId.values()];
141
152
  }
142
153
 
143
154
  /** The NAMED-KEYS spec for one engine domain (the serialization of `module(...)`). */
@@ -180,9 +191,13 @@ export interface DomainModuleSpec {
180
191
  * (an empty optional key must be ABSENT or every domain's canonical manifest hash drifts).
181
192
  */
182
193
  export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
194
+ const domain = spec.domain ?? spec.name;
195
+ const conflictPolicies = conflictPoliciesOf(spec.modules, domain);
183
196
  let merged: Mod = {};
184
197
  for (const m of spec.modules) merged = { ...merged, ...m };
185
198
  merged = expandBusinessBirthExports(merged, spec.domain ?? spec.name);
199
+ merged = withOwnedCustodyExports(merged);
200
+ merged = withBusinessCorrectionExports(merged);
186
201
  // `capability()` bundles expand into discoverable entries (the package plane's
187
202
  // merge point) — the quartet validation below then passes without re-exports.
188
203
  merged = expandCapabilityExports(merged);
@@ -207,7 +222,7 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
207
222
 
208
223
  const byId = new Map<string, AggregateHandle>();
209
224
  for (const a of [...aggregatesOf(merged), ...(spec.extraAggregates ?? [])]) byId.set(a.id, a);
210
- const directives = directivesOf(merged);
225
+ const directives = applyConflictPolicyResolvers(directivesOf(merged), conflictPolicies);
211
226
  // RELATIONS AUTO-ENSURE + AUTO-COMPOSE (co2 recovery DX): a domain that USES relation law — declares a
212
227
  // relation schema, a `.requires(...)`-gated directive, or relate-family/signer directives ensuring
213
228
  // RelationTuple — needs the framework RelationTuple aggregate IN the schema on TWO counts: (1) the deploy
@@ -232,9 +247,20 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
232
247
  const declaresPrivateRead = [...byId.values()].some(
233
248
  (a) => (a as { visibility?: unknown }).visibility !== undefined,
234
249
  );
250
+ const declaresOwnedRead = [...byId.values()].some(
251
+ (a) => (a as { audience?: { kind?: unknown } }).audience?.kind === "owned",
252
+ );
235
253
  if (declaresPrivateRead) {
236
- if (!byId.has(AuthProvider.id)) byId.set(AuthProvider.id, AuthProvider);
237
- if (!directives.some((d) => d.id === setAuthProvider.id)) directives.push(setAuthProvider);
254
+ if (!byId.has(AuthProvider.id)) {
255
+ byId.set(AuthProvider.id, declaresOwnedRead
256
+ ? { ...AuthProvider, businessBirthInternal: true } as AggregateHandle
257
+ : AuthProvider);
258
+ }
259
+ if (!directives.some((d) => d.id === setAuthProvider.id)) {
260
+ directives.push(declaresOwnedRead
261
+ ? { ...setAuthProvider, businessBirthInternal: true } as Directive<any>
262
+ : setAuthProvider);
263
+ }
238
264
  }
239
265
  const usesRelations =
240
266
  declaresPrivateRead ||
@@ -242,7 +268,9 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
242
268
  directives.some((d) => (d as { requiresRelationGate?: unknown }).requiresRelationGate !== undefined) ||
243
269
  directives.some((d) => d.aggregateId === RelationTuple.id);
244
270
  if (usesRelations && !byId.has(RelationTuple.id)) {
245
- byId.set(RelationTuple.id, RelationTuple);
271
+ byId.set(RelationTuple.id, declaresOwnedRead
272
+ ? { ...RelationTuple, businessBirthInternal: true } as AggregateHandle
273
+ : RelationTuple);
246
274
  }
247
275
  // BIRTH-METER AUTO-ENSURE (metered birth, slice A — the same "discoverable without re-exports"
248
276
  // courtesy as RelationTuple): a domain declaring ≥1 `.births()` directive gets the meter aggregates
@@ -292,7 +320,11 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
292
320
  directives.push(dir);
293
321
  autoDeclaredEnsures.push(ty);
294
322
  }
295
- const aggregates = [...byId.values()];
323
+ const aggregates = applyConflictPoliciesToAggregates(
324
+ [...byId.values()],
325
+ conflictPolicies,
326
+ domain,
327
+ );
296
328
  const directiveIds = new Set(directives.map((d) => d.id));
297
329
  for (const task of spec.impureCapabilities ?? []) {
298
330
  for (const directive of [task.obligation, task.complete, task.fail, task.block, task.deadLetter]) {
@@ -306,7 +338,11 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
306
338
  }
307
339
  const readModels = spec.readModelAggregates ?? aggregates.map((a) => a.id);
308
340
 
309
- const queries = [...(spec.queries ?? [])];
341
+ // As with directives, one stable query id is one declaration. This also makes an internal framework
342
+ // wrapper win over an older public re-export discovered earlier in the module bag.
343
+ const queryById = new Map<string, QueryDecl>();
344
+ for (const query of spec.queries ?? []) queryById.set(query.id, query);
345
+ const queries = [...queryById.values()];
310
346
  const counts = [...(spec.counts ?? [])];
311
347
  const spatials = [...(spec.spatials ?? [])];
312
348
  const deriveds = [...(spec.deriveds ?? [])];
@@ -316,7 +352,23 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
316
352
  const mins = [...(spec.mins ?? [])];
317
353
  const maxes = [...(spec.maxes ?? [])];
318
354
  const workspaceTypes = [...(spec.workspaceTypes ?? [])];
319
- const workspaceInvariants = [...(spec.workspaceInvariants ?? [])];
355
+ // Invariants exported by framework auto-composition must enter the manifest at this same seam; callers of
356
+ // composeDomainModule (tests and package builders alike) should not need to rediscover/rethread them.
357
+ const workspaceInvariantById = new Map<string, WorkspaceInvariantDecl>();
358
+ for (const value of Object.values(merged)) {
359
+ if (
360
+ value !== null &&
361
+ typeof value === "object" &&
362
+ (value as { __isWorkspaceInvariant?: unknown }).__isWorkspaceInvariant === true
363
+ ) {
364
+ const invariant = value as WorkspaceInvariantDecl;
365
+ workspaceInvariantById.set(invariant.id, invariant);
366
+ }
367
+ }
368
+ for (const invariant of spec.workspaceInvariants ?? []) {
369
+ workspaceInvariantById.set(invariant.id, invariant);
370
+ }
371
+ const workspaceInvariants = [...workspaceInvariantById.values()];
320
372
  if (declaresBirth) {
321
373
  const haveInvariant = new Set(workspaceInvariants.map((w) => w.id));
322
374
  for (const lifecycle of [
@@ -343,12 +395,30 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
343
395
  if (declaresBirth) {
344
396
  const haveQuery = new Set(queries.map((q) => q.id));
345
397
  for (const q of BIRTH_FRAMEWORK_QUERIES) {
346
- if (!haveQuery.has(q.id)) queries.push(q);
398
+ if (!haveQuery.has(q.id)) {
399
+ queries.push({ ...q, businessBirthInternal: true } as QueryDecl);
400
+ }
347
401
  }
348
402
  // The inheritance read (`.reads("authProvider")` on birthHome) routes like any declared query.
349
403
  if (!haveQuery.has(authProvider.id)) queries.push(authProvider);
404
+ // The client's private birth ceremony checks the actor's delegation before creating it. Carry the
405
+ // structural read in certified routing law for every `.births()` domain, but keep it off tenant codegen.
406
+ if (!haveQuery.has(signerDelegationByUser.id)) {
407
+ const internalDelegationQuery: QueryDecl & { readonly businessBirthInternal: true } = {
408
+ ...signerDelegationByUser,
409
+ businessBirthInternal: true,
410
+ };
411
+ queries.push(internalDelegationQuery);
412
+ }
413
+ }
414
+ const relationTypeByKey = new Map<string, RelationTypeDecl>();
415
+ for (const value of Object.values(merged)) {
416
+ if (isRelationTypeDecl(value)) relationTypeByKey.set(`${value.global === true ? "global" : "local"}:${value.type}`, value);
417
+ }
418
+ for (const value of spec.relationTypes ?? []) {
419
+ relationTypeByKey.set(`${value.global === true ? "global" : "local"}:${value.type}`, value);
350
420
  }
351
- const relationTypes = [...(spec.relationTypes ?? [])];
421
+ const relationTypes = [...relationTypeByKey.values()];
352
422
  // RELATION-USING LAW AUTO-COMPOSES ITS AUDIT READ (co2 recovery DX — same idiom as the meter
353
423
  // queries above): a domain that declares relation law (a relation schema, a `.requires(...)` gated
354
424
  // directive, or relate-family/signer directives ensuring RelationTuple) gets the framework's
@@ -363,10 +433,14 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
363
433
  // Auth-provider auto-compose (sibling of the RelationTuple query carry): a private package gets the
364
434
  // `authProvider` read so the workspace can surface its declared verifier.
365
435
  if (declaresPrivateRead && !queries.some((q) => q.id === authProvider.id)) {
366
- queries.push(authProvider);
436
+ queries.push(declaresOwnedRead
437
+ ? { ...authProvider, businessBirthInternal: true } as QueryDecl
438
+ : authProvider);
367
439
  }
368
440
  if (usesRelations && !queries.some((q) => q.id === relationTuplesByObject.id)) {
369
- queries.push(relationTuplesByObject);
441
+ queries.push(declaresOwnedRead
442
+ ? { ...relationTuplesByObject, businessBirthInternal: true } as QueryDecl
443
+ : relationTuplesByObject);
370
444
  }
371
445
  // FIRST-CLASS VALUE OBJECTS (value_object.ts): the deduped, sorted descriptors of every
372
446
  // named VO the domain's aggregates carry (transitive nested VOs included) — the input to
@@ -492,10 +566,11 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
492
566
  // closed is dissolved by construction. See `dsl/src/framework/signing_identity.ts::enrollSigner`.)
493
567
 
494
568
  // THE SURFACED-RESOLVER LINT (surfaced_lint.ts — the fail-closed teeth): every
495
- // `.merge(Surfaced(...))` field must have EXACTLY ONE `.resolves()` directive, whose plan
569
+ // Surfaced field must have EXACTLY ONE designated merge directive, whose plan
496
570
  // provably set()s the field from a payload field. Returns the resolver bindings the typed
497
- // `resolve…` helper codegen consumes. Compile-time only — no manifest/wire/hash effect;
498
- // a Surfaced-free domain runs zero checks (byte-identical).
571
+ // chained conflict-reference codegen consumes AND the canonical manifest records as the
572
+ // hash-bearing conflictResolution contract. A Surfaced-free domain runs zero checks and is
573
+ // byte-identical; a resolver policy deliberately becomes part of business law.
499
574
  const surfacedResolvers = assertSurfacedResolvers(
500
575
  aggregates,
501
576
  directives,
@@ -512,8 +587,9 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
512
587
  // ROLE SUGAR codegen metadata — NOT law (not hashed): drives the `canEdit<Agg>`/`canView<Agg>`
513
588
  // UI-hint helpers. Omit-when-empty (a role-free domain is byte-identical).
514
589
  ...(roles.length > 0 ? { roles } : {}),
515
- // SURFACED RESOLVER BINDINGS — NOT law (not hashed, like `roles`): drives the generated
516
- // typed `resolve…` helpers + the `pendingConflicts` accessor. Omit-when-empty (era rule).
590
+ // SURFACED RESOLVER BINDINGS — the aggregate/field/directive tuple is lowered into the
591
+ // hash-bearing manifest; payload-field tracing also drives generated typed references.
592
+ // Omit-when-empty because ordinary domains need no resolver metadata.
517
593
  ...(surfacedResolvers.length > 0 ? { surfacedResolvers } : {}),
518
594
  ...(queries.length > 0 ? { queries } : {}),
519
595
  ...(counts.length > 0 ? { counts } : {}),