@githolon/dsl 0.103.4 → 0.103.6

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 (40) hide show
  1. package/dart/CHANGELOG.md +8 -0
  2. package/dart/lib/src/runner/node-entry.mjs +333 -117
  3. package/dart/lib/src/runner/offline-install.mjs +173 -67
  4. package/dart/lib/src/session_bound.dart +111 -2
  5. package/dart/lib/src/test_harness.dart +51 -41
  6. package/dart/lib/src/webview_bridge.dart +714 -60
  7. package/dart/pubspec.lock +1 -1
  8. package/dart/pubspec.yaml +2 -2
  9. package/dart/test/acquire_session_test.dart +62 -5
  10. package/dart/test/cloud_bound_topology_test.dart +59 -38
  11. package/dart/test/session_bound_test.dart +25 -10
  12. package/dart/test/webview_bridge_test.dart +326 -7
  13. package/dart_types/CHANGELOG.md +5 -0
  14. package/dart_types/lib/nomos_types.dart +1 -0
  15. package/dart_types/lib/src/git_object_id.dart +37 -0
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/package.json +1 -1
  18. package/runtime/generated-application-host.js +347 -113
  19. package/src/births.ts +16 -1
  20. package/src/business_birth.ts +55 -34
  21. package/src/codegen_dart.ts +68 -12
  22. package/src/codegen_dart_application.ts +114 -19
  23. package/src/codegen_dart_client.ts +149 -6
  24. package/src/codegen_proof.ts +16 -9
  25. package/src/codegen_provider_dart.ts +5 -0
  26. package/src/codegen_ts.ts +115 -19
  27. package/src/compile_package_main.ts +159 -43
  28. package/src/fields.ts +43 -0
  29. package/src/framework/domain_lifecycle.ts +108 -88
  30. package/src/framework/genesis.ts +2 -1
  31. package/src/framework/home.ts +6 -43
  32. package/src/framework/workspaces.ts +42 -25
  33. package/src/idof_lint.ts +65 -51
  34. package/src/index.ts +21 -1
  35. package/src/intent.ts +11 -1
  36. package/src/law_graph.ts +81 -11
  37. package/src/relation_schema.ts +18 -0
  38. package/src/shape_compat.ts +3 -0
  39. package/src/stable_ids_for_module.ts +7 -1
  40. package/src/value_object.ts +40 -0
@@ -20,10 +20,16 @@
20
20
  import { z } from "zod";
21
21
  import { aggregate, instance } from "../aggregate.js";
22
22
  import { Lww, MapOf } from "../drivers.js";
23
- import { attributionField, t } from "../fields.js";
23
+ import {
24
+ attributionField,
25
+ gitObjectId,
26
+ sha256Digest,
27
+ t,
28
+ type Sha256Digest,
29
+ } from "../fields.js";
24
30
  import { directive } from "../directive.js";
25
31
  import { set, withMarker, addToSet, type PlannedOp } from "../ops.js";
26
- import { key, relationType, self, user } from "../relation_schema.js";
32
+ import { key, relationRef, relationType, self, user } from "../relation_schema.js";
27
33
 
28
34
  /** The two authenticated capabilities which may execute a lifecycle install.
29
35
  * Callers choose a role; the serialized USDA route is an implementation detail. */
@@ -31,7 +37,31 @@ export enum DomainInstallExecutor {
31
37
  StageZero = "bootstrap",
32
38
  LifecycleController = "nomos",
33
39
  }
34
- export const INSTALL_DOMAIN_DIRECTIVE = "installDomain" as const;
40
+
41
+ /** One typed vocabulary shared by declaration, gate, genesis seat and generated clients. */
42
+ export const MODEL_LIFECYCLE_AUTHORITY = relationRef("installDomain");
43
+ export const DOMAIN_LIFECYCLE_DIRECTIVES = {
44
+ Install: "installDomain",
45
+ Stage: "stageDomain",
46
+ Promote: "promoteDomain",
47
+ Rollback: "rollbackDomain",
48
+ } as const;
49
+ export type DomainLifecycleDirectiveId =
50
+ (typeof DOMAIN_LIFECYCLE_DIRECTIVES)[keyof typeof DOMAIN_LIFECYCLE_DIRECTIVES];
51
+ export const INSTALL_DOMAIN_DIRECTIVE = DOMAIN_LIFECYCLE_DIRECTIVES.Install;
52
+
53
+ /** A symbolic reference resolved by the kernel against the parent's active law frontier. */
54
+ export type CurrentLawReference = string & { readonly __nomosCurrentLawReference: unique symbol };
55
+ export type LawClosureReference = Sha256Digest | CurrentLawReference;
56
+
57
+ const CURRENT_LAW_PREFIX = "nomos:current-law:";
58
+
59
+ /** Construct the only symbolic law reference admitted in a child genesis recipe. */
60
+ export function currentLawReference(domain: string): CurrentLawReference {
61
+ const name = domain.trim();
62
+ if (name.length === 0) throw new TypeError("a current-law reference requires a non-empty domain");
63
+ return `${CURRENT_LAW_PREFIX}${name}` as CurrentLawReference;
64
+ }
35
65
 
36
66
  /** One complete, contract-shaped law-install call in a child genesis sequence. Keeping this
37
67
  * constructor beside the directive schema makes omission of required install provenance a type
@@ -41,16 +71,16 @@ export interface DomainInstallGenesisOffer extends Readonly<Record<string, unkno
41
71
  readonly directiveId: typeof INSTALL_DOMAIN_DIRECTIVE;
42
72
  readonly actor: string;
43
73
  readonly payload: {
44
- readonly domainHash: string;
74
+ readonly domainHash: LawClosureReference;
45
75
  readonly installedBy: string;
46
- readonly lawClosureIdentity: string;
76
+ readonly lawClosureIdentity: LawClosureReference;
47
77
  };
48
- readonly domainHash: string;
78
+ readonly domainHash: LawClosureReference;
49
79
  }
50
80
 
51
81
  export function domainInstallGenesisOffer(
52
82
  domain: DomainInstallExecutor,
53
- domainHash: string,
83
+ domainHash: LawClosureReference,
54
84
  actor: string,
55
85
  ): DomainInstallGenesisOffer {
56
86
  return {
@@ -67,32 +97,42 @@ export function domainInstallGenesisOffer(
67
97
  * delegation remains an ordinary, replayable Zanzibar edge. */
68
98
  export const modelLifecycleRelations = relationType("workspace")
69
99
  .asGlobal()
70
- .relation("installDomain", { rewrite: self(), allow: [user(), key()] });
100
+ .relation(MODEL_LIFECYCLE_AUTHORITY, { rewrite: self(), allow: [user(), key()] });
101
+
102
+ /** The lifecycle state machine is a framework type, not a bag of coincidentally matching strings. */
103
+ export enum DomainInstallationPhase {
104
+ Pending = "Pending",
105
+ Active = "Active",
106
+ Disabled = "Disabled",
107
+ Superseded = "Superseded",
108
+ Retiring = "Retiring",
109
+ Retired = "Retired",
110
+ Failed = "Failed",
111
+ }
71
112
 
72
113
  export const DOMAIN_INSTALLATION_PHASES = [
73
- "Pending",
74
- "Active",
75
- "Disabled",
76
- "Superseded",
77
- "Retiring",
78
- "Retired",
79
- "Failed",
114
+ DomainInstallationPhase.Pending,
115
+ DomainInstallationPhase.Active,
116
+ DomainInstallationPhase.Disabled,
117
+ DomainInstallationPhase.Superseded,
118
+ DomainInstallationPhase.Retiring,
119
+ DomainInstallationPhase.Retired,
120
+ DomainInstallationPhase.Failed,
80
121
  ] as const;
81
- export type DomainInstallationPhase = (typeof DOMAIN_INSTALLATION_PHASES)[number];
82
122
 
83
123
  export const DomainInstallation = aggregate("DomainInstallation", {
84
- "spec.domainHash": t.string().merge(Lww),
85
- "spec.packageHash": t.string().merge(Lww),
124
+ "spec.domainHash": t.sha256Digest().merge(Lww),
125
+ "spec.packageHash": t.sha256Digest().merge(Lww),
86
126
  "spec.authorityScope": t.string().merge(Lww),
87
127
  "spec.installedBy": t.string().merge(Lww),
88
- "spec.lawClosureIdentity": t.string().merge(Lww),
128
+ "spec.lawClosureIdentity": t.sha256Digest().merge(Lww),
89
129
  "spec.dependencies": t.set(t.string()),
90
- "spec.supersedes": t.string().merge(Lww).optional(),
130
+ "spec.supersedes": t.sha256Digest().merge(Lww).optional(),
91
131
  "spec.finalizers": t.set(t.string()),
92
132
  "metadata.generation": t.int().merge(Lww),
93
133
  "status.phase": t.enum(DOMAIN_INSTALLATION_PHASES).merge(Lww),
94
134
  "status.observedGeneration": t.int().merge(Lww),
95
- "status.supersededBy": t.string().merge(Lww).optional(),
135
+ "status.supersededBy": t.sha256Digest().merge(Lww).optional(),
96
136
  "status.conditions": t.map(t.json()).merge(MapOf(Lww)),
97
137
  }).public();
98
138
 
@@ -106,13 +146,13 @@ export const RELEASE_RECEIPT_AGGREGATE = "nomos_internal_ReleaseReceipt_6f0d3a";
106
146
  export const RELEASE_RECEIPT_PREFIX = "nomos-release-receipt:";
107
147
 
108
148
  export const ReleaseReceipt = aggregate(RELEASE_RECEIPT_AGGREGATE, {
109
- candidateHash: t.string().merge(Lww),
110
- fromHash: t.string().merge(Lww).optional(),
149
+ candidateHash: t.sha256Digest().merge(Lww),
150
+ fromHash: t.sha256Digest().merge(Lww).optional(),
111
151
  domain: t.string().merge(Lww),
112
- atHash: t.string().merge(Lww),
113
- atStateRoot: t.string().merge(Lww),
114
- historyHash: t.string().merge(Lww),
115
- reportHash: t.string().merge(Lww),
152
+ atHash: t.gitObjectId().merge(Lww),
153
+ atStateRoot: t.gitObjectId().merge(Lww),
154
+ historyHash: t.sha256Digest().merge(Lww),
155
+ reportHash: t.sha256Digest().merge(Lww),
116
156
  reportJson: t.string().merge(Lww),
117
157
  complete: t.bool(),
118
158
  evolveOk: t.bool(),
@@ -121,26 +161,26 @@ export const ReleaseReceipt = aggregate(RELEASE_RECEIPT_AGGREGATE, {
121
161
  replayedIntents: t.int().merge(Lww),
122
162
  candidateIntents: t.int().merge(Lww),
123
163
  divergenceCount: t.int().merge(Lww),
124
- baselineRoot: t.string().merge(Lww),
125
- candidateRoot: t.string().merge(Lww),
164
+ baselineRoot: t.sha256Digest().merge(Lww),
165
+ candidateRoot: t.sha256Digest().merge(Lww),
126
166
  acceptanceReason: t.string().merge(Lww).optional(),
127
167
  acceptedBy: attributionField(t.string().merge(Lww)),
128
168
  acceptedAt: t.int().merge(Lww),
129
169
  }).public();
130
170
 
131
- export function domainInstallationAggregateId(domainHash: string): string {
171
+ export function domainInstallationAggregateId(domainHash: Sha256Digest): string {
132
172
  return `domain-installation:${domainHash}`;
133
173
  }
134
174
 
135
- function domainInstallationInstance(domainHash: string) {
175
+ function domainInstallationInstance(domainHash: Sha256Digest) {
136
176
  return instance(DomainInstallation, domainInstallationAggregateId(domainHash));
137
177
  }
138
178
 
139
- export function releaseReceiptAggregateId(reportHash: string): string {
179
+ export function releaseReceiptAggregateId(reportHash: Sha256Digest): string {
140
180
  return `${RELEASE_RECEIPT_PREFIX}${reportHash}`;
141
181
  }
142
182
 
143
- function releaseReceiptInstance(reportHash: string) {
183
+ function releaseReceiptInstance(reportHash: Sha256Digest) {
144
184
  return instance(ReleaseReceipt, releaseReceiptAggregateId(reportHash));
145
185
  }
146
186
 
@@ -151,13 +191,13 @@ const dispositions = z.object({
151
191
  ackBy: z.string().optional(),
152
192
  }).optional();
153
193
 
154
- const lowerHex64 = z.string().regex(/^[0-9a-f]{64}$/);
155
- const gitOid = z.string().regex(/^[0-9a-f]{40}$/);
194
+ const lowerHex64 = sha256Digest();
195
+ const gitOid = gitObjectId();
156
196
  const preflightReceipt = z.object({
157
197
  candidateHash: lowerHex64,
158
198
  domain: z.string().min(1),
159
199
  atHash: gitOid,
160
- atStateRoot: lowerHex64,
200
+ atStateRoot: gitOid,
161
201
  historyHash: lowerHex64,
162
202
  reportHash: lowerHex64,
163
203
  reportJson: z.string().min(2),
@@ -181,36 +221,29 @@ const preflightReceipt = z.object({
181
221
  }
182
222
  });
183
223
 
184
- const packagePayload = {
224
+ const installPackagePayload = {
185
225
  domainHash: lowerHex64,
186
226
  installedBy: z.string(),
187
227
  lawClosureIdentity: lowerHex64,
188
228
  authorityScope: z.string().default("workspace/root"),
189
229
  // Omission means the empty set; keep that in the plan instead of encoding a composite default
190
230
  // into canonical USDA merely to save `?? []` at runtime.
191
- dependencies: z.array(z.string()).optional(),
192
- supersedes: z.string().optional(),
193
- finalizers: z.array(z.string()).optional(),
231
+ dependencies: z.array(sha256Digest()).optional(),
232
+ finalizers: z.array(sha256Digest()).optional(),
194
233
  };
195
234
 
196
- export const installDomain = directive("installDomain")
235
+ const stagedPackagePayload = {
236
+ ...installPackagePayload,
237
+ supersedes: sha256Digest().optional(),
238
+ };
239
+
240
+ export const installDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Install)
197
241
  .ensures(DomainInstallation)
198
- .payload(
199
- z.object({
200
- ...packagePayload,
201
- // THE FRONTIER TRANSITION (install-lifecycle): `replaces` names a PRIOR install this
202
- // deploy makes current UNEQUIVOCALLY — the prior install is flipped out of the served
203
- // frontier (`Superseded`, or `Retired` when `retireReplaced`) and gets the reverse
204
- // `status.supersededBy` edge to this install. Absent ⇒ install-BESIDE (backward-compat:
205
- // byte-identical ops to the pre-lifecycle plan). `replaces` also sets `spec.supersedes`.
206
- replaces: z.string().optional(),
207
- retireReplaced: z.boolean().default(false),
208
- dispositions,
209
- }).refine((p) => p.replaces === undefined || p.replaces !== p.domainHash, {
210
- message: "a domain package cannot replace itself",
211
- path: ["replaces"],
212
- }),
213
- )
242
+ // Installation adds law for domain keys that are not currently served. Replacement is a
243
+ // different operation with different evidence: stageDomain followed by promoteDomain. Keeping
244
+ // predecessor/retirement fields out of this type makes bypassing that state machine impossible
245
+ // for generated clients instead of relying on callers to remember a convention.
246
+ .payload(z.object({ ...installPackagePayload }).strict())
214
247
  .plan((p): PlannedOp[] => {
215
248
  const install = domainInstallationInstance(p.domainHash);
216
249
  const ops: PlannedOp[] = [
@@ -220,38 +253,25 @@ export const installDomain = directive("installDomain")
220
253
  set(install, "spec.installedBy", p.installedBy),
221
254
  set(install, "spec.lawClosureIdentity", p.lawClosureIdentity),
222
255
  set(install, "metadata.generation", 1),
223
- set(install, "status.phase", "Active"),
256
+ set(install, "status.phase", DomainInstallationPhase.Active),
224
257
  set(install, "status.observedGeneration", 1),
225
258
  ];
226
259
  if ((p.dependencies ?? []).length > 0) {
227
260
  ops.push(addToSet(install, "spec.dependencies", p.dependencies ?? []));
228
261
  }
229
- // The predecessor back-link: `supersedes` (explicit) or `replaces` (the actionable form).
230
- const supersedes = p.supersedes ?? p.replaces;
231
- if (supersedes !== undefined) {
232
- ops.push(set(install, "spec.supersedes", supersedes));
233
- }
234
- // The transition on the PRIOR install: drop it from the served frontier + point it here.
235
- if (p.replaces !== undefined) {
236
- const replaced = domainInstallationInstance(p.replaces);
237
- ops.push(
238
- set(replaced, "status.phase", p.retireReplaced ? "Retired" : "Superseded"),
239
- );
240
- ops.push(set(replaced, "status.supersededBy", p.domainHash));
241
- }
242
262
  if ((p.finalizers ?? []).length > 0) {
243
263
  ops.push(addToSet(install, "spec.finalizers", p.finalizers ?? []));
244
264
  }
245
265
  return ops;
246
266
  })
247
- .requires("installDomain");
267
+ .requires(MODEL_LIFECYCLE_AUTHORITY);
248
268
 
249
269
  /** Store candidate law in the ledger without changing the served law frontier. The
250
270
  * kernel content-verifies the package and lifecycle pair, while the Pending phase is
251
271
  * deliberately invisible to dispatch, reads and replay's current-law map. */
252
- export const stageDomain = directive("stageDomain")
272
+ export const stageDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Stage)
253
273
  .ensures(DomainInstallation)
254
- .payload(z.object({ ...packagePayload }).refine(
274
+ .payload(z.object({ ...stagedPackagePayload }).refine(
255
275
  (p) => p.supersedes === undefined || p.supersedes !== p.domainHash,
256
276
  { message: "a staged domain package cannot supersede itself", path: ["supersedes"] },
257
277
  ))
@@ -264,7 +284,7 @@ export const stageDomain = directive("stageDomain")
264
284
  set(install, "spec.installedBy", p.installedBy),
265
285
  set(install, "spec.lawClosureIdentity", p.lawClosureIdentity),
266
286
  set(install, "metadata.generation", 1),
267
- set(install, "status.phase", "Pending"),
287
+ set(install, "status.phase", DomainInstallationPhase.Pending),
268
288
  set(install, "status.observedGeneration", 0),
269
289
  ];
270
290
  if ((p.dependencies ?? []).length > 0) ops.push(addToSet(install, "spec.dependencies", p.dependencies ?? []));
@@ -272,17 +292,17 @@ export const stageDomain = directive("stageDomain")
272
292
  if ((p.finalizers ?? []).length > 0) ops.push(addToSet(install, "spec.finalizers", p.finalizers ?? []));
273
293
  return ops;
274
294
  })
275
- .requires("installDomain");
295
+ .requires(MODEL_LIFECYCLE_AUTHORITY);
276
296
 
277
297
  /** Make a staged package current. The package bytes are already committed; the kernel
278
298
  * re-loads them from prefix state and runs the evolve gate on this phase transition.
279
299
  * The prior version stays Active by default so gradual-rollout clients can still have
280
300
  * their declared era checked; the newer activation stamp alone makes this law current. */
281
- export const promoteDomain = directive("promoteDomain")
301
+ export const promoteDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Promote)
282
302
  .mutates(DomainInstallation)
283
303
  .payload(z.object({
284
- domainHash: z.string(),
285
- replaces: z.string().optional(),
304
+ domainHash: sha256Digest(),
305
+ replaces: sha256Digest().optional(),
286
306
  retireReplaced: z.boolean().default(false),
287
307
  dispositions,
288
308
  preflight: preflightReceipt,
@@ -297,7 +317,7 @@ export const promoteDomain = directive("promoteDomain")
297
317
  const promoted = domainInstallationInstance(p.domainHash);
298
318
  const receipt = releaseReceiptInstance(p.preflight.reportHash);
299
319
  const ops: PlannedOp[] = [
300
- set(promoted, "status.phase", "Active"),
320
+ set(promoted, "status.phase", DomainInstallationPhase.Active),
301
321
  set(promoted, "status.observedGeneration", 1),
302
322
  withMarker(set(receipt, "candidateHash", p.domainHash), "ensures"),
303
323
  ...(p.replaces !== undefined ? [set(receipt, "fromHash", p.replaces)] : []),
@@ -326,23 +346,23 @@ export const promoteDomain = directive("promoteDomain")
326
346
  const replaced = domainInstallationInstance(p.replaces);
327
347
  ops.push(set(promoted, "spec.supersedes", p.replaces));
328
348
  if (p.retireReplaced) {
329
- ops.push(set(replaced, "status.phase", "Retired"));
349
+ ops.push(set(replaced, "status.phase", DomainInstallationPhase.Retired));
330
350
  ops.push(set(replaced, "status.supersededBy", p.domainHash));
331
351
  }
332
352
  }
333
353
  return ops;
334
354
  })
335
355
  .alsoEnsures(ReleaseReceipt)
336
- .requires("installDomain");
356
+ .requires(MODEL_LIFECYCLE_AUTHORITY);
337
357
 
338
358
  /** Rollback is a new governed transition, never a history-pointer rewind. The prior
339
359
  * package is promoted from committed custody and the failed candidate becomes Disabled;
340
360
  * the kernel applies the same evolve judgment in the reverse direction. */
341
- export const rollbackDomain = directive("rollbackDomain")
361
+ export const rollbackDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Rollback)
342
362
  .mutates(DomainInstallation)
343
363
  .payload(z.object({
344
- candidateHash: z.string(),
345
- priorHash: z.string(),
364
+ candidateHash: sha256Digest(),
365
+ priorHash: sha256Digest(),
346
366
  dispositions,
347
367
  preflight: preflightReceipt,
348
368
  }).refine((p) => p.candidateHash !== p.priorHash, {
@@ -360,9 +380,9 @@ export const rollbackDomain = directive("rollbackDomain")
360
380
  const prior = domainInstallationInstance(p.priorHash);
361
381
  const receipt = releaseReceiptInstance(p.preflight.reportHash);
362
382
  return [
363
- set(candidate, "status.phase", "Disabled"),
383
+ set(candidate, "status.phase", DomainInstallationPhase.Disabled),
364
384
  set(candidate, "status.supersededBy", p.priorHash),
365
- set(prior, "status.phase", "Active"),
385
+ set(prior, "status.phase", DomainInstallationPhase.Active),
366
386
  set(prior, "status.observedGeneration", 1),
367
387
  withMarker(set(receipt, "candidateHash", p.priorHash), "ensures"),
368
388
  set(receipt, "fromHash", p.candidateHash),
@@ -389,4 +409,4 @@ export const rollbackDomain = directive("rollbackDomain")
389
409
  ];
390
410
  })
391
411
  .alsoEnsures(ReleaseReceipt)
392
- .requires("installDomain");
412
+ .requires(MODEL_LIFECYCLE_AUTHORITY);
@@ -34,6 +34,7 @@ import { Lww } from "../drivers.js";
34
34
  import { directive } from "../directive.js";
35
35
  import { set, type PlannedOp } from "../ops.js";
36
36
  import { RelationTuple, writeTuple } from "./relations.js";
37
+ import { MODEL_LIFECYCLE_AUTHORITY } from "./domain_lifecycle.js";
37
38
 
38
39
  /**
39
40
  * Deterministic singleton id of the founding warrant — MUST equal
@@ -100,7 +101,7 @@ export const seedGenesis = directive("seedGenesis")
100
101
  ...writeGenesisWarrant(p),
101
102
  ...writeTuple({
102
103
  object: "workspace:self",
103
- relation: "installDomain",
104
+ relation: MODEL_LIFECYCLE_AUTHORITY,
104
105
  subject: /^(user|key):/.test(p.principal) ? p.principal : `user:${p.principal}`,
105
106
  grantedBy: p.principal,
106
107
  grantedAt: p.bornAt,
@@ -27,9 +27,7 @@
27
27
  import { z } from "zod";
28
28
  import type { ApplicationHomeGenesis } from "../application_home_genesis.js";
29
29
  import { aggregate, instance, OWNED_READ_RELATION } from "../aggregate.js";
30
- // The device-key-hash wire shape (same validator signing_identity/workspaces use module-locally).
31
- const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256 hex (the owner's device pubkey hash)");
32
- import { t } from "../fields.js";
30
+ import { sha256Digest, t } from "../fields.js";
33
31
  import { Lww } from "../drivers.js";
34
32
  import { directive } from "../directive.js";
35
33
  import { ensure } from "../authoring.js";
@@ -319,27 +317,11 @@ export function birthReceivingGroupForDomain(
319
317
  owner: z.string().min(1),
320
318
  /** The bootstrap framework controller content hash — tenant-pinned at governance compile (the child
321
319
  * installs it IN-CHAIN as intent 0; the kernel resolves the bytes by hash from the parent's custody). */
322
- frameworkHash: z.string().min(1),
320
+ frameworkHash: sha256Digest(),
323
321
  /** The HOME law package domainHash — tenant-pinned at governance compile (the child's second install). */
324
- lawHash: z.string().min(1),
322
+ lawHash: sha256Digest(),
325
323
  /** ISO-8601 birth time, caller-stamped (the timestamp doctrine — the plan reads no clock). */
326
324
  bornAt: z.string().min(1),
327
- /** THE OWNER'S DEVICE KEY HASH (sha256hex of the ed25519 public half — the SAME key `homeCert` is signed
328
- * by and the `signer` relation enrols). The platform holds it from the user's onboarding and ENROLS it
329
- * in the home's genesis, so the born home is SIGNER-READY: it can sign + birth its own children (estates,
330
- * catalogues) from intent 0. WITHOUT this the home is born warranted + owned but signer-less — it can
331
- * never act (the co2 estate-birth bug). Mirrors `buildGenesisChain`'s `enrollSigner` for a platform.
332
- * REQUIRED — a payload without it refuses at compile/validation, not an hour into a live deploy with a
333
- * bricked signer-less home. (The kernel injecting it from the verified cert's `parent_key` is the
334
- * eventual generalization; until then the field is loud.) */
335
- ownerKeyHash: sha256Hex,
336
- /** THE DEVICE-SIGNED HOME CERT (JSON `executor::birth_cert::BirthCert`), signed CLIENT-SIDE by the
337
- * owner's device key `S_U` (the SAME key `ownerKeyHash` hashes + the `signer` relation enrols). The
338
- * kernel's birth offer-effect VERIFIES this cert's signature against U's enrolled signer pubkey `P_U`
339
- * AND that its `founder == ctx.principal()` (the gate verdict, CAS-as-law) BEFORE accepting it as
340
- * `links[0]` of the travelling chain — the kernel signs nothing (it holds no secret; it only verifies).
341
- * Optional: absent ⇒ the era stays inert for this birth (pre-feature lane). */
342
- homeCert: z.string().optional(),
343
325
  /** AUTHPROVIDER INHERITANCE — DEFAULT ON, opt out with `false`. Verification is PER-WORKSPACE (the
344
326
  * kernel never walks up to the parent), so a PRIVATE child born without its own AuthProvider cannot
345
327
  * verify any token — its own owner is locked out of verified reads (the co2 phase-6 wall). By
@@ -356,9 +338,9 @@ export function birthReceivingGroupForDomain(
356
338
  .plan((p) => {
357
339
  const founder = p.owner; // the actor for the child's own genesis steps (its self-bound owner subject)
358
340
  const groupName = receivingGroupNameOf(p.owner);
359
- // THE PARENT'S DECLARED VERIFIER (one captured read serves BOTH consumers below): the inherited
360
- // `setAuthProvider` genesis step AND the enroll step's `verifiedVia` provenance stamp. Read it up
361
- // front so the enroll step (which precedes the inheritance step in the chain) can see the issuer.
341
+ // THE PARENT'S DECLARED VERIFIER. This captured read supplies the inherited provider seed.
342
+ // Birth-certificate verification and founding-signer enrollment belong to the kernel-owned
343
+ // attestation step, so neither fact is duplicated in this tenant plan or payload.
362
344
  const apRows = p.inheritAuthProvider !== false
363
345
  ? readFrameworkDeclared<Array<Record<string, unknown>>>("authProvider", { scope: "platform" })
364
346
  : undefined;
@@ -371,15 +353,6 @@ export function birthReceivingGroupForDomain(
371
353
  const v = ap?.[k];
372
354
  return typeof v === "string" && v.length > 0 ? v : undefined;
373
355
  };
374
- // PROVENANCE (the verified-lineage thread, 2026-07-16): the kernel derives every later write's
375
- // `principal_verified` from the ENROLL LEAF's `verifiedVia` — so a verified birth must stamp it or the
376
- // born home's owner writes UNVERIFIED forever (and its own children are then born `verified:false`,
377
- // whose chains refuse `verify_to_root`). The cert's `verified` IS the gate verdict post-admission
378
- // (assemble_birth_attestation CAS-refuses a mismatch), so reading it in-plan is trustworthy — a lying
379
- // cert never reaches this fold. An unverified birth stamps nothing (the unverified lane stays honest).
380
- let certVerified = false;
381
- try { certVerified = p.homeCert !== undefined && JSON.parse(p.homeCert).verified === true; } catch { /* malformed cert ⇒ unverified (the gate will refuse the birth anyway) */ }
382
- const verifiedVia = certVerified ? apStr("issuer") : undefined;
383
356
  const genesisChain: Array<{ domain: string; directiveId: string; actor: string; payload: Record<string, unknown>; domainHash?: string }> = [
384
357
  // THE ORDER-DECLARED LAW INSTALL: two typed closure identities. The kernel resolves each
385
358
  // closure from the parent's custody when it folds the birth; law bytes never ride the intent.
@@ -433,16 +406,6 @@ export function birthReceivingGroupForDomain(
433
406
  bornAt: p.bornAt,
434
407
  })),
435
408
  })),
436
- // ENROL THE OWNER'S DEVICE on the warrant-free prefix (when supplied) — enrollSigner self-binds to
437
- // ctx.principal() (the owner), so this binds `ownerKeyHash` as the owner's signer BEFORE the warrant
438
- // closes bootstrap. This is what makes the born home SIGNER-READY (able to sign + self-serve-birth its own
439
- // estates/catalogues). Mirrors buildGenesisChain's founding-admin enrollSigner for a platform — homes were
440
- // the inconsistent one. THE GENESIS ORDER IS THE CONTRACT (BREAK 1): the parent's offer-effect APPENDS
441
- // `seedBirthAttestation` + `seedGenesis` (the WARRANT) AFTER these steps — the warrant lands LAST (E→W
442
- // ladder: every seed runs UNSIGNED on the still-warrant-free prefix; the warrant closes it). ERA-SAFE: a
443
- // home born earlier (pre-feature law, no such step in its recipe) replays byte-identical — the era rule
444
- // rides the LAW hash, not this branch.
445
- { domain: groupLawDomain, directiveId: "enrollSigner", actor: founder, payload: { keyHash: p.ownerKeyHash, grantedBy: p.owner, grantedAt: 1, ...(verifiedVia !== undefined ? { verifiedVia } : {}) } },
446
409
  ];
447
410
  // AUTHPROVIDER INHERITANCE (default ON — see the payload doc): copy the parent's declared provider
448
411
  // (the up-front captured read above) AS DATA into the child genesis. The child owns the copy as its
@@ -68,9 +68,9 @@ import { z } from "zod";
68
68
  import { aggregate, instance } from "../aggregate.js";
69
69
  import { create, ensure } from "../authoring.js";
70
70
  import { birth } from "../births.js";
71
- import { directive } from "../directive.js";
71
+ import { directive, type Directive } from "../directive.js";
72
72
  import { Lww } from "../drivers.js";
73
- import { t } from "../fields.js";
73
+ import { sha256Digest, t, type Sha256Digest } from "../fields.js";
74
74
  import { set, withMarker } from "../ops.js";
75
75
  import { query } from "../query.js";
76
76
  import { read } from "../read.js";
@@ -79,7 +79,8 @@ import { writeTuple, removeTuple, RelationTuple, relationTupleInstanceId } from
79
79
  import { relationType, self, user, key } from "../relation_schema.js";
80
80
  import { GENESIS_WARRANT_ID } from "./genesis.js";
81
81
  import { idOf } from "../value_object.js";
82
- import { signerObject, SIGNER_RELATION } from "./signing_identity.js";
82
+ import { enrollSigner, signerObject, SIGNER_RELATION } from "./signing_identity.js";
83
+ import { seedBirthPolicy } from "./birth_meter.js";
83
84
  import {
84
85
  DomainInstallExecutor,
85
86
  domainInstallGenesisOffer,
@@ -92,7 +93,7 @@ import {
92
93
  export { GenesisWarrant, seedGenesis } from "./genesis.js";
93
94
  export { identityRelations, enrollSigner } from "./signing_identity.js";
94
95
  // ERA 0 — the keyless verified first-device door rides the GOVERNANCE law too: a freshly BORN platform
95
- // has a virgin roster and a cert-ATTESTED AuthProvider (the S_root-signed platformCert), so its founder's
96
+ // has a virgin roster and a cert-ATTESTED AuthProvider (the S_root-signed birth witness), so its founder's
96
97
  // FIRST action is `enrollFirstDevice` (token-verified against the attested provider) followed by a SIGNED
97
98
  // self-`grantAdmin` (admitted by the kernel genesis clause). Without this export a born platform is
98
99
  // un-administrable — the admin bootstrap has no door.
@@ -109,6 +110,10 @@ export { BIRTH_ATTESTATION_ID, BirthAttestation, birthAttestation, seedBirthAtte
109
110
  // onboarding signer S_P) so the kernel reads it at `signer-delegation:<user>` when assembling a home's chain.
110
111
  export { SignerDelegation, enrollDelegation, signerDelegationByUser } from "./signing_identity.js";
111
112
 
113
+ /** Canonical governance-law namespace. Genesis routes refer to this declaration, never a repeated
114
+ * string literal, so renaming the module cannot silently strand part of the founding ceremony. */
115
+ export const WORKSPACES_DOMAIN = "workspaces" as const;
116
+
112
117
  // ── WORKSPACE SELF-IDENTITY (the law reads its OWN install-context) ──────────────────────────────────
113
118
  // The governance bundle runs on root AND every born platform. A workspace knows its ROLE via this
114
119
  // singleton, at a CONSTANT fixed instance id (the RelationTuple discipline — a literal, never minted, so
@@ -232,7 +237,7 @@ export const WORKSPACE_STATUSES = [
232
237
  export const GRANT_STATUSES = ["active", "revoked"] as const;
233
238
 
234
239
  /** sha256 hex — the ONLY shape a credential may take inside the law. */
235
- const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256 hex");
240
+ const sha256Hex = sha256Digest();
236
241
 
237
242
  /** Epoch-ms, caller-stamped (a plan never reads a clock). */
238
243
  const epochMs = z.number().int().nonnegative();
@@ -535,8 +540,8 @@ export function buildGenesisChain(p: {
535
540
  * the recipe PREPENDS `bootstrap/installDomain(framework)` + `nomos/installDomain(law)` — the two-layer
536
541
  * install the genesis bootstrap does — so the child installs ITS OWN law as intent 0 and self-validates
537
542
  * from there. Absent ⇒ the recipe resolves an already-installed law by shape (pre-G3 / test lanes). */
538
- frameworkHash?: string;
539
- lawHash?: string;
543
+ frameworkHash?: Sha256Digest;
544
+ lawHash?: Sha256Digest;
540
545
  /** METERED BIRTH (slice A3 — the descendant budget). Required: a governance child may not be
541
546
  * born without explicit BirthPolicy state. Zero is a legitimate declared "cannot birth" policy. */
542
547
  descendantPool: number;
@@ -545,7 +550,7 @@ export function buildGenesisChain(p: {
545
550
  * ancestry while `parent` stays the SUPERSEDED record's parent (a rebirth may never reparent). */
546
551
  rebornFrom?: string;
547
552
  }): string {
548
- const founder = "user:" + p.principal;
553
+ const founder: `user:${string}` = `user:${p.principal}`;
549
554
  // THE ORDER-DECLARED LAW INSTALL (G3): the child installs the EXACT typed USDA closures the order names.
550
555
  // Bytes are NOT in the recipe; the kernel resolves each identity from its own custody when it folds the
551
556
  // birth. Two layers, matching the genesis bootstrap: bootstrap controller, then law.
@@ -587,20 +592,28 @@ export function buildGenesisChain(p: {
587
592
  // METERED BIRTH (slice A3 — THE POOL CHAINS TO ROOT): every governance child is BORN BOUNDED.
588
593
  // Historical chains keep replaying under their historical law; this constructor authors only the
589
594
  // current law, whose seedBirthPolicy directive is compiler-carried. Missing policy is invalid genesis.
595
+ const governanceGenesisStep = <P extends Record<string, unknown>>(
596
+ declaration: Directive<P>,
597
+ payload: P,
598
+ ) => ({
599
+ domain: WORKSPACES_DOMAIN,
600
+ directiveId: declaration.id,
601
+ actor: founder,
602
+ payload,
603
+ });
590
604
  const poolSteps = [
591
- {
592
- domain: "workspaces",
593
- directiveId: "seedBirthPolicy",
594
- actor: founder,
595
- payload: { maxBirthsPerPrincipal: p.descendantPool, setBy: p.principal, setAt: p.bornAt },
596
- },
605
+ governanceGenesisStep(seedBirthPolicy, {
606
+ maxBirthsPerPrincipal: p.descendantPool,
607
+ setBy: p.principal,
608
+ setAt: p.bornAt,
609
+ }),
597
610
  ];
598
611
  return JSON.stringify([
599
612
  ...installSteps,
600
613
  ...poolSteps,
601
614
  // ERA 0 — THE GENESIS-ORDER CONTRACT (the birthHome ordering, unified): every recipe seed below runs
602
615
  // UNSIGNED on the still-WARRANT-FREE prefix; the recipe carries NO seedGenesis and NO grantAdmin. The
603
- // KERNEL's birth effect — after verifying the offer's S_root-signed platformCert against the pinned
616
+ // KERNEL's birth effect — after verifying the offer's typed S_root-signed certificate against the pinned
604
617
  // K_root — appends `seedBirthAttestation` (the travelling cert) and THEN `seedGenesis` (the warrant,
605
618
  // self-id == name) LAST, so the platform is born WARRANTED + ATTESTED and every post-genesis write is
606
619
  // signed AND cert-gated. A birth whose cert fails verification seeds neither and is REFUSED atomically
@@ -614,11 +627,22 @@ export function buildGenesisChain(p: {
614
627
  // (`genesis_admits`: a virgin #admin roster + the warrant binding the captured principal). A baked
615
628
  // in-genesis grantAdmin can never fold (post-warrant writes need a signature the birth effect cannot
616
629
  // produce — the pre-era-0 recipe was structurally unbornable); pre-warrant enroll + post-birth signed
617
- // self-grant is the lawful shape. (The keyless variant — enrollFirstDevice against the platformCert's
630
+ // self-grant is the lawful shape. (The keyless variant — enrollFirstDevice against the birth certificate's
618
631
  // ATTESTED AuthProvider — remains for founders with an IdP instead of a key.)
619
- { domain: "workspaces", directiveId: "enrollSigner", actor: founder, payload: { keyHash: p.foundingAdminHash, grantedBy: p.principal, grantedAt: p.requestedAt } },
632
+ governanceGenesisStep(enrollSigner, {
633
+ keyHash: p.foundingAdminHash,
634
+ grantedBy: p.principal,
635
+ grantedAt: p.requestedAt,
636
+ }),
620
637
  // No `role` — it was a vestigial node-class label (collapsed with `kind`); birthCloud ignores it now.
621
- { domain: "workspaces", directiveId: "birthCloud", actor: "", payload: { principal: p.principal, name: p.name, tier: p.tier, bornAt: p.requestedAt, bornUnder, ...(p.rebornFrom ? { rebornFrom: p.rebornFrom } : {}) } },
638
+ governanceGenesisStep(birthCloud, {
639
+ principal: p.principal,
640
+ name: p.name,
641
+ tier: p.tier,
642
+ bornAt: p.requestedAt,
643
+ bornUnder,
644
+ ...(p.rebornFrom ? { rebornFrom: p.rebornFrom } : {}),
645
+ }),
622
646
  ]);
623
647
  }
624
648
 
@@ -1264,13 +1288,6 @@ export const birthChild = directive("birthChild")
1264
1288
  * own custody (never the host). Absent ⇒ the recipe resolves an already-installed law by shape. */
1265
1289
  frameworkHash: sha256Hex.optional(),
1266
1290
  lawHash: sha256Hex.optional(),
1267
- /** THE PLATFORM ATTESTATION (era 0 — one attested birth lane for every holon kind): the 1-link
1268
- * platform_cert JSON (`executor::birth_cert::BirthCert`), SIGNED OFFLINE BY S_root (the K_root
1269
- * holder). The kernel's birth effect VERIFIES it (child == name, founder == `user:<principal>`,
1270
- * signature vs the pinned K_root) and injects `seedBirthAttestation` + `seedGenesis` (the warrant)
1271
- * LAST into the child's genesis. REQUIRED for a genesis birth to complete — a birth without a
1272
- * valid cert is refused atomically (no unwarranted platform lane survives era 0). */
1273
- platformCert: z.string().optional(),
1274
1291
  /** GOVERNANCE POSTURE (governance_postures.md) — the born child's declared posture; default
1275
1292
  * `managed`. Mirrored onto its lineage record so root's bailiff can consult it on the hot path
1276
1293
  * (the tenant's own {@link GovernancePosture} singleton is authoritative). Additive/era-safe. */