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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-githolon",
3
- "version": "0.81.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
- ## FIRST decide the child's cardinality: **instance** or **singleton**
32
+ ## Every birth is **keyed** — so you can find the workspace again
33
33
 
34
- A born child's workspace name **is its identity**, so before you write the birth you must answer one
35
- question: *can a parent have MANY of these children, or exactly ONE per key?* The DSL makes you state the
36
- answer at the call site — the same way a field states its merge driver — so you can't get it wrong by
37
- accident. Do **not** hand-roll the name.
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
- | You want… | Use | Naming | Re-birth |
40
- |---|---|---|---|
41
- | **Many children per parent** (a user has many estates, a project has many docs) | `birth.instance({ from, … })` | Nomos **mints** the child id from a `create()`d record — unique, captured, unforgeable | each call is a **new** child |
42
- | **Exactly one child per key** (one home per user, one settings-workspace per team) | `birth.singleton({ keyedBy, … })` | **derived** deterministically from the key | idempotent — same key ⇒ **same** child |
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 birthEstateChild = directive("birthEstateChild")
60
- .creates(EstateOrder) // the birth's lineage record on YOUR chain
47
+ export const birthEstate = directive("birthEstate")
48
+ .creates(EstateBirth) // your lineage/audit row
61
49
  .payload(z.object({
62
- owner: z.string().min(1), // the verified owner (a user:<uid> subject)
63
- frameworkHash: z.string().min(1), // pinned at your governance compile
64
- lawHash: z.string().min(1), // your ESTATE domain law package hash
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
- const order = create(EstateOrder).set("owner", p.owner).set("bornAt", p.bornAt);
70
- const child = birth.instance({
71
- from: order, // the child's id IS this record's minted id (no name to invent)
72
- prefix: "estate-", // optional, human-legible
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: "seedEstateChild", actor, payload: { owner: p.owner, estateName: estate, bornAt: p.bornAt } },
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() // declares the birth — the kernel folds the recipe
83
- .requires("creator"); // the birth authority (see below)
69
+ .births()
70
+ .requires("estateCreator");
84
71
  ```
85
72
 
86
- ### `birth.singleton` — exactly one child per key (e.g. a home)
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
- The framework's own `birthHome` is a singleton: one home per user, `home-<hash(owner)>`, idempotent. That is
89
- the *correct* use of a derived name — and `birth.singleton` derives it for you:
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
- const home = birth.singleton({
93
- keyedBy: p.owner, // one home per owner; re-birth is a no-op (same name)
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
- `keyedBy` may be a compound key (`["user:ada", "team-7"]`, order-sensitive) for one-child-per-pair.
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 })` remains the low-level escape hatch for a name that is genuinely
102
- externally-determined — but if you're deriving it from your own data, use one of the two verbs above.)
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 from a field** (`estate-<sha256(owner)>`). That is the cardinality
129
- trap — it caps the parent at one child per key. Use `birth.instance` (many) or `birth.singleton` (one);
130
- they own the naming so you can't collapse it by accident.
131
- - **Don't ask for a "child-birth primitive."** It already exists — `.births()` + `birth.instance` /
132
- `birth.singleton`. A home is a first-class workspace; it births children exactly as root births platforms.
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.instance` (many per owner) and is exercised by
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