@githolon/dsl 0.100.0 → 0.100.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.100.0",
3
+ "version": "0.100.2",
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",
@@ -14,8 +14,8 @@
14
14
  * counts (the same composed modules every other emitter sees) and emits a
15
15
  * domain-shaped live proof:
16
16
  *
17
- * throwaway workspace → deploy `build/<name>.deploy.json` → connect via the
18
- * GENERATED typed client → fetch-TRAPPED offline dispatch of the first `.creates`
17
+ * signed session home → connect via the GENERATED typed client →
18
+ * fetch-TRAPPED offline dispatch of the first `.creates`
19
19
  * directive (payload synthesized from its zod schema) → first declared query +
20
20
  * first count answered LOCALLY → sync (edge admission) → the CLOUD answers →
21
21
  * two-client AddWins concurrency (when the law has the shape for it) →
@@ -30,8 +30,7 @@
30
30
  * the remedy — the compile then SKIPS the proof (the law still ships) and says why.
31
31
  *
32
32
  * Deterministic codegen: the emitted bytes are a pure function of the modules +
33
- * package name + domain hash — no timestamps, no randomness (the proof picks its
34
- * throwaway workspace name at RUN time, not at generation time).
33
+ * package name + domain hash — no timestamps or generation-time randomness.
35
34
  */
36
35
  import type { z } from "zod";
37
36
 
@@ -2148,7 +2147,7 @@ export interface TsProofOptions {
2148
2147
  * · `standalone-provable` — a `.creates` write proven offline (dispatch → declared query/count locally).
2149
2148
  * · `child-birth-provable` — a `.births()` directive proven END-TO-END into the child it births.
2150
2149
  * · `relation-bootstrap-provable` — a `.requires(R)`-gated write proven to SUCCEED after granting R (seed-then-prove).
2151
- * · `live-cloud-provable` — the full cloud loop (the `.mts` `--live` lane) completes (the primary domain). */
2150
+ * · `live-cloud-provable` — the same generated application proof completes in durable cloud custody. */
2152
2151
  export type ProofKind = "compile-valid" | "standalone-provable" | "child-birth-provable" | "relation-bootstrap-provable" | "live-cloud-provable";
2153
2152
 
2154
2153
  /** The machine-readable OFFLINE legs for ONE domain (the `@nomos-proof-legs`
@@ -2534,52 +2533,34 @@ export function proofLegsIndex(modules: readonly DomainModule[]): { default: str
2534
2533
  // ── shared `.mts` fragments (the v2 contract — G1: no host secret; deploy is OPEN + kernel-gated) ─────────
2535
2534
 
2536
2535
  /**
2537
- * THE V2 PREAMBLE (G1, gap 4): create a throwaway workspace (NO secret comes back — ownership is a ledger
2538
- * fact), then deploy YOUR compiled law via the OPEN lane (`POST /domains`, no Bearer; write-authority is
2539
- * kernel-judged at the offer gate). Identity rides `x-nomos-principal` (transitional bare-uid lane), the
2540
- * same header `cli/src/cloud.ts deploy()` + the 0.18 host-strip lane present. Declares `fail`/`ok`/`deploy`/
2541
- * `r`/`d`/`runError`/`PRINCIPAL` and opens the `try {` (closed by {@link v2CatchFinally}).
2536
+ * THE LIVE PREAMBLE: `githolon proof --live` has already established the signed session home and deployed
2537
+ * this package into it. Generated proof code receives only that ready session and drives the application verb.
2538
+ * The old raw create/deploy lane was a second product model and could only create registry/custody orphans.
2542
2539
  */
2543
2540
  function v2IdentityAndCleanupPreamble(opts: TsProofOptions, hashConst: string, n: () => number): string[] {
2541
+ void opts;
2542
+ void hashConst;
2544
2543
  return [
2545
- `// IDENTITY NOTE: this proof uses the bare x-nomos-principal lane so it is SELF-CONTAINED (a throwaway`,
2546
- `// workspace, no stored credentials). Real apps use \`githolon login --agent\` (a VERIFIED identity)`,
2547
- `// + \`githolon ws create/deploy\`. (G1: a create returns NO workspaceSecret — ownership is a ledger`,
2548
- `// fact; deploy is OPEN + kernel-gated, authenticated by the principal, not a host secret.)`,
2544
+ `// Nomos prepared this signed home and installed the package before entering generated application code.`,
2545
+ `// The application proof supplies business facts only; identity/custody/law selection stay underneath.`,
2549
2546
  `const fail: (m: string) => never = (m) => { throw new Error(m); };`,
2550
2547
  `const ok = (m: string) => console.log("✓ " + m);`,
2551
2548
  `const PRINCIPAL = process.env.NOMOS_PRINCIPAL || "githolon-proof";`,
2549
+ `const AUTHOR_SECRET = process.env.NOMOS_AUTHOR_SECRET || "";`,
2552
2550
  ``,
2553
- `const deploy = JSON.parse(readFileSync(new URL(${JSON.stringify(`./${opts.packageName}.deploy.json`)}, import.meta.url), "utf8"));`,
2554
- ``,
2555
- `// CLEANUP CONTRACT: the throwaway workspace is RETIRED on every exit (green or jammed) — a proof never`,
2556
- `// leaves orphans. PROOF_KEEP=1 (githolon proof --live --keep) keeps it and prints the persist command.`,
2551
+ `let d: any;`,
2557
2552
  `let runError: unknown = null;`,
2558
- `let born_ws = false;`,
2559
2553
  `try {`,
2560
2554
  ``,
2561
- `// ${n()}. a throwaway workspace — born by the create (G1: NO secret is returned; ownership is a ledger fact)`,
2562
- `let r = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}\`, { method: "POST", headers: { "x-nomos-principal": PRINCIPAL } });`,
2563
- `let d = await r.json();`,
2564
- `if (!d.ok) fail(\`create workspace: \${JSON.stringify(d)}\`);`,
2565
- `born_ws = true;`,
2566
- `ok(\`workspace \${WS} created (no host secret — ownership is a ledger fact; PROOF_KEEP=1 keeps it)\`);`,
2567
- ``,
2568
- `// ${n()}. deploy YOUR compiled law via the OPEN lane (POST /domains, no Bearer — kernel-judged authority,`,
2569
- `// authenticated as PRINCIPAL via x-nomos-principal; a refusal answers as a TYPED kernel verdict 422)`,
2570
- `r = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/domains\`, { method: "POST", headers: { "content-type": "application/json", "x-nomos-principal": PRINCIPAL }, body: JSON.stringify(deploy) });`,
2571
- `d = await r.json();`,
2572
- `if (!d.ok || d.installation?.[0]?.data?.["status.phase"] !== "Active") fail(\`deploy: \${JSON.stringify(d)}\`);`,
2573
- `if (d.domainHash !== ${hashConst}) fail(\`deploy hash \${d.domainHash} != the generated client's \${${hashConst}}\`);`,
2574
- `ok(\`law deployed Active — \${${hashConst}.slice(0, 12)}… (the content hash baked into your typed client)\`);`,
2555
+ `// ${n()}. the one managed entry: the logged-in user's durable Nomos home`,
2556
+ `if (process.env.NOMOS_PROOF_MANAGED !== "1" || !AUTHOR_SECRET) fail("run this cloud proof through: githolon proof --live");`,
2557
+ `ok(\`Nomos home ready — \${WS}\`);`,
2575
2558
  ];
2576
2559
  }
2577
2560
 
2578
2561
  /**
2579
- * THE V2 CATCH/FINALLY (G1, gap 4 · 0.34.1): retirement is a SIGNED governance offer — the open self-retire
2580
- * lane is DEAD post-warrant-flip, so the proof itself never retires. Under `githolon proof --live`
2581
- * (NOMOS_PROOF_MANAGED=1) the CLI created the workspace REGISTERED and retires it lawfully after exit;
2582
- * standalone, the proof warns LOUDLY and names the manual command. PROOF_KEEP=1 keeps it.
2562
+ * The generated cloud proof only owns its application call. Its durable signed home is framework state,
2563
+ * so there is no parallel throwaway-provisioning or cleanup protocol to expose here.
2583
2564
  */
2584
2565
  function v2CatchFinally(): string[] {
2585
2566
  return [
@@ -2587,19 +2568,6 @@ function v2CatchFinally(): string[] {
2587
2568
  ` runError = e;`,
2588
2569
  ` console.error("✗ " + ((e as Error)?.message ?? e));`,
2589
2570
  `} finally {`,
2590
- ` // THE CLEANUP CONTRACT: never a SILENTLY orphaned cloud workspace. Retirement is a SIGNED`,
2591
- ` // governance offer (retireOwnWorkspace at the parent) — the old open-lane POST /retire is dead`,
2592
- ` // post-warrant-flip. \`githolon proof --live\` orchestrates it (NOMOS_PROOF_MANAGED=1: it created`,
2593
- ` // the workspace REGISTERED and retires it after this process exits). Run standalone, this proof`,
2594
- ` // cannot lawfully retire (no signing key) — so it says so LOUDLY and names the exact command.`,
2595
- ` if (born_ws && process.env.PROOF_KEEP === "1") {`,
2596
- ` console.log(\`workspace \${WS} KEPT — retire it when done: githolon ws retire \${WS}\`);`,
2597
- ` } else if (born_ws && process.env.NOMOS_PROOF_MANAGED === "1") {`,
2598
- ` console.log(\` workspace \${WS} — retirement handled by githolon proof --live (signed retireOwnWorkspace)\`);`,
2599
- ` } else if (born_ws) {`,
2600
- ` console.error(\`⚠ THROWAWAY WORKSPACE NOT RETIRED: \${WS} (retirement is a signed governance offer this standalone proof cannot author)\`);`,
2601
- ` console.error(\`⚠ retire it manually: githolon ws retire \${WS} (run the managed lane next time: githolon proof --live)\`);`,
2602
- ` }`,
2603
2571
  ` process.exit(runError ? 1 : 0);`,
2604
2572
  `}`,
2605
2573
  ];
@@ -2672,6 +2640,39 @@ function liveAuthorityChainLines(setup: RelationBootstrapLeg, actorVar: string,
2672
2640
  return lines;
2673
2641
  }
2674
2642
 
2643
+ function liveBusinessBirthLegLines(cb: ChildBirthLeg, factory: string, n: () => number): string[] {
2644
+ const businessPayload = Object.fromEntries(
2645
+ Object.entries(cb.birthPayload).filter(([field]) => !["principal", "bornAt", "ownerKeyHash", "birthCert"].includes(field)),
2646
+ );
2647
+ return [
2648
+ `// ${n()}. THE APPLICATION SURFACE: one generated business birth`,
2649
+ `{`,
2650
+ ` const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "business-birth-proof", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
2651
+ ` const app = ${factory}(holon, { actor: PRINCIPAL });`,
2652
+ ` const facts: Record<string, unknown> = ${JSON.stringify(businessPayload)};`,
2653
+ ` const run = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);`,
2654
+ ` for (const [field, value] of Object.entries(facts)) {`,
2655
+ ` if (typeof value === "string" && value.startsWith("sample-") && value.length > 7) {`,
2656
+ ` facts[field] = ("sample-" + run).slice(0, value.length);`,
2657
+ ` }`,
2658
+ ` }`,
2659
+ ` const born = await app.birth(facts as never);`,
2660
+ ` const childWs = born.workspace;`,
2661
+ ` if (!childWs) fail("app.birth committed without returning the born workspace");`,
2662
+ ` ok(\`app.birth(${cb.businessAggregateId}) — business facts became \${childWs}\`);`,
2663
+ ` ok("identity, provenance, persistence and recovery verified before app.birth resolved");`,
2664
+ ...(cb.childSeedInstanceId !== undefined
2665
+ ? [
2666
+ ` const restored = await fetch(\`\${CLOUD}/v2/workspaces/\${childWs}/aggregates/\${encodeURIComponent(${JSON.stringify(cb.childSeedInstanceId)})}\`).then((r) => r.json());`,
2667
+ ` if (!(restored.rows?.length >= 1)) fail(\`recovered ${cb.businessAggregateId} has no business root: \${JSON.stringify(restored)}\`);`,
2668
+ ` ok("business root read back from durable custody");`,
2669
+ ]
2670
+ : []),
2671
+ `}`,
2672
+ ``,
2673
+ ];
2674
+ }
2675
+
2675
2676
  function liveChildBirthLegLines(cb: ChildBirthLeg, opts: TsProofOptions, n: () => number): string[] {
2676
2677
  const hashConst = tsHashConstName(opts.packageName);
2677
2678
  // The birth payload literal: the synthesized values + the REAL hashes onto the discovered fields + the
@@ -2807,11 +2808,10 @@ function generateBirthOnlyProof(
2807
2808
  `// recompile, and this proof reshapes itself — no test rewriting. Run it:`,
2808
2809
  `//`,
2809
2810
  `// npx githolon proof (offline legs on a local holon)`,
2810
- `// npx githolon proof --live (or: npx tsx build/${opts.packageName}.proof.mts — the full cloud loop)`,
2811
+ `// npx githolon proof --live (the same application proof in durable Nomos custody)`,
2811
2812
  `//`,
2812
2813
  `// Regenerated on every compile; do not edit.`,
2813
2814
  `// @nomos-proof-legs ${legsMarker}`,
2814
- `import { readFileSync } from "node:fs";`,
2815
2815
  `import { connect, encodeIntentOfferBytes } from "@githolon/client";`,
2816
2816
  `import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
2817
2817
  `void connect; void encodeIntentOfferBytes; void ${factory};`,
@@ -2821,7 +2821,9 @@ function generateBirthOnlyProof(
2821
2821
  ``,
2822
2822
  ...v2IdentityAndCleanupPreamble(opts, hashConst, n),
2823
2823
  ``,
2824
- ...liveChildBirthLegLines(cb, opts, n),
2824
+ ...(cb.businessAggregateId !== undefined
2825
+ ? liveBusinessBirthLegLines(cb, factory, n)
2826
+ : liveChildBirthLegLines(cb, opts, n)),
2825
2827
  // THE RELATION-BOOTSTRAP LEG (incl. LAYER 4 born-context): a birth-only domain may ALSO carry a gated
2826
2828
  // non-birth directive whose authority is established by the birth genesis (co2 home's updateHomeProfile
2827
2829
  // via birthHome→seedHomeCreator). Emit it so the birth-only proof names it (the born-context proof itself
@@ -2871,7 +2873,7 @@ function generateBootstrapOnlyProof(
2871
2873
  `// law, recompile, and this proof reshapes itself — no test rewriting. Run it:`,
2872
2874
  `//`,
2873
2875
  `// npx githolon proof (offline legs on a local holon)`,
2874
- `// npx githolon proof --live (or: npx tsx build/${opts.packageName}.proof.mts — the full cloud loop)`,
2876
+ `// npx githolon proof --live (the same application proof in durable Nomos custody)`,
2875
2877
  `//`,
2876
2878
  `// Regenerated on every compile; do not edit.`,
2877
2879
  `// @nomos-proof-legs ${legsMarker}`,
@@ -2886,7 +2888,7 @@ function generateBootstrapOnlyProof(
2886
2888
  ...v2IdentityAndCleanupPreamble(opts, hashConst, n),
2887
2889
  ``,
2888
2890
  `// ${n()}. connect + bind the GENERATED typed client (the relation-bootstrap leg drives it with an actor)`,
2889
- `const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-1" });`,
2891
+ `const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-1", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
2890
2892
  `ok(\`web client connected — ${factory} bound (replica \${holon.replica.slice(0, 6)}…)\`);`,
2891
2893
  ``,
2892
2894
  // The SAME relation-bootstrap emitter the standalone proof uses — seed authority, then prove the GATED
@@ -3037,7 +3039,6 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
3037
3039
  `// The flow mirrors the scaffold's test/e2e.mts (docs/ in a scaffolded app explains`,
3038
3040
  `// every call — start at docs/01-mental-model.md). Regenerated on every compile; do not edit.`,
3039
3041
  `// @nomos-proof-legs ${legsMarker}`,
3040
- `import { readFileSync } from "node:fs";`,
3041
3042
  `import { connect, encodeIntentOfferBytes } from "@githolon/client";`,
3042
3043
  `import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
3043
3044
  ``,
@@ -3047,7 +3048,7 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
3047
3048
  ...v2IdentityAndCleanupPreamble(opts, hashConst, n),
3048
3049
  ``,
3049
3050
  `// ${n()}. connect + bind the GENERATED typed client`,
3050
- `const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-1" });`,
3051
+ `const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-1", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
3051
3052
  `const app = ${factory}(holon);`,
3052
3053
  `ok(\`web client connected — ${factory} bound (replica \${holon.replica.slice(0, 6)}…)\`);`,
3053
3054
  ``,
@@ -3213,8 +3214,8 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
3213
3214
  `// ${n()}. CONCURRENCY — two writers, two clientIds, BLIND to each other: each adds to`,
3214
3215
  `// the AddWins set \`${agg.id}.${setField}\` via \`${domain}/${cd.id}\` offline; admission`,
3215
3216
  `// merges both and the union keeps every add.`,
3216
- `const writerA = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-a" });`,
3217
- `const writerB = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-b" });`,
3217
+ `const writerA = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-a", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
3218
+ `const writerB = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-b", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
3218
3219
  `await ${factory}(writerA).${cd.id}(${concPayload("a")});`,
3219
3220
  `await ${factory}(writerB).${cd.id}(${concPayload("b")});`,
3220
3221
  `await writerA.sync();`,
package/src/codegen_ts.ts CHANGED
@@ -1069,6 +1069,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1069
1069
  ` principal?: string;`,
1070
1070
  ` /** Persist local custody and reconcile its configured remote. Business birth awaits this when available. */`,
1071
1071
  ` sync?(): Promise<unknown>;`,
1072
+ ` /** Complete durable custody and cold-chain verification for a newly born aggregate. */`,
1073
+ ` settleBirth?(workspace: string, opts?: { timeoutMs?: number; pollMs?: number }): Promise<unknown>;`,
1072
1074
  ...(encPresent
1073
1075
  ? [
1074
1076
  ` /** \`keys\` (E2E sealing): the reader's held scope keys, injected so encrypted fields decrypt;`,
@@ -1946,6 +1948,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1946
1948
  ` const born = typeof result === "object" && result !== null && Array.isArray(result.born) ? result.born[0] : undefined;`,
1947
1949
  ` if (typeof born !== "object" || born === null || typeof born.workspace !== "string" || !born.workspace) throw new Error("birth() committed without returning the born workspace");`,
1948
1950
  ` await holon.sync?.();`,
1951
+ ` if (!holon.settleBirth) throw new Error("birth() requires the current @githolon/client to complete durable recovery");`,
1952
+ ` await holon.settleBirth(born.workspace);`,
1949
1953
  ` return { workspace: born.workspace, type: ${JSON.stringify(businessBirth.aggregateId)}, id: ${JSON.stringify(businessBirth.rootId)} };`,
1950
1954
  ` },`,
1951
1955
  );
@@ -156,7 +156,8 @@ export const workspaceSelfWriteOnce = workspaceInvariant("WorkspaceSelfWriteOnce
156
156
  // and the ONE wasm gate runs `engine.check(schema, "workspace:self", relation, principal)` over the
157
157
  // pre-apply folded relation tuples — refusing any author who does not HOLD the relation. The schema +
158
158
  // the `.requires` together arm the gate (the `nomosRelations` + `nomosRelationGate` era keys); the
159
- // bespoke `workspaceInvariant` authz gates are GONE (this IS their replacement, as real native law).
159
+ // static governance roles use the native relation gate. Creation posture is deliberately dynamic law:
160
+ // open/unset admits any proven author, invite checks `creator`, and closed checks `admin`.
160
161
  //
161
162
  // THE CONTROL-PLANE CARVE-OUT — LEAF-ONLY (`allow: [user(), key()]`, never a userset/wildcard). A
162
163
  // `workspace:self#admin@<group>#member` or `@<type>:*` would let a group/wildcard escalate to admin
@@ -164,8 +165,8 @@ export const workspaceSelfWriteOnce = workspaceInvariant("WorkspaceSelfWriteOnce
164
165
  // side) DISCARDED at evaluation. Each relation is a flat `self()` (directly-stored tuples) — NO rewrite
165
166
  // algebra (no computed/from/union): control-plane authority is direct membership only.
166
167
  // * admin — grantAdmin/revokeAdmin + every admin-mutating governance directive.
167
- // * creator — THE birth authority: createWorkspace + birthChild (leaf OR platform). The tier
168
- // collapsed onto this one relation (2026-06-24); a subject is user: or key:.
168
+ // * creator — the invite-posture birth role for createWorkspace + birthChild. Open posture needs
169
+ // no pre-seated role; closed posture uses admin. A subject is user: or key:.
169
170
  // * platformCreator — VESTIGIAL: its directives (birthPlatform/grant·revokePlatformCreation) are removed;
170
171
  // no .requires references it. Kept until the #58 disposition pass (with kind/role).
171
172
  // SELF-RELATIVE: custody guarantees a workspace only ever folds its OWN relations table, so
@@ -370,9 +371,8 @@ export const CloudPolicy = aggregate("CloudPolicy", {
370
371
  * The workspace-CREATION posture dial (cloud_sovereignty.md S6) —
371
372
  * `"open"` (anyone creates), `"invite"` (an active CreationGrant key
372
373
  * required), or `"closed"` (admins only). OPTIONAL: absent keeps the
373
- * implicit posture the cloud has always had (zero active CreationGrants ⇒
374
- * open, any ⇒ invite-governed) — pre-dial chains and policies behave
375
- * exactly as before.
374
+ * implicit open posture. Invite and closed are explicit law facts; merely
375
+ * granting somebody authority never changes the door for everybody else.
376
376
  */
377
377
  creationPosture: t.string().merge(Lww).optional(),
378
378
  /** Who last set the policy (attribution). */
@@ -400,8 +400,8 @@ export const QuotaGrant = aggregate("QuotaGrant", {
400
400
 
401
401
  /**
402
402
  * PlatformGrant — legacy audit surface for platform invites. Platform creation now rides
403
- * {@link birthChild} with genesis data and is gated by `#creator`; any remaining PlatformGrant rows are
404
- * inert compatibility/audit data, never a worker config.
403
+ * {@link birthChild} with genesis data under the same open/invite/closed creation posture as every birth;
404
+ * any remaining PlatformGrant rows are inert compatibility/audit data, never a worker config.
405
405
  */
406
406
  export const PlatformGrant = aggregate("PlatformGrant", {
407
407
  /** The auth-provider principal uid invited to create platforms. */
@@ -466,8 +466,8 @@ export const CapabilityBinding = aggregate("CapabilityBinding", {
466
466
  // and seeds the genesis. Absent ⇒ a plain lineage record, materialized by boot-on-touch (never drained).
467
467
  // `kind` is set PURELY as a derived label off genesis presence — a vestige for old readers; NOTHING decides
468
468
  // on it (the host now keys off foundingAdminHash, not the label). There is NO node-class: a platform is just
469
- // a workspace whose birth order carried a genesis. (`birthChild` is the one unified directive — the tier
470
- // was collapsed onto `#creator`; `birthPlatform`/`createWorkspace` remain only as deprecated compat aliases.)
469
+ // a workspace whose birth order carried a genesis. (`birthChild` is the one unified directive;
470
+ // `createWorkspace` remains only as a deprecated compatibility alias.)
471
471
  // Returns the record builder so a caller (birthChild) can author the law-authored genesis chain onto it.
472
472
  function planBirthRecord(
473
473
  p: {
@@ -708,7 +708,7 @@ function quotaVerdict(
708
708
  * worker provisions and authors {@link provisionWorkspace}.
709
709
  *
710
710
  * @deprecated SUPERSEDED by {@link birthChild} (the `kind`-collapse) — a plain birth is `birthChild`
711
- * with no genesis. Kept as a compat alias (same `#creator` gate) during migration; the host still
711
+ * with no genesis. Kept as a compat alias (same creation-posture law) during migration; the host still
712
712
  * authors it by name for namespaced-child + upload births. Retire once those callers move to birthChild.
713
713
  */
714
714
  export const createWorkspace = directive("createWorkspace")
@@ -746,8 +746,7 @@ export const createWorkspace = directive("createWorkspace")
746
746
  planBirthRecord(p);
747
747
  return [];
748
748
  })
749
- .reads(workspacesByPrincipal, quotaByPrincipal, cloudPolicy) // arm the captured-read invariant gate (S2)
750
- .requires("creator"); // posture-gated creation: only a #creator holder may request a workspace
749
+ .reads(workspacesByPrincipal, quotaByPrincipal, cloudPolicy); // quota + creation posture are law invariants
751
750
 
752
751
  /**
753
752
  * birthCloud — THE CLOUD'S OWN BIRTH INTENT. Root authors this once, into its own
@@ -892,7 +891,7 @@ export const retireWorkspace = directive("retireWorkspace")
892
891
 
893
892
  /**
894
893
  * retireOwnWorkspace — SELF-SERVICE retirement: the principal who CREATED a workspace may retire it (you
895
- * made it, you can withdraw it — e.g. `githolon proof --live` cleaning up its throwaway workspace). Same
894
+ * made it, you can withdraw it — e.g. an operator removing an obsolete sandbox). Same
896
895
  * effect as {@link retireWorkspace} (status → retired, custody untouched) but gated by `creator` (the birth
897
896
  * authority) and SCOPED by {@link retireOwnOnly} to records THIS principal created — so a creator can only
898
897
  * retire its OWN workspaces, never another's. Admins keep the unrestricted {@link retireWorkspace}.
@@ -1205,11 +1204,11 @@ export const rebirthConsentByWorkspace = query("rebirthConsentByWorkspace")
1205
1204
  .returns(RebirthConsent);
1206
1205
 
1207
1206
  // ─────────────────────────── BIRTH AS A NOMOS PRIMITIVE ──────────────────────────────────────────────
1208
- // (birthPlatform is GONE — the tier collapsed onto #creator; birthChild is the one birth directive.)
1207
+ // (birthPlatform is GONE; birthChild is the one birth directive.)
1209
1208
  // THE RECORD IS THE OBLIGATION (architecture/githolon_host_contract.md "Birth is a capability" +
1210
1209
  // provisioning_as_obligations.md). Birth is an IMPURE capability: the parent ORDERS (the law-gated
1211
1210
  // birthChild mints a CloudWorkspace at status:requested carrying foundingAdminHash + the LAW-AUTHORED
1212
- // genesisChain — gated by .requires("creator")), the BIRTH CAPABILITY PROVIDER fulfils it (materialize:
1211
+ // genesisChain under the parent's creation posture), the BIRTH CAPABILITY PROVIDER fulfils it (materialize:
1213
1212
  // install the parent's law + REPLAY the genesisChain opaquely — the child's OWN kernel admitting each), and
1214
1213
  // authors provisionWorkspace (the receipt) the parent TRUSTS. No separate obligation aggregate — the minted
1215
1214
  // CloudWorkspace IS the order. The provider owns only the impure boot + replay; the LAW owns the recipe (P1)
@@ -1219,13 +1218,13 @@ export const rebirthConsentByWorkspace = query("rebirthConsentByWorkspace")
1219
1218
  // ─────────────────────────── birthChild — THE ONE BIRTH DIRECTIVE (the `kind`-collapse) ───────────────
1220
1219
  // "There are only workspaces, all equal until a domain is installed." birthChild is the SINGLE directive
1221
1220
  // that births any child — a plain leaf OR a platform; the difference is the GENESIS DATA the payload carries,
1222
- // never a node-class. THE TIER IS COLLAPSED (Jack, 2026-06-24): `#creator` is THE one birth authority — a
1223
- // creator may spawn a leaf AND a platform (the prior `#platformCreator` second tier is retired; platform
1224
- // birth under root is still invite-only because `creator` on root is admin-granted, just renamed/unified).
1221
+ // never a node-class. The same creation posture governs leaf and platform births: open/unset admits any
1222
+ // proven author, invite requires `#creator`, and closed requires `#admin`. The prior `#platformCreator`
1223
+ // second tier is retired.
1225
1224
  // • genesis ABSENT → a plain lineage record (no foundingAdminHash); boot-on-touch, never drained.
1226
1225
  // • genesis PRESENT → carries {foundingAdminHash, pool}: the birth capability provider drains it
1227
1226
  // (the order carries a foundingAdminHash) → materialize + seed genesis. A platform.
1228
- // Self-relative: the gate is `workspace:self#creator`, so the SAME directive on root births a platform and
1227
+ // Self-relative: the posture reads `workspace:self`, so the SAME directive on root births a platform and
1229
1228
  // on a platform births a child — no per-level host code (the recursion is the law). Supersedes
1230
1229
  // createWorkspace + birthPlatform (kept as compat aliases during migration). See
1231
1230
  // architecture/provisioning_as_obligations.md "The `kind`-collapse".
@@ -1311,8 +1310,7 @@ export const birthChild = directive("birthChild")
1311
1310
  return [];
1312
1311
  })
1313
1312
  .reads(workspacesByPrincipal, quotaByPrincipal, cloudPolicy) // arm the WorkspaceQuota captured-read gate (S2)
1314
- .births() // BIRTH IS A DECLARED KERNEL PRIMITIVE (G3): emits the `nomosBirthGate` era key; the kernel folds the carried recipe
1315
- .requires("creator"); // THE ONE birth authority — a #creator may birth a leaf OR a platform (tier collapsed)
1313
+ .births(); // BIRTH IS A DECLARED KERNEL PRIMITIVE (G3): the kernel folds the carried recipe; posture is invariant law
1316
1314
 
1317
1315
  /** workspacesByStatus — the birth provider drains `requested` CloudWorkspaces that CARRY A foundingAdminHash
1318
1316
  * (a genesis order, no ownerGrantHash) → materialize → provisionWorkspace. Plain requested records (no
@@ -1751,15 +1749,13 @@ export const capabilityBindingByKeyHash = query("capabilityBindingByKeyHash")
1751
1749
  export { relationTuplesByObject } from "./relations.js";
1752
1750
  export { RelationTuple };
1753
1751
 
1754
- // ── GOVERNANCE WRITE-AUTHZ IS NATIVE ZANZIBAR — the ONE gate, no bespoke predicates ───────────────────
1755
- // The bespoke `workspaceInvariant` authz gates (AuthorHoldsAdmin / CreationGate / PlatformCreationGate)
1756
- // are DELETED. Their job is now done by the full-native relation gate: each governance directive carries
1757
- // `.requires("<relation>")` (above), the compiler emits `nomosRelations` (the schema) + `nomosRelationGate`
1758
- // (directive→relation), and the ONE wasm gate runs `engine.check(schema, "workspace:self", relation,
1759
- // principal)` over the PRE-APPLY folded relation tuples — refusing any author who does not HOLD the
1760
- // relation, on every door (author / edge admission / verify_chain replay). The author is the CAPTURED
1761
- // principal (`key:<sha256(bearer)>` for admin/creator, `user:<uid>` for platformCreator), pinned to the
1762
- // sealed Attribution.actor on every peer — so "who may grant" is a LAW fact, never a host pre-check. The
1752
+ // ── GOVERNANCE WRITE-AUTHZ: NATIVE ROLES + DYNAMIC CREATION POSTURE ───────────────────────────────────
1753
+ // Static governance requirements use the full-native relation gate: a directive carrying
1754
+ // `.requires("<relation>")` is checked by the ONE wasm gate over PRE-APPLY tuples on every door (author /
1755
+ // edge admission / verify_chain replay). Workspace creation is the intentional exception because it is a
1756
+ // three-posture policy, not one static role: the invariant below admits a proven author under open/unset,
1757
+ // checks `#creator` under invite, and checks `#admin` under closed. The captured author principal is pinned
1758
+ // to the sealed Attribution.actor on every peer — so "who may grant or create" is LAW, never a host pre-check. The
1763
1759
  // pre-apply roster means `grantAdmin(@key:self)` cannot self-grant (the not-yet-written subject is absent
1764
1760
  // in the pre-apply state). SELF-RELATIVE: custody guarantees a workspace only folds its OWN tuples, so the
1765
1761
  // SAME law governs root and every born platform. The host's `_adminOk`/`_creationLawOk`/`_platformCheck`
@@ -1859,6 +1855,76 @@ export const reclaimRequiresRetired = workspaceInvariant("ReclaimRequiresRetired
1859
1855
  });
1860
1856
 
1861
1857
  // ── THE WORKSPACE-QUOTA GATE (S2 — THE QUOTA LAW, ENFORCED) ────────────────────────────────────────────
1858
+ // The native `.requires("creator")` replacement accidentally collapsed a THREE-POSTURE law into one
1859
+ // permanently invite-only door. Creation is not a static role check: open/unset admits any PROVEN author;
1860
+ // invite admits #creator; closed admits #admin. Keep that policy in an invariant over the committed prefix,
1861
+ // while the native relation evaluator remains the one meaning of "holds #creator/#admin" everywhere else.
1862
+ //
1863
+ // The same invariant binds quota/lineage identity to the proven author. Without it, an open caller could
1864
+ // charge births to a victim principal (or evade its own quota) by changing two payload strings. A historical
1865
+ // bare uid and its canonical `user:<uid>` subject are the same identity; a self-authenticating `key:<hash>`
1866
+ // must match byte-for-byte.
1867
+ const CREATOR_TUPLE_ID = "pre:" + relationTupleInstanceId(SELF_ADMIN_OBJECT, "creator");
1868
+ const ADMIN_TUPLE_ID = "pre:" + relationTupleInstanceId(SELF_ADMIN_OBJECT, "admin");
1869
+ const CREATION_POLICY_ID = "pre:cloud-policy:cloud";
1870
+
1871
+ function canonicalBirthIdentity(value: unknown): string {
1872
+ const s = String(value ?? "").trim();
1873
+ return s.startsWith("user:") ? s.slice("user:".length) : s;
1874
+ }
1875
+
1876
+ function activeRelationSubject(row: unknown, subject: string): boolean {
1877
+ let leaf = ((row as { subjects?: Record<string, unknown> } | undefined)?.subjects ?? {})[subject];
1878
+ if (typeof leaf === "string") {
1879
+ try { leaf = JSON.parse(leaf) as unknown; } catch { return false; }
1880
+ }
1881
+ return !!leaf && (leaf as { status?: string }).status === "active";
1882
+ }
1883
+
1884
+ function creationPostureVerdict(
1885
+ snapshots: Record<string, Record<string, unknown>>,
1886
+ ctx?: { intent: Record<string, unknown>; principal?: string },
1887
+ ): WorkspaceInvariantVerdict {
1888
+ const actor = String(ctx?.principal ?? "").trim();
1889
+ const claimed = canonicalBirthIdentity(ctx?.intent?.principal);
1890
+ const requestedBy = canonicalBirthIdentity(ctx?.intent?.requestedBy);
1891
+ const proven = canonicalBirthIdentity(actor);
1892
+ if (actor === "" || claimed === "" || requestedBy === "" || claimed !== proven || requestedBy !== proven) {
1893
+ return { reject: "creation.identity-mismatch" };
1894
+ }
1895
+ const posture = String((snapshots.policy as { creationPosture?: string } | undefined)?.creationPosture ?? "open");
1896
+ if (posture === "open" || posture === "") return { accept: true };
1897
+ if (posture === "invite") {
1898
+ return activeRelationSubject(snapshots.creators, actor)
1899
+ ? { accept: true }
1900
+ : { reject: "creation.not-invited" };
1901
+ }
1902
+ if (posture === "closed") {
1903
+ return activeRelationSubject(snapshots.admins, actor)
1904
+ ? { accept: true }
1905
+ : { reject: "creation.closed" };
1906
+ }
1907
+ return { reject: "creation.invalid-posture" };
1908
+ }
1909
+
1910
+ const creationPostureReads = () => [
1911
+ refAs("policy", "CloudPolicy", CREATION_POLICY_ID),
1912
+ refAs("creators", "RelationTuple", CREATOR_TUPLE_ID),
1913
+ refAs("admins", "RelationTuple", ADMIN_TUPLE_ID),
1914
+ ];
1915
+
1916
+ /** Compatibility create: open/unset ⇒ proven author; invite ⇒ creator; closed ⇒ admin. */
1917
+ export const creationPostureCreate = workspaceInvariant("CreationPostureCreate")
1918
+ .on("createWorkspace")
1919
+ .reads(creationPostureReads)
1920
+ .assert(creationPostureVerdict);
1921
+
1922
+ /** Unified child birth: exactly the same posture and identity law as compatibility create. */
1923
+ export const creationPostureBirth = workspaceInvariant("CreationPostureBirth")
1924
+ .on("birthChild")
1925
+ .reads(creationPostureReads)
1926
+ .assert(creationPostureVerdict);
1927
+
1862
1928
  // The CloudPolicy/QuotaGrant law-state used to be enforced by NOBODY — `createWorkspace`/`birthChild` were
1863
1929
  // gated only by `.requires("creator")`, so a single #creator could mint unbounded workspaces. NOW the
1864
1930
  // COUNT quota is REAL law at the gate: each birth directive CAPTURES (via the #58 captured-read lane,