create-githolon 0.17.0 → 0.19.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.17.0",
3
+ "version": "0.19.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",
@@ -22,8 +22,8 @@ the principal births will ride; sessions auto-refresh from
22
22
  **Path A — deploy your law** (the cloud births the workspace):
23
23
 
24
24
  ```bash
25
- npx githolon ws create <ws> # birth; the ONE-TIME workspaceSecret is saved to ~/.holon
26
- npx githolon deploy <ws> # POST build/<pkg>.deploy.json with the stored secret
25
+ npx githolon ws create <ws> # birth; NO secret is minted — ownership is a law fact in the chain
26
+ npx githolon deploy <ws> # POST build/<pkg>.deploy.json, authenticated as your principal
27
27
  ```
28
28
 
29
29
  **Path B — the upload birth** (you birth it, the cloud verifies):
@@ -38,18 +38,23 @@ proves itself either way ([05-superpowers.md](./05-superpowers.md) walks B).
38
38
 
39
39
  ## Ownership
40
40
 
41
- The workspace secret gates LAW (deploys, retirement, secret rotation). End-user
42
- apps carry NO secret: reads and session pushes are open, judged by edge
43
- admission; the session lane structurally refuses control-plane intents. So law
44
- only ever enters through the secret-gated deploy. Lose the secret? It was
45
- shown once`githolon secret rotate` with the current one, or ask the
46
- operator. Never embed it in an app.
41
+ There is no host secret. Ownership is a LAW FACT bound IN THE CHAIN —
42
+ `sha256(the push password)` written onto the law record at the upload birth (push
43
+ with no password born OPEN). Born-ness derives from custody (the ledger has a
44
+ `main`); `/boot` returns no secret. DEPLOY write-authority is judged by the
45
+ KERNEL at the offer gate you authenticate as a principal the law grants deploy
46
+ authority (`x-nomos-auth`, or transitionally `x-nomos-principal`), not a Bearer
47
+ secret. End-user apps carry no credential: reads and session pushes are open,
48
+ judged by edge admission; the session lane structurally refuses control-plane
49
+ intents. So law only ever enters through a deploy the kernel authorizes. There is
50
+ nothing host-side to rotate (`/rotate-secret` is retired, 410); to re-own, re-birth
51
+ via the upload lane with a new push password — or ask the operator.
47
52
 
48
53
  ## Dead-letter retry — the operator's unjam
49
54
 
50
55
  Domain-rejected intents land in the workspace DLQ with full provenance
51
- (`GET /v1/workspaces/<ws>/dead-letters`, owner-keyed). Ship the law fix via
52
- the deploy lane, then `POST /v1/workspaces/<ws>/dead-letters/retry?id=` (or
56
+ (`GET /v2/workspaces/<ws>/dead-letters`, owner-keyed). Ship the law fix via
57
+ the deploy lane, then `POST /v2/workspaces/<ws>/dead-letters/retry?id=` (or
53
58
  client-side `retryDeadLetter(id)`). The refused work — somebody's actual
54
59
  writes — lands on main. DELETE discards, explicitly.
55
60
 
@@ -31,8 +31,9 @@ export const Note = aggregate("Note",
31
31
  // (the bindings the read gate reads). Forgetting this is the #1 mistake.
32
32
  export const UserPermission = userPermissionAggregate;
33
33
 
34
- // The owner bestows the cap on a reader. Authored through the secret-gated
35
- // `/author` lane (or `githolon author`) only the owner can grant.
34
+ // The owner bestows the cap on a reader. Authored through the gateway offer lane
35
+ // (`POST /v2/workspaces/<ws>/grantReader`, authenticated as the owning principal)
36
+ // write-authority is kernel-judged at the offer gate; there is no host secret.
36
37
  export const grantReader = directive("grantReader").ensures(UserPermission)
37
38
  .payload(z.object({ principal: z.string(), grantedBy: z.string(), grantedAt: z.string() }))
38
39
  .plan((p) => grant({
@@ -71,9 +72,11 @@ How it behaves:
71
72
  the maintained projection — cost scales with the number of *identities*, not
72
73
  your data volume (revoking one reader over 10k private records stays fast).
73
74
 
74
- The owner grants through the secret-gated lane; your end-user app carries **no
75
- secret** its writes are judged by edge admission, and it simply can't author a
76
- grant. Authority enters only through the owner.
75
+ The owner grants through the gateway offer lane, authenticated as the owning
76
+ principal (write-authority is kernel-judged at the offer gate no host secret);
77
+ your end-user app carries **no** credential — its writes are judged by edge
78
+ admission, and it simply can't author a grant. Authority enters only through a
79
+ principal the law authorizes.
77
80
 
78
81
  ## Layer 2 — encrypted fields: operator-blind, end-to-end
79
82
 
@@ -0,0 +1,78 @@
1
+ # Birthing child workspaces (law-at-birth)
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 estate 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.
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, an estate item) 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
+ ## Copy `birthHome` — the canonical template
33
+
34
+ `birthHome` (in `@githolon/dsl`'s framework) is exactly this pattern for the **home** domain. To birth an
35
+ **estate child** (or any leaf running your law), copy it and swap the seed:
36
+
37
+ ```ts
38
+ import { directive, birth, z } from "@githolon/dsl";
39
+
40
+ export const birthEstateChild = directive("birthEstateChild")
41
+ .creates(EstateChild) // the child lineage record on YOUR chain
42
+ .payload(z.object({
43
+ child: z.string().min(1), // the child workspace name
44
+ owner: z.string().min(1), // the verified owner (a user:<uid> subject)
45
+ frameworkHash: z.string().min(1), // pinned at your governance compile
46
+ lawHash: z.string().min(1), // your ESTATE domain law package hash
47
+ bornAt: z.string().min(1), // ISO-8601, caller-stamped (determinism)
48
+ }))
49
+ .plan((p) => {
50
+ const actor = "user:" + p.owner;
51
+ birth({
52
+ workspace: p.child,
53
+ genesisChain: [
54
+ { domain: "bootstrap", directiveId: "installDomain", actor, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash, domainPackageB64: "" },
55
+ { domain: "nomos", directiveId: "installDomain", actor, payload: { domainHash: p.lawHash }, domainHash: p.lawHash, domainPackageB64: "" },
56
+ { domain: "estate", directiveId: "seedEstateChild", actor, payload: { owner: p.owner, bornAt: p.bornAt } },
57
+ ],
58
+ });
59
+ return [];
60
+ })
61
+ .births() // declares the birth — the kernel folds the recipe
62
+ .requires("creator"); // the birth authority (see below)
63
+ ```
64
+
65
+ ## Authority — who may birth
66
+
67
+ `.requires("creator")` gates the birth on a relation tuple. Grant the home owner `#creator` on their own
68
+ home (self-bound at birth, the same way the owner is seeded) so a verified owner can birth their estate
69
+ children — and only their own. (Relations: `grant` / `writeTuple`, see `07-security.md`. Use whatever
70
+ relation models your policy; `creator` is the convention `birthChild` uses.)
71
+
72
+ ## What NOT to do
73
+
74
+ - **Don't ask for a "child-birth primitive."** It already exists — `.births()` + `birth(recipe)`. A home is
75
+ a first-class workspace; it births children exactly as root births platforms. Recursion is the law.
76
+ - **Don't hand-roll a second recipe *builder*** across your domains — keep the recipe inline (it's small)
77
+ or share one tiny helper *in your own code*; the framework deliberately keeps **one** birth verb.
78
+ - **Don't put a clock/random in the plan** — `bornAt` rides the payload (the timestamp doctrine).