create-githolon 0.94.0 → 0.94.2

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.94.0",
3
+ "version": "0.94.2",
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