@githolon/dsl 0.100.6 → 0.101.1

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 (82) hide show
  1. package/dart/CHANGELOG.md +6 -0
  2. package/dart/lib/src/dev_cloud.dart +46 -15
  3. package/dart/lib/src/test_harness.dart +78 -16
  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/bundle_version_pairing_test.dart +11 -22
  8. package/dart/test/co2_product_gauntlet_test.dart +228 -0
  9. package/dart/test/errors_test.dart +63 -12
  10. package/dart/test/first_app_gauntlet_test.dart +29 -3
  11. package/dart/test/webview_bridge_test.dart +274 -0
  12. package/dart_types/CHANGELOG.md +6 -0
  13. package/dart_types/lib/src/errors.dart +136 -30
  14. package/dart_types/lib/src/surfaced.dart +48 -2
  15. package/dart_types/lib/src/wire.dart +3 -1
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/dart_types/test/surfaced_test.dart +19 -0
  18. package/package.json +1 -1
  19. package/src/activity.ts +43 -4
  20. package/src/aggregate.ts +132 -22
  21. package/src/attested_core.ts +1 -1
  22. package/src/authoring.ts +119 -6
  23. package/src/birth_meter.ts +4 -4
  24. package/src/birth_meter_ensure.ts +9 -1
  25. package/src/births.ts +53 -22
  26. package/src/build_package.ts +55 -14
  27. package/src/business_birth.ts +48 -7
  28. package/src/codegen_dart.ts +267 -37
  29. package/src/codegen_dart_client.ts +921 -82
  30. package/src/codegen_openapi.ts +28 -5
  31. package/src/codegen_proof.ts +107 -11
  32. package/src/codegen_ts.ts +961 -135
  33. package/src/compile_package_main.ts +155 -45
  34. package/src/conflicts.ts +252 -0
  35. package/src/describe_model.ts +448 -0
  36. package/src/directive.ts +5 -38
  37. package/src/drivers.ts +12 -4
  38. package/src/engine_entry.ts +4 -0
  39. package/src/evolve_preview.ts +72 -11
  40. package/src/fields.ts +201 -55
  41. package/src/framework/application_home.ts +103 -0
  42. package/src/framework/application_home_domain.ts +2 -0
  43. package/src/framework/attested_receipts.ts +2 -2
  44. package/src/framework/auth_provider_law.ts +51 -24
  45. package/src/framework/business_corrections.ts +170 -0
  46. package/src/framework/disclosure.ts +1 -1
  47. package/src/framework/domain_lifecycle.ts +250 -16
  48. package/src/framework/encryption_identity.ts +1 -1
  49. package/src/framework/encryption_scope.ts +21 -13
  50. package/src/framework/era_certification.ts +1 -1
  51. package/src/framework/framework_aggregates.ts +2 -0
  52. package/src/framework/genesis.ts +1 -1
  53. package/src/framework/home.ts +22 -9
  54. package/src/framework/identity.ts +5 -5
  55. package/src/framework/impure_capability.ts +2 -2
  56. package/src/framework/owned_custody.ts +561 -0
  57. package/src/framework/platform_admin.ts +1 -1
  58. package/src/framework/relations.ts +9 -1
  59. package/src/framework/repair.ts +1 -1
  60. package/src/framework/signing_identity.ts +2 -2
  61. package/src/framework/source_roster.ts +1 -1
  62. package/src/framework/workspaces.ts +17 -10
  63. package/src/generated_client_names.ts +39 -0
  64. package/src/index.ts +20 -15
  65. package/src/kernel_capabilities.ts +25 -1
  66. package/src/law_check.ts +4 -1
  67. package/src/manifest.ts +356 -63
  68. package/src/read_projection_manifest.ts +8 -0
  69. package/src/read_projection_namespace.ts +4 -0
  70. package/src/relation.ts +1 -1
  71. package/src/shape_compat.ts +47 -2
  72. package/src/stable_ids.ts +38 -6
  73. package/src/stable_ids_types.ts +30 -0
  74. package/src/surfaced_lint.ts +0 -0
  75. package/src/usd.ts +62 -11
  76. package/src/usd_layers.ts +54 -3
  77. package/src/value_object.ts +23 -19
  78. package/src/wire.ts +1 -0
  79. package/src/wire_encode.ts +48 -4
  80. package/src/workspace_routing.ts +57 -0
  81. package/src/workspace_sharding.ts +9 -9
  82. 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
 
@@ -59,7 +59,13 @@ import {
59
59
  signerDelegationByUser,
60
60
  signerObject,
61
61
  } from "./framework/signing_identity.js";
62
- import { AuthProvider, authProvider, setAuthProvider } from "./framework/auth_provider_law.js";
62
+ import {
63
+ AuthProvider,
64
+ authProvider,
65
+ authProviderSeedBeforeWarrant,
66
+ seedAuthProvider,
67
+ setAuthProvider,
68
+ } from "./framework/auth_provider_law.js";
63
69
  import { refAs, workspaceInvariant } from "./framework/workspace_invariant.js";
64
70
 
65
71
  /** Lightweight copy of the framework's founding-signer guard, kept here to avoid importing the whole
@@ -191,6 +197,8 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
191
197
  internalize("__nomos_seedBirthAttestation", seedBirthAttestation);
192
198
  internalize("__nomos_birthAttestation", birthAttestation);
193
199
  internalize("__nomos_AuthProvider", AuthProvider);
200
+ internalize("__nomos_seedAuthProvider", seedAuthProvider);
201
+ reserve("__nomos_authProviderSeedBeforeWarrant", authProviderSeedBeforeWarrant);
194
202
  internalize("__nomos_setAuthProvider", setAuthProvider);
195
203
  internalize("__nomos_authProvider", authProvider);
196
204
  reserve("__nomos_founderSignerWriteOnce", bornFounderSignerWriteOnce);
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,7 +69,7 @@ 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";
@@ -81,7 +81,14 @@ import { parentAttestedBirthMarker } from "./births.js";
81
81
  import { discoverUnsanctionedEnsures, sanctioningEnsureDirective } from "./secondary_ensure.js";
82
82
  import { assertIdOfLint } from "./idof_lint.js";
83
83
  import { assertSurfacedResolvers } from "./surfaced_lint.js";
84
+ import {
85
+ applyConflictPoliciesToAggregates,
86
+ applyConflictPolicyResolvers,
87
+ conflictPoliciesOf,
88
+ } from "./conflicts.js";
84
89
  import { expandBusinessBirthExports } from "./business_birth.js";
90
+ import { withOwnedCustodyExports } from "./framework/owned_custody.js";
91
+ import { withBusinessCorrectionExports } from "./framework/business_corrections.js";
85
92
  import {
86
93
  reclaimChildAsAdminRequiresRetired,
87
94
  reclaimChildRequiresRetired,
@@ -184,9 +191,13 @@ export interface DomainModuleSpec {
184
191
  * (an empty optional key must be ABSENT or every domain's canonical manifest hash drifts).
185
192
  */
186
193
  export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
194
+ const domain = spec.domain ?? spec.name;
195
+ const conflictPolicies = conflictPoliciesOf(spec.modules, domain);
187
196
  let merged: Mod = {};
188
197
  for (const m of spec.modules) merged = { ...merged, ...m };
189
198
  merged = expandBusinessBirthExports(merged, spec.domain ?? spec.name);
199
+ merged = withOwnedCustodyExports(merged);
200
+ merged = withBusinessCorrectionExports(merged);
190
201
  // `capability()` bundles expand into discoverable entries (the package plane's
191
202
  // merge point) — the quartet validation below then passes without re-exports.
192
203
  merged = expandCapabilityExports(merged);
@@ -211,7 +222,7 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
211
222
 
212
223
  const byId = new Map<string, AggregateHandle>();
213
224
  for (const a of [...aggregatesOf(merged), ...(spec.extraAggregates ?? [])]) byId.set(a.id, a);
214
- const directives = directivesOf(merged);
225
+ const directives = applyConflictPolicyResolvers(directivesOf(merged), conflictPolicies);
215
226
  // RELATIONS AUTO-ENSURE + AUTO-COMPOSE (co2 recovery DX): a domain that USES relation law — declares a
216
227
  // relation schema, a `.requires(...)`-gated directive, or relate-family/signer directives ensuring
217
228
  // RelationTuple — needs the framework RelationTuple aggregate IN the schema on TWO counts: (1) the deploy
@@ -236,9 +247,20 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
236
247
  const declaresPrivateRead = [...byId.values()].some(
237
248
  (a) => (a as { visibility?: unknown }).visibility !== undefined,
238
249
  );
250
+ const declaresOwnedRead = [...byId.values()].some(
251
+ (a) => (a as { audience?: { kind?: unknown } }).audience?.kind === "owned",
252
+ );
239
253
  if (declaresPrivateRead) {
240
- if (!byId.has(AuthProvider.id)) byId.set(AuthProvider.id, AuthProvider);
241
- 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
+ }
242
264
  }
243
265
  const usesRelations =
244
266
  declaresPrivateRead ||
@@ -246,7 +268,9 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
246
268
  directives.some((d) => (d as { requiresRelationGate?: unknown }).requiresRelationGate !== undefined) ||
247
269
  directives.some((d) => d.aggregateId === RelationTuple.id);
248
270
  if (usesRelations && !byId.has(RelationTuple.id)) {
249
- byId.set(RelationTuple.id, RelationTuple);
271
+ byId.set(RelationTuple.id, declaresOwnedRead
272
+ ? { ...RelationTuple, businessBirthInternal: true } as AggregateHandle
273
+ : RelationTuple);
250
274
  }
251
275
  // BIRTH-METER AUTO-ENSURE (metered birth, slice A — the same "discoverable without re-exports"
252
276
  // courtesy as RelationTuple): a domain declaring ≥1 `.births()` directive gets the meter aggregates
@@ -296,7 +320,11 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
296
320
  directives.push(dir);
297
321
  autoDeclaredEnsures.push(ty);
298
322
  }
299
- const aggregates = [...byId.values()];
323
+ const aggregates = applyConflictPoliciesToAggregates(
324
+ [...byId.values()],
325
+ conflictPolicies,
326
+ domain,
327
+ );
300
328
  const directiveIds = new Set(directives.map((d) => d.id));
301
329
  for (const task of spec.impureCapabilities ?? []) {
302
330
  for (const directive of [task.obligation, task.complete, task.fail, task.block, task.deadLetter]) {
@@ -383,7 +411,14 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
383
411
  queries.push(internalDelegationQuery);
384
412
  }
385
413
  }
386
- const relationTypes = [...(spec.relationTypes ?? [])];
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);
420
+ }
421
+ const relationTypes = [...relationTypeByKey.values()];
387
422
  // RELATION-USING LAW AUTO-COMPOSES ITS AUDIT READ (co2 recovery DX — same idiom as the meter
388
423
  // queries above): a domain that declares relation law (a relation schema, a `.requires(...)` gated
389
424
  // directive, or relate-family/signer directives ensuring RelationTuple) gets the framework's
@@ -398,10 +433,14 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
398
433
  // Auth-provider auto-compose (sibling of the RelationTuple query carry): a private package gets the
399
434
  // `authProvider` read so the workspace can surface its declared verifier.
400
435
  if (declaresPrivateRead && !queries.some((q) => q.id === authProvider.id)) {
401
- queries.push(authProvider);
436
+ queries.push(declaresOwnedRead
437
+ ? { ...authProvider, businessBirthInternal: true } as QueryDecl
438
+ : authProvider);
402
439
  }
403
440
  if (usesRelations && !queries.some((q) => q.id === relationTuplesByObject.id)) {
404
- queries.push(relationTuplesByObject);
441
+ queries.push(declaresOwnedRead
442
+ ? { ...relationTuplesByObject, businessBirthInternal: true } as QueryDecl
443
+ : relationTuplesByObject);
405
444
  }
406
445
  // FIRST-CLASS VALUE OBJECTS (value_object.ts): the deduped, sorted descriptors of every
407
446
  // named VO the domain's aggregates carry (transitive nested VOs included) — the input to
@@ -527,10 +566,11 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
527
566
  // closed is dissolved by construction. See `dsl/src/framework/signing_identity.ts::enrollSigner`.)
528
567
 
529
568
  // THE SURFACED-RESOLVER LINT (surfaced_lint.ts — the fail-closed teeth): every
530
- // `.merge(Surfaced(...))` field must have EXACTLY ONE `.resolves()` directive, whose plan
569
+ // Surfaced field must have EXACTLY ONE designated merge directive, whose plan
531
570
  // provably set()s the field from a payload field. Returns the resolver bindings the typed
532
- // `resolve…` helper codegen consumes. Compile-time only — no manifest/wire/hash effect;
533
- // 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.
534
574
  const surfacedResolvers = assertSurfacedResolvers(
535
575
  aggregates,
536
576
  directives,
@@ -547,8 +587,9 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
547
587
  // ROLE SUGAR codegen metadata — NOT law (not hashed): drives the `canEdit<Agg>`/`canView<Agg>`
548
588
  // UI-hint helpers. Omit-when-empty (a role-free domain is byte-identical).
549
589
  ...(roles.length > 0 ? { roles } : {}),
550
- // SURFACED RESOLVER BINDINGS — NOT law (not hashed, like `roles`): drives the generated
551
- // 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.
552
593
  ...(surfacedResolvers.length > 0 ? { surfacedResolvers } : {}),
553
594
  ...(queries.length > 0 ? { queries } : {}),
554
595
  ...(counts.length > 0 ? { counts } : {}),
@@ -1,6 +1,7 @@
1
1
  // NOMOS — one business birth at the tenant surface; compute-science mechanics lower here.
2
2
 
3
3
  import { z } from "zod";
4
+ import { BUSINESS_BIRTH_SEED_PAYLOAD_FIELD } from "./aggregate.js";
4
5
  import { ensure } from "./authoring.js";
5
6
  import { directive, type Directive } from "./directive.js";
6
7
  import {
@@ -11,7 +12,13 @@ import {
11
12
  type ParentAttestedBirthMarker,
12
13
  } from "./births.js";
13
14
  import { readFrameworkDeclared } from "./read.js";
14
- import { AuthProvider, authProvider, setAuthProvider } from "./framework/auth_provider_law.js";
15
+ import {
16
+ AuthProvider,
17
+ authProvider,
18
+ authProviderSeedBeforeWarrant,
19
+ seedAuthProvider,
20
+ setAuthProvider,
21
+ } from "./framework/auth_provider_law.js";
15
22
  import {
16
23
  BirthGrant,
17
24
  BirthPolicy,
@@ -49,6 +56,7 @@ export interface BusinessBirthMetadata {
49
56
  readonly publicPayloadSchema: z.ZodObject<z.ZodRawShape>;
50
57
  readonly rootId: string;
51
58
  readonly namespace: string;
59
+ readonly keyField: string;
52
60
  }
53
61
 
54
62
  export interface BusinessBirthDirective extends Directive<Record<string, unknown>> {
@@ -68,7 +76,13 @@ function internalSurface<T extends object>(value: T): T & { readonly businessBir
68
76
  return { ...value, businessBirthInternal: true };
69
77
  }
70
78
 
71
- const INTERNAL_FIELDS = ["principal", "bornAt", "ownerKeyHash", "birthCert"] as const;
79
+ const INTERNAL_FIELDS = [
80
+ "principal",
81
+ "bornAt",
82
+ "ownerKeyHash",
83
+ "birthCert",
84
+ BUSINESS_BIRTH_SEED_PAYLOAD_FIELD,
85
+ ] as const;
72
86
  const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/);
73
87
 
74
88
  /**
@@ -107,16 +121,20 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
107
121
  const seedId = `nomosSeed${stem}`;
108
122
  const birthId = `nomosBirth${stem}`;
109
123
  const publicSchema = decl.payloadSchema as z.ZodObject<z.ZodRawShape>;
110
- const internalSchema = publicSchema.extend({
124
+ const encryptedBirth = (decl.aggregate.encryptedFields?.length ?? 0) > 0;
125
+ const seedSchema = publicSchema.extend({
111
126
  principal: z.string().min(1),
112
127
  bornAt: z.string().min(1),
113
128
  ownerKeyHash: sha256Hex.optional(),
114
129
  birthCert: z.string().optional(),
115
130
  });
131
+ const parentSchema = seedSchema.extend({
132
+ ...(encryptedBirth ? { [BUSINESS_BIRTH_SEED_PAYLOAD_FIELD]: z.string().min(1) } : {}),
133
+ });
116
134
 
117
135
  const seedBase = directive(seedId)
118
136
  .ensures(decl.aggregate)
119
- .payload(internalSchema)
137
+ .payload(seedSchema)
120
138
  .plan((p) => {
121
139
  decl.plan(ensure(decl.aggregate, rootId), p);
122
140
  return [];
@@ -128,10 +146,30 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
128
146
 
129
147
  const birthBase = directive(birthId)
130
148
  .creates(decl.aggregate)
131
- .payload(internalSchema)
149
+ .payload(parentSchema)
132
150
  .plan((p) => {
133
151
  const founder = p.principal as string;
134
152
  const child = workspaceNameForKey(namespace, String(decl.key(p)));
153
+ const childPayload = { ...(p as Record<string, unknown>) };
154
+ if (encryptedBirth) {
155
+ const rawSeed = childPayload[BUSINESS_BIRTH_SEED_PAYLOAD_FIELD];
156
+ if (typeof rawSeed !== "string") {
157
+ throw new Error(
158
+ `birth(${decl.aggregate.id}): Nomos did not carry the internally sealed genesis facts`,
159
+ );
160
+ }
161
+ let sealed: unknown;
162
+ try {
163
+ sealed = JSON.parse(rawSeed);
164
+ } catch {
165
+ throw new Error(`birth(${decl.aggregate.id}): the internally sealed genesis facts are malformed`);
166
+ }
167
+ if (sealed === null || typeof sealed !== "object" || Array.isArray(sealed)) {
168
+ throw new Error(`birth(${decl.aggregate.id}): the internally sealed genesis facts are malformed`);
169
+ }
170
+ Object.assign(childPayload, sealed as Record<string, unknown>);
171
+ }
172
+ delete childPayload[BUSINESS_BIRTH_SEED_PAYLOAD_FIELD];
135
173
  const chain: Array<Record<string, unknown>> = [
136
174
  {
137
175
  domain: "bootstrap",
@@ -153,7 +191,7 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
153
191
  domain,
154
192
  directiveId: seedId,
155
193
  actor: founder,
156
- payload: p,
194
+ payload: childPayload,
157
195
  },
158
196
  ];
159
197
 
@@ -184,7 +222,7 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
184
222
  if (apStr("issuer")) {
185
223
  chain.push({
186
224
  domain,
187
- directiveId: "setAuthProvider",
225
+ directiveId: "seedAuthProvider",
188
226
  actor: founder,
189
227
  payload: {
190
228
  issuer: apStr("issuer"),
@@ -212,6 +250,7 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
212
250
  publicPayloadSchema: publicSchema,
213
251
  rootId,
214
252
  namespace,
253
+ keyField: decl.keyField,
215
254
  };
216
255
  const parent = { ...birthBase, businessBirth: metadata } as BusinessBirthDirective;
217
256
  return {
@@ -236,6 +275,8 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
236
275
  __nomos_enrollDelegation: internalDirective(enrollDelegation),
237
276
  __nomos_signerDelegationByUser: internalSurface(signerDelegationByUser),
238
277
  __nomos_AuthProvider: internalSurface(AuthProvider),
278
+ __nomos_seedAuthProvider: internalDirective(seedAuthProvider),
279
+ __nomos_authProviderSeedBeforeWarrant: internalSurface(authProviderSeedBeforeWarrant),
239
280
  __nomos_setAuthProvider: internalDirective(setAuthProvider),
240
281
  __nomos_authProvider: internalSurface(authProvider),
241
282
  // Obscure framework export keys minimize accidental tenant collisions. Stable law ids remain the real