create-githolon 0.99.0 → 0.100.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/index.mjs +7 -11
- package/package.json +1 -1
- package/template/CLAUDE.md +9 -32
- package/template/README.md +51 -132
- package/template/domains/todo.ts +18 -70
- package/template/nomos.package.mjs +1 -7
- package/template/package.json +1 -2
- package/template/docs/01-mental-model.md +0 -57
- package/template/docs/02-authoring.md +0 -437
- package/template/docs/03-client.md +0 -166
- package/template/docs/04-cloud.md +0 -78
- package/template/docs/05-superpowers.md +0 -69
- package/template/docs/06-law-evolution.md +0 -166
- package/template/docs/07-security.md +0 -195
- package/template/docs/08-births.md +0 -66
- package/template/docs/09-attested-reads.md +0 -87
- package/template/docs/10-flutter-layering.md +0 -79
- package/template/docs/11-governance-postures.md +0 -89
- package/template/test/e2e.mts +0 -114
package/index.mjs
CHANGED
|
@@ -17,7 +17,7 @@ var NOMOS_VERSION = (() => {
|
|
|
17
17
|
var args = process.argv.slice(2);
|
|
18
18
|
if (args.includes("-h") || args.includes("--help")) {
|
|
19
19
|
process.stdout.write(
|
|
20
|
-
"create-githolon \u2014 scaffold a Nomos
|
|
20
|
+
"create-githolon \u2014 scaffold a Nomos business-law package\n\nUsage:\n npm create githolon <dir> (or: npx create-githolon <dir>)\n\nScaffolds one aggregate, one business birth, the compile config, and a\none-page explanation into <dir> (default: my-holon-app). The generated\nproof exercises the real local engine.\n\nFlags:\n --no-git skip git init\n"
|
|
21
21
|
);
|
|
22
22
|
process.exit(0);
|
|
23
23
|
}
|
|
@@ -41,7 +41,7 @@ if (existsSync(path.join(targetDir, "vscode"))) {
|
|
|
41
41
|
}
|
|
42
42
|
var appHash = `${appName.replace(/[-\s]+/g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase()}_DOMAIN_HASH`;
|
|
43
43
|
var subst = (s) => s.replaceAll("__APP_NAME__", appName).replaceAll("__APP_HASH__", appHash).replaceAll("__NOMOS_VERSION__", NOMOS_VERSION);
|
|
44
|
-
for (const f of ["package.json", "README.md", "nomos.package.mjs", "CLAUDE.md", "domains/todo.ts"
|
|
44
|
+
for (const f of ["package.json", "README.md", "nomos.package.mjs", "CLAUDE.md", "domains/todo.ts"]) {
|
|
45
45
|
const p = path.join(targetDir, f);
|
|
46
46
|
if (existsSync(p)) writeFileSync(p, subst(readFileSync(p, "utf8")), "utf8");
|
|
47
47
|
}
|
|
@@ -59,19 +59,15 @@ process.stdout.write(
|
|
|
59
59
|
`
|
|
60
60
|
Scaffolded ${appName} into ${rel}/${gitInited ? " (git repo)" : ""}
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Nomos in one page: ${path.join(rel, "README.md")}
|
|
63
63
|
|
|
64
64
|
Next:
|
|
65
65
|
cd ${rel}
|
|
66
66
|
npm install
|
|
67
|
-
npx githolon compile #
|
|
68
|
-
npx githolon proof #
|
|
69
|
-
npx githolon login --agent # a verified identity, no browser
|
|
67
|
+
npx githolon compile # law + typed client + generated proof
|
|
68
|
+
npx githolon proof # births your model in the real local engine
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
you control births homes/children \u2014 birthHome/birthChild). There is no host-side create \u2014 the
|
|
74
|
-
flat 'ws create' lane is retired (orphanless holarchy). See docs/04-cloud.md for the birth-on-
|
|
75
|
-
parent flow; self-serve keyless birth-under-root is in flight.
|
|
70
|
+
The starter exposes one creation idea: app.birth(businessFacts).
|
|
71
|
+
Identity, authorship, persistence, synchronisation, provenance, and recovery stay underneath it.
|
|
76
72
|
`
|
|
77
73
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-githolon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.100.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",
|
package/template/CLAUDE.md
CHANGED
|
@@ -1,36 +1,13 @@
|
|
|
1
|
-
# __APP_NAME__
|
|
1
|
+
# __APP_NAME__
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`domains/`, compiled into ONE deterministic GitHolon artifact, deployed to a workspace.
|
|
5
|
-
Read `docs/01-mental-model.md` first — the docs travel with this repo, offline.
|
|
6
|
-
|
|
7
|
-
## For AI agents
|
|
8
|
-
|
|
9
|
-
This project's law is guarded by an author-time gate (static twins). Use it:
|
|
10
|
-
|
|
11
|
-
- **Lint the law** — before compiling or deploying, run the gate over `nomos.package.mjs`:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx githolon check # human report; exits non-zero on any REFUSE
|
|
15
|
-
npx githolon check --json # the findings as a machine-readable array (file:line)
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
- **Preview a law evolution** — before deploying a change to already-deployed law, diff
|
|
19
|
-
against the prior deployed law and paste any generated `dispositions` block:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npx githolon check --against build/<name>.deploy.json
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
- **Editor / agent diagnostics** — `npx githolon lsp` is a standard stdio Language Server
|
|
26
|
-
(red squiggles as you write law). For Claude Code, the `nomos-law` plugin ships it as a
|
|
27
|
-
plugin LSP; for Codex or other MCP clients, `githolon mcp` exposes the same gate as the
|
|
28
|
-
`nomos_check` and `nomos_evolve_preview` tools (`codex mcp add nomos -- githolon mcp`).
|
|
29
|
-
|
|
30
|
-
## The loop
|
|
3
|
+
This is a Nomos business-law package. Keep application code at the level of aggregates, `birth`, directives, and reads.
|
|
31
4
|
|
|
32
5
|
```bash
|
|
33
|
-
npx githolon
|
|
34
|
-
npx githolon
|
|
35
|
-
npx githolon
|
|
6
|
+
npx githolon check
|
|
7
|
+
npx githolon compile
|
|
8
|
+
npx githolon proof
|
|
36
9
|
```
|
|
10
|
+
|
|
11
|
+
`check` reports statically provable refusals. `compile` emits the law and typed client. `proof` exercises the generated business path in the real local engine.
|
|
12
|
+
|
|
13
|
+
Do not introduce application-facing identity, signing, storage, deployment, or recovery ceremonies. They belong beneath the generated `birth(payload)` surface.
|
package/template/README.md
CHANGED
|
@@ -1,144 +1,63 @@
|
|
|
1
|
-
# __APP_NAME__
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
Nomos lets you describe a business and leave the compute science to the framework.
|
|
4
|
+
|
|
5
|
+
You write three things:
|
|
6
|
+
|
|
7
|
+
- aggregates: the business things that exist;
|
|
8
|
+
- birth: the facts required to give one of those things a durable life;
|
|
9
|
+
- directives and reads: what may happen to it and what the application may ask.
|
|
10
|
+
|
|
11
|
+
The starter in [`domains/todo.ts`](domains/todo.ts) gives a todo list its own durable life:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
export const TodoList = aggregate("TodoList", {
|
|
15
|
+
reference: t.string().merge(Lww),
|
|
16
|
+
title: t.string().merge(Lww),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const todoList = birth(TodoList, {
|
|
20
|
+
payload: z.object({
|
|
21
|
+
reference: z.string().min(1),
|
|
22
|
+
title: z.string().min(1),
|
|
23
|
+
}),
|
|
24
|
+
key: (facts) => facts.reference,
|
|
25
|
+
plan: (list, facts) => list
|
|
26
|
+
.set("reference", facts.reference)
|
|
27
|
+
.set("title", facts.title),
|
|
28
|
+
});
|
|
29
|
+
```
|
|
6
30
|
|
|
7
|
-
|
|
8
|
-
docs travel with you, offline (the full index is [below](#docs--in-the-box)).
|
|
31
|
+
The generated client exposes the same business idea:
|
|
9
32
|
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
33
|
+
```ts
|
|
34
|
+
const list = await app.birth({
|
|
35
|
+
reference: "PERSONAL",
|
|
36
|
+
title: "Things to do",
|
|
37
|
+
});
|
|
15
38
|
```
|
|
16
39
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
cloud answers your declared query and count. Paths A/B below are for keeping a
|
|
21
|
-
real workspace afterwards.
|
|
40
|
+
That is the model. A born aggregate is a holon: its law, facts, and history travel together. It can act locally, survive disconnection, converge with other copies, explain every accepted change, and be reconstructed from its history.
|
|
41
|
+
|
|
42
|
+
Nomos owns everything underneath `birth`: durable identity, authorship, law installation, local execution, validation, persistence, synchronisation, provenance, and recovery. Those mechanisms are implementation details, not application design choices.
|
|
22
43
|
|
|
23
|
-
|
|
44
|
+
The development loop is one path:
|
|
24
45
|
|
|
25
46
|
```bash
|
|
26
|
-
|
|
27
|
-
npx githolon
|
|
28
|
-
|
|
47
|
+
npm install
|
|
48
|
+
npx githolon compile
|
|
49
|
+
npx githolon proof
|
|
29
50
|
```
|
|
30
51
|
|
|
31
|
-
|
|
52
|
+
`compile` emits the law, typed client, and a proof generated from this domain. `proof` drives the real engine locally, births the list, discards its resident copy, restores it from durable custody, verifies the full history again, and reads the business root back.
|
|
32
53
|
|
|
33
|
-
|
|
34
|
-
own machine, then have independently validated in the cloud. `ledger init`
|
|
35
|
-
builds a brand-new GitHolon ledger locally — genesis (the nomos controller),
|
|
36
|
-
then YOUR compiled law, every intent stamped `installedBy: <your identity>` —
|
|
37
|
-
and verifies the chain with the SAME byte-identical wasm gate the cloud runs.
|
|
38
|
-
Pushing it to an unborn workspace makes the cloud replay-verify the whole chain
|
|
39
|
-
from genesis and re-derive your exact verdict (same head, same plans re-run)
|
|
40
|
-
before serving anything. Authority lives in the git; no holon outranks another.
|
|
54
|
+
Make it yours by renaming `TodoList`, changing its fields, and changing the birth payload. Keep the rule: application code supplies business facts; Nomos owns the machinery that makes those facts durable and trustworthy.
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
git push nomos refs/heads/session/<id> # sync your writes to the edge (main is not push-writable)
|
|
49
|
-
npx githolon deploy <ws> # idempotent (alreadyInstalled) + stages the read manifests so queries route
|
|
50
|
-
cd ..
|
|
51
|
-
```
|
|
56
|
+
Useful files:
|
|
57
|
+
|
|
58
|
+
- [`domains/todo.ts`](domains/todo.ts): the business model;
|
|
59
|
+
- [`nomos.package.mjs`](nomos.package.mjs): the modules compiled into the package;
|
|
60
|
+
- `build/__APP_NAME__.client.ts`: the generated typed client after `compile`;
|
|
61
|
+
- `build/__APP_NAME__.proof.mts`: the generated proof after `compile`.
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
with `git pull`. (`githolon git setup` installed a git credential helper, so plain
|
|
55
|
-
git just works against the cloud.)
|
|
56
|
-
|
|
57
|
-
## Make it YOURS — the five-minute walkthrough
|
|
58
|
-
|
|
59
|
-
The starter is the tutorial; this is the exact path from the todo list to your own law.
|
|
60
|
-
|
|
61
|
-
1. **Rename the law file** and reshape it:
|
|
62
|
-
`git mv domains/todo.ts domains/<yours>.ts` — keep the patterns
|
|
63
|
-
(aggregate fields each tagged a merge driver — `Lww` scalar, `AddWins` set;
|
|
64
|
-
directives = zod payload → pure plan; `addToSet` for AddWins sets — `set()` on
|
|
65
|
-
a set field is refused; `done` is a 2-value Lww enum because there is no
|
|
66
|
-
`t.bool()`; sharing is one native Zanzibar tuple via `writeTuple`).
|
|
67
|
-
2. **Point the compile config at it** — `nomos.package.mjs`:
|
|
68
|
-
```js
|
|
69
|
-
export default { name: "<pkg>", domains: [{ key: "<pkg>", modules: ["./domains/<yours>.ts"] }] };
|
|
70
|
-
```
|
|
71
|
-
**Naming convention: there isn't one.** `<pkg>` can be whatever you'd actually
|
|
72
|
-
call it — `potluck`, `tool-library`, `study_group`, even `Tool Library`.
|
|
73
|
-
Artifact filenames keep your name verbatim (`build/<pkg>.client.ts`); the
|
|
74
|
-
generated symbols normalize it for you (`toolLibraryClient(holon)`,
|
|
75
|
-
`TOOL_LIBRARY_DOMAIN_HASH`).
|
|
76
|
-
3. **Compile and read what you built:**
|
|
77
|
-
```bash
|
|
78
|
-
npx githolon compile && cat build/<pkg>.summary.txt
|
|
79
|
-
```
|
|
80
|
-
4. **Re-point the proof** — `build/<pkg>.proof.mts` needs nothing: it is
|
|
81
|
-
REGENERATED from your law on every compile (`npx githolon proof` runs it).
|
|
82
|
-
The narrated `test/e2e.mts` is yours to reshape: update the client import and
|
|
83
|
-
the directive/query/count names; `npm run typecheck` names every stale
|
|
84
|
-
reference until it's clean.
|
|
85
|
-
5. **Prove it live:** `npm run e2e` — your law deploys to a throwaway workspace,
|
|
86
|
-
an offline write syncs through admission, the cloud answers your declared
|
|
87
|
-
query and count, and one native sharing tuple is recorded. (Prove it OFFLINE
|
|
88
|
-
first with `npx githolon proof` — the generated proof on a local holon, no
|
|
89
|
-
cloud.)
|
|
90
|
-
|
|
91
|
-
Then deploy it for real: `npx githolon login --agent && npx githolon ws create <ws> && npx githolon deploy <ws>`.
|
|
92
|
-
|
|
93
|
-
## Docs — in the box
|
|
94
|
-
|
|
95
|
-
`docs/` travels with you — offline, like everything else here. Seven short
|
|
96
|
-
pages, each under a screen and a half:
|
|
97
|
-
|
|
98
|
-
1. [The mental model](./docs/01-mental-model.md) — holons, content-addressed
|
|
99
|
-
law, custody-not-truth, admission, no server authority.
|
|
100
|
-
2. [Authoring law](./docs/02-authoring.md) — aggregates, field kinds, the
|
|
101
|
-
merge-driver table, plan ops (`addToSet` vs `set`), determinism rules,
|
|
102
|
-
declared reads (query/count/derived/sum).
|
|
103
|
-
3. [The client](./docs/03-client.md) — `connect`, the generated client
|
|
104
|
-
contract, `sync` semantics (`admission: null` is normal), watches, dead
|
|
105
|
-
letters, the two-clientId concurrency proof.
|
|
106
|
-
4. [Nomos Cloud](./docs/04-cloud.md) — both deploy lanes, `login --agent`
|
|
107
|
-
identity, retirement + data retention per the license, DLQ retry, quotas.
|
|
108
|
-
5. [Superpowers](./docs/05-superpowers.md) — mint a holon locally, the upload
|
|
109
|
-
birth, byte-identical wasm everywhere, the enforced-offline proof.
|
|
110
|
-
6. [Evolving law](./docs/06-law-evolution.md) — what's safe to change once
|
|
111
|
-
real data exists (add freely; rename/retype honestly), the era rule, and
|
|
112
|
-
the one trap: old clients after a breaking upgrade.
|
|
113
|
-
7. [Security](./docs/07-security.md) — private aggregates (`requires(cap)` +
|
|
114
|
-
grant/revoke, the holon gates its own reads) and operator-blind E2E
|
|
115
|
-
encrypted fields (`.encrypted()`, the key handshake, `githolon decrypt`).
|
|
116
|
-
|
|
117
|
-
## What's here
|
|
118
|
-
|
|
119
|
-
- `docs/` — the seven pages above; the offline reference.
|
|
120
|
-
- `domains/todo.ts` — the starter domain (the law): a shared, offline-first todo
|
|
121
|
-
list — two aggregates, the directives, a declared query, a where-filtered count,
|
|
122
|
-
and one native Zanzibar sharing tuple. Rename it, reshape it, or add domains
|
|
123
|
-
with `npx githolon generate domain <name>`.
|
|
124
|
-
- `nomos.package.mjs` — the compile config (module list per domain key; reads
|
|
125
|
-
are auto-discovered from exports).
|
|
126
|
-
- `build/__APP_NAME__.client.ts` — GENERATED typed TS client (payload types from
|
|
127
|
-
the engine's zod, read models from the field kinds, the law hash baked in).
|
|
128
|
-
- `build/__APP_NAME__.proof.mts` — GENERATED runnable proof, synthesized from your
|
|
129
|
-
own directives/queries/counts on every compile; `npx githolon proof` runs it.
|
|
130
|
-
- `test/e2e.mts` — proves compile → deploy → typed offline write/query →
|
|
131
|
-
edge admission → cloud declared query, against the live cloud.
|
|
132
|
-
- `holon/` (after `ledger init`) — YOUR ledger, a normal git repo. Inspect it:
|
|
133
|
-
every commit is an intent envelope; `git log` IS the audit trail.
|
|
134
|
-
|
|
135
|
-
## Authoring rules (the engine enforces these)
|
|
136
|
-
|
|
137
|
-
- A directive's plan is a PURE function of `(payload, ports)` — timestamps ride
|
|
138
|
-
in the payload as ISO strings; no `Date.now()` / `Math.random()`. You rarely
|
|
139
|
-
stamp one by hand: payload fields named `…At` are auto-filled with ISO now()
|
|
140
|
-
by the generated client when you omit them (an explicit value always wins).
|
|
141
|
-
- `create(Agg)` mints ids — `.creates` payloads never carry one.
|
|
142
|
-
- Merge drivers are the law: `Lww`, `AddWins`, `MapOf(Lww)`.
|
|
143
|
-
|
|
144
|
-
Everything you write here, and everything the tools generate for you, is yours.
|
|
63
|
+
If you need to understand distributed systems, cryptography, storage topology, or recovery ceremonies to model the business, that concern has leaked out of Nomos.
|
package/template/domains/todo.ts
CHANGED
|
@@ -1,77 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* __APP_NAME__ —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* rest. Rename it, add fields, add directives — it's yours from the first byte.
|
|
2
|
+
* __APP_NAME__ — the complete starter model.
|
|
3
|
+
*
|
|
4
|
+
* The app supplies business facts. Nomos gives the aggregate a durable life and owns identity,
|
|
5
|
+
* authorship, local execution, synchronisation, persistence, provenance, and recovery beneath it.
|
|
7
6
|
*/
|
|
8
|
-
import {
|
|
9
|
-
z, aggregate, t, Lww, AddWins, directive, create, instance, set, addToSet,
|
|
10
|
-
query, count, writeTuple, RelationTuple, relationTuplesByObject,
|
|
11
|
-
} from "@githolon/dsl";
|
|
7
|
+
import { Lww, aggregate, birth, t, z } from "@githolon/dsl";
|
|
12
8
|
|
|
13
|
-
// ── what we store ─────────────────────────────────────────────────────────────
|
|
14
9
|
export const TodoList = aggregate("TodoList", {
|
|
15
|
-
|
|
10
|
+
/** A stable business key the caller already knows. */
|
|
11
|
+
reference: t.string().merge(Lww),
|
|
12
|
+
title: t.string().merge(Lww),
|
|
16
13
|
});
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
/** The one public creation idea: give a todo list its own durable life. */
|
|
16
|
+
export const todoList = birth(TodoList, {
|
|
17
|
+
payload: z.object({
|
|
18
|
+
reference: z.string().min(1),
|
|
19
|
+
title: z.string().min(1),
|
|
20
|
+
}),
|
|
21
|
+
key: (facts) => facts.reference,
|
|
22
|
+
plan: (list, facts) => list
|
|
23
|
+
.set("reference", facts.reference)
|
|
24
|
+
.set("title", facts.title),
|
|
23
25
|
});
|
|
24
|
-
|
|
25
|
-
// ── what people do (directives PLAN ops; the sealed engine replays them) ────────
|
|
26
|
-
export const createList = directive("createList")
|
|
27
|
-
.creates(TodoList) // Nomos MINTS the id — callers never supply one
|
|
28
|
-
.payload(z.object({ title: z.string().min(1) }))
|
|
29
|
-
.plan((p) => { create(TodoList).set("title", p.title); return []; });
|
|
30
|
-
|
|
31
|
-
export const addTodo = directive("addTodo")
|
|
32
|
-
.creates(Todo)
|
|
33
|
-
.payload(z.object({ listId: z.string().min(1), text: z.string().min(1) }))
|
|
34
|
-
.plan((p) => {
|
|
35
|
-
create(Todo).set("listId", p.listId).set("text", p.text).set("done", false);
|
|
36
|
-
return [];
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const renameTodo = directive("renameTodo")
|
|
40
|
-
.mutates(Todo)
|
|
41
|
-
.payload(z.object({ todoId: z.string(), text: z.string().min(1) }))
|
|
42
|
-
.plan((p) => [set(instance(Todo, p.todoId), "text", p.text)]);
|
|
43
|
-
|
|
44
|
-
// The caller decides done/not-done and rides it IN the payload (a plan is pure — no clock,
|
|
45
|
-
// no random). Concurrent toggles are Lww: the later HLC wins, and both peers converge.
|
|
46
|
-
export const toggleTodo = directive("toggleTodo")
|
|
47
|
-
.mutates(Todo)
|
|
48
|
-
.payload(z.object({ todoId: z.string(), done: z.boolean() }))
|
|
49
|
-
.plan((p) => [set(instance(Todo, p.todoId), "done", p.done)]);
|
|
50
|
-
|
|
51
|
-
// addToSet is the ONLY additive write to an AddWins set (set() would overwrite).
|
|
52
|
-
export const tagTodo = directive("tagTodo")
|
|
53
|
-
.mutates(Todo)
|
|
54
|
-
.payload(z.object({ todoId: z.string(), tags: z.array(z.string()).min(1) }))
|
|
55
|
-
.plan((p) => [addToSet(instance(Todo, p.todoId), "tags", p.tags)]);
|
|
56
|
-
|
|
57
|
-
// Sharing = ONE native Zanzibar tuple: TodoList:<id>#<role>@user:<user>.
|
|
58
|
-
export const shareList = directive("shareList")
|
|
59
|
-
.ensures(RelationTuple)
|
|
60
|
-
.payload(z.object({
|
|
61
|
-
listId: z.string().min(1), userId: z.string().min(1),
|
|
62
|
-
role: z.enum(["editor", "viewer"]), sharedBy: z.string().min(1), sharedAt: z.string(),
|
|
63
|
-
}))
|
|
64
|
-
.plan((p) => writeTuple({
|
|
65
|
-
object: `TodoList:${p.listId}`, relation: p.role, subject: `user:${p.userId}`,
|
|
66
|
-
grantedBy: p.sharedBy, grantedAt: p.sharedAt,
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
// ── what people read (auto-discovered by shape; routed to every peer) ───────────
|
|
70
|
-
export const todosByList = query("todosByList").key("listId").returns(Todo);
|
|
71
|
-
|
|
72
|
-
// An O(1) maintained tally: how many todos are still open (done = false), per list.
|
|
73
|
-
export const openTodosByList = count("openTodosByList")
|
|
74
|
-
.of(Todo).where((p) => p.field("done").eq(false)).by("listId");
|
|
75
|
-
|
|
76
|
-
// The raw sharing tuples for a list — the audit surface (re-exported so it routes here).
|
|
77
|
-
export { relationTuplesByObject };
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
// The
|
|
2
|
-
// Read-side declarations (the query + the count) are auto-discovered from the
|
|
3
|
-
// module's exports by shape — nothing to wire here. The framework RelationTuple
|
|
4
|
-
// aggregate is auto-injected by shareList's `.ensures(RelationTuple)`; todo.ts only
|
|
5
|
-
// re-exports the relationTuplesByObject query (the read side).
|
|
6
|
-
// Building a Flutter app too? Add `dart: true` for the typed Dart frontend
|
|
7
|
-
// (build/dart/) alongside the TS client.
|
|
1
|
+
// The package is just the business-law modules Nomos should compile.
|
|
8
2
|
export default {
|
|
9
3
|
name: "__APP_NAME__",
|
|
10
4
|
domains: [{ key: "todo", modules: ["./domains/todo.ts"] }],
|
package/template/package.json
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "A Nomos
|
|
6
|
+
"description": "A Nomos business-law package: aggregates, birth, directives, and reads.",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"compile": "githolon compile",
|
|
9
9
|
"proof": "githolon proof",
|
|
10
|
-
"e2e": "tsx test/e2e.mts",
|
|
11
10
|
"typecheck": "tsc --noEmit"
|
|
12
11
|
},
|
|
13
12
|
"dependencies": {
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# The mental model
|
|
2
|
-
|
|
3
|
-
Five ideas. Everything else in this scaffold is a consequence of them.
|
|
4
|
-
|
|
5
|
-
## Holons
|
|
6
|
-
|
|
7
|
-
A holon is the runtime: kernel + SQLite projection + an embedded deterministic
|
|
8
|
-
JS engine, compiled into ONE `wasm32-wasip1` artifact. Every peer — the cloud
|
|
9
|
-
edge, your laptop, a browser tab, the e2e test — runs the SAME bytes. That is
|
|
10
|
-
why results agree: not because peers trust each other, but because they cannot
|
|
11
|
-
diverge. There is no "server build" and "client build". There is one build.
|
|
12
|
-
|
|
13
|
-
## Law
|
|
14
|
-
|
|
15
|
-
Your domain — aggregates + directives in `domains/*.ts` — compiles to a
|
|
16
|
-
package whose sha256 IS its identity (`policy:{hash}`). Law is
|
|
17
|
-
content-addressed: a workspace doesn't run "version 3 of guestbook", it runs
|
|
18
|
-
`policy:ab12…`, and the generated client carries that exact hash baked in.
|
|
19
|
-
A write is judged against the hash it was authored under. No ambient config,
|
|
20
|
-
no environment drift — if the bytes differ, it is different law.
|
|
21
|
-
|
|
22
|
-
## Custody, not truth
|
|
23
|
-
|
|
24
|
-
The ledger is a git repo. Git stores and transports the chain; it does not
|
|
25
|
-
vouch for it. Every holon re-verifies everything it replays: contiguity,
|
|
26
|
-
admission of every intent, plans re-run and byte-compared. So possession of
|
|
27
|
-
the repo confers nothing — a chain is valid because it verifies, wherever it
|
|
28
|
-
sits. This is why `git clone` of your workspace works with stock git, and why
|
|
29
|
-
a chain minted on your laptop is exactly as good as one minted in the cloud.
|
|
30
|
-
|
|
31
|
-
## Admission
|
|
32
|
-
|
|
33
|
-
Clients author OFFLINE under the pulled law, then push to an untrusted
|
|
34
|
-
`session/<clientId>` branch. The edge holon re-admits each intent — validates
|
|
35
|
-
the payload, re-runs the plan, byte-compares the result — before merging to
|
|
36
|
-
`main`. Verification, never trust: `refs/heads/main` is written only through
|
|
37
|
-
that gate. A rejected intent is dead-lettered on both sides, never lost
|
|
38
|
-
(see [03-client.md](./03-client.md)).
|
|
39
|
-
|
|
40
|
-
## No server authority
|
|
41
|
-
|
|
42
|
-
No holon outranks another. The cloud edge is custody plus a convenient
|
|
43
|
-
always-on peer — its verdicts are re-derivable by anyone with the chain and
|
|
44
|
-
the wasm. That's the point of Path B in the README: mint a ledger locally,
|
|
45
|
-
verify it locally, push it, and watch the cloud re-derive your exact verdict
|
|
46
|
-
before serving anything. Authority lives in the git, not in a hostname.
|
|
47
|
-
|
|
48
|
-
## What this buys you
|
|
49
|
-
|
|
50
|
-
- Offline is the normal case, not a degraded mode. Writes, queries, counts,
|
|
51
|
-
derived fields all run locally; sync is reconciliation, not submission.
|
|
52
|
-
- Merges are law, not luck: every field declares its merge driver, so
|
|
53
|
-
concurrent edits resolve the same way on every peer
|
|
54
|
-
(see [02-authoring.md](./02-authoring.md)).
|
|
55
|
-
- The audit trail is `git log`. Every commit is an intent envelope.
|
|
56
|
-
|
|
57
|
-
Next: [02-authoring.md](./02-authoring.md) — what you actually write.
|