create-githolon 0.98.3 → 0.99.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-githolon",
3
- "version": "0.98.3",
3
+ "version": "0.99.0",
4
4
  "type": "module",
5
5
  "description": "Scaffold a Nomos domain package: the starter domain + compile config + live e2e. `npm create githolon my-app`.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -63,10 +63,10 @@ The four-layer split (the blessed shape):
63
63
  final platform = await engine.open(cloud: cloud, workspace: platformWs);
64
64
  final platformClient = await PlatformClient.bind(platform);
65
65
 
66
- // A `.births()` directive takes NO parent — the parent IS the session workspace:
67
- final birth = await platformClient.birthProjectWorkspace(payload: order);
66
+ // One business birth; the parent is the bound session and Nomos owns the ceremony:
67
+ final projectRef = await platformClient.birth(project);
68
68
  // NomosRef is THE cross-workspace primitive — follow it, bind the next client:
69
- final project = await engine.follow(birth.bornRefs.single);
69
+ final project = await engine.follow(projectRef);
70
70
  final projectClient = await ProjectClient.bind(project);
71
71
  ```
72
72
 
@@ -1,213 +1,66 @@
1
- # Birthing child workspaces (law-at-birth)
1
+ # Birth: give a business thing its own life
2
2
 
3
- A directive in your law can **birth a child workspace** that is born already running its own domain law.
4
- A home births project children; a platform births workspaces; root births platforms — all the **same one
5
- way**. You do *not* ask Nomos for a new primitive and you do *not* hand a finished workspace to the cloud.
3
+ Use `birth(Aggregate, spec)` when a business thing should be independently durable, portable, and
4
+ recoverable. Describe the business facts once; Nomos owns the workspace, identity proof, provenance,
5
+ persistence, and recovery beneath it.
6
6
 
7
- ## How it works (birth is a kernel concern, defined by the ledger)
8
-
9
- Your directive declares `.births()` and, in its `plan`, authors the child's **genesis recipe** — the
10
- ordered intents the child will run — then calls `birth({ workspace, genesisChain })`. That's it. When the
11
- parent's offer is admitted, the kernel's birth offer-effect:
12
-
13
- 1. spawns the child's (empty) custody,
14
- 2. **installs the child's law in-chain** and folds the genesis recipe **through the child's own gate**,
15
- 3. so the child **self-validates from intent 0** — it is not trusted, it proves itself.
16
-
17
- The host installs nothing and decides nothing. The recipe is a **ledger fact**, not a runtime trick.
18
-
19
- ## The leaf recipe = two installs + your seed
20
-
21
- A leaf child (a home, a project) is born with exactly:
22
-
23
- ```
24
- [ bootstrap/installDomain(<frameworkHash>), // the bootstrap controller (bytes-by-hash, kernel-resolved)
25
- nomos/installDomain(<lawHash>), // YOUR child's domain law package
26
- <your own seed step(s)> ] // e.g. seed the owner / initial state
27
- ```
28
-
29
- `frameworkHash` + `lawHash` are the content hashes of the two packages, pinned at compile time (the kernel
30
- resolves the bytes from custody by hash — they are never shipped on the wire).
31
-
32
- ## Every birth is **keyed** — so you can find the workspace again
33
-
34
- A workspace exists to be **used**, and using it — sync it, open it, add a task to it — means **finding it
35
- again**, often from another device or after a restart. So a born workspace's name is **never** opaque or
36
- minted-and-forgotten: it is always **derived from a key you already own** (a business id like `projectId`, or
37
- an owner uid). One verb: **`birth.keyed`**.
38
-
39
- > **The one rule.** The name comes from a key you own → the client re-derives the same name from that same
40
- > key with `deriveWorkspaceName(namespace, key)` → the workspace is addressable **forever, with zero
41
- > persistence**. Same key ⇒ same workspace (re-birth is idempotent). You never hand-roll a naming scheme, and
42
- > you never store a minted name to find it later.
43
-
44
- ```ts
45
- import { directive, birth, create, z } from "@githolon/dsl";
46
-
47
- export const birthProject = directive("birthProject")
48
- .creates(ProjectBirth) // your lineage/audit row
49
- .payload(z.object({
50
- projectId: z.string().min(1), // THE KEY — an id you mint app-side; the workspace is FOR it
51
- owner: z.string().min(1),
52
- frameworkHash: z.string().min(1),
53
- lawHash: z.string().min(1),
54
- bornAt: z.string().min(1), // ISO-8601, caller-stamped (determinism)
55
- }))
56
- .plan((p) => {
57
- const actor = "user:" + p.owner;
58
- birth.keyed({
59
- key: p.projectId, // ← the workspace name is derived from this
60
- namespace: "project", // → project-<sha256(project ⏎ projectId)[:40]>
61
- genesisChain: (ws) => [ // the builder receives the resolved name
62
- { domain: "bootstrap", directiveId: "installDomain", actor, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash, domainPackageB64: "" },
63
- { domain: "nomos", directiveId: "installDomain", actor, payload: { domainHash: p.lawHash }, domainHash: p.lawHash, domainPackageB64: "" },
64
- { domain: "project", directiveId: "seedProject", actor, payload: { projectId: p.projectId, workspaceName: ws, bornAt: p.bornAt } },
65
- ],
66
- });
67
- return [];
68
- })
69
- .births()
70
- .requires("projectCreator");
71
- ```
72
-
73
- Then in the app you address that project from just its id — **no lookup table, no persisted name**:
74
-
75
- ```dart
76
- // Dart (nomos_client)
77
- final ws = deriveWorkspaceName('project', projectId);
78
- final session = await engine.open(ws); // open / sync / add tasks — any device, any time
79
- ```
80
7
  ```ts
81
- // TS (@githolon/client)
82
- const ws = await deriveWorkspaceName('project', projectId);
83
- const holon = await connect({ cloud, workspace: ws, clientId });
8
+ import { Lww, aggregate, birth, t, z } from "@githolon/dsl";
9
+
10
+ export const Project = aggregate("Project", {
11
+ reference: t.string().merge(Lww),
12
+ name: t.string().merge(Lww),
13
+ country: t.string().merge(Lww),
14
+ });
15
+
16
+ export const project = birth(Project, {
17
+ payload: z.object({
18
+ reference: z.string().min(1),
19
+ name: z.string().min(1),
20
+ country: z.string().length(2),
21
+ }),
22
+ key: (facts) => facts.reference,
23
+ plan: (project, facts) => project
24
+ .set("reference", facts.reference)
25
+ .set("name", facts.name)
26
+ .set("country", facts.country),
27
+ });
84
28
  ```
85
29
 
86
- `deriveWorkspaceName` computes the byte-identical name the law's `birth.keyed` produced (contract-tested
87
- across the engine, Dart, and TS). This is the helper that replaces every hand-rolled
88
- `projectWorkspaceForProjectId` — delete yours and use this.
89
-
90
- ### One-per-user things (a home): `birth.singleton`
91
-
92
- A "singleton" is just a keyed birth whose key is a natural single key — one home per user, so `key = owner`.
93
- Sugar over `birth.keyed`:
30
+ The generated client has one birth method:
94
31
 
95
32
  ```ts
96
- birth.singleton({ keyedBy: p.owner, namespace: "home", genesisChain: (home) => [ /* installs + seed */ ] });
97
- // app: deriveWorkspaceName('home', owner)
98
- ```
99
-
100
- > ⚠️ **`birth.instance` is deprecated — don't use it.** It mints a name you throw away, so the born
101
- > workspace can only be reached via the birth result. There is no workspace you make and never want to find
102
- > again. Always `birth.keyed` (or `birth.singleton`).
103
-
104
- (Raw `birth({ workspace, genesisChain })` stays the low-level escape hatch for a name that is genuinely
105
- externally-determined — but if you're deriving it from your own data, use `birth.keyed`.)
106
-
107
- ## From the app (Dart) — parentless, session-bound
108
-
109
- The generated Dart client emits a typed method per `.births()` directive, bound to the SESSION whose
110
- workspace IS the parent — no loose parent string, and the born child comes back as a followable ref:
111
-
112
- ```dart
113
- final parentClient = await MyPlatformClient.bind(parentSession);
114
- final birth = await parentClient.birthProjectChild(payload: order); // parent = the session workspace
115
- final child = await engine.follow(birth.bornRefs.single); // mount the born child
116
- final childClient = await ProjectClient.bind(child); // typed client on the child
33
+ const born = await app.birth({
34
+ reference: "PROJECT-001",
35
+ name: "Foundry retrofit",
36
+ country: "GB",
37
+ });
117
38
  ```
118
39
 
119
- (The old `parent:` parameter survives one minor version as a deprecated legacy-gateway shim; its verdict
120
- rides `outcome.gatewayVerdict`.)
121
-
122
- ## Authority — who may birth
123
-
124
- `.requires("creator")` gates the birth on a relation tuple. Grant the home owner `#creator` on their own
125
- home (self-bound at birth, the same way the owner is seeded) so a verified owner can birth their project
126
- children — and only their own. (Relations: `grant` / `writeTuple`, see `07-security.md`. Use whatever
127
- relation models your policy; `creator` is the convention `birthChild` uses.)
40
+ `born` is the typed reference to the business root. The call returns only after the birth is committed
41
+ locally and persistence has been requested from the bound Nomos session.
128
42
 
129
- ## What NOT to do
43
+ That is the tenant contract. Do not create a birth directive, construct a child address, assemble setup
44
+ steps, or expose framework hashes in application code. Those are framework implementation details.
130
45
 
131
- - **Don't hand-roll the child's name** (`project-<sha256(owner)>`, or your own `projectWorkspaceForProjectId`).
132
- `birth.keyed` owns the naming and `deriveWorkspaceName` gives the client the identical name — hand-rolling
133
- either half is how the two drift and a workspace becomes unfindable.
134
- - **Don't use `birth.instance`.** It's deprecated — it mints a name you can't re-derive, so the workspace is
135
- addressable only via the birth result. Every workspace needs to be found again; birth it keyed.
136
- - **Don't ask for a "child-birth primitive."** It already exists — `.births()` + `birth.keyed`. A home is a
137
- first-class workspace; it births children exactly as root births platforms.
138
- - **Don't hand-roll a second recipe *builder*** across your domains — keep the recipe inline (it's small)
139
- or share one tiny helper *in your own code*; the framework deliberately keeps **one** birth primitive.
140
- - **Don't put a clock/random in the plan** — `bornAt` rides the payload (the timestamp doctrine).
46
+ ## One root per domain
141
47
 
142
- ## Prove it — the 30-second loop
48
+ A domain declares at most one business birth. This keeps the generated method unambiguous and makes the
49
+ born aggregate the stable root of its workspace. Model other records beneath that root using ordinary
50
+ aggregates and directives.
143
51
 
144
- Every compile validates your genesis recipe statically (child domain composed in the package? seed
145
- directive exists? recipe payload satisfies its schema? every plan returns an **array** of ops) and prints
146
- any problem as a `⚠ births` warning **containing the fix**. Then:
52
+ ## Prove it locally
147
53
 
148
54
  ```bash
149
- npx githolon compile # ⚠ births warnings = fix these first (the message says how)
150
- npx githolon proof # OFFLINE: drives your .births() directive on the real kernel, folds the
151
- # child through its OWN gate, reads the seed back, verify_chain green
55
+ npx githolon compile
56
+ npx githolon proof
152
57
  ```
153
58
 
154
- A red proof names the exact genesis step the child's gate refused. The most common first-timer halt: a
155
- plan that returns a fluent builder — builders record themselves, so end the plan with `return [];`.
156
- A complete working two-domain births package to copy: `bench/scale/fixtures/home-delegated-estate` (nomos2
157
- repo) — it births child workspaces with `birth.keyed` (addressable by key) and is exercised by
158
- `bench/scale/home_delegated_estate.smoke.mjs`.
159
-
160
- ## Posture at birth
161
-
162
- `birthChild` / `createWorkspace` take an optional `posture: "managed" | "sovereign"` (default **managed**)
163
- — the child's declared governance posture, mirrored onto its lineage record. A **Managed** child can be
164
- recovered by the operator on request (a rebirth heal = "ask us"); a **Sovereign** child's rebirth requires
165
- the child's own consent fact first (a heal = consent first). See
166
- [11-governance-postures.md](./11-governance-postures.md) for the two postures, the consent ceremony, and
167
- the exit rights.
168
-
169
- ## Offline workspace creation via delegated authority (`delegatedRole` / `.delegatedFrom(parent)`)
170
-
171
- Sometimes a CHILD must birth its own children **offline**, under authority a PARENT granted — with no
172
- contact with the parent at authoring time. Example: a platform **P** lets a user **U** create projects; U
173
- holds a **home** holon **H** on-device; U births a project **E** as a child of H, offline, gated by H's
174
- law. The parent's grant cannot be an author-time attested read (that needs connectivity). Instead the
175
- grant is **delivered into H as a signed cert and folded as a local authority fact** — then read offline.
176
-
177
- Author the delivery directive with `.delegatedRole(relation).delegatedFrom(parent)`:
178
-
179
- ```ts
180
- export const acceptDelegatedRole = directive("acceptDelegatedRole")
181
- .creates(ProjectCreatorGrant)
182
- .payload(z.object({ subject: roleSubject, delegationCert: z.string().min(1), grantedBy: roleSubject, grantedAt: z.string() }))
183
- .plan((p) => {
184
- create(ProjectCreatorGrant).set("subject", p.subject).set("grantedAt", p.grantedAt);
185
- return writeTuple({ object: "workspace:self", relation: "projectCreator", subject: p.subject, grantedBy: p.grantedBy, grantedAt: p.grantedAt });
186
- })
187
- .grants("projectCreator")
188
- .delegatedRole("projectCreator", { subjectField: "subject", certField: "delegationCert" })
189
- .delegatedFrom({ parentKeyObject: "birth-attestation:self", parentKeyField: "parentKey" }); // H's parent P's key
190
-
191
- // the birth itself is gated on the DELIVERED local tuple — read OFFLINE, no parent contact:
192
- export const birthProjectWorkspace = directive("birthProjectWorkspace")
193
- .creates(ProjectBirthOrder).payload(/* … */).plan(/* birth(...) */)
194
- .births()
195
- .requires("projectCreator"); // ← reads the delegated tuple folded by acceptDelegatedRole
196
- ```
59
+ The offline proof drives the real kernel. It checks that the child installs its law in-chain, authors the
60
+ business root, verifies its full history, packs its custody, discards the resident copy, reopens from that
61
+ custody, verifies again, and can still read the root.
197
62
 
198
- How it works:
199
- - **The one gate re-verifies P's grant on every lane** (author / edge admission / verify_chain): the
200
- carried `DelegationCert` must be signed by a grantor whose key chains to the pinned `K_root`, bind the
201
- same `(relation, subject)` the tuple writes, and — `.delegatedFrom(parent)` — whose grantor key IS this
202
- workspace's parent key. A forged / mis-bound / wrong-parent grant fails closed. This is the
203
- `nomosDelegationGate` era key — a law that declares no `.delegatedRole()` is byte-identical.
204
- - **The birth is authored OFFLINE**, gated only by H's LOCAL folded tuple; `verify_chain` re-verifies the
205
- delegation to `K_root` with no cross-workspace read.
206
- - **Revocation is forward-only + evidence-preserving.** P delivers a revoke (`removeTuple`, same gate);
207
- the folded tuple flips revoked (`RemoveWins`), so U's NEXT birth refuses — but a project born WHILE the
208
- grant was live re-verifies green forever (verify_chain re-folds the chain prefix at each intent's
209
- position). Both the grant and the revoke are permanent ledger facts.
63
+ ## Framework compatibility
210
64
 
211
- The platform produces the cert with the `delegation_cert_sign` holon op (grantor secret injected per call,
212
- never persisted), carrying its lineage to `K_root`; the bailiff delivers it host-free. Worked end-to-end:
213
- `bench/scale/home_delegated_estate.smoke.mjs`; doctrine: `architecture/delegated_authority_births.md`.
65
+ The lower-level recipe APIs still exist for Nomos' built-in workspace laws and for older packages. They
66
+ are not the tenant authoring surface. New business domains use `birth(Aggregate, spec)`.