@githolon/dsl 0.99.0 → 0.100.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.99.0",
3
+ "version": "0.100.1",
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/births.ts CHANGED
@@ -56,29 +56,8 @@ export function __drainBirths(): BirthRecipe[] {
56
56
  * chains carry no key, so they replay byte-green and the kernel does nothing). A plan may birth at most one
57
57
  * child per intent (one offer, one defined consequence — the unix model).
58
58
  *
59
- * TO BIRTH A LEAF CHILD RUNNING ITS OWN LAW (a home, an estate child) — copy `framework/home.ts`'s
60
- * `birthHome`. The recipe is just the two-layer law install + your own seed step; there is NO helper to
61
- * fabricate it and you do NOT need a new primitive (recursion is the law — a home births children exactly
62
- * as root births platforms). `buildGenesisChain` is the ONE builder for the PLATFORM shape (it adds the
63
- * `seedGenesis → grantAdmin → birthCloud` tail); a leaf just omits that tail:
64
- *
65
- * ```ts
66
- * export const birthEstateChild = directive("birthEstateChild")
67
- * .creates(EstateChild)
68
- * .payload(z.object({ child: z.string(), owner: z.string(),
69
- * frameworkHash: z.string(), lawHash: z.string(), bornAt: z.string() }))
70
- * .plan((p) => {
71
- * const actor = "user:" + p.owner;
72
- * birth({ workspace: p.child, genesisChain: [
73
- * { domain: "bootstrap", directiveId: "installDomain", actor, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash, domainPackageB64: "" },
74
- * { domain: "nomos", directiveId: "installDomain", actor, payload: { domainHash: p.lawHash }, domainHash: p.lawHash, domainPackageB64: "" },
75
- * { domain: "estate", directiveId: "seedEstateChild", actor, payload: { owner: p.owner, bornAt: p.bornAt } },
76
- * ] });
77
- * return [];
78
- * })
79
- * .births() // the kernel's run_birth_effect folds the recipe through the CHILD's own gate
80
- * .requires("creator"); // grant the parent's owner #creator (self-bound) — the birth authority
81
- * ```
59
+ * This is framework implementation plumbing. Application packages declare
60
+ * `birth(Aggregate, spec)` and never construct a recipe.
82
61
  */
83
62
  /**
84
63
  * A business thing that is important enough to have its own durable, sovereign life.
@@ -112,10 +91,10 @@ export interface BusinessBirthSpec<
112
91
  readonly namespace?: string;
113
92
  }
114
93
 
115
- /** @internal Framework lane: record an already-lowered child recipe during a directive plan. Tenant law
116
- * should declare `birth(Aggregate, spec)` and let Nomos own the recipe, identity and recovery machinery. */
94
+ /** @internal Record an already-lowered child recipe during a framework directive plan. Application law
95
+ * declares `birth(Aggregate, spec)` and leaves the recipe, identity, and recovery machinery to Nomos. */
117
96
  export function birth(recipe: BirthRecipe): void;
118
- /** Tenant lane: declare that one business aggregate is born as a durable workspace root. */
97
+ /** Declare that one business aggregate is given its own durable life. */
119
98
  export function birth<P extends Record<string, unknown>, F extends Record<string, Field>>(
120
99
  aggregate: AggregateHandle<string, F>,
121
100
  spec: BusinessBirthSpec<P, F>,
@@ -184,39 +163,19 @@ interface MintedRef {
184
163
 
185
164
  /**
186
165
  * @deprecated Tenant law should declare `birth(Aggregate, spec)`. This framework helper remains for the
187
- * built-in workspace laws that implement that declaration.
188
- *
189
- * `birth.keyed(...)` — THE way to birth a child workspace. A workspace exists to be USED, and using it means
190
- * FINDING IT AGAIN, so its name is ALWAYS derived from a `key` the caller owns (a business id, an owner uid)
191
- * via {@link workspaceNameForKey}. The client re-derives the SAME name with `deriveWorkspaceName(namespace,
192
- * key)` — so the born workspace is addressable from the key alone, with zero persistence. Same key ⇒ same
193
- * workspace (re-birth is idempotent). This is the only birth verb you need.
166
+ * built-in framework laws that implement that declaration.
194
167
  *
195
- * ```ts
196
- * export const birthEstate = directive("birthEstate")
197
- * .creates(EstateBirth)
198
- * .payload(z.object({ estateId: z.string(), owner: z.string(), frameworkHash: z.string(), lawHash: z.string(), bornAt: z.string() }))
199
- * .plan((p) => {
200
- * birth.keyed({ key: p.estateId, namespace: "estate", genesisChain: (ws) => [
201
- * { domain: "bootstrap", directiveId: "installDomain", actor: p.owner, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash, domainPackageB64: "" },
202
- * { domain: "nomos", directiveId: "installDomain", actor: p.owner, payload: { domainHash: p.lawHash }, domainHash: p.lawHash, domainPackageB64: "" },
203
- * { domain: "estate", directiveId: "seedEstate", actor: p.owner, payload: { estateId: p.estateId, workspaceName: ws, bornAt: p.bornAt } },
204
- * ] });
205
- * return [];
206
- * })
207
- * .births().requires("estateCreator");
208
- * // app (Dart / TS): final ws = client.deriveWorkspaceName('estate', estateId); // open it, any time, any device
209
- * ```
168
+ * Internal compatibility helper: derives a child address from a stable key. Application packages use
169
+ * `birth(Aggregate, spec)`; generated clients expose only `birth(businessFacts)`.
210
170
  */
211
171
  export const birthKeyed = ({
212
172
  key,
213
173
  namespace,
214
174
  genesisChain,
215
175
  }: {
216
- /** The id you already own that this workspace is FOR (an estateId, an owner uid). The client addresses the
217
- * born workspace by re-deriving its name from this same key — so pick a key the client will have in hand. */
176
+ /** A stable id from which the framework can re-derive the durable address. */
218
177
  key: string;
219
- /** The workspace-name namespace (e.g. `"estate"` → `estate-<hash>`). Groups a domain's children. */
178
+ /** Internal address namespace. */
220
179
  namespace: string;
221
180
  genesisChain: GenesisChainOrBuilder;
222
181
  }): string => {
@@ -258,7 +258,7 @@ export function expandBusinessBirthExports<M extends Record<string, unknown>>(
258
258
  if (declarations.length > 1) {
259
259
  throw new Error(
260
260
  `domain '${domain}' declares ${declarations.length} business births; one generated birth(payload) cannot be ` +
261
- "ambiguous — model one workspace root per domain",
261
+ "ambiguous — model one born aggregate root per domain",
262
262
  );
263
263
  }
264
264
  const lowered = lowerOne(declarations[0]!, domain);
@@ -348,8 +348,8 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
348
348
  }).businessBirth;
349
349
  if (businessBirth !== undefined) {
350
350
  methods.push(
351
- ` /// Give one ${businessBirth.aggregateId} its own durable life. Nomos owns identity proof, signer`,
352
- ` /// seating, provenance, persistence and recovery; the app supplies only business facts.`,
351
+ ` /// Give one ${businessBirth.aggregateId} its own durable life. Nomos owns identity, authorship,`,
352
+ ` /// provenance, persistence and recovery; the app supplies only business facts.`,
353
353
  ` Future<NomosRef> birth(${payloadClass} payload) async {`,
354
354
  ` final principal = actor ?? bridge.principal;`,
355
355
  ` if (principal == null || principal.trim().isEmpty) {`,
@@ -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
 
@@ -1720,6 +1719,9 @@ interface DiscoveredGenesisStep {
1720
1719
  * synthesized payload (the synthesizer only produces `sample-*` placeholders, which the child install
1721
1720
  * would refuse). */
1722
1721
  export interface ChildBirthLeg {
1722
+ /** Present for the one application-facing `birth(Aggregate, spec)` declaration. Proof renderers use the
1723
+ * business noun and keep the lowered framework ceremony out of normal output. */
1724
+ readonly businessAggregateId?: string;
1723
1725
  /** The domain the `.births()` directive lives in. */
1724
1726
  readonly parentDomain: string;
1725
1727
  /** The `.births()` directive id the parent drives to birth the child. */
@@ -2021,9 +2023,11 @@ export function discoverChildBirthLegOrReason(
2021
2023
  if (frameworkHashField !== undefined) delete birthPayload[frameworkHashField];
2022
2024
  if (lawHashField !== undefined) delete birthPayload[lawHashField];
2023
2025
 
2026
+ const businessAggregateId = (birthDir as unknown as { businessBirth?: { aggregateId?: string } }).businessBirth?.aggregateId;
2024
2027
  const leg: ChildBirthLeg = {
2025
2028
  parentDomain,
2026
2029
  birthDirectiveId: birthDir.id,
2030
+ ...(businessAggregateId !== undefined ? { businessAggregateId } : {}),
2027
2031
  birthPayload,
2028
2032
  birthAutoStamped: synth.autoStamped,
2029
2033
  ...(actorField !== undefined ? { birthActorField: actorField } : {}),
@@ -2143,7 +2147,7 @@ export interface TsProofOptions {
2143
2147
  * · `standalone-provable` — a `.creates` write proven offline (dispatch → declared query/count locally).
2144
2148
  * · `child-birth-provable` — a `.births()` directive proven END-TO-END into the child it births.
2145
2149
  * · `relation-bootstrap-provable` — a `.requires(R)`-gated write proven to SUCCEED after granting R (seed-then-prove).
2146
- * · `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. */
2147
2151
  export type ProofKind = "compile-valid" | "standalone-provable" | "child-birth-provable" | "relation-bootstrap-provable" | "live-cloud-provable";
2148
2152
 
2149
2153
  /** The machine-readable OFFLINE legs for ONE domain (the `@nomos-proof-legs`
@@ -2529,52 +2533,34 @@ export function proofLegsIndex(modules: readonly DomainModule[]): { default: str
2529
2533
  // ── shared `.mts` fragments (the v2 contract — G1: no host secret; deploy is OPEN + kernel-gated) ─────────
2530
2534
 
2531
2535
  /**
2532
- * THE V2 PREAMBLE (G1, gap 4): create a throwaway workspace (NO secret comes back — ownership is a ledger
2533
- * fact), then deploy YOUR compiled law via the OPEN lane (`POST /domains`, no Bearer; write-authority is
2534
- * kernel-judged at the offer gate). Identity rides `x-nomos-principal` (transitional bare-uid lane), the
2535
- * same header `cli/src/cloud.ts deploy()` + the 0.18 host-strip lane present. Declares `fail`/`ok`/`deploy`/
2536
- * `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.
2537
2539
  */
2538
2540
  function v2IdentityAndCleanupPreamble(opts: TsProofOptions, hashConst: string, n: () => number): string[] {
2541
+ void opts;
2542
+ void hashConst;
2539
2543
  return [
2540
- `// IDENTITY NOTE: this proof uses the bare x-nomos-principal lane so it is SELF-CONTAINED (a throwaway`,
2541
- `// workspace, no stored credentials). Real apps use \`githolon login --agent\` (a VERIFIED identity)`,
2542
- `// + \`githolon ws create/deploy\`. (G1: a create returns NO workspaceSecret — ownership is a ledger`,
2543
- `// 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.`,
2544
2546
  `const fail: (m: string) => never = (m) => { throw new Error(m); };`,
2545
2547
  `const ok = (m: string) => console.log("✓ " + m);`,
2546
2548
  `const PRINCIPAL = process.env.NOMOS_PRINCIPAL || "githolon-proof";`,
2549
+ `const AUTHOR_SECRET = process.env.NOMOS_AUTHOR_SECRET || "";`,
2547
2550
  ``,
2548
- `const deploy = JSON.parse(readFileSync(new URL(${JSON.stringify(`./${opts.packageName}.deploy.json`)}, import.meta.url), "utf8"));`,
2549
- ``,
2550
- `// CLEANUP CONTRACT: the throwaway workspace is RETIRED on every exit (green or jammed) — a proof never`,
2551
- `// leaves orphans. PROOF_KEEP=1 (githolon proof --live --keep) keeps it and prints the persist command.`,
2551
+ `let d: any;`,
2552
2552
  `let runError: unknown = null;`,
2553
- `let born_ws = false;`,
2554
2553
  `try {`,
2555
2554
  ``,
2556
- `// ${n()}. a throwaway workspace — born by the create (G1: NO secret is returned; ownership is a ledger fact)`,
2557
- `let r = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}\`, { method: "POST", headers: { "x-nomos-principal": PRINCIPAL } });`,
2558
- `let d = await r.json();`,
2559
- `if (!d.ok) fail(\`create workspace: \${JSON.stringify(d)}\`);`,
2560
- `born_ws = true;`,
2561
- `ok(\`workspace \${WS} created (no host secret — ownership is a ledger fact; PROOF_KEEP=1 keeps it)\`);`,
2562
- ``,
2563
- `// ${n()}. deploy YOUR compiled law via the OPEN lane (POST /domains, no Bearer — kernel-judged authority,`,
2564
- `// authenticated as PRINCIPAL via x-nomos-principal; a refusal answers as a TYPED kernel verdict 422)`,
2565
- `r = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/domains\`, { method: "POST", headers: { "content-type": "application/json", "x-nomos-principal": PRINCIPAL }, body: JSON.stringify(deploy) });`,
2566
- `d = await r.json();`,
2567
- `if (!d.ok || d.installation?.[0]?.data?.["status.phase"] !== "Active") fail(\`deploy: \${JSON.stringify(d)}\`);`,
2568
- `if (d.domainHash !== ${hashConst}) fail(\`deploy hash \${d.domainHash} != the generated client's \${${hashConst}}\`);`,
2569
- `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}\`);`,
2570
2558
  ];
2571
2559
  }
2572
2560
 
2573
2561
  /**
2574
- * THE V2 CATCH/FINALLY (G1, gap 4 · 0.34.1): retirement is a SIGNED governance offer — the open self-retire
2575
- * lane is DEAD post-warrant-flip, so the proof itself never retires. Under `githolon proof --live`
2576
- * (NOMOS_PROOF_MANAGED=1) the CLI created the workspace REGISTERED and retires it lawfully after exit;
2577
- * 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.
2578
2564
  */
2579
2565
  function v2CatchFinally(): string[] {
2580
2566
  return [
@@ -2582,19 +2568,6 @@ function v2CatchFinally(): string[] {
2582
2568
  ` runError = e;`,
2583
2569
  ` console.error("✗ " + ((e as Error)?.message ?? e));`,
2584
2570
  `} finally {`,
2585
- ` // THE CLEANUP CONTRACT: never a SILENTLY orphaned cloud workspace. Retirement is a SIGNED`,
2586
- ` // governance offer (retireOwnWorkspace at the parent) — the old open-lane POST /retire is dead`,
2587
- ` // post-warrant-flip. \`githolon proof --live\` orchestrates it (NOMOS_PROOF_MANAGED=1: it created`,
2588
- ` // the workspace REGISTERED and retires it after this process exits). Run standalone, this proof`,
2589
- ` // cannot lawfully retire (no signing key) — so it says so LOUDLY and names the exact command.`,
2590
- ` if (born_ws && process.env.PROOF_KEEP === "1") {`,
2591
- ` console.log(\`workspace \${WS} KEPT — retire it when done: githolon ws retire \${WS}\`);`,
2592
- ` } else if (born_ws && process.env.NOMOS_PROOF_MANAGED === "1") {`,
2593
- ` console.log(\` workspace \${WS} — retirement handled by githolon proof --live (signed retireOwnWorkspace)\`);`,
2594
- ` } else if (born_ws) {`,
2595
- ` console.error(\`⚠ THROWAWAY WORKSPACE NOT RETIRED: \${WS} (retirement is a signed governance offer this standalone proof cannot author)\`);`,
2596
- ` console.error(\`⚠ retire it manually: githolon ws retire \${WS} (run the managed lane next time: githolon proof --live)\`);`,
2597
- ` }`,
2598
2571
  ` process.exit(runError ? 1 : 0);`,
2599
2572
  `}`,
2600
2573
  ];
@@ -2667,6 +2640,39 @@ function liveAuthorityChainLines(setup: RelationBootstrapLeg, actorVar: string,
2667
2640
  return lines;
2668
2641
  }
2669
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
+
2670
2676
  function liveChildBirthLegLines(cb: ChildBirthLeg, opts: TsProofOptions, n: () => number): string[] {
2671
2677
  const hashConst = tsHashConstName(opts.packageName);
2672
2678
  // The birth payload literal: the synthesized values + the REAL hashes onto the discovered fields + the
@@ -2802,11 +2808,10 @@ function generateBirthOnlyProof(
2802
2808
  `// recompile, and this proof reshapes itself — no test rewriting. Run it:`,
2803
2809
  `//`,
2804
2810
  `// npx githolon proof (offline legs on a local holon)`,
2805
- `// 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)`,
2806
2812
  `//`,
2807
2813
  `// Regenerated on every compile; do not edit.`,
2808
2814
  `// @nomos-proof-legs ${legsMarker}`,
2809
- `import { readFileSync } from "node:fs";`,
2810
2815
  `import { connect, encodeIntentOfferBytes } from "@githolon/client";`,
2811
2816
  `import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
2812
2817
  `void connect; void encodeIntentOfferBytes; void ${factory};`,
@@ -2816,7 +2821,9 @@ function generateBirthOnlyProof(
2816
2821
  ``,
2817
2822
  ...v2IdentityAndCleanupPreamble(opts, hashConst, n),
2818
2823
  ``,
2819
- ...liveChildBirthLegLines(cb, opts, n),
2824
+ ...(cb.businessAggregateId !== undefined
2825
+ ? liveBusinessBirthLegLines(cb, factory, n)
2826
+ : liveChildBirthLegLines(cb, opts, n)),
2820
2827
  // THE RELATION-BOOTSTRAP LEG (incl. LAYER 4 born-context): a birth-only domain may ALSO carry a gated
2821
2828
  // non-birth directive whose authority is established by the birth genesis (co2 home's updateHomeProfile
2822
2829
  // via birthHome→seedHomeCreator). Emit it so the birth-only proof names it (the born-context proof itself
@@ -2866,7 +2873,7 @@ function generateBootstrapOnlyProof(
2866
2873
  `// law, recompile, and this proof reshapes itself — no test rewriting. Run it:`,
2867
2874
  `//`,
2868
2875
  `// npx githolon proof (offline legs on a local holon)`,
2869
- `// 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)`,
2870
2877
  `//`,
2871
2878
  `// Regenerated on every compile; do not edit.`,
2872
2879
  `// @nomos-proof-legs ${legsMarker}`,
@@ -2881,7 +2888,7 @@ function generateBootstrapOnlyProof(
2881
2888
  ...v2IdentityAndCleanupPreamble(opts, hashConst, n),
2882
2889
  ``,
2883
2890
  `// ${n()}. connect + bind the GENERATED typed client (the relation-bootstrap leg drives it with an actor)`,
2884
- `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 });`,
2885
2892
  `ok(\`web client connected — ${factory} bound (replica \${holon.replica.slice(0, 6)}…)\`);`,
2886
2893
  ``,
2887
2894
  // The SAME relation-bootstrap emitter the standalone proof uses — seed authority, then prove the GATED
@@ -3032,7 +3039,6 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
3032
3039
  `// The flow mirrors the scaffold's test/e2e.mts (docs/ in a scaffolded app explains`,
3033
3040
  `// every call — start at docs/01-mental-model.md). Regenerated on every compile; do not edit.`,
3034
3041
  `// @nomos-proof-legs ${legsMarker}`,
3035
- `import { readFileSync } from "node:fs";`,
3036
3042
  `import { connect, encodeIntentOfferBytes } from "@githolon/client";`,
3037
3043
  `import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
3038
3044
  ``,
@@ -3042,7 +3048,7 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
3042
3048
  ...v2IdentityAndCleanupPreamble(opts, hashConst, n),
3043
3049
  ``,
3044
3050
  `// ${n()}. connect + bind the GENERATED typed client`,
3045
- `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 });`,
3046
3052
  `const app = ${factory}(holon);`,
3047
3053
  `ok(\`web client connected — ${factory} bound (replica \${holon.replica.slice(0, 6)}…)\`);`,
3048
3054
  ``,
@@ -3208,8 +3214,8 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
3208
3214
  `// ${n()}. CONCURRENCY — two writers, two clientIds, BLIND to each other: each adds to`,
3209
3215
  `// the AddWins set \`${agg.id}.${setField}\` via \`${domain}/${cd.id}\` offline; admission`,
3210
3216
  `// merges both and the union keeps every add.`,
3211
- `const writerA = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-a" });`,
3212
- `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 });`,
3213
3219
  `await ${factory}(writerA).${cd.id}(${concPayload("a")});`,
3214
3220
  `await ${factory}(writerB).${cd.id}(${concPayload("b")});`,
3215
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
  );
@@ -2961,19 +2961,16 @@ async function main(): Promise<void> {
2961
2961
  console.log(` parses on every kernel (larger deploy body; may need the signed-upload deploy lane).`);
2962
2962
  }
2963
2963
  console.log(``);
2964
- console.log(`deploy (Nomos Cloud):`);
2965
- console.log(` githolon login --agent && githolon ws create <ws> && githolon deploy <ws> # no secret — deploy is OPEN, kernel-judged at the offer gate`);
2966
- console.log(` # raw lane: curl -X POST -H 'content-type: application/json' -H 'x-nomos-principal: <your-auth-uid>' \\`);
2967
- console.log(` # --data-binary @${rel(deployPath)} https://nomos.captainapp.co.uk/v2/workspaces/<ws>/domains`);
2964
+ console.log(`your business path:`);
2968
2965
  if (proofPath !== undefined) {
2969
- console.log(`prove it OFFLINE first: npx githolon proof # the generated proof's offline legs on a LOCAL holon (no cloud workspace)`);
2970
- console.log(` inner loop: npx githolon dev # watch -> recompile -> law live locally -> offline proof, on every save`);
2971
- console.log(` then the cloud loop: npx githolon proof --live # throwaway workspace, retired on exit (--keep keeps it)`);
2966
+ console.log(` npx githolon proof # generated birth -> verify -> durable restore -> business read-back`);
2967
+ console.log(` npx githolon dev # rerun that proof on every save`);
2968
+ console.log(` npx githolon proof --live # the same business proof against Nomos Cloud`);
2972
2969
  if (proofDomains.length > 1) {
2973
2970
  console.log(` this package proves ${proofDomains.length} domains (${proofDomains.join(", ")}) — default '${proofDomains[0]}'; pick another: npx githolon proof --domain <key>`);
2974
2971
  }
2975
2972
  } else {
2976
- console.log(`prove it: npm run e2e # offline write -> sync -> admission -> cloud query, live`);
2973
+ console.log(` add a business birth declaration to generate an end-to-end proof`);
2977
2974
  }
2978
2975
 
2979
2976
  if (analyzeFlag) runDartAnalyze(dartOut);
@@ -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,