create-githolon 0.81.0 → 0.83.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 +1 -1
- package/template/docs/08-births.md +57 -52
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-githolon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.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",
|
|
@@ -29,77 +29,80 @@ A leaf child (a home, an estate item) is born with exactly:
|
|
|
29
29
|
`frameworkHash` + `lawHash` are the content hashes of the two packages, pinned at compile time (the kernel
|
|
30
30
|
resolves the bytes from custody by hash — they are never shipped on the wire).
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## Every birth is **keyed** — so you can find the workspace again
|
|
33
33
|
|
|
34
|
-
A
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
A workspace exists to be **used**, and using it — sync it, open it, place an asset in 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 `estateId`, or
|
|
37
|
+
an owner uid). One verb: **`birth.keyed`**.
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
> ⚠️ **The trap:** births are deterministic (no `Math.random`/`Date.now` in a plan), so the tempting move
|
|
45
|
-
> is to derive the name from a stable field — `estate-<sha256(owner)>`. That silently caps the owner at
|
|
46
|
-
> **one** estate forever. If you find yourself hashing a field to name a child, you almost always want
|
|
47
|
-
> `birth.instance` (many) — reach for a derived name **only** when one-per-key is the actual intent, and
|
|
48
|
-
> then let `birth.singleton` derive it for you.
|
|
49
|
-
|
|
50
|
-
### `birth.instance` — many children per parent (the common case)
|
|
51
|
-
|
|
52
|
-
The child's id is **minted**, exactly like `create(Agg)` mints an aggregate id: you `create()` a record for
|
|
53
|
-
the birth (the audit/lineage row you'd want anyway) and its captured id names the child. It's unique by
|
|
54
|
-
construction and reproduces byte-identically at `verify_chain` (the minted id rides the committed intent).
|
|
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.
|
|
55
43
|
|
|
56
44
|
```ts
|
|
57
45
|
import { directive, birth, create, z } from "@githolon/dsl";
|
|
58
46
|
|
|
59
|
-
export const
|
|
60
|
-
.creates(
|
|
47
|
+
export const birthEstate = directive("birthEstate")
|
|
48
|
+
.creates(EstateBirth) // your lineage/audit row
|
|
61
49
|
.payload(z.object({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
estateId: 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),
|
|
65
54
|
bornAt: z.string().min(1), // ISO-8601, caller-stamped (determinism)
|
|
66
55
|
}))
|
|
67
56
|
.plan((p) => {
|
|
68
57
|
const actor = "user:" + p.owner;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
genesisChain: (estate) => [ // the builder receives the resolved child name
|
|
58
|
+
birth.keyed({
|
|
59
|
+
key: p.estateId, // ← the workspace name is derived from this
|
|
60
|
+
namespace: "estate", // → estate-<sha256(estate ⏎ estateId)[:40]>
|
|
61
|
+
genesisChain: (ws) => [ // the builder receives the resolved name
|
|
74
62
|
{ domain: "bootstrap", directiveId: "installDomain", actor, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash, domainPackageB64: "" },
|
|
75
63
|
{ domain: "nomos", directiveId: "installDomain", actor, payload: { domainHash: p.lawHash }, domainHash: p.lawHash, domainPackageB64: "" },
|
|
76
|
-
{ domain: "estate", directiveId: "
|
|
64
|
+
{ domain: "estate", directiveId: "seedEstate", actor, payload: { estateId: p.estateId, workspaceName: ws, bornAt: p.bornAt } },
|
|
77
65
|
],
|
|
78
66
|
});
|
|
79
|
-
order.set("workspaceName", child); // record the minted name on the lineage row
|
|
80
67
|
return [];
|
|
81
68
|
})
|
|
82
|
-
.births()
|
|
83
|
-
.requires("
|
|
69
|
+
.births()
|
|
70
|
+
.requires("estateCreator");
|
|
84
71
|
```
|
|
85
72
|
|
|
86
|
-
|
|
73
|
+
Then in the app you address that estate from just its id — **no lookup table, no persisted name**:
|
|
74
|
+
|
|
75
|
+
```dart
|
|
76
|
+
// Dart (nomos_client)
|
|
77
|
+
final ws = deriveWorkspaceName('estate', estateId);
|
|
78
|
+
final session = await engine.open(ws); // open / sync / place assets — any device, any time
|
|
79
|
+
```
|
|
80
|
+
```ts
|
|
81
|
+
// TS (@githolon/client)
|
|
82
|
+
const ws = await deriveWorkspaceName('estate', estateId);
|
|
83
|
+
const holon = await connect({ cloud, workspace: ws, clientId });
|
|
84
|
+
```
|
|
85
|
+
|
|
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
|
+
`estateWorkspaceForEstateId` — delete yours and use this.
|
|
89
|
+
|
|
90
|
+
### One-per-user things (a home): `birth.singleton`
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
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`:
|
|
90
94
|
|
|
91
95
|
```ts
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
namespace: "home", // → home-<hash>
|
|
95
|
-
genesisChain: (home) => [ /* installs + seed the owner, using `home` as the name */ ],
|
|
96
|
-
});
|
|
96
|
+
birth.singleton({ keyedBy: p.owner, namespace: "home", genesisChain: (home) => [ /* installs + seed */ ] });
|
|
97
|
+
// app: deriveWorkspaceName('home', owner)
|
|
97
98
|
```
|
|
98
99
|
|
|
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`).
|
|
100
103
|
|
|
101
|
-
(Raw `birth({ workspace, genesisChain })`
|
|
102
|
-
externally-determined — but if you're deriving it from your own data, use
|
|
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`.)
|
|
103
106
|
|
|
104
107
|
## From the app (Dart) — parentless, session-bound
|
|
105
108
|
|
|
@@ -125,11 +128,13 @@ relation models your policy; `creator` is the convention `birthChild` uses.)
|
|
|
125
128
|
|
|
126
129
|
## What NOT to do
|
|
127
130
|
|
|
128
|
-
- **Don't hand-roll the child's name
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
- **Don't
|
|
132
|
-
|
|
131
|
+
- **Don't hand-roll the child's name** (`estate-<sha256(owner)>`, or your own `estateWorkspaceForEstateId`).
|
|
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.
|
|
133
138
|
- **Don't hand-roll a second recipe *builder*** across your domains — keep the recipe inline (it's small)
|
|
134
139
|
or share one tiny helper *in your own code*; the framework deliberately keeps **one** birth primitive.
|
|
135
140
|
- **Don't put a clock/random in the plan** — `bornAt` rides the payload (the timestamp doctrine).
|
|
@@ -149,7 +154,7 @@ npx githolon proof # OFFLINE: drives your .births() directive on the real
|
|
|
149
154
|
A red proof names the exact genesis step the child's gate refused. The most common first-timer halt: a
|
|
150
155
|
plan that returns a fluent builder — builders record themselves, so end the plan with `return [];`.
|
|
151
156
|
A complete working two-domain births package to copy: `bench/scale/fixtures/home-delegated-estate` (nomos2
|
|
152
|
-
repo) — it births estates with `birth.
|
|
157
|
+
repo) — it births estates with `birth.keyed` (addressable by estate key) and is exercised by
|
|
153
158
|
`bench/scale/home_delegated_estate.smoke.mjs`.
|
|
154
159
|
|
|
155
160
|
## Posture at birth
|