@githolon/dsl 0.16.2 → 0.17.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.
package/dart/CHANGELOG.md CHANGED
@@ -1,2 +1,7 @@
1
+ ## 0.17.0
2
+ - Lockstep bump with the birth-primitive release (see `@githolon/dsl` 0.17.0): birth is one law primitive
3
+ (`birthChild`), the platform-creator tier collapsed onto `#creator`, `grantCreation` takes a user or key
4
+ subject. The Dart wire types track the current /v2 governance surface.
5
+
1
6
  ## 0.16.0
2
7
  - First pub.dev release: kernel wire types, `NomosBridge` protocol, typed offer/query/watch surface.
package/dart/pubspec.yaml CHANGED
@@ -11,7 +11,7 @@ description: >-
11
11
  Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
12
12
  code-generated per-domain aggregate + directive-payload types. (De)serializes
13
13
  the EXACT serde JSON the Rust kernel + projection produce/consume.
14
- version: 0.16.0
14
+ version: 0.17.0
15
15
 
16
16
  environment:
17
17
  sdk: ^3.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.16.2",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
package/src/codegen_ts.ts CHANGED
@@ -534,7 +534,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
534
534
  // the obligation the birth provider materializes. parentWs = "root" for a top-level platform,
535
535
  // else the parent holon's own name. timestamps ride IN the payload (the determinism doctrine);
536
536
  // a platform's foundingAdminHash = sha256(your chosen platformAdminKey) — the host never mints it.
537
- const emit = (name: string, doc: string, params: string[], lane: { kind: "boot" } | { kind: "offer"; parentWs: string; directiveId: "createWorkspace" | "birthPlatform" }) => {
537
+ const emit = (name: string, doc: string, params: string[], lane: { kind: "boot" } | { kind: "offer"; parentWs: string; directiveId: "createWorkspace" | "birthChild" }) => {
538
538
  if (emittedBirths.has(name)) return;
539
539
  emittedBirths.add(name);
540
540
  const sig = `opts: NomosBirthAuth & { cloud: string; ${params.join("; ")} }`;
@@ -547,14 +547,18 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
547
547
  );
548
548
  return;
549
549
  }
550
- const isPlatformOffer = lane.directiveId === "birthPlatform";
550
+ // A platform birth is birthChild WITH genesis (the tier collapsed onto #creator; birthPlatform is
551
+ // gone). The genesis stamps ride IN the payload (the determinism doctrine): foundingAdminHash =
552
+ // sha256(your chosen platformAdminKey), bornAt = the ISO birth time. The host replays the
553
+ // law-authored chain — it mints no genesis.
554
+ const isPlatformOffer = lane.directiveId === "birthChild";
551
555
  const bodyFields = [
552
556
  `name: opts.name`,
553
557
  `parent: ${lane.parentWs}`,
554
558
  `principal: opts.principal ?? ""`,
555
559
  `requestedBy: opts.principal ?? ""`,
556
560
  `requestedAt: Date.now()`,
557
- ...(isPlatformOffer ? [`foundingAdminHash: await nomosKeyHash(opts.platformAdminKey)`] : []),
561
+ ...(isPlatformOffer ? [`foundingAdminHash: await nomosKeyHash(opts.platformAdminKey)`, `bornAt: new Date().toISOString()`] : []),
558
562
  ];
559
563
  out.push(
560
564
  `/** ${doc} */`,
@@ -570,13 +574,13 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
570
574
  const isPlatform = t.childIds.length > 0;
571
575
  if (!childIds.has(t.id)) {
572
576
  // A TOP-LEVEL type: a workspace births on the boot lane; a platform-kind
573
- // coordinator births via the root holon's birthPlatform offer.
577
+ // coordinator births via the root holon's birthChild offer (with genesis).
574
578
  emit(
575
579
  fnName(t.id),
576
- `Birth a '${t.id}' workspace${isPlatform ? " (a platform-kind coordinator: hosts its own subtree; PRESENT your chosen platformAdminKey) via root's birthPlatform offer" : " on the boot lane (the one-time secret comes back)"} — derived from the taxonomy. Placement is automatic (measured shape) — no tier to pick.`,
580
+ `Birth a '${t.id}' workspace${isPlatform ? " (a platform-kind coordinator: hosts its own subtree; PRESENT your chosen platformAdminKey) via root's birthChild offer" : " on the boot lane (the one-time secret comes back)"} — derived from the taxonomy. Placement is automatic (measured shape) — no tier to pick.`,
577
581
  isPlatform ? [`name: string`, `platformAdminKey: string`] : [`name: string`],
578
582
  isPlatform
579
- ? { kind: "offer", parentWs: `"root"`, directiveId: "birthPlatform" }
583
+ ? { kind: "offer", parentWs: `"root"`, directiveId: "birthChild" }
580
584
  : { kind: "boot" },
581
585
  );
582
586
  }
@@ -587,11 +591,11 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
587
591
  const childIsPlatform = child.childIds.length > 0;
588
592
  emit(
589
593
  fnName(child.id),
590
- `Birth a '${child.id}' under a '${t.id}' — the taxonomy's hasMany edge, a governance offer on the parent holon (POST /v2/workspaces/<${parentParam}>/${childIsPlatform ? "birthPlatform" : "createWorkspace"}; lineage + pool enforced by the parent's law).${childIsPlatform ? " PRESENT your chosen platformAdminKey." : ""} Placement is automatic (measured shape).`,
594
+ `Birth a '${child.id}' under a '${t.id}' — the taxonomy's hasMany edge, a governance offer on the parent holon (POST /v2/workspaces/<${parentParam}>/${childIsPlatform ? "birthChild" : "createWorkspace"}; lineage + pool enforced by the parent's law).${childIsPlatform ? " PRESENT your chosen platformAdminKey." : ""} Placement is automatic (measured shape).`,
591
595
  childIsPlatform
592
596
  ? [`${parentParam}: string`, `name: string`, `platformAdminKey: string`]
593
597
  : [`${parentParam}: string`, `name: string`],
594
- { kind: "offer", parentWs: `opts.${parentParam}`, directiveId: childIsPlatform ? "birthPlatform" : "createWorkspace" },
598
+ { kind: "offer", parentWs: `opts.${parentParam}`, directiveId: childIsPlatform ? "birthChild" : "createWorkspace" },
595
599
  );
596
600
  }
597
601
  }
@@ -96,18 +96,24 @@ export const WORKSPACE_ROLES = ["cloud-root", "platform", "tenant"] as const;
96
96
  const WORKSPACE_SELF_ID = "workspaceself-self";
97
97
  const SELF_ADMIN_OBJECT = "workspace:self";
98
98
  export const WorkspaceSelf = aggregate("WorkspaceSelf", {
99
+ // VESTIGIAL node-class label (the same smell as `kind`): NOTHING decides on its value — the kernel, host,
100
+ // and gate read it nowhere; the write-once invariants below key on `name`'s PRESENCE (the "born" marker),
101
+ // never role. No longer set on new records (birthCloud/ensureWorkspaceSelf stopped writing it). Kept only
102
+ // for era-safety (old records carry it); slated for removal with `kind` in one #58 disposition pass.
99
103
  role: t.enum(WORKSPACE_ROLES).merge(Lww),
100
104
  name: t.string().merge(Lww),
101
105
  });
102
106
  // The one-off SELF-IDENTITY backfill for an already-born workspace (root predates WorkspaceSelf). UNGATED
103
107
  // by the admin gate — it must run BEFORE the admin tuple/gate are live — but WRITE-ONCE (the invariant
104
- // below refuses a re-set once the role exists), so a race or a malicious re-set cannot flip the role and
105
- // freeze governance. The HOST authors this in the serialized genesis re-entry, winning any race.
108
+ // below refuses a re-set once the self-record exists), so a race or a malicious re-set cannot freeze
109
+ // governance. The HOST authors this in the serialized genesis re-entry, winning any race.
106
110
  export const ensureWorkspaceSelf = directive("ensureWorkspaceSelf")
107
111
  .ensures(WorkspaceSelf)
108
- .payload(z.object({ role: z.enum(WORKSPACE_ROLES), name: z.string().min(1) }))
112
+ // `role` accepted-but-ignored (vestigial — see WorkspaceSelf): callers may still pass it; the plan no
113
+ // longer sets it. `name` is the self-record's only meaningful field + the born marker.
114
+ .payload(z.object({ role: z.enum(WORKSPACE_ROLES).optional(), name: z.string().min(1) }))
109
115
  .plan((p) => {
110
- ensure(WorkspaceSelf, WORKSPACE_SELF_ID).set("role", p.role).set("name", p.name);
116
+ ensure(WorkspaceSelf, WORKSPACE_SELF_ID).set("name", p.name);
111
117
  return [];
112
118
  });
113
119
  export const workspaceSelfWriteOnce = workspaceInvariant("WorkspaceSelfWriteOnce")
@@ -118,8 +124,11 @@ export const workspaceSelfWriteOnce = workspaceInvariant("WorkspaceSelfWriteOnce
118
124
  // 2026-06-15). Bare matches — exactly the convention the sharding invariants already fire under.
119
125
  .on("ensureWorkspaceSelf")
120
126
  .reads(() => [refAs("self", "WorkspaceSelf", "pre:" + WORKSPACE_SELF_ID)])
127
+ // WRITE-ONCE on the self-record's PRESENCE via `name` (not the vestigial `role`). At genesis name + role
128
+ // were always co-written, so an existing born workspace has `name` ⇒ behaviour preserved; a fresh one has
129
+ // neither ⇒ first set admitted.
121
130
  .assert(({ self }) =>
122
- (self as { role?: string })?.role ? { reject: "workspace-self-already-set" } : { accept: true },
131
+ (self as { name?: string })?.name ? { reject: "workspace-self-already-set" } : { accept: true },
123
132
  );
124
133
 
125
134
  // ── THE GOVERNANCE RELATION SCHEMA (full-native Zanzibar — the ONE authz model) ──────────────────────
@@ -136,8 +145,10 @@ export const workspaceSelfWriteOnce = workspaceInvariant("WorkspaceSelfWriteOnce
136
145
  // side) DISCARDED at evaluation. Each relation is a flat `self()` (directly-stored tuples) — NO rewrite
137
146
  // algebra (no computed/from/union): control-plane authority is direct membership only.
138
147
  // * admin — grantAdmin/revokeAdmin + every admin-mutating governance directive.
139
- // * creator — createWorkspace (under invite/closed posture; see the host's _creationLawOk).
140
- // * platformCreator — birthPlatform (always invite-only).
148
+ // * creator — THE birth authority: createWorkspace + birthChild (leaf OR platform). The tier
149
+ // collapsed onto this one relation (2026-06-24); a subject is user: or key:.
150
+ // * platformCreator — VESTIGIAL: its directives (birthPlatform/grant·revokePlatformCreation) are removed;
151
+ // no .requires references it. Kept until the #58 disposition pass (with kind/role).
141
152
  // SELF-RELATIVE: custody guarantees a workspace only ever folds its OWN relations table, so
142
153
  // `workspace:root#admin` collapses to `workspace:self#admin` — the SAME law governs root and every
143
154
  // born platform with no name plumbing. (A pure tenant runs no governance ⇒ no schema ⇒ the gate is
@@ -224,9 +235,12 @@ export const CloudWorkspace = aggregate("CloudWorkspace", {
224
235
  */
225
236
  byteCap: t.int().merge(Lww).optional(),
226
237
  /**
227
- * What this node IS: absent/`"workspace"` = a leaf; `"platform"` = a PLATFORM —
228
- * a workspace that hosts the SAME governance law and parents its own subtree
229
- * (the cloud, one level down; born via {@link birthPlatform}).
238
+ * DERIVED LABEL (the `kind`-collapse, architecture/provisioning_as_obligations.md): set to `"platform"`
239
+ * iff the birth order carried a genesis (a `foundingAdminHash`), else absent/`"workspace"`. It is NOT a
240
+ * node-class and NOTHING decides on it — the birth provider keys off `foundingAdminHash`, the genuine
241
+ * signal that a child is born running the governance law. Kept only so old readers/queries that filter by
242
+ * label still resolve; slated for removal (a #58 disposition) once no reader depends on it. "All
243
+ * workspaces are equal until a domain is installed" — a platform is one with the governance domain.
230
244
  */
231
245
  kind: t.enum(["workspace", "platform"]).merge(Lww).optional(),
232
246
  /**
@@ -237,6 +251,17 @@ export const CloudWorkspace = aggregate("CloudWorkspace", {
237
251
  * request at the cloud level).
238
252
  */
239
253
  pool: t.int().merge(Lww).optional(),
254
+ /**
255
+ * THE LAW-AUTHORED GENESIS CHAIN (P1, architecture/provisioning_as_obligations.md "law and law alone").
256
+ * For a genesis birth, `birthChild` authors the child's genesis RECIPE here as data — a JSON array of
257
+ * `{domain, directiveId, payload, actor}` steps (`seedGenesis → grantAdmin → birthCloud`). The birth
258
+ * capability provider REPLAYS this opaque list on the child (it names nothing — the directive ids come
259
+ * from the law, not host source); that is the difference between the host *reconstructing* the genesis
260
+ * (today) and the host *delivering* a law-authored one. Absent for plain births + pre-P1 records (the
261
+ * provider falls back to its inline recipe — flag-gated until live byte-equivalence is proven). Naming
262
+ * the genesis directives HERE is law naming law (legitimate); the prohibition binds only the host.
263
+ */
264
+ genesisChain: t.json().merge(Lww).optional(),
240
265
  });
241
266
 
242
267
  // AdminGrant / CreationGrant aggregates: DELETED (full-native Zanzibar). Admin/creation authority is
@@ -356,11 +381,79 @@ export const CapabilityBinding = aggregate("CapabilityBinding", {
356
381
 
357
382
  // --- workspace directives ---------------------------------------------------------
358
383
 
384
+ // ── THE ONE BIRTH RECORD (the `kind`-collapse — architecture/provisioning_as_obligations.md) ───────────
385
+ // Every birth — a plain child OR a platform — mints the SAME `CloudWorkspace` order; the ONLY difference is
386
+ // the GENESIS DATA the order carries. `genesis` present (a founding-admin hash + pool) ⇒ this child is born
387
+ // running the governance law: the birth capability provider drains it (the order carries a foundingAdminHash)
388
+ // and seeds the genesis. Absent ⇒ a plain lineage record, materialized by boot-on-touch (never drained).
389
+ // `kind` is set PURELY as a derived label off genesis presence — a vestige for old readers; NOTHING decides
390
+ // on it (the host now keys off foundingAdminHash, not the label). There is NO node-class: a platform is just
391
+ // a workspace whose birth order carried a genesis. (`birthChild` is the one unified directive — the tier
392
+ // was collapsed onto `#creator`; `birthPlatform`/`createWorkspace` remain only as deprecated compat aliases.)
393
+ // Returns the record builder so a caller (birthChild) can author the law-authored genesis chain onto it.
394
+ function planBirthRecord(
395
+ p: {
396
+ name: string;
397
+ parent: string;
398
+ principal: string;
399
+ tier: (typeof WORKSPACE_TIERS)[number];
400
+ requestedBy: string;
401
+ requestedAt: number;
402
+ },
403
+ genesis?: { foundingAdminHash: string; pool: number },
404
+ ) {
405
+ const rec = create(CloudWorkspace)
406
+ .set("name", p.name)
407
+ .set("parent", p.parent)
408
+ .set("principal", p.principal)
409
+ .set("tier", p.tier)
410
+ .set("status", "requested" as const)
411
+ .set("ownerGrantHash", "") // no owner credential until provisioned
412
+ .set("createdBy", p.requestedBy)
413
+ .set("createdAt", p.requestedAt);
414
+ if (genesis) {
415
+ rec
416
+ .set("kind", "platform" as const) // DERIVED LABEL ONLY — the order's foundingAdminHash is the real signal
417
+ .set("pool", genesis.pool)
418
+ .set("foundingAdminHash", genesis.foundingAdminHash); // the provider grants admin to this hash at materialize
419
+ }
420
+ return rec;
421
+ }
422
+
423
+ // ── THE LAW-AUTHORED GENESIS CHAIN (P1 — "law and law alone") ──────────────────────────────────────────
424
+ // The parent's `birthChild` AUTHORS the child's genesis recipe as DATA — byte-faithful to what the host's
425
+ // `/materialize` reconstructs today (seedGenesis → grantAdmin → birthCloud), but now owned by the LAW. The
426
+ // host replays this opaque list on the child (naming nothing). Timestamps ride IN the payload (the doctrine):
427
+ // `bornAt` (ISO) for the warrant, `requestedAt` (epoch-ms) for the ms-stamped grants — both caller-stamped,
428
+ // so the chain is a pure function of the payload (deterministic on every replaying peer). Naming the genesis
429
+ // directives here is law naming law; the prohibition binds only the host. The law package bytes are NOT in
430
+ // here — the install-by-hash stays a custody concern of the provider (the bytes-in-pack refinement is later).
431
+ export function buildGenesisChain(p: {
432
+ name: string;
433
+ principal: string;
434
+ tier: (typeof WORKSPACE_TIERS)[number];
435
+ foundingAdminHash: string;
436
+ bornAt: string;
437
+ requestedAt: number;
438
+ }): string {
439
+ const founder = "user:" + p.principal;
440
+ return JSON.stringify([
441
+ { domain: "workspaces", directiveId: "seedGenesis", actor: founder, payload: { principal: founder, workspaceId: "", bornAt: p.bornAt } },
442
+ { domain: "workspaces", directiveId: "grantAdmin", actor: founder, payload: { keyHash: p.foundingAdminHash, label: `founding admin (born for ${p.principal})`, grantedBy: p.principal, grantedAt: p.requestedAt } },
443
+ // No `role` — it was a vestigial node-class label (collapsed with `kind`); birthCloud ignores it now.
444
+ { domain: "workspaces", directiveId: "birthCloud", actor: "", payload: { principal: p.principal, name: p.name, tier: p.tier, bornAt: p.requestedAt } },
445
+ ]);
446
+ }
447
+
359
448
  /**
360
449
  * createWorkspace — record the lawful REQUEST for a workspace. Nomos MINTS the
361
450
  * `CloudWorkspace` id (the payload carries none); the record is born
362
451
  * `status:"requested"` with `ownerGrantHash:""` — no bearer exists until the
363
452
  * worker provisions and authors {@link provisionWorkspace}.
453
+ *
454
+ * @deprecated SUPERSEDED by {@link birthChild} (the `kind`-collapse) — a plain birth is `birthChild`
455
+ * with no genesis. Kept as a compat alias (same `#creator` gate) during migration; the host still
456
+ * authors it by name for namespaced-child + upload births. Retire once those callers move to birthChild.
364
457
  */
365
458
  export const createWorkspace = directive("createWorkspace")
366
459
  .creates(CloudWorkspace)
@@ -378,17 +471,8 @@ export const createWorkspace = directive("createWorkspace")
378
471
  }),
379
472
  )
380
473
  .plan((p) => {
381
- create(CloudWorkspace)
382
- .set("name", p.name)
383
- .set("parent", p.parent)
384
- .set("principal", p.principal)
385
- .set("tier", p.tier)
386
- .set("status", "requested" as const)
387
- .set("ownerGrantHash", "") // no owner credential until provisioned
388
- .set("createdBy", p.requestedBy)
389
- .set("createdAt", p.requestedAt);
390
- // A plain workspace record (no kind:platform) — boot materializes it directly; the birth capability
391
- // provider only drains kind:platform records, so this is a lineage record, not a genesis order.
474
+ // No genesis ⇒ a plain lineage record (no foundingAdminHash) — never drained; materialized by boot-on-touch.
475
+ planBirthRecord(p);
392
476
  return [];
393
477
  })
394
478
  .requires("creator"); // posture-gated creation: only a #creator holder may request a workspace
@@ -409,9 +493,9 @@ export const birthCloud = directive("birthCloud")
409
493
  /** The genesis auth-provider principal uid upon which the world rests. */
410
494
  principal: z.string().min(1),
411
495
  name: z.string().min(1).default("root"),
412
- /** The workspace's ROLE for the governance domain — the host stamps "cloud-root" at root genesis,
413
- * "platform" when birthing a platform's own chain. Drives the self-relative authz gate. */
414
- role: z.enum(WORKSPACE_ROLES).default("cloud-root"),
496
+ /** VESTIGIAL (see WorkspaceSelf): accepted-but-ignored — the value drives nothing. The authz gate is
497
+ * self-relative (`workspace:self#<relation>`), NOT role-keyed. Kept optional for caller compat. */
498
+ role: z.enum(WORKSPACE_ROLES).optional(),
415
499
  tier: z.enum(WORKSPACE_TIERS).default("edge"),
416
500
  bornAt: epochMs,
417
501
  }),
@@ -428,8 +512,9 @@ export const birthCloud = directive("birthCloud")
428
512
  .set("createdAt", p.bornAt)
429
513
  .set("provisionedAt", p.bornAt);
430
514
  // The self-identity singleton at the CONSTANT id (ungated — birthCloud is not admin-mutating), so the
431
- // gate's self-record exists before any admin write. ensure() = deterministic-id sibling birth.
432
- ensure(WorkspaceSelf, WORKSPACE_SELF_ID).set("role", p.role).set("name", p.name);
515
+ // gate's self-record exists before any admin write. ensure() = deterministic-id sibling birth. `name` is
516
+ // the born marker; `role` is no longer written (vestigial node-class — see WorkspaceSelf).
517
+ ensure(WorkspaceSelf, WORKSPACE_SELF_ID).set("name", p.name);
433
518
  return [];
434
519
  });
435
520
 
@@ -552,67 +637,75 @@ export const reclaimWorkspace = directive("reclaimWorkspace")
552
637
  })
553
638
  .requires("admin");
554
639
 
555
- /**
556
- * birthPlatform — A PLATFORM IS BORN: the cloud, one level down. Records the
557
- * platform-kind workspace (parented to root, owned by the invited principal,
558
- * carrying its POOL). The worker then provisions it exactly like the cloud
559
- * provisioned itself: deploys the SAME governance law into it, seeds ITS founding
560
- * AdminGrant, and the platform claims itself with its own birth intent on its own
561
- * chain. Recursion, not new machinery.
562
- */
563
- export const birthPlatform = directive("birthPlatform")
640
+ // ─────────────────────────── BIRTH AS A NOMOS PRIMITIVE ──────────────────────────────────────────────
641
+ // (birthPlatform is GONE — the tier collapsed onto #creator; birthChild is the one birth directive.)
642
+ // THE RECORD IS THE OBLIGATION (architecture/githolon_host_contract.md "Birth is a capability" +
643
+ // provisioning_as_obligations.md). Birth is an IMPURE capability: the parent ORDERS (the law-gated
644
+ // birthChild mints a CloudWorkspace at status:requested carrying foundingAdminHash + the LAW-AUTHORED
645
+ // genesisChain — gated by .requires("creator")), the BIRTH CAPABILITY PROVIDER fulfils it (materialize:
646
+ // install the parent's law + REPLAY the genesisChain opaquely — the child's OWN kernel admitting each), and
647
+ // authors provisionWorkspace (the receipt) the parent TRUSTS. No separate obligation aggregate — the minted
648
+ // CloudWorkspace IS the order. The provider owns only the impure boot + replay; the LAW owns the recipe (P1)
649
+ // AND who/what (the gate); the parent trusts the DID-IT-HAPPEN. A child born this way runs THIS SAME law and
650
+ // births its children identically — the recursion is the law.
651
+
652
+ // ─────────────────────────── birthChild — THE ONE BIRTH DIRECTIVE (the `kind`-collapse) ───────────────
653
+ // "There are only workspaces, all equal until a domain is installed." birthChild is the SINGLE directive
654
+ // that births any child — a plain leaf OR a platform; the difference is the GENESIS DATA the payload carries,
655
+ // never a node-class. THE TIER IS COLLAPSED (Jack, 2026-06-24): `#creator` is THE one birth authority — a
656
+ // creator may spawn a leaf AND a platform (the prior `#platformCreator` second tier is retired; platform
657
+ // birth under root is still invite-only because `creator` on root is admin-granted, just renamed/unified).
658
+ // • genesis ABSENT → a plain lineage record (no foundingAdminHash); boot-on-touch, never drained.
659
+ // • genesis PRESENT → carries {foundingAdminHash, pool}: the birth capability provider drains it
660
+ // (the order carries a foundingAdminHash) → materialize + seed genesis. A platform.
661
+ // Self-relative: the gate is `workspace:self#creator`, so the SAME directive on root births a platform and
662
+ // on a platform births a child — no per-level host code (the recursion is the law). Supersedes
663
+ // createWorkspace + birthPlatform (kept as compat aliases during migration). See
664
+ // architecture/provisioning_as_obligations.md "The `kind`-collapse".
665
+ export const birthChild = directive("birthChild")
564
666
  .creates(CloudWorkspace)
565
667
  .payload(
566
- z.object({
567
- name: z.string().min(1),
568
- /** The auth-provider uid of the platform OWNER (must hold a PlatformGrant). */
569
- principal: z.string().min(1),
570
- /** Parent CloudWorkspace aggregate id (root's record). */
571
- parent: z.string().default(""),
572
- /** The pool: how many live children the platform may parent. */
573
- pool: z.number().int().positive().default(50),
574
- tier: z.enum(WORKSPACE_TIERS).default("heavy"),
575
- /** sha256 of the founding-admin key the requester PRESENTED (the provider grants admin to the hash;
576
- * the requester keeps the key — it is the platform's admin credential, used to govern + deploy). */
577
- foundingAdminHash: sha256Hex,
578
- requestedBy: z.string().min(1),
579
- requestedAt: epochMs,
580
- }),
668
+ z
669
+ .object({
670
+ name: z.string().min(1),
671
+ /** The auth-provider principal uid the request claimed; `""` = unclaimed (a plain leaf may be). */
672
+ principal: z.string().default(""),
673
+ /** Parent CloudWorkspace aggregate id; `""` (default) = root. */
674
+ parent: z.string().default(""),
675
+ /** Hosting tier; defaults to the edge DO (a genesis/platform birth typically passes `heavy`). */
676
+ tier: z.enum(WORKSPACE_TIERS).default("edge"),
677
+ requestedBy: z.string().min(1),
678
+ requestedAt: epochMs,
679
+ /** GENESIS (optional): present ⇒ this child is born running the governance law (a platform). The
680
+ * sha256 of the founding-admin key the requester presented — the provider grants admin to the hash. */
681
+ foundingAdminHash: sha256Hex.optional(),
682
+ /** PLATFORM POOL: how many live children the born platform may parent. Only meaningful with genesis. */
683
+ pool: z.number().int().positive().default(50),
684
+ /** GENESIS birth time as an ISO string (caller-stamped — the timestamp doctrine). Arms the warrant
685
+ * in the law-authored genesis chain; required for (and only used by) a genesis birth. */
686
+ bornAt: z.string().optional(),
687
+ })
688
+ // A genesis birth (a platform) must carry a principal AND a bornAt — it owns a subtree, a lineage
689
+ // anchor, and a law-authored genesis chain whose warrant is stamped at bornAt.
690
+ .refine((d) => !d.foundingAdminHash || (d.principal.length > 0 && !!d.bornAt), {
691
+ message: "a genesis birth (foundingAdminHash present) requires a non-empty principal and bornAt (ISO)",
692
+ path: ["principal"],
693
+ }),
581
694
  )
582
695
  .plan((p) => {
583
- create(CloudWorkspace)
584
- .set("name", p.name)
585
- .set("parent", p.parent)
586
- .set("principal", p.principal)
587
- .set("kind", "platform" as const)
588
- .set("pool", p.pool)
589
- .set("tier", p.tier)
590
- .set("status", "requested" as const)
591
- .set("ownerGrantHash", "")
592
- .set("foundingAdminHash", p.foundingAdminHash) // the provider grants admin to this hash at materialize
593
- .set("createdBy", p.requestedBy)
594
- .set("createdAt", p.requestedAt);
595
- // THE RECORD IS THE OBLIGATION: a kind:platform CloudWorkspace at status:requested with no ownerGrantHash
596
- // is an order the birth capability provider drains (materialize + install the parent's law + seed genesis
597
- // + grant the founding admin + birthCloud), closing it with provisionWorkspace (the receipt). No separate
598
- // obligation aggregate — the minted CloudWorkspace IS it (sidesteps the id-mint gate; reuses the lifecycle).
696
+ const rec = planBirthRecord(p, p.foundingAdminHash ? { foundingAdminHash: p.foundingAdminHash, pool: p.pool } : undefined);
697
+ // LAW AUTHORS THE GENESIS RECIPE (P1): a genesis birth carries its child's seedGenesis→grantAdmin→
698
+ // birthCloud chain as data; the provider replays it opaquely. (refine guarantees principal + bornAt here.)
699
+ if (p.foundingAdminHash) {
700
+ rec.set("genesisChain", buildGenesisChain({ name: p.name, principal: p.principal, tier: p.tier, foundingAdminHash: p.foundingAdminHash, bornAt: p.bornAt as string, requestedAt: p.requestedAt }));
701
+ }
599
702
  return [];
600
703
  })
601
- .requires("platformCreator"); // invite-only: an active #platformCreator tuple admits the birth
704
+ .requires("creator"); // THE ONE birth authority — a #creator may birth a leaf OR a platform (tier collapsed)
602
705
 
603
- // ─────────────────────────── BIRTH AS A NOMOS PRIMITIVE ──────────────────────────────────────────────
604
- // THE RECORD IS THE OBLIGATION (architecture/githolon_host_contract.md "Birth is a capability"). Birth is
605
- // an IMPURE capability: the parent ORDERS (the law-gated birthPlatform mints a CloudWorkspace at
606
- // status:requested, kind:platform, carrying foundingAdminHash — gated by .requires("platformCreator")),
607
- // the BIRTH CAPABILITY PROVIDER fulfils it (materialize: install the PARENT's law + seed genesis + grant
608
- // the founding admin + birthCloud — the child's OWN kernel admitting each), and authors provisionWorkspace
609
- // (the receipt) the parent TRUSTS. No separate obligation aggregate — the minted CloudWorkspace IS the
610
- // order (it sidesteps the id-mint gate and reuses the requested→provisioned lifecycle). The provider knows
611
- // HOW (the Capability Exception sanctions a declared effect); the law owns WHO/WHAT; the parent trusts the
612
- // DID-IT-HAPPEN. A child born this way runs THIS SAME law and births its children identically.
613
-
614
- /** workspacesByStatus — the birth provider drains `requested` CloudWorkspaces (kind:platform, no
615
- * ownerGrantHash) → materialize → provisionWorkspace. The law's order queue; the host polls, never decides. */
706
+ /** workspacesByStatus — the birth provider drains `requested` CloudWorkspaces that CARRY A foundingAdminHash
707
+ * (a genesis order, no ownerGrantHash) → materialize → provisionWorkspace. Plain requested records (no
708
+ * foundingAdminHash) are lineage only, boot-on-touch. The law's order queue; the host polls, never decides. */
616
709
  export const workspacesByStatus = query("workspacesByStatus").key("status").returns(CloudWorkspace);
617
710
 
618
711
  /** setPlatformPool — the pool edition of a granted quota request. */
@@ -632,39 +725,10 @@ export const setPlatformPool = directive("setPlatformPool")
632
725
  })
633
726
  .requires("admin");
634
727
 
635
- /**
636
- * grantPlatformCreation / revokePlatformCreation — open and close the
637
- * become-a-platform door for one principal (see {@link PlatformGrant}).
638
- */
639
- // grantPlatformCreation — become-a-platform authority IS the self-relative `#platformCreator` tuple
640
- // (one authz spine, all the way down). Writes `workspace:self#platformCreator@user:<principal>` (no
641
- // PlatformGrant aggregate) — the host's platform-birth invite check reads the SAME tuple via Check.
642
- export const grantPlatformCreation = directive("grantPlatformCreation")
643
- .ensures(RelationTuple)
644
- .payload(
645
- z.object({
646
- principal: z.string().min(1),
647
- label: z.string().min(1),
648
- grantedBy: z.string().min(1),
649
- grantedAt: epochMs,
650
- }),
651
- )
652
- .plan((p) => writeTuple({ object: SELF_ADMIN_OBJECT, relation: "platformCreator", subject: "user:" + p.principal, grantedBy: p.grantedBy, grantedAt: String(p.grantedAt) }))
653
- .requires("admin"); // only an admin opens the become-a-platform door
654
-
655
- // revokePlatformCreation — flip the `#platformCreator@user:<principal>` leaf to revoked. BREAKING vs the
656
- // aggregate model: keyed by PRINCIPAL (the tuple subject), not a minted grantId.
657
- export const revokePlatformCreation = directive("revokePlatformCreation")
658
- .ensures(RelationTuple)
659
- .payload(
660
- z.object({
661
- principal: z.string().min(1),
662
- revokedBy: z.string().min(1),
663
- revokedAt: epochMs,
664
- }),
665
- )
666
- .plan((p) => removeTuple({ object: SELF_ADMIN_OBJECT, relation: "platformCreator", subject: "user:" + p.principal, grantedBy: p.revokedBy, grantedAt: String(p.revokedAt), revokedBy: p.revokedBy, revokedAt: String(p.revokedAt) }))
667
- .requires("admin");
728
+ // grantPlatformCreation / revokePlatformCreation — REMOVED (the tier collapsed onto #creator, 2026-06-24).
729
+ // Platform-birth authority is now just #creator (grantCreation, which takes a user OR key subject). The
730
+ // `#platformCreator` relation + its (vestigial) PlatformGrant aggregate / platformGrantByPrincipal query
731
+ // retire in the same #58 disposition pass as kind/role; any historical #platformCreator tuple is inert data.
668
732
 
669
733
  /**
670
734
  * AuthProvider — BYOAuth: WHO VOUCHES FOR THIS SUBTREE'S PRINCIPALS. A law-state
@@ -980,30 +1044,41 @@ export const revokeAdmin = directive("revokeAdmin")
980
1044
  // grantCreation — creation authority IS the self-relative `#creator` tuple the CreationGate reads.
981
1045
  // Writes `workspace:self#creator@key:<keyHash>` (no CreationGrant aggregate). Under posture invite/closed
982
1046
  // the gate admits only holders of an active #creator subject; under open/unset it admits anyone.
1047
+ // #creator is ONE relation over ONE principal graph; a subject is `key:<hash>` (proof by holding a secret —
1048
+ // a Bearer) OR `user:<uid>` (proof of identity — an auth-provider token). The relation allows BOTH
1049
+ // (allow:[user(),key()]); these are not two authorities, just two ways a principal authenticates. So
1050
+ // grantCreation takes EITHER — exactly one of keyHash / principal. This is what lets a USER birth platforms
1051
+ // via birthChild (no "creator key" dance) and dissolves the user/key migration wrinkle.
983
1052
  export const grantCreation = directive("grantCreation")
984
1053
  .ensures(RelationTuple)
985
1054
  .payload(
986
- z.object({
987
- keyHash: sha256Hex,
988
- label: z.string().min(1),
989
- grantedBy: z.string().min(1),
990
- grantedAt: epochMs,
991
- }),
1055
+ z
1056
+ .object({
1057
+ keyHash: sha256Hex.optional(),
1058
+ principal: z.string().min(1).optional(),
1059
+ label: z.string().min(1),
1060
+ grantedBy: z.string().min(1),
1061
+ grantedAt: epochMs,
1062
+ })
1063
+ .refine((d) => !!d.keyHash !== !!d.principal, { message: "grantCreation needs exactly one of keyHash (a key: subject) or principal (a user: subject)", path: ["keyHash"] }),
992
1064
  )
993
- .plan((p) => writeTuple({ object: SELF_ADMIN_OBJECT, relation: "creator", subject: "key:" + p.keyHash, grantedBy: p.grantedBy, grantedAt: String(p.grantedAt) }))
1065
+ .plan((p) => writeTuple({ object: SELF_ADMIN_OBJECT, relation: "creator", subject: p.principal ? "user:" + p.principal : "key:" + p.keyHash, grantedBy: p.grantedBy, grantedAt: String(p.grantedAt) }))
994
1066
  .requires("admin"); // only an admin grants creation authority
995
1067
 
996
- /** revokeCreation — flip the `#creator@key:<keyHash>` leaf to revoked (see {@link revokeAdmin}). */
1068
+ /** revokeCreation — flip the `#creator@<subject>` leaf to revoked (key OR user, mirroring grantCreation). */
997
1069
  export const revokeCreation = directive("revokeCreation")
998
1070
  .ensures(RelationTuple)
999
1071
  .payload(
1000
- z.object({
1001
- keyHash: sha256Hex,
1002
- revokedBy: z.string().min(1),
1003
- revokedAt: epochMs,
1004
- }),
1072
+ z
1073
+ .object({
1074
+ keyHash: sha256Hex.optional(),
1075
+ principal: z.string().min(1).optional(),
1076
+ revokedBy: z.string().min(1),
1077
+ revokedAt: epochMs,
1078
+ })
1079
+ .refine((d) => !!d.keyHash !== !!d.principal, { message: "revokeCreation needs exactly one of keyHash or principal", path: ["keyHash"] }),
1005
1080
  )
1006
- .plan((p) => removeTuple({ object: SELF_ADMIN_OBJECT, relation: "creator", subject: "key:" + p.keyHash, grantedBy: p.revokedBy, grantedAt: String(p.revokedAt), revokedBy: p.revokedBy, revokedAt: String(p.revokedAt) }))
1081
+ .plan((p) => removeTuple({ object: SELF_ADMIN_OBJECT, relation: "creator", subject: p.principal ? "user:" + p.principal : "key:" + p.keyHash, grantedBy: p.revokedBy, grantedAt: String(p.revokedAt), revokedBy: p.revokedBy, revokedAt: String(p.revokedAt) }))
1007
1082
  .requires("admin");
1008
1083
 
1009
1084
  // --- declared read queries (the worker's ENTIRE enforcement surface) -----------------
@@ -1156,8 +1231,9 @@ export const founderSignerWriteOnce = workspaceInvariant("FounderSignerWriteOnce
1156
1231
  export const birthCloudOnce = workspaceInvariant("BirthCloudOnce")
1157
1232
  .on("birthCloud")
1158
1233
  .reads(() => [refAs("self", "WorkspaceSelf", "pre:" + WORKSPACE_SELF_ID)])
1234
+ // WRITE-ONCE on the self-record's PRESENCE via `name` (not the vestigial `role`) — a workspace is born once.
1159
1235
  .assert(({ self }) =>
1160
- (self as { role?: string })?.role ? { reject: "cloud-already-born" } : { accept: true },
1236
+ (self as { name?: string })?.name ? { reject: "cloud-already-born" } : { accept: true },
1161
1237
  );
1162
1238
 
1163
1239
  /** provisionWorkspace: the birth RECEIPT may only close a `requested` record (no hijacking a live