create-githolon 0.94.0 → 0.95.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.95.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",
|
|
@@ -24,6 +24,19 @@ back structured) · `t.ref(Agg)` (an id-valued reference) ·
|
|
|
24
24
|
`t.hasMany(Child).via("parent")` (the virtual read side of a child's ref —
|
|
25
25
|
nothing stored on the parent). Add `.optional()` where absence is legal.
|
|
26
26
|
|
|
27
|
+
**Cross-workspace links are TYPED — never a bare string.** A reference into ANOTHER
|
|
28
|
+
workspace (its own app-specific ledger — a Task pointing at a Space, a Project at a
|
|
29
|
+
catalogue) is `t.foreignRef({ workspace: "<sibling field holding the foreign workspace
|
|
30
|
+
id>", target: SpaceOwner })`. It stores the foreign aggregate id, types the edge as
|
|
31
|
+
`ForeignRef<SpaceOwnerData>`, and the generated client emits `.resolve(nomos)` /
|
|
32
|
+
`.watch(nomos)` to open the other holon on demand. Cross-domain imports are first-class
|
|
33
|
+
(`import { SpaceOwner } from "./space.ts"`). It is a typed LOCATOR, not a gate-enforced
|
|
34
|
+
foreign key (separate ledgers = no cross-workspace referential integrity at the gate);
|
|
35
|
+
law that must depend on the foreign FACT opts into attested evidence via `trustSource`
|
|
36
|
+
+ `.readsFrom(...)`. Worked four-layer example (home → Space + Project with a typed
|
|
37
|
+
cross-ref): `bench/scale/fixtures/four-layer-nested-topology`. **Do NOT store a foreign
|
|
38
|
+
workspace name in a plain `t.string()` — that throws away the type and the resolver.**
|
|
39
|
+
|
|
27
40
|
## Relationships: reference aggregates, don't hand-roll composite ids
|
|
28
41
|
|
|
29
42
|
When one aggregate points at another — a listing's primary image, its many
|
|
@@ -127,6 +127,37 @@ AddWins union keeps every add. No locks, no "last writer wins the whole
|
|
|
127
127
|
record", no merge code — the field's declared driver did it
|
|
128
128
|
(see [02-authoring.md](./02-authoring.md)).
|
|
129
129
|
|
|
130
|
+
## Typed-client patterns (the three gotchas)
|
|
131
|
+
|
|
132
|
+
Building with the generated typed client (`XClient.bind(session)` — no string
|
|
133
|
+
directive ids), three things trip people up once and never again:
|
|
134
|
+
|
|
135
|
+
- **Success = `outcome.ok`.** A directive method returns a `NomosOfferOutcome`;
|
|
136
|
+
`outcome.ok` is true when it committed (a refusal *throws* / surfaces its typed
|
|
137
|
+
law error, it doesn't return a false outcome). For a `.creates` you also get the
|
|
138
|
+
minted id back with no query round-trip; for a `.births` you get `outcome.born`.
|
|
139
|
+
- **Timestamps are caller-stamped — pass them.** Payload fields like `createdAt` /
|
|
140
|
+
`bornAt` are `required` BY DESIGN, not an oversight: a plan is a pure function of
|
|
141
|
+
its payload and reads no clock (determinism — every peer replays the same bytes).
|
|
142
|
+
Stamp `DateTime.now().toUtc().toIso8601String()` at the call site. This is the
|
|
143
|
+
timestamp doctrine, not friction to remove.
|
|
144
|
+
- **Apps hold MANY workspaces open at once — that is the model, not an edge case.**
|
|
145
|
+
`bridge.session(ws)` returns a live session from the REALM (one engine, N mounted
|
|
146
|
+
workspaces, LRU park/remount above a budget). A Space AND a Project AND the home
|
|
147
|
+
are all open concurrently; there is no singleton "current workspace" (that surface
|
|
148
|
+
would be the bug). See [10-flutter-layering.md](./10-flutter-layering.md).
|
|
149
|
+
- **A cross-workspace `ForeignRef` has TWO read paths — don't confuse them.**
|
|
150
|
+
`task.space` (a `ForeignRef<SpaceOwnerData>`) is a typed LOCATOR.
|
|
151
|
+
- **App/presentation read:** `await task.space.resolve(nomos)` / `.watch(nomos)`
|
|
152
|
+
reads the target THROUGH the open realm — instant when that Space is already a
|
|
153
|
+
live session (it usually is), a mount otherwise. This is live UI data, not law.
|
|
154
|
+
- **Law read (a plan's DECISION depends on the foreign fact):** never a live
|
|
155
|
+
resolve — declare `trustSource` + `.readsFrom(...)` so the fact is CAPTURED onto
|
|
156
|
+
the intent and re-verified on every replay (the captured-ports / attested-read
|
|
157
|
+
doctrine, deterministic). `foreignRef` also generates `.attest(nomos)` for that
|
|
158
|
+
evidence. Separate ledgers mean readability is policy, not a gate-checked FK —
|
|
159
|
+
see [09-attested-reads.md](./09-attested-reads.md).
|
|
160
|
+
|
|
130
161
|
## Persistence
|
|
131
162
|
|
|
132
163
|
`holon.export()` → bytes; `connect({ restoreFrom: bytes })` restores. Pending
|