@githolon/dsl 0.113.4 → 0.115.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/compile_package.mjs +23 -3
  3. package/compile_proof.d.mts +7 -0
  4. package/compile_proof.mjs +38 -3
  5. package/dart/CHANGELOG.md +60 -0
  6. package/dart/lib/src/application_framework_calls.dart +16 -6
  7. package/dart/lib/src/console_enrolment.dart +7 -0
  8. package/dart/lib/src/console_http_io.dart +22 -15
  9. package/dart/lib/src/dev_cloud.dart +62 -0
  10. package/dart/lib/src/generated_call.dart +14 -3
  11. package/dart/lib/src/nms_generated_runtime.dart +7 -0
  12. package/dart/lib/src/node_runner_transport.dart +77 -8
  13. package/dart/lib/src/nomos_trace.dart +107 -0
  14. package/dart/lib/src/operation_receipts.dart +74 -7
  15. package/dart/lib/src/reception/dart_reception.dart +174 -17
  16. package/dart/lib/src/rows_watch.dart +390 -0
  17. package/dart/lib/src/runner/node-entry.mjs +7678 -2860
  18. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  19. package/dart/lib/src/sync_status_projection.dart +94 -1
  20. package/dart/lib/src/sync_status_reducer.dart +5 -2
  21. package/dart/lib/src/test_harness.dart +107 -41
  22. package/dart/lib/src/testing/test_peer.dart +107 -99
  23. package/dart/lib/src/testing/test_peer_host.dart +305 -0
  24. package/dart/lib/src/typed_watch_registry.dart +26 -3
  25. package/dart/pubspec.lock +1 -1
  26. package/dart/pubspec.yaml +2 -2
  27. package/dart/test/background_model_adoption_test.dart +1 -0
  28. package/dart/test/console_enrolment_test.dart +47 -0
  29. package/dart/test/custodian_warming_test.dart +105 -0
  30. package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
  31. package/dart/test/kernel_author_after_read_test.dart +6 -3
  32. package/dart/test/kernel_fence_test.dart +4 -1
  33. package/dart/test/model_adoption_test.dart +66 -0
  34. package/dart/test/nomos_trace_test.dart +91 -0
  35. package/dart/test/own_offer_watch_order_test.dart +90 -0
  36. package/dart/test/receiving_group_open_test.dart +4 -8
  37. package/dart/test/replica_readiness_test.dart +14 -4
  38. package/dart/test/rows_watch_test.dart +331 -0
  39. package/dart/test/runtime_projections_test.dart +41 -0
  40. package/dart/test/test_peer_host_selection_test.dart +49 -0
  41. package/dart_types/CHANGELOG.md +39 -0
  42. package/dart_types/lib/src/approved_model.dart +100 -6
  43. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +1063 -3
  44. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +89 -0
  45. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
  46. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +2272 -130
  47. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
  48. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +118 -2
  49. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
  50. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
  51. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  52. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
  53. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  54. package/dart_types/pubspec.yaml +1 -1
  55. package/package.json +6 -2
  56. package/src/aggregate.ts +93 -0
  57. package/src/anonymous_value_interning.ts +205 -0
  58. package/src/baseline.ts +27 -0
  59. package/src/baseline_export.ts +432 -0
  60. package/src/baseline_import.ts +312 -0
  61. package/src/baseline_usda.ts +186 -0
  62. package/src/birth_key_rule.ts +472 -0
  63. package/src/births.ts +207 -24
  64. package/src/build_provenance.ts +17 -2
  65. package/src/business_birth.ts +50 -4
  66. package/src/check_package_main.ts +50 -6
  67. package/src/codegen_call_proto.ts +5 -18
  68. package/src/codegen_dart.ts +95 -3
  69. package/src/codegen_dart_client.ts +73 -15
  70. package/src/codegen_dart_facade.ts +58 -35
  71. package/src/codegen_dart_framework_calls.ts +32 -10
  72. package/src/codegen_dart_scenario.ts +7 -0
  73. package/src/codegen_proof.ts +5 -2
  74. package/src/codegen_result_proto.ts +8 -6
  75. package/src/codegen_result_usda.ts +4 -0
  76. package/src/codegen_ts.ts +135 -34
  77. package/src/compatibility_graph.ts +41 -4
  78. package/src/compatibility_usda.ts +11 -1
  79. package/src/compile_package_main.ts +272 -105
  80. package/src/composed_wire_identity.ts +24 -0
  81. package/src/directive.ts +6 -0
  82. package/src/domain_requirements.ts +75 -0
  83. package/src/engine_entry.ts +4 -48
  84. package/src/engine_javascript.ts +12 -6
  85. package/src/fields.ts +109 -0
  86. package/src/identity_usda.ts +77 -51
  87. package/src/index.ts +25 -1
  88. package/src/intent.ts +5 -0
  89. package/src/law_graph.ts +142 -1
  90. package/src/law_graph_usda.ts +25 -141
  91. package/src/law_layer_prims.ts +199 -0
  92. package/src/law_unit_selection.ts +45 -0
  93. package/src/law_unit_set_identity.ts +44 -0
  94. package/src/law_version.ts +107 -0
  95. package/src/package_config.ts +44 -0
  96. package/src/package_dependencies.ts +8 -0
  97. package/src/query/compile.ts +0 -0
  98. package/src/release_inspection.ts +24 -0
  99. package/src/release_publication.ts +298 -0
  100. package/src/value_object.ts +6 -3
  101. package/src/wire.ts +2 -0
  102. package/src/wire_paths.ts +17 -0
  103. package/src/workspace_routing.ts +1 -1
package/src/births.ts CHANGED
@@ -4,10 +4,19 @@
4
4
  // QuickJS engine}. If a file isn't this / hosting this / authoring for this / proving this — it's gone.
5
5
 
6
6
  import { z } from "zod";
7
- import type { AggregateHandle } from "./aggregate.js";
7
+ import { __declareBirthKeyWriteOnce, type AggregateHandle } from "./aggregate.js";
8
8
  import type { AggregateRef } from "./authoring.js";
9
9
  import type { Ports } from "./ctx.js";
10
- import type { Field, FieldValue, NomosRefValue } from "./fields.js";
10
+ import {
11
+ CANONICAL_UUID_RE,
12
+ isUuidIdSchema,
13
+ typedIdShapeRefusal,
14
+ type Field,
15
+ type FieldValue,
16
+ type NomosRefValue,
17
+ type TypedId,
18
+ } from "./fields.js";
19
+ import { brandedIdMetaOf } from "./value_object.js";
11
20
  import {
12
21
  object,
13
22
  relationType,
@@ -42,6 +51,10 @@ export interface BirthRecipe {
42
51
  /** Target-owned roles granted at genesis to the cryptographically verified immediate parent workspace.
43
52
  * Values are law constants; the kernel supplies the parent workspace id after verifying the birth. */
44
53
  readonly parentWorkspaceRoles?: readonly WorkspaceRole[];
54
+ /** The typed key the {@link workspace} name hashes (`workspaceNameForKey(namespace, birthKey)`). Carried
55
+ * on the recipe (`nomos:birthKey`) by a business birth whose law declares its key type, so the kernel can
56
+ * check the key's shape and re-derive the name. Absent on every other recipe (historical law included). */
57
+ readonly birthKey?: string;
45
58
  }
46
59
 
47
60
  declare const WORKSPACE_ROLE: unique symbol;
@@ -129,13 +142,20 @@ export interface BusinessBirthDecl<
129
142
  readonly __isBusinessBirth: true;
130
143
  readonly aggregate: AggregateHandle<string, F>;
131
144
  readonly payloadSchema: z.ZodObject<z.ZodRawShape> & z.ZodType<P>;
132
- /** A stable business key the caller already knows (an estate reference, account number, etc.). */
145
+ /** How this birth's workspace name is keyed (see `architecture/choosing_a_birth_key.md`). `"minted"` —
146
+ * the default — keys the name by a Nomos-minted id drawn inside the birth itself; `"field"` keys it by one
147
+ * declared, birth-only TYPED ID fact. */
148
+ readonly keyMode: "minted" | "field";
149
+ /** The key's typed-id family and brand (the kernel's era-ruled `nomos:birthKeyType` declaration). */
150
+ readonly keyType: Omit<BirthKeyDeclaration, "namespace">;
151
+ /** The declared business key of a `"field"`-keyed birth. A `"minted"` birth has no business key:
152
+ * calling this throws, because its name is drawn by Nomos when the birth runs. */
133
153
  readonly key: (payload: P) => string;
134
- /** The complete custody address this declaration gives the born root.
135
- * Carry it in parent projections instead of reconstructing workspace names in tenant code. */
154
+ /** The complete custody address a `"field"`-keyed birth gives the born root. A `"minted"` birth's
155
+ * address is the born workspace its birth returns (carry that `NomosRef`); calling this throws. */
136
156
  readonly reference: (payload: P) => NomosRefValue;
137
- /** The declared business fact used by {@link key}; retained for generated clients to pre-address custody. */
138
- readonly keyField: string;
157
+ /** The declared business fact used by {@link key} (`"field"` births only; `undefined` when minted). */
158
+ readonly keyField: string | undefined;
139
159
  /** Framework-derived initial authoring. Tenant code marks aggregate fields `.fromBirth()` instead. */
140
160
  readonly plan: (root: AggregateRef<F>, payload: P, ctx: Ports) => unknown;
141
161
  /** Business roles the founder holds from the first valid state. Nomos seats them inside genesis. */
@@ -160,17 +180,100 @@ export type BirthFactKeys<F extends Record<string, Field>> = {
160
180
  [K in keyof F]-?: F[K] extends { readonly isFromBirth: true } ? K : never;
161
181
  }[keyof F] & string;
162
182
 
163
- export type BirthStringFactKeys<F extends Record<string, Field>> = {
164
- [K in BirthFactKeys<F>]: FieldValue<F[K]> extends string ? K : never;
183
+ /**
184
+ * The `.fromBirth()` facts that may key a birth: TYPED IDS ONLY — a `t.brandedId(Name)`, `t.uuid()` or
185
+ * field (value type {@link TypedId}), or a string reference (`t.ref` / `t.foreignRef`, the field form of `idOf`).
186
+ * A plain `t.string()`, an enum, a number, or anything else is NOT a key, and naming it in `key:` is a
187
+ * compile error. There is no name heuristic: `estateId: t.string()` is a plain string however it is named.
188
+ */
189
+ export type BirthKeyFactKeys<F extends Record<string, Field>> = {
190
+ [K in BirthFactKeys<F>]: F[K] extends { readonly kind: "ref" }
191
+ ? (FieldValue<F[K]> extends string ? K : never)
192
+ : FieldValue<F[K]> extends TypedId<string> ? K : never;
165
193
  }[BirthFactKeys<F>] & string;
166
194
 
167
195
  export type BusinessBirthPayload<F extends Record<string, Field>> = {
168
196
  readonly [K in BirthFactKeys<F>]: FieldValue<F[K]>;
169
197
  };
170
198
 
199
+ /** Where every birth-key compile error points. */
200
+ export const CHOOSING_A_BIRTH_KEY_DOC = "architecture/choosing_a_birth_key.md";
201
+
202
+ /**
203
+ * The typed-id families a birth key may have. The kernel reads this off the law (the
204
+ * `nomos:birthKeyType` era key on the birthing intent) and checks every carried birth's key against it.
205
+ * - `minted` — the default: `<Aggregate>_<uuidv7>`, drawn by Nomos inside the birth;
206
+ * - `brandedId` — `t.brandedId(Name)`;
207
+ * - `uuid` — `t.uuid()`, canonical lowercase;
208
+ * - `ref` — a string `t.ref(Aggregate)` / `t.foreignRef` (the field form of `idOf`): an `Aggregate` id.
209
+ */
210
+ export type BirthKeyType = "minted" | "brandedId" | "uuid" | "ref";
211
+
212
+ /** The law's declaration of a birth's key: its typed-id family, the brand (the `brandedId` name, `Uuid`,
213
+ * the referenced aggregate, or for `minted` the born aggregate), and the name's namespace. */
214
+ export interface BirthKeyDeclaration {
215
+ readonly type: BirthKeyType;
216
+ readonly brand: string;
217
+ readonly namespace: string;
218
+ }
219
+
220
+ /** The typed-id family of a `.fromBirth()` field, or `undefined` when it is not a typed id. Reads the
221
+ * field's TYPE (its schema brand / reference kind) — never its name. */
222
+ export function birthKeyTypeOfField(field: Field): Omit<BirthKeyDeclaration, "namespace"> | undefined {
223
+ if (field.kind === "ref") {
224
+ if (field.nomosRef !== undefined || field.versionedForeignRef !== undefined) return undefined;
225
+ const target = field.refAggregateId;
226
+ return target === undefined ? undefined : { type: "ref", brand: target };
227
+ }
228
+ if (field.kind !== "string") return undefined;
229
+ const schema = field.zod as z.ZodTypeAny;
230
+ if (isUuidIdSchema(schema)) return { type: "uuid", brand: "Uuid" };
231
+ const brand = brandedIdMetaOf(schema);
232
+ if (brand !== undefined) return { type: "brandedId", brand };
233
+ return undefined;
234
+ }
235
+
236
+ const MINTED_KEY_UUIDV7 = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
237
+
238
+ /**
239
+ * Why `key` is not a well-formed key for `declaration` (`undefined` when it is). The ONE rule set, mirrored
240
+ * byte-for-byte by the kernel (`kernel/src/birth_stage.rs` `typed_birth_key_refusal`), which re-checks every
241
+ * carried birth whose law declares a typed key:
242
+ * - every key is a typed id value ({@link typedIdShapeRefusal}: non-empty, ≤ 256 UTF-8 bytes, no control
243
+ * character — so the `(namespace, key)` unit separator can never appear inside a key);
244
+ * - `minted` is exactly `<brand>_<uuidv7>`; `uuid` is the canonical lowercase uuid.
245
+ */
246
+ export function birthKeyShapeRefusal(declaration: BirthKeyDeclaration, key: unknown): string | undefined {
247
+ const shape = typedIdShapeRefusal(key);
248
+ if (shape !== undefined) return shape;
249
+ const text = key as string;
250
+ switch (declaration.type) {
251
+ case "minted": {
252
+ const prefix = `${declaration.brand}_`;
253
+ return text.startsWith(prefix) && MINTED_KEY_UUIDV7.test(text.slice(prefix.length))
254
+ ? undefined
255
+ : `a minted key is ${declaration.brand}_<uuidv7>`;
256
+ }
257
+ case "uuid":
258
+ return CANONICAL_UUID_RE.test(text) ? undefined : "a uuid key is a canonical lowercase uuid";
259
+ case "brandedId":
260
+ case "ref":
261
+ return undefined;
262
+ }
263
+ }
264
+
171
265
  export interface BusinessBirthSpec<F extends Record<string, Field>> {
172
- /** One declared birth fact that gives this business life its stable address. */
173
- readonly key: BirthStringFactKeys<F>;
266
+ /**
267
+ * How the born workspace is named.
268
+ * - OMIT IT (the default): Nomos mints the key inside the birth (`<Aggregate>_<uuidv7>`), so two births
269
+ * never share a name.
270
+ * - `key: "estateId"` — a `.fromBirth()` TYPED ID field: `t.brandedId(Name)`, `t.uuid()` or a string
271
+ * `t.ref(Aggregate)` (the field form of `idOf`). A plain `t.string()` is a compile error (see {@link BirthKeyFactKeys}).
272
+ * A keyed name is `workspaceNameForKey(namespace, key)` — global to the namespace and fixed at birth: Nomos
273
+ * refuses any later write that changes the key, and the kernel re-checks the key's shape and the name it
274
+ * derives on every birth. See `architecture/choosing_a_birth_key.md`.
275
+ */
276
+ readonly key?: BirthKeyFactKeys<F>;
174
277
  readonly namespace?: string;
175
278
  /** Business policy: roles held by the founder from the aggregate's first valid state. */
176
279
  readonly founder?: {
@@ -221,15 +324,39 @@ export function birth<F extends Record<string, Field>>(
221
324
  `birth(${aggregate.id}): no birth facts are declared — mark every required initial business field with .fromBirth()`,
222
325
  );
223
326
  }
224
- const keyField = (aggregate.fields as Record<string, Field>)[spec.key];
225
- if (keyField === undefined || keyField.isFromBirth !== true) {
226
- throw new Error(`birth(${aggregate.id}): key '${String(spec.key)}' must be a declared .fromBirth() field`);
227
- }
228
- if (keyField.kind !== "string" && keyField.kind !== "enum" && keyField.kind !== "ref") {
327
+ const rawKey = (spec as { key?: unknown }).key;
328
+ if (rawKey !== undefined && typeof rawKey !== "string") {
229
329
  throw new Error(
230
- `birth(${aggregate.id}): key '${String(spec.key)}' must be a string-like business fact; got ${keyField.kind}`,
330
+ `birth(${aggregate.id}): a birth key is \`key: "<typed-id fromBirth field>"\`; omit \`key\` for the ` +
331
+ `default Nomos-minted key. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
231
332
  );
232
333
  }
334
+ const keyFieldName: string | undefined = rawKey;
335
+ let keyType: Omit<BirthKeyDeclaration, "namespace"> = { type: "minted", brand: aggregate.id };
336
+ if (keyFieldName !== undefined) {
337
+ const keyField = (aggregate.fields as Record<string, Field>)[keyFieldName];
338
+ if (keyField === undefined || keyField.isFromBirth !== true) {
339
+ throw new Error(
340
+ `birth(${aggregate.id}): key field '${keyFieldName}' must be a declared .fromBirth() field — a birth key ` +
341
+ `is a fact the birth itself supplies, fixed for the life of the workspace. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
342
+ );
343
+ }
344
+ // The compile-time type (BirthKeyFactKeys) already refuses this; the same judgement by TYPE refuses an
345
+ // untyped (JS / cast) caller. Never by the field's name.
346
+ const typed = birthKeyTypeOfField(keyField);
347
+ if (typed === undefined) {
348
+ throw new Error(
349
+ `birth(${aggregate.id}): key field '${keyFieldName}' is a '${keyField.kind}' field, not a typed id, so it ` +
350
+ `cannot name a workspace. A birth key is t.brandedId(Name), t.uuid() or a string t.ref(Aggregate) ` +
351
+ `— or omit \`key\` for the Nomos-minted default. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
352
+ );
353
+ }
354
+ keyType = typed;
355
+ // The name is fixed at birth, so the key is write-once — enforced by the framework at the kernel
356
+ // (the aggregate's composed invariant), not by tenant code. An encrypted key (an `.owned()` content
357
+ // field) is ciphertext in the snapshot and cannot be compared, so it is not registered.
358
+ if (keyField.isEncrypted !== true) __declareBirthKeyWriteOnce(aggregate as AggregateHandle, keyFieldName);
359
+ }
233
360
  const payloadSchema = z.object(Object.fromEntries(facts.map(([name, field]) => [name, field.zod])));
234
361
  const founderRoles = [...new Set(spec.founder?.roles ?? [])];
235
362
  for (const role of founderRoles) {
@@ -272,13 +399,31 @@ export function birth<F extends Record<string, Field>>(
272
399
  __isBusinessBirth: true,
273
400
  aggregate,
274
401
  payloadSchema,
275
- key: (payload: Payload) => String(payload[spec.key]),
276
- reference: (payload: Payload): NomosRefValue => ({
277
- workspace: workspaceNameForKey(namespace, String(payload[spec.key])),
278
- type: aggregate.id,
279
- id: rootId,
280
- }),
281
- keyField: spec.key,
402
+ keyMode: keyFieldName === undefined ? "minted" as const : "field" as const,
403
+ keyType,
404
+ key: (payload: Payload) => {
405
+ if (keyFieldName === undefined) {
406
+ throw new Error(
407
+ `birth(${aggregate.id}) is keyed by a Nomos-minted id drawn when the birth runs; it has no business key. ` +
408
+ `Carry the born workspace's NomosRef instead. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
409
+ );
410
+ }
411
+ return String(payload[keyFieldName as keyof Payload]);
412
+ },
413
+ reference: (payload: Payload): NomosRefValue => {
414
+ if (keyFieldName === undefined) {
415
+ throw new Error(
416
+ `birth(${aggregate.id}).reference(facts): a Nomos-minted birth's address is not derivable from its facts — ` +
417
+ `it is the born workspace the birth returns. Carry that NomosRef. See ${CHOOSING_A_BIRTH_KEY_DOC}.`,
418
+ );
419
+ }
420
+ return {
421
+ workspace: workspaceNameForKey(namespace, String(payload[keyFieldName as keyof Payload])),
422
+ type: aggregate.id,
423
+ id: rootId,
424
+ };
425
+ },
426
+ keyField: keyFieldName,
282
427
  plan: (root: AggregateRef<F>, payload: Payload, ctx: Ports) => {
283
428
  const writable = root as unknown as { set(field: string, value: unknown): unknown };
284
429
  for (const [name] of facts) writable.set(name, payload[name as keyof Payload]);
@@ -325,12 +470,50 @@ declare const nomos: { sha256(input: string): string };
325
470
  * ("estate", "estate-abc123") -> estate-83662e729ba18cd04bde52c7a8d854594833d70e
326
471
  * ("home", "user:ada") -> home-561af44c94b5b80824f1a9e8cfa1095105f3cb34
327
472
  * ("catalogue", "cat-42") -> catalogue-f50bf3b9732dc36961b05b8e6503d96df5f3e88e
473
+ *
474
+ * NAMES ARE FIXED AT BIRTH. A business child (`birth(Aggregate, { key })`) is named exactly this way too —
475
+ * the birthing parent is NOT part of the name. CAP-788 briefly folded the parent into the hash; CAP-803
476
+ * withdrew that before release, because every child born before it is addressed by the name it was born
477
+ * with and a recomputation under a rule it was not born with addresses a workspace that does not exist.
478
+ * One parent per node is enforced where custody is, by the kernel's typed `[birth-child-already-parented]`
479
+ * refusal: a second parent's birth of an existing child name is refused, never re-routed.
480
+ *
481
+ * THE KEY IS MINTED BY DEFAULT (CAP-804). `birth(Aggregate, spec)` with no `key` hashes a Nomos-minted id
482
+ * ({@link mintBirthKey}) drawn inside the birth plan, so names cannot collide by construction; the address
483
+ * the app keeps is the born workspace's NomosRef. An explicit `key: { field, scope: "global" }` hashes a
484
+ * birth-only business fact exactly as above — see `architecture/choosing_a_birth_key.md`.
328
485
  */
329
486
  export const WORKSPACE_NAME_SEP = "\u001f"; // unit separator disambiguates (namespace,key)
330
487
  export function workspaceNameForKey(namespace: string, key: string): string {
331
488
  return namespace + "-" + nomos.sha256(String(namespace) + WORKSPACE_NAME_SEP + String(key)).slice(0, 40);
332
489
  }
333
490
 
491
+ /**
492
+ * @internal THE DEFAULT BIRTH KEY: a typed `<Aggregate>_<uuidv7>` drawn with `nomos.mint` INSIDE the birth
493
+ * plan. The sealed engine derives it from the intent's captured clock and captured entropy seed, so the
494
+ * author draws a fresh, unguessable id and every replay of that intent reproduces it byte-for-byte; the
495
+ * born name is then `workspaceNameForKey(namespace, mintedKey)` like any other birth.
496
+ */
497
+ /** @internal The hidden payload field through which a generated client that must know a minted birth's
498
+ * address BEFORE dispatch (an encrypted `.owned()` birth seals to it) supplies a key it reserved from the
499
+ * kernel's own minter (`mintId`). Absent, the birth plan mints. Never part of the application payload. */
500
+ export const BUSINESS_BIRTH_KEY_PAYLOAD_FIELD = "__nomos_birth_key_4b9e2d";
501
+
502
+ /** @internal A key Nomos minted for `aggregateId`: the typed `<Aggregate>_<uuidv7>` shape, nothing else. */
503
+ export function isMintedBirthKey(aggregateId: string, key: string): boolean {
504
+ return birthKeyShapeRefusal({ type: "minted", brand: aggregateId, namespace: "" }, key) === undefined;
505
+ }
506
+
507
+ export function mintBirthKey(aggregateId: string): string {
508
+ const host = (globalThis as { nomos?: { mint?: (typeTag: string) => string } }).nomos;
509
+ if (typeof host?.mint !== "function") {
510
+ throw new Error(
511
+ `birth(${aggregateId}): Nomos mints this birth's key inside the sealed engine, but no nomos.mint capability is bound here`,
512
+ );
513
+ }
514
+ return host.mint(aggregateId);
515
+ }
516
+
334
517
  /** A genesis chain, or a builder that receives the resolved child workspace name (so a seed step can embed
335
518
  * it — e.g. `seedEstate` needs to write its own `estateName`). Passing a builder is the common case. */
336
519
  // An array of genesis steps, OR a builder that receives the resolved child name. Typed as `unknown[]`
@@ -43,8 +43,10 @@ export interface ProvenanceSubject {
43
43
  export interface BuildProvenanceInput {
44
44
  /** Package name from the project config. */
45
45
  readonly packageName: string;
46
- /** `sha256(package bytes)` — the composed typed USDA closure identity. */
46
+ /** The PACKAGE identity: this compiler's closure of `<name>.law.usdz`. Not what a unit-lane target serves. */
47
47
  readonly domainHash: string;
48
+ /** THE LAW IDENTITY: the unit set identity of `<name>.units` (law_unit_set_identity.ts, CAP-773). */
49
+ readonly unitSetIdentity: string;
48
50
  /** The transports the domainHash addresses (law archive, package asset, …). */
49
51
  readonly artifacts: readonly { readonly name: string; readonly sha256?: string }[];
50
52
  /** Per-domain compiled surface, already computed for the summary. */
@@ -61,6 +63,13 @@ export interface BuildProvenanceInput {
61
63
  readonly builder: { readonly id: string; readonly version: string };
62
64
  /** Source law modules, by feature — the external parameters of the build. */
63
65
  readonly sourceModules?: readonly string[];
66
+ /** The released baseline the candidate extends (CAP-660): pinned by content address, naming its source. */
67
+ readonly baseline?: {
68
+ readonly sha256: string;
69
+ readonly source: "release" | "installed" | "units";
70
+ readonly release: string;
71
+ readonly unitSet: string;
72
+ };
64
73
  }
65
74
 
66
75
  /**
@@ -71,8 +80,11 @@ export interface BuildProvenanceInput {
71
80
  */
72
81
  export function buildProvenanceStatement(input: BuildProvenanceInput): Record<string, unknown> {
73
82
  const subject: ProvenanceSubject[] = [
74
- // The law closure identity is THE subject: `sha256(package bytes) = domainHash`.
83
+ // The package: `<name>.law` is the compiler's closure of the `.law.usdz` bytes (the package identity).
75
84
  { name: `${input.packageName}.law`, digest: { sha256: input.domainHash } },
85
+ // The law: `<name>.units` names the unit set identity, what releases, clients and qualification name.
86
+ // A target serves a closure its own kernel composes from these units, which neither digest names.
87
+ { name: `${input.packageName}.units`, digest: { sha256: input.unitSetIdentity } },
76
88
  ...input.artifacts
77
89
  .filter((a): a is { name: string; sha256: string } => typeof a.sha256 === "string")
78
90
  .map((a) => ({ name: a.name, digest: { sha256: a.sha256 } })),
@@ -90,6 +102,9 @@ export function buildProvenanceStatement(input: BuildProvenanceInput): Record<st
90
102
  ...(input.sourceModules === undefined
91
103
  ? {}
92
104
  : { sourceModules: [...input.sourceModules].sort() }),
105
+ // The baseline is an external parameter: the same source compiled against another
106
+ // baseline is a different build, and a publisher can gate on which release it extends.
107
+ ...(input.baseline === undefined ? {} : { baseline: { ...input.baseline } }),
93
108
  },
94
109
  // What the compile RESOLVED — the surface each domain actually exposes. A
95
110
  // consumer diffing two provenances sees the law's shape change without
@@ -13,6 +13,11 @@ import { ensure } from "./authoring.js";
13
13
  import { directive, type Directive } from "./directive.js";
14
14
  import {
15
15
  birth,
16
+ BUSINESS_BIRTH_KEY_PAYLOAD_FIELD,
17
+ birthKeyShapeRefusal,
18
+ isMintedBirthKey,
19
+ type BirthKeyDeclaration,
20
+ mintBirthKey,
16
21
  requireParentAttestedBirth,
17
22
  workspaceNameForKey,
18
23
  type BusinessBirthDecl,
@@ -164,7 +169,13 @@ export interface BusinessBirthMetadata {
164
169
  readonly publicPayloadSchema: z.ZodObject<z.ZodRawShape>;
165
170
  readonly rootId: string;
166
171
  readonly namespace: string;
167
- readonly keyField: string;
172
+ /** `"minted"` (default): the name hashes a Nomos-minted id; `"field"`: it hashes {@link keyField}. */
173
+ readonly keyMode: "minted" | "field";
174
+ /** The declared business key field of a `"field"`-keyed birth; absent for a minted birth. */
175
+ readonly keyField?: string;
176
+ /** The key's typed-id family and brand: generated clients type the key by it (a `brandedId`/`uuid` key is
177
+ * its branded type, never a bare string). */
178
+ readonly keyType: { readonly type: import("./births.js").BirthKeyType; readonly brand: string };
168
179
  /** Exact child-genesis offer route prepared before the parent birth dispatch. */
169
180
  readonly seedDirectiveId: string;
170
181
  /** Compiler-owned child-genesis routes which may be appended after the business seed. */
@@ -198,6 +209,7 @@ const INTERNAL_FIELDS = [
198
209
  "principal",
199
210
  "bornAt",
200
211
  BUSINESS_BIRTH_SEED_PAYLOAD_FIELD,
212
+ BUSINESS_BIRTH_KEY_PAYLOAD_FIELD,
201
213
  ] as const;
202
214
 
203
215
  /**
@@ -271,8 +283,16 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
271
283
  });
272
284
  const parentSchema = seedSchema.extend({
273
285
  ...(encryptedBirth ? { [BUSINESS_BIRTH_SEED_PAYLOAD_FIELD]: seedCarrierSchema } : {}),
286
+ // A minted birth's client may reserve its key ahead of dispatch (an encrypted .owned() birth seals to
287
+ // the child's address). A field-keyed birth's law is unchanged: it declares no such field.
288
+ ...(decl.keyMode === "minted" ? { [BUSINESS_BIRTH_KEY_PAYLOAD_FIELD]: z.string().min(1).optional() } : {}),
274
289
  });
275
290
 
291
+ // THE TYPED BIRTH KEY (era key `nomos:birthKeyType`/`nomos:birthKeyBrand`/`nomos:birthKeyNamespace` on the
292
+ // birthing intent): the kernel reads it off the law and checks every carried recipe's `nomos:birthKey`
293
+ // against it — its shape, and that the child's name is `workspaceNameForKey(namespace, key)`.
294
+ const birthKeyDeclaration: BirthKeyDeclaration = { ...decl.keyType, namespace };
295
+
276
296
  const seedBase = directive(seedId)
277
297
  .ensures(decl.aggregate)
278
298
  .payload(seedSchema)
@@ -327,7 +347,28 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
327
347
  .payload(parentSchema)
328
348
  .plan((p, ctx) => {
329
349
  const founder = p.principal as string;
330
- const child = workspaceNameForKey(namespace, String(decl.key(p as P)));
350
+ // A name is fixed at birth: a minted birth (the default) hashes an id Nomos draws here, inside the
351
+ // plan, from the intent's captured entropy (replay reproduces it); a field-keyed birth hashes its
352
+ // declared birth-only fact. Either way the name is `workspaceNameForKey(namespace, key)`.
353
+ const reserved = (p as Record<string, unknown>)[BUSINESS_BIRTH_KEY_PAYLOAD_FIELD];
354
+ if (reserved !== undefined && !isMintedBirthKey(decl.aggregate.id, String(reserved))) {
355
+ throw new Error(
356
+ `birth(${decl.aggregate.id}): a reserved birth key must be one Nomos minted for ${decl.aggregate.id}; ` +
357
+ "an application cannot choose a workspace name",
358
+ );
359
+ }
360
+ const birthKey = decl.keyMode === "minted"
361
+ ? (reserved !== undefined ? String(reserved) : mintBirthKey(decl.aggregate.id))
362
+ : decl.key(p as P);
363
+ // The same shape rule the kernel re-checks on the carried recipe (`nomos:birthKey`): refuse here, as
364
+ // the author, before a name is derived from a key the kernel would refuse.
365
+ const keyRefusal = birthKeyShapeRefusal(birthKeyDeclaration, birthKey);
366
+ if (keyRefusal !== undefined) {
367
+ throw new Error(
368
+ `[birth-key-shape] birth(${decl.aggregate.id}): the ${birthKeyDeclaration.type} key is malformed: ${keyRefusal}`,
369
+ );
370
+ }
371
+ const child = workspaceNameForKey(namespace, birthKey);
331
372
  const foundingKeyHash = foundedBirthKeyHash(founder);
332
373
  const founded = foundingKeyHash !== undefined;
333
374
  if (founded) {
@@ -358,6 +399,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
358
399
  Object.assign(childPayload, sealedSeed as Record<string, unknown>);
359
400
  }
360
401
  delete childPayload[BUSINESS_BIRTH_SEED_PAYLOAD_FIELD];
402
+ delete childPayload[BUSINESS_BIRTH_KEY_PAYLOAD_FIELD];
361
403
  const chain: Array<Record<string, unknown>> = [
362
404
  domainInstallGenesisOffer(
363
405
  DomainInstallExecutor.StageZero,
@@ -417,7 +459,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
417
459
  if (founded) {
418
460
  // No provider verifier, inherited model authority (operators ride the founding step instead),
419
461
  // parent workspace role or epoch edge: those belong to the certificate lane's attestation.
420
- birth({ workspace: child, genesisChain: chain });
462
+ birth({ workspace: child, genesisChain: chain, birthKey });
421
463
  return [];
422
464
  }
423
465
 
@@ -443,6 +485,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
443
485
  birth({
444
486
  workspace: child,
445
487
  genesisChain: chain,
488
+ birthKey,
446
489
  ...(decl.parentWorkspaceRoles.length > 0
447
490
  ? { parentWorkspaceRoles: decl.parentWorkspaceRoles }
448
491
  : {}),
@@ -475,7 +518,9 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
475
518
  publicPayloadSchema: publicSchema,
476
519
  rootId,
477
520
  namespace,
478
- keyField: decl.keyField,
521
+ keyMode: decl.keyMode,
522
+ ...(decl.keyField !== undefined ? { keyField: decl.keyField } : {}),
523
+ keyType: decl.keyType,
479
524
  seedDirectiveId: seedId,
480
525
  genesisDirectiveIds: [
481
526
  "enrollSigner",
@@ -493,6 +538,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
493
538
  };
494
539
  const parent = {
495
540
  ...birthBase,
541
+ birthKey: birthKeyDeclaration,
496
542
  businessBirth: metadata,
497
543
  [BUSINESS_BIRTH_POLICY_BINDING]: (domains: readonly string[]) => {
498
544
  decl.__nomosBirthPolicyDomains = domains;
@@ -32,6 +32,9 @@ import {
32
32
  NOMOS_IDENTITY_FILENAME,
33
33
  parseIdentityUsda,
34
34
  } from "./identity_usda.js";
35
+ import { identityRegistryFromModel } from "./identity_model.js";
36
+ import { NOMOS_BASELINE_FILENAME, isBaselineUsda, parseBaselineUsda, type ReleasedBaseline } from "./baseline_usda.js";
37
+ import { BaselineRefusal, importBaseline } from "./baseline_import.js";
35
38
  import {
36
39
  validateNomosPackageConfig,
37
40
  type NormalizedNomosPackageConfig,
@@ -150,11 +153,13 @@ export async function loadComposedModulesWithSources(
150
153
  }
151
154
 
152
155
  /**
153
- * Load the PRIOR identity surface for `--against <path>` (the evolve preview's baseline). The one
154
- * supported artifact is the committed `nomos.identity.usda` layer, directly or via its package dir.
155
- * There is no interface/JSON sidecar lane: prior executable-law inspection belongs to the holon.
156
+ * Load the PRIOR identity surface for `--against <path>` (the evolve preview's baseline). Two
157
+ * artifacts are supported, directly or via a package dir: the committed `nomos.identity.usda`
158
+ * layer, and a released `nomos.baseline.usda` (CAP-660) — the continuity exported from custody,
159
+ * which also pre-fires the compile's baseline verification as findings. There is no
160
+ * interface/JSON sidecar lane: prior executable-law inspection belongs to the holon.
156
161
  */
157
- function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note?: string } {
162
+ function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note?: string; baseline?: ReleasedBaseline } {
158
163
  if (/^https?:\/\//.test(arg)) {
159
164
  fail(
160
165
  `--against does not support a live URL (${arg}) — the installed package's #58 stable-ids are ` +
@@ -167,12 +172,29 @@ function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note
167
172
  let file = abs;
168
173
  if (statSync(abs).isDirectory()) {
169
174
  const identity = path.join(abs, NOMOS_IDENTITY_FILENAME);
175
+ const baseline = path.join(abs, NOMOS_BASELINE_FILENAME);
170
176
  if (existsSync(identity)) file = identity;
171
- else fail(`--against: no ${NOMOS_IDENTITY_FILENAME} under ${abs}`);
177
+ else if (existsSync(baseline)) file = baseline;
178
+ else fail(`--against: no ${NOMOS_IDENTITY_FILENAME} or ${NOMOS_BASELINE_FILENAME} under ${abs}`);
179
+ }
180
+ const text = readFileSync(file, "utf8");
181
+ if (isBaselineUsda(text)) {
182
+ let baseline: ReleasedBaseline;
183
+ try {
184
+ baseline = parseBaselineUsda(text);
185
+ } catch (e) {
186
+ fail(`--against: could not parse ${file}: ${(e as Error).message}`);
187
+ }
188
+ return {
189
+ law: evolveLawFromLockfile(baseline.continuity),
190
+ source: file,
191
+ baseline,
192
+ note: `released baseline (${baseline.release === "none" ? "unreleased units" : `release ${baseline.release}`}, law ${baseline.unitSet}): identities and wire allocations are verified against the ledger; value contracts are not carried by released law.`,
193
+ };
172
194
  }
173
195
  let lock: CompilerIdentityModel;
174
196
  try {
175
- lock = parseIdentityUsda(readFileSync(file, "utf8"));
197
+ lock = parseIdentityUsda(text);
176
198
  } catch (e) {
177
199
  fail(`--against: could not parse ${file}: ${(e as Error).message}`);
178
200
  }
@@ -229,6 +251,28 @@ async function main(): Promise<void> {
229
251
  const current = currentEvolveLaw(modules, lock);
230
252
  const diff = evolveDiff(prior.law, current);
231
253
  findings.push(...diff.findings);
254
+ if (prior.baseline !== undefined) {
255
+ // The compile's own baseline verification, pre-fired here with the same typed reasons
256
+ // (baseline_import.ts): a diverged ledger or wire allocation is named before any compile.
257
+ try {
258
+ importBaseline(
259
+ identityRegistryFromModel(lock),
260
+ prior.baseline,
261
+ modules.flatMap((mod) => [mod.domain ?? mod.name, mod.name]),
262
+ );
263
+ } catch (error) {
264
+ if (!(error instanceof BaselineRefusal)) throw error;
265
+ const details = error.details.length === 0 ? [error.message] : error.details;
266
+ for (const detail of details) {
267
+ findings.push({
268
+ domain: modules[0]?.domain ?? modules[0]?.name ?? "package",
269
+ level: "refuse",
270
+ what: `[${error.code}] ${detail}`,
271
+ fix: error.message.split("\n")[0] ?? error.message,
272
+ });
273
+ }
274
+ }
275
+ }
232
276
  if (!asJson) console.log(`githolon check: evolve preview against ${prior.source}`);
233
277
  }
234
278
 
@@ -14,9 +14,14 @@ import type {
14
14
  import type { GeometryKind } from "./fields.js";
15
15
  import { dartMemberName, unionArmClassName } from "./codegen_dart.js";
16
16
  import { mintStableId } from "./stable_ids.js";
17
+ import { composedWireIdentity } from "./composed_wire_identity.js";
17
18
  import { protobufFieldNumber } from "./protobuf_field_number.js";
18
19
  import { provenanceRowIdentity, provenanceFieldIdentity, provenanceAuthorIdentity, provenanceContestedIdentity } from "./provenance_wire.js";
19
20
 
21
+ // The composed wire identity lives in its own node-free module so the compatibility graph can reach
22
+ // it from the law bundle; the generated codecs keep importing it from here.
23
+ export { composedWireIdentity };
24
+
20
25
  export interface TsCallProtoFieldAccess {
21
26
  readonly value: string;
22
27
  readonly present?: string;
@@ -87,24 +92,6 @@ function requireUniqueFieldNumbers(
87
92
  }
88
93
  }
89
94
 
90
- /**
91
- * OpenUSD composition may share structurally identical anonymous records between
92
- * operation contracts. `compileSparseLawGraphUsda` records that sharing by
93
- * redirecting each duplicate field path to the canonical field prim. The wire
94
- * identity must follow that composed path too; using the duplicate graph-local
95
- * SID makes generated clients speak a different Protobuf schema from the law the
96
- * kernel actually installs.
97
- */
98
- export function composedWireIdentity(
99
- paths: ReadonlyMap<string, string>,
100
- entity: { readonly id: string; readonly sid: string; readonly wireKey?: string },
101
- ): string {
102
- if (entity.wireKey !== undefined) return entity.wireKey;
103
- const path = paths.get(entity.id);
104
- const canonical = path?.match(/\/(?:Fields\/f_|Variants\/v_)([0-9a-f]{16})$/i)?.[1];
105
- return canonical ?? entity.sid;
106
- }
107
-
108
95
  interface NominalSumArm {
109
96
  readonly discriminator: string;
110
97
  readonly variant: LawAnonymousValueVariantNode;