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.
|
|
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;
|
|
26
|
-
npx githolon deploy <ws> # POST build/<pkg>.deploy.json
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 /
|
|
52
|
-
the deploy lane, then `POST /
|
|
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
|
|
35
|
-
//
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
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).
|