@githolon/dsl 0.2.3 → 0.4.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.
@@ -0,0 +1,1284 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded
4
+ // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
+ // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
+
8
+ /**
9
+ * THE DERIVED SHARDING LAW (sharding §3/§5/§6 — slice 2 placement, slice 3a homing
10
+ * invariants, slice 3 delta lane + least-loaded placement).
11
+ *
12
+ * `nomos-compile` APPENDS this module to a TAXONOMY-BEARING domain's module list —
13
+ * both in the canonical manifests (the placement law is hash-bearing law) and in the
14
+ * generated engine entry (the plans ship in the lump). A taxonomy-FREE package never
15
+ * imports it (subpath `@githolon/dsl/workspace-sharding`, never the runtime barrel —
16
+ * the slice-1 hash-stability law: its bytes must not enter other tenants' lumps).
17
+ *
18
+ * What it derives, per PACKED axis (`EstateWs.hasMany(SiteHome)` with
19
+ * `SiteHome.packed()`):
20
+ *
21
+ * * `NomosShardAssignment` — THE SHARD MAP AS LAW on the coordinator. SLICE 3:
22
+ * one DETERMINISTIC row per home (`nomos-shard-assignment:<homeKey>`, an Ensure)
23
+ * — placement uniqueness BY CONSTRUCTION, idempotent re-offer by Lww. The shard
24
+ * is picked LEAST-LOADED by the dispatching client/worker over committed
25
+ * subtotal rows (law-state load, §3) and carried IN the payload; the
26
+ * `nomosPlacementUnique:*` invariant refuses a CONFLICTING re-offer typed
27
+ * (`placement-exists:<heldShard>`) — same home, same shard, forever, until the
28
+ * slice-5 move lane flips it through `splitShard`/`sealHandoff`.
29
+ * * `NomosHomeReceipt` — §3's "its assignment" fact IN THE SHARD'S OWN CHAIN (the
30
+ * assignment RECEIPT leg): `nomosReceiveAssignment` ensures one receipt row per
31
+ * home the shard owns, judged by `nomosReceiptHome:*` against the shard's own
32
+ * declared identity. The homing invariant (`nomosWrongHome:<directive>`) now
33
+ * judges routed writes against THESE receipts — shard-local law-state, no static
34
+ * placement formula (the slice-2 fnv pick is retired with least-loaded placement).
35
+ * * THE §5.2 DELTA LANE — `nomosPropagateSummary`: shard admission's coalesced
36
+ * Order carrying per-(read, group) ABSOLUTE subtotals + the covered range's
37
+ * event deltas as content-addressed evidence. The coordinator's ONE gate
38
+ * adjudicates it (ruling 4 — full gate recomputation day one):
39
+ * 1. the PLAN recomputes every claimed absolute from the claimed previous +
40
+ * the carried event deltas and REFUSES on mismatch (pure arithmetic over
41
+ * the payload — replays byte-identically on every peer);
42
+ * 2. the `nomosSummaryGate:*` workspace invariant verifies the CLAIMS against
43
+ * HELD law-state via `pre:` snapshots (the committed pre-apply reads the
44
+ * executor resolves for `pre:`-prefixed refs): `fromOid` must equal the
45
+ * recorded per-shard frontier (typed `frontier-gap:<expected>`), and every
46
+ * claimed `prev` must equal the held subtotal value (typed
47
+ * `summary-mismatch:…`). Held + carried-events ⇒ claimed, byte-compared.
48
+ * 3. the FOLD is plain Lww over `NomosSummarySubtotal` rows — idempotent,
49
+ * replay-safe, kernel-lowered today. The estate total is an ORDINARY
50
+ * maintained sum over subtotal rows (`nomosEstateSummary` by `bucket`) —
51
+ * O(1) by #31. Subtotal rows ARE checkpoints (ruling 1): each carries the
52
+ * shard frontier it is true at; deep-verify (replay-on-suspicion) mounts
53
+ * the shard chain, re-verifies from genesis, recounts, byte-compares.
54
+ * * `NomosShardRegistry` + `NomosShardPolicy` — the OPEN-SHARD set and the split
55
+ * policy as law-state: the worker auto-births `s(k+1)` through the platform lane
56
+ * when EVERY open shard's row count crosses the split threshold (ruling 5 — 75%
57
+ * of the CAPACITY.md wall by default, law-amendable via `nomosSetShardPolicy`).
58
+ * * ROUTE-TAGGED IN-PLAN MINTS — {@link installRouteTaggedMint}: the generated
59
+ * entry of a taxonomy-bearing package wraps the sealed sandbox's
60
+ * `globalThis.nomos.mint` (at lump top-level eval, pre-freeze) so `create(Agg)`
61
+ * mints of PACKED-HOMED aggregates fold the dispatched intent's home ROUTE TAG
62
+ * (48 bits of sha256, §4) into the UUIDv7 timestamp slot — same draw budget
63
+ * (19 captured rng draws), no clock read, deterministic, byte-identical on
64
+ * replay. No wasm change: the tag rides the slot the id-mint law itself demotes
65
+ * to metadata.
66
+ *
67
+ * ENGINE-BUNDLE-SAFE: no node builtin anywhere in this module's import graph.
68
+ */
69
+ import { z } from "zod";
70
+
71
+ import { aggregate, instance } from "./aggregate.js";
72
+ import { t } from "./fields.js";
73
+ import { Lww } from "./drivers.js";
74
+ import { directive } from "./directive.js";
75
+ import { set, strike, withMarker, type PlannedOp } from "./ops.js";
76
+ import { query } from "./query.js";
77
+ import { sum } from "./sum.js";
78
+ import { min, max } from "./extremum.js";
79
+ import { refAs, workspaceInvariant } from "./framework/workspace_invariant.js";
80
+
81
+ /** One packed axis the factory derives a placement lane for. */
82
+ export interface ShardingAxisSpec {
83
+ /** The packed workspace-type id (e.g. `site`). */
84
+ readonly axisType: string;
85
+ /** The axis ROOT aggregate's wire id (e.g. `Site`). */
86
+ readonly axisRoot: string;
87
+ /**
88
+ * The INITIAL open-shard count (the parent type's `.pool(n)`): shards `s0…s{n-1}`
89
+ * are open at genesis. SLICE 3: the set GROWS — the worker auto-births `s(k+1)`
90
+ * (recorded as a `NomosShardRegistry` row through the coordinator's own gate)
91
+ * when every open shard crosses the split threshold.
92
+ */
93
+ readonly shardCount: number;
94
+ }
95
+
96
+ /** One derived directive route the homing invariant judges (`via:"axis"` only — a
97
+ * `via:"id"` misroute targets an aggregate that does not exist on the wrong shard,
98
+ * which the referential gate already refuses; see §3 of the sharding design). */
99
+ export interface ShardingRouteSpec {
100
+ /** The routed directive id. */
101
+ readonly directive: string;
102
+ /** The PACKED workspace-type id the write homes on (matches an axis' `axisType`). */
103
+ readonly home: string;
104
+ /** The top-level payload field carrying the home key. */
105
+ readonly key: string;
106
+ /** How the key rides: `"axis"` = the axis-root id itself; `"id"` = a homed minted id. */
107
+ readonly via: "axis" | "id";
108
+ }
109
+
110
+ export interface ShardingLawSpec {
111
+ readonly axes: readonly ShardingAxisSpec[];
112
+ /**
113
+ * THE DERIVED ROUTES (slice 2's canonical-manifest `routes`, fed back into the law):
114
+ * per routed directive, the homing invariant `nomosWrongHome:<directive>` is derived
115
+ * so THE CHAIN GATE ITSELF refuses a wrong-home write (#41 — slice 3a; the edge
116
+ * bailiff stays as defense-in-depth). OMITTED routes ⇒ no invariants (slice-2 shape).
117
+ */
118
+ readonly routes?: readonly ShardingRouteSpec[];
119
+ /**
120
+ * THE PACKED HOMING TABLE (slice 3 — the in-plan mint tagger's input): aggregate
121
+ * wire id → the packed axis type it homes on. Only PACKED-homed aggregates appear.
122
+ * OMITTED ⇒ in-plan mints stay plain (the slice-2 shape).
123
+ */
124
+ readonly homes?: Readonly<Record<string, string>>;
125
+ }
126
+
127
+ // ── well-known law-state row ids (deterministic — Ensure singletons / keyed rows) ──
128
+
129
+ /** The well-known Ensure id the homing invariant resolves the identity under. */
130
+ export const NOMOS_SHARD_IDENTITY_ID = "nomos-shard-identity:self";
131
+ /** The shard-policy singleton's row id (coordinator law-state). */
132
+ export const NOMOS_SHARD_POLICY_ID = "nomos-shard-policy:self";
133
+ /**
134
+ * Deterministic-row-id escape: a framework row id must never PARSE as a
135
+ * kernel-minted id (`<tag>_<uuid>` — the FIRST "_" splits, and a minted home key
136
+ * embedded verbatim would make the row id minted-shaped and refuse at the id-mint
137
+ * gate). Embedded key material therefore escapes "_" %-style — injective, pure,
138
+ * sandbox-safe ("%"→"%25" first, then "_"→"%5F").
139
+ */
140
+ export const escapeRowKey = (s: string): string => s.replace(/%/g, "%25").replace(/_/g, "%5F").replace(/\|/g, "%7C");
141
+ /** The shard map row for a home key (coordinator law-state; uniqueness by construction). */
142
+ export const shardAssignmentRowId = (homeKey: string): string => `nomos-shard-assignment:${escapeRowKey(homeKey)}`;
143
+ /** The shard's own receipt row for a home it owns (shard law-state). */
144
+ export const homeReceiptRowId = (homeKey: string): string => `nomos-home-receipt:${escapeRowKey(homeKey)}`;
145
+ /** The open-shard registry row for a label (coordinator law-state). */
146
+ export const shardRegistryRowId = (label: string): string => `nomos-shard-registry:${label}`;
147
+ /** The per-shard delta-lane frontier watermark (coordinator law-state). */
148
+ export const summaryFrontierRowId = (shard: string): string => `nomos-summary-frontier:${shard}`;
149
+ /** One per-(shard, read, group) committed subtotal row (coordinator law-state). */
150
+ export const summarySubtotalRowId = (shard: string, readId: string, group: string): string =>
151
+ `nomos-subtotal:${shard}|${escapeRowKey(readId)}|${escapeRowKey(group)}`;
152
+ /** The recorded deep-verify verdict row for a shard (coordinator law-state). */
153
+ export const deepVerifyRowId = (shard: string): string => `nomos-deep-verify:${shard}`;
154
+ /** One sealed checkpoint row (§5.5, slice 4 — coordinator law-state, by seal sequence). */
155
+ export const checkpointSealRowId = (seq: number | string): string => `nomos-checkpoint-seal:${seq}`;
156
+ /** The LATEST seal (the head pointer — same aggregate, well-known id; O(1) to find). */
157
+ export const NOMOS_CHECKPOINT_HEAD_ID = "nomos-checkpoint-seal:head";
158
+
159
+ /** The estate-total bucket of a scoped read's group (`nomosEstateSummary` group key).
160
+ * `|` — printable (the kernel's canonical event decode refuses raw control bytes);
161
+ * unambiguous because a SCOPED read's id may not contain `|` (compile-refused);
162
+ * group values are free text (the bucket is only ever CONSTRUCTED, never parsed). */
163
+ export const SUMMARY_BUCKET_SEP = "|";
164
+ export const summaryBucket = (readId: string, group: string): string =>
165
+ `${readId}${SUMMARY_BUCKET_SEP}${group}`;
166
+
167
+ /** The synthetic per-shard row-count read the load policy consumes (capacity headroom). */
168
+ export const NOMOS_SHARD_ROWS_READ = "nomosShardRows";
169
+
170
+ /**
171
+ * THE MOVE STATUS (slice 5 — §6's `moving(target)`, encoded IN the status string so
172
+ * the assignment row's schema never moves): an assignment mid-handoff reads
173
+ * `moving:<targetLabel>`; `sealHandoff` flips it back to `active` with
174
+ * `shard = target` and the map version bumped. One row per home, always.
175
+ */
176
+ export const MOVING_STATUS_PREFIX = "moving:";
177
+ export const movingStatus = (target: string): string => `${MOVING_STATUS_PREFIX}${target}`;
178
+ /** The move target of a `moving:<label>` status, or undefined for any other status. */
179
+ export const movingTargetOf = (status: unknown): string | undefined =>
180
+ typeof status === "string" && status.startsWith(MOVING_STATUS_PREFIX)
181
+ ? status.slice(MOVING_STATUS_PREFIX.length)
182
+ : undefined;
183
+
184
+ /** The CAPACITY.md wall + ruling-5 default threshold (rows; law-amendable via policy). */
185
+ export const DEFAULT_WALL_ROWS = 581000;
186
+ export const DEFAULT_SPLIT_THRESHOLD_ROWS = 435750; // 75% of the wall — ruling 5
187
+
188
+ /**
189
+ * THE SHARD MAP ROW (coordinator law-state). One aggregate type across all axes —
190
+ * `homeType` carries the axis. `shard` is the LABEL (`s0`…): the addressable shard
191
+ * workspace is `<coordinator>--<label>` (custody naming, composed by the hosts —
192
+ * ids embed the HOME KEY, never the shard ordinal, §4). SLICE 3: the row id is
193
+ * DETERMINISTIC (`nomos-shard-assignment:<homeKey>`) — one home, one row, forever.
194
+ */
195
+ export const NomosShardAssignment = aggregate("NomosShardAssignment", {
196
+ homeType: t.string().merge(Lww),
197
+ homeKey: t.string().merge(Lww),
198
+ shard: t.string().merge(Lww),
199
+ mapVersion: t.int().merge(Lww),
200
+ status: t.string().merge(Lww), // active | moving | moved (slice 5 flips these)
201
+ placedAt: t.string().merge(Lww),
202
+ });
203
+
204
+ /**
205
+ * THE SHARD'S OWN IDENTITY — law-state in the shard's OWN chain (§3's "its assignment"
206
+ * fact, the slice-3a slice of it): which shard label this workspace IS. An Ensure
207
+ * singleton (well-known id `nomos-shard-identity:self`): the worker (the bailiff)
208
+ * authors it through the shard's own gate right after the law deploys — a custody fact
209
+ * (the workspace's name) recorded AS LAW, every change attributed + replayable. The
210
+ * homing invariant reads it; a workspace that never declares one (the coordinator, a
211
+ * direct unsharded holon, every pre-#41 chain) is judged vacuously (the edge bailiff
212
+ * remains the outer guard there).
213
+ */
214
+ export const NomosShardIdentity = aggregate("NomosShardIdentity", {
215
+ scope: t.string().merge(Lww), // always "self" — the singleton key
216
+ label: t.string().merge(Lww), // "s0"…, or "" = the coordinator
217
+ coordinator: t.string().merge(Lww), // the coordinator workspace's name
218
+ declaredAt: t.string().merge(Lww),
219
+ });
220
+
221
+ /**
222
+ * THE ASSIGNMENT RECEIPT (slice 3 — §3's receipt leg): the shard's own chain records
223
+ * WHICH HOMES IT OWNS, one Ensure row per home key. Authored by the coordinator's
224
+ * worker after a placement admits, AND optimistically by the routed client before its
225
+ * first write to a fresh placement (idempotent — same row, same values; the edge
226
+ * bailiff verifies every session-lane receipt against the coordinator's shard map
227
+ * before it merges). The homing invariant judges routed writes against these rows.
228
+ */
229
+ export const NomosHomeReceipt = aggregate("NomosHomeReceipt", {
230
+ homeKey: t.string().merge(Lww),
231
+ shard: t.string().merge(Lww),
232
+ mapVersion: t.int().merge(Lww),
233
+ status: t.string().merge(Lww), // active | moved (slice 5 — the tombstone IS a redirect: `shard` then names the target)
234
+ receivedAt: t.string().merge(Lww),
235
+ });
236
+
237
+ /** The OPEN-SHARD registry row (coordinator law-state; `s0…s{pool-1}` are implicit). */
238
+ export const NomosShardRegistry = aggregate("NomosShardRegistry", {
239
+ label: t.string().merge(Lww),
240
+ status: t.string().merge(Lww), // open | retired (slice 5)
241
+ openedAt: t.string().merge(Lww),
242
+ });
243
+
244
+ /**
245
+ * THE SPLIT POLICY singleton (coordinator law-state — ruling 5: 75% ships, the dial
246
+ * is law-amendable). `wallRows` is the capacitymeter-informed per-shard wall
247
+ * (CAPACITY.md: ~581k under the co2 shape); `splitThresholdRows` is the auto-birth
248
+ * trigger (defaults to 75% of the wall when amended without one).
249
+ */
250
+ export const NomosShardPolicy = aggregate("NomosShardPolicy", {
251
+ scope: t.string().merge(Lww), // always "self"
252
+ wallRows: t.int().merge(Lww),
253
+ splitThresholdRows: t.int().merge(Lww),
254
+ // §5.5 CHECKPOINT CADENCE (slice 4, ruling 1): seal after every N admitted deltas.
255
+ // Absent ⇒ the host default (64); 0 ⇒ cadence off (explicit seals only).
256
+ checkpointEveryDeltas: t.int().merge(Lww),
257
+ setAt: t.string().merge(Lww),
258
+ });
259
+
260
+ /**
261
+ * THE CHECKPOINT SEAL (§5.5, slice 4 — ruling 1 made periodic): a committed
262
+ * `{shard → frontier}` snapshot + the content hash of the coordinator's subtotal
263
+ * state at the seal. Subtotal rows ARE the checkpoints (each already carries its
264
+ * shard frontier + the gate verdict that sealed it); the seal binds a SET of them
265
+ * to one sequence point, which makes the delta suffix BEHIND the sealed frontiers
266
+ * ARCHIVAL: replay from genesis still works (nothing is erased), but an auditor
267
+ * may anchor at the latest seal and deep-verify forward from it. `frontiers` is
268
+ * canonical JSON (`[{shard, frontier}]`, shard-sorted — the plan re-sorts, so the
269
+ * folded bytes never depend on payload order); `stateHash` is the host's
270
+ * content-addressed claim over the shard-sorted subtotal rows at the seal —
271
+ * deep-verify recomputes it from law-state and byte-compares (anchoring).
272
+ */
273
+ export const NomosCheckpointSeal = aggregate("NomosCheckpointSeal", {
274
+ seq: t.int().merge(Lww),
275
+ frontiers: t.string().merge(Lww), // canonical JSON: [{shard, frontier}] sorted by shard
276
+ stateHash: t.string().merge(Lww), // sha256 over the coordinator's subtotal state at the seal
277
+ sealedAt: t.string().merge(Lww),
278
+ });
279
+
280
+ /** The canonical (shard-sorted) frontier-set bytes a seal folds — ONE shape on every peer. */
281
+ export const canonicalSealFrontiers = (
282
+ frontiers: readonly { shard: string; frontier: string }[],
283
+ ): string =>
284
+ JSON.stringify(
285
+ [...frontiers]
286
+ .map((f) => ({ shard: f.shard, frontier: f.frontier }))
287
+ .sort((a, b) => (a.shard < b.shard ? -1 : a.shard > b.shard ? 1 : 0)),
288
+ );
289
+
290
+ /**
291
+ * ONE COMMITTED SUBTOTAL — §5.1's `SummarySubtotal`, the checkpoint row (ruling 1):
292
+ * the ABSOLUTE per-(shard, read, group) value, true at the shard commit `frontier`,
293
+ * folded as plain Lww after the gate recomputed it from carried evidence. `bucket`
294
+ * (= `readId group`) is the maintained estate-total's group key; `kind` records
295
+ * what the value tallies (`count` | `sum` | `rows` — the load policy reads `rows` —
296
+ * and, SLICE 8, `min` | `max`: the shard's maintained extremum at the frontier).
297
+ *
298
+ * `empty` (slice 8, EXTREMUM kinds only): `1` = the shard's group has NO
299
+ * contributing member — an empty group has no extremum and `0` is a legitimate
300
+ * extremum value (the `extremum.ts` null law), so emptiness is a FLAG, never a
301
+ * sentinel value. The maintained estate extremums below exclude `empty:1` rows;
302
+ * additive kinds never set the field (their rows fold byte-identically to before
303
+ * it existed).
304
+ */
305
+ export const NomosSummarySubtotal = aggregate("NomosSummarySubtotal", {
306
+ readId: t.string().merge(Lww),
307
+ group: t.string().merge(Lww),
308
+ shard: t.string().merge(Lww),
309
+ kind: t.string().merge(Lww),
310
+ bucket: t.string().merge(Lww),
311
+ value: t.int().merge(Lww),
312
+ frontier: t.string().merge(Lww),
313
+ empty: t.int().merge(Lww).optional(),
314
+ });
315
+
316
+ /** THE PER-SHARD DELTA FRONTIER watermark (§5.2 step 1 — contiguity law-state). */
317
+ export const NomosSummaryFrontier = aggregate("NomosSummaryFrontier", {
318
+ shard: t.string().merge(Lww),
319
+ frontier: t.string().merge(Lww), // the shard main oid the subtotals are true at
320
+ readManifestHash: t.string().merge(Lww),
321
+ updatedAt: t.string().merge(Lww),
322
+ });
323
+
324
+ /** THE RECORDED DEEP-VERIFY VERDICT (§5.5 — replay-on-suspicion, made operational). */
325
+ export const NomosDeepVerify = aggregate("NomosDeepVerify", {
326
+ shard: t.string().merge(Lww),
327
+ frontier: t.string().merge(Lww), // the coordinator-held frontier at check time
328
+ head: t.string().merge(Lww), // the shard main head the replay verified
329
+ verdict: t.string().merge(Lww), // verified | mismatch:<…> | invalid:<…>
330
+ detail: t.string().merge(Lww), // JSON: the per-(read,group) byte-compare table
331
+ checkedAt: t.string().merge(Lww),
332
+ });
333
+
334
+ export const nomosDeclareShardIdentity = directive("nomosDeclareShardIdentity")
335
+ .ensures(NomosShardIdentity)
336
+ .payload(
337
+ z.object({
338
+ label: z.string(), // "" = the coordinator
339
+ coordinator: z.string().min(1),
340
+ declaredAt: z.string(),
341
+ }),
342
+ )
343
+ .plan((p) => {
344
+ const me = instance(NomosShardIdentity, NOMOS_SHARD_IDENTITY_ID);
345
+ return [
346
+ withMarker(set(me, "scope", "self"), "ensures"),
347
+ set(me, "label", p.label),
348
+ set(me, "coordinator", p.coordinator),
349
+ set(me, "declaredAt", p.declaredAt),
350
+ ];
351
+ });
352
+
353
+ /** The receipt leg: record (idempotently) that THIS chain owns `homeKey`. */
354
+ export const nomosReceiveAssignment = directive("nomosReceiveAssignment")
355
+ .ensures(NomosHomeReceipt)
356
+ .payload(
357
+ z.object({
358
+ homeKey: z.string().min(1),
359
+ shard: z.string().regex(/^s\d+$/),
360
+ mapVersion: z.number().int().min(1),
361
+ receivedAt: z.string(),
362
+ }),
363
+ )
364
+ .plan((p) => {
365
+ const row = instance(NomosHomeReceipt, homeReceiptRowId(p.homeKey));
366
+ return [
367
+ withMarker(set(row, "homeKey", p.homeKey), "ensures"),
368
+ set(row, "shard", p.shard),
369
+ set(row, "mapVersion", p.mapVersion),
370
+ set(row, "status", "active"),
371
+ set(row, "receivedAt", p.receivedAt),
372
+ ];
373
+ });
374
+
375
+ // ── §6 — THE SHARD LIFECYCLE (slice 5: splits / migration / sealHandoff) ─────────────
376
+ //
377
+ // The move is THREE law intents, every leg through a gate, custody never truth:
378
+ // 1. `nomosSplitShard` (coordinator) flips the chosen homes' SAME deterministic
379
+ // assignment rows `active → moving:<target>` — from that commit on, the edge
380
+ // bailiff parks writes to those homes with the typed `home-moving` refusal.
381
+ // 2. MIGRATION = RE-ADMISSION (the host leg, no directive): the target shard pulls
382
+ // the moving homes' intents from the source chain and re-admits EACH through its
383
+ // OWN gate (`apply_intent` — plan re-run, byte-compared, invariants judged;
384
+ // author preserved, committer = the target's kernel). Then `nomosSealHome`
385
+ // (source) STRIKES the migrated intents out of the source's fold (append-then-
386
+ // strike — history is never erased; the projection sheds the rows, so the
387
+ // source's tallies, deep-verify recount and capacity load all stay honest) and
388
+ // tombstones the home's receipt: `status = moved`, `shard = <target>` — the
389
+ // tombstone IS the redirect, and the homing invariant's existing
390
+ // `wrong-home:<receiptShard>` verdict now NAMES the new home for free.
391
+ // 3. `sealHandoff`'s subtotal close/open is one `nomosPropagateSummary` PAIR
392
+ // through the SAME summary gate (the close offers the source's post-strike
393
+ // retally against its held frontier; the open re-derives the target's suffix) —
394
+ // then `nomosSealHandoff` (coordinator) flips the rows `moving → active` with
395
+ // `shard = target` and the map version BUMPED (wrong-home refusals carry it;
396
+ // clients refresh and retry — §4's self-healing lane).
397
+
398
+ /** Begin a move: flip each home's assignment to `moving:<toShard>` (gate-verified). */
399
+ export const nomosSplitShard = directive("nomosSplitShard")
400
+ .mutates(NomosShardAssignment)
401
+ .payload(
402
+ z.object({
403
+ fromShard: z.string().regex(/^s\d+$/),
404
+ toShard: z.string().regex(/^s\d+$/),
405
+ homeKeys: z.array(z.string().min(1)).min(1).max(64),
406
+ startedAt: z.string(),
407
+ }),
408
+ )
409
+ .plan((p) => {
410
+ if (p.fromShard === p.toShard) {
411
+ throw new Error(`split-degenerate: fromShard and toShard are both '${p.toShard}' — a move needs two shards`);
412
+ }
413
+ if (new Set(p.homeKeys).size !== p.homeKeys.length) {
414
+ throw new Error("split-duplicate: a home key appears twice in one split");
415
+ }
416
+ return p.homeKeys.flatMap((homeKey) => {
417
+ const row = instance(NomosShardAssignment, shardAssignmentRowId(homeKey));
418
+ return [set(row, "status", movingStatus(p.toShard))];
419
+ });
420
+ });
421
+
422
+ /**
423
+ * Seal a completed handoff: flip each home's assignment `moving:<toShard>` →
424
+ * `active` on the TARGET shard, with the map version bumped (gate-verified:
425
+ * the held row must be mid-move to exactly this target, and the bump must be
426
+ * exactly held+1 — a stale or replayed seal refuses typed).
427
+ */
428
+ export const nomosSealHandoff = directive("nomosSealHandoff")
429
+ .mutates(NomosShardAssignment)
430
+ .payload(
431
+ z.object({
432
+ fromShard: z.string().regex(/^s\d+$/),
433
+ toShard: z.string().regex(/^s\d+$/),
434
+ homeKeys: z.array(z.string().min(1)).min(1).max(64),
435
+ mapVersion: z.number().int().min(2),
436
+ sealedAt: z.string(),
437
+ }),
438
+ )
439
+ .plan((p) =>
440
+ p.homeKeys.flatMap((homeKey) => {
441
+ const row = instance(NomosShardAssignment, shardAssignmentRowId(homeKey));
442
+ return [
443
+ set(row, "shard", p.toShard),
444
+ set(row, "status", "active"),
445
+ set(row, "mapVersion", p.mapVersion),
446
+ ];
447
+ }),
448
+ );
449
+
450
+ /**
451
+ * THE SOURCE'S TERMINAL SEAL (authored on the SOURCE shard's own chain by the host
452
+ * after migration completes — the edge refuses it on the session lane: the seal pen
453
+ * is the bailiff's): STRIKE every migrated intent out of the fold (append-then-strike
454
+ * — the kernel's strike-correct reprojection sheds the moved rows, so tallies,
455
+ * deep-verify and the capacity load stay honest) and tombstone each home's receipt
456
+ * (`status=moved`, `shard=<target>` — the redirect the homing invariant serves).
457
+ */
458
+ export const nomosSealHome = directive("nomosSealHome")
459
+ .ensures(NomosHomeReceipt)
460
+ .payload(
461
+ z.object({
462
+ homeKeys: z.array(z.string().min(1)).min(1).max(64),
463
+ target: z.string().regex(/^s\d+$/),
464
+ intentIds: z.array(z.string().min(1)).max(2048),
465
+ sealedAt: z.string(),
466
+ }),
467
+ )
468
+ .plan((p) => {
469
+ const ops: PlannedOp[] = [];
470
+ for (const homeKey of p.homeKeys) {
471
+ const row = instance(NomosHomeReceipt, homeReceiptRowId(homeKey));
472
+ ops.push(
473
+ withMarker(set(row, "homeKey", homeKey), "ensures"),
474
+ set(row, "shard", p.target),
475
+ set(row, "status", "moved"),
476
+ set(row, "receivedAt", p.sealedAt),
477
+ );
478
+ }
479
+ for (const intentId of p.intentIds) ops.push(strike(intentId));
480
+ return ops;
481
+ });
482
+
483
+ /** Open shard `s<k>` (worker-authored when the auto-birth policy trips — ruling 5). */
484
+ export const nomosOpenShard = directive("nomosOpenShard")
485
+ .ensures(NomosShardRegistry)
486
+ .payload(z.object({ label: z.string().regex(/^s\d+$/), openedAt: z.string() }))
487
+ .plan((p) => {
488
+ const row = instance(NomosShardRegistry, shardRegistryRowId(p.label));
489
+ return [
490
+ withMarker(set(row, "label", p.label), "ensures"),
491
+ set(row, "status", "open"),
492
+ set(row, "openedAt", p.openedAt),
493
+ ];
494
+ });
495
+
496
+ /** Amend the split policy (the ruling-5 dial; omitted threshold derives 75% of the wall). */
497
+ export const nomosSetShardPolicy = directive("nomosSetShardPolicy")
498
+ .ensures(NomosShardPolicy)
499
+ .payload(
500
+ z.object({
501
+ wallRows: z.number().int().min(1),
502
+ splitThresholdRows: z.number().int().min(1).optional(),
503
+ checkpointEveryDeltas: z.number().int().min(0).optional(), // 0 = cadence off
504
+ setAt: z.string(),
505
+ }),
506
+ )
507
+ .plan((p) => {
508
+ const row = instance(NomosShardPolicy, NOMOS_SHARD_POLICY_ID);
509
+ const threshold = p.splitThresholdRows ?? Math.max(1, Math.floor((p.wallRows * 3) / 4));
510
+ return [
511
+ withMarker(set(row, "scope", "self"), "ensures"),
512
+ set(row, "wallRows", p.wallRows),
513
+ set(row, "splitThresholdRows", threshold),
514
+ ...(p.checkpointEveryDeltas !== undefined ? [set(row, "checkpointEveryDeltas", p.checkpointEveryDeltas)] : []),
515
+ set(row, "setAt", p.setAt),
516
+ ];
517
+ });
518
+
519
+ /**
520
+ * Amend a shard's delta frontier DIRECTLY (an Ensure on the watermark row). Two jobs:
521
+ * it DECLARES `NomosSummaryFrontier` as an ensures target (the id-mint gate admits an
522
+ * Ensure-introduced aggregate only for declared targets — `nomosPropagateSummary`'s
523
+ * plan writes this row but targets the subtotal type), and it is the slice-5
524
+ * `sealHandoff` primitive (closing/opening frontiers across a move). Day to day the
525
+ * frontier only ever moves THROUGH `nomosPropagateSummary`'s gate.
526
+ */
527
+ export const nomosAmendSummaryFrontier = directive("nomosAmendSummaryFrontier")
528
+ .ensures(NomosSummaryFrontier)
529
+ .payload(
530
+ z.object({
531
+ shard: z.string().regex(/^s\d+$/),
532
+ frontier: z.string(),
533
+ readManifestHash: z.string(),
534
+ updatedAt: z.string(),
535
+ }),
536
+ )
537
+ .plan((p) => {
538
+ const wm = instance(NomosSummaryFrontier, summaryFrontierRowId(p.shard));
539
+ return [
540
+ withMarker(set(wm, "shard", p.shard), "ensures"),
541
+ set(wm, "frontier", p.frontier),
542
+ set(wm, "readManifestHash", p.readManifestHash),
543
+ set(wm, "updatedAt", p.updatedAt),
544
+ ];
545
+ });
546
+
547
+ /** Record a deep-verify verdict (the §5.5 replay-on-suspicion op's committed outcome). */
548
+ export const nomosRecordDeepVerify = directive("nomosRecordDeepVerify")
549
+ .ensures(NomosDeepVerify)
550
+ .payload(
551
+ z.object({
552
+ shard: z.string().regex(/^s\d+$/),
553
+ frontier: z.string(),
554
+ head: z.string(),
555
+ verdict: z.string().min(1),
556
+ detail: z.string(),
557
+ checkedAt: z.string(),
558
+ }),
559
+ )
560
+ .plan((p) => {
561
+ const row = instance(NomosDeepVerify, deepVerifyRowId(p.shard));
562
+ return [
563
+ withMarker(set(row, "shard", p.shard), "ensures"),
564
+ set(row, "frontier", p.frontier),
565
+ set(row, "head", p.head),
566
+ set(row, "verdict", p.verdict),
567
+ set(row, "detail", p.detail),
568
+ set(row, "checkedAt", p.checkedAt),
569
+ ];
570
+ });
571
+
572
+ /**
573
+ * SEAL A CHECKPOINT (§5.5, slice 4): bind the held per-shard frontiers + the
574
+ * subtotal state hash to the next sequence point. Host-authored on the cadence
575
+ * (the worker counts admitted deltas against the policy dial) or explicitly
576
+ * (`POST /v1/workspaces/<coordinator>/checkpoint`) — but gate-verified either
577
+ * way: the `nomosCheckpointGate` invariant pins every claimed frontier to the
578
+ * HELD `NomosSummaryFrontier` watermark (`pre:` reads) and the sequence to
579
+ * held-head + 1. A seal can never claim a frontier the delta lane never
580
+ * recorded, and never replay/skip a sequence point.
581
+ */
582
+ export const nomosCheckpointSeal = directive("nomosCheckpointSeal")
583
+ .ensures(NomosCheckpointSeal)
584
+ .payload(
585
+ z.object({
586
+ seq: z.number().int().min(1),
587
+ frontiers: z
588
+ .array(z.object({ shard: z.string().regex(/^s\d+$/), frontier: z.string().min(1) }))
589
+ .min(1)
590
+ .max(4096),
591
+ stateHash: z.string().min(1),
592
+ sealedAt: z.string(),
593
+ }),
594
+ )
595
+ .plan((p) => {
596
+ const seen = new Set<string>();
597
+ for (const f of p.frontiers) {
598
+ if (seen.has(f.shard)) throw new Error(`seal-duplicate: shard '${f.shard}' appears twice in one seal`);
599
+ seen.add(f.shard);
600
+ }
601
+ const canonical = canonicalSealFrontiers(p.frontiers);
602
+ const ops: PlannedOp[] = [];
603
+ // The numbered seal row AND the head pointer — same aggregate, two deterministic ids.
604
+ for (const id of [checkpointSealRowId(p.seq), NOMOS_CHECKPOINT_HEAD_ID]) {
605
+ const row = instance(NomosCheckpointSeal, id);
606
+ ops.push(
607
+ withMarker(set(row, "seq", p.seq), "ensures"),
608
+ set(row, "frontiers", canonical),
609
+ set(row, "stateHash", p.stateHash),
610
+ set(row, "sealedAt", p.sealedAt),
611
+ );
612
+ }
613
+ return ops;
614
+ });
615
+
616
+ // ── §5.2 — THE DELTA (the one Order the lane rides) ──────────────────────────────────
617
+
618
+ const summarySubtotalSchema = z.object({
619
+ readId: z.string().min(1),
620
+ group: z.string(), // "" = the grand-total group
621
+ // SLICE 8: `min`/`max` join the lane. Their `prev`/`value` are NULLABLE — null =
622
+ // "no contributing member" (the extremum.ts empty-group law; 0 is a legitimate
623
+ // extremum). The PLAN refuses a null on any additive kind (typed) — nullability
624
+ // is the extremum kinds' vocabulary, never a loosening of counts/sums/rows.
625
+ kind: z.enum(["count", "sum", "rows", "min", "max"]),
626
+ prev: z.number().int().nullable(), // the CLAIMED previous absolute (gate-verified against held)
627
+ value: z.number().int().nullable(), // the CLAIMED new absolute (plan-verified against prev + carried evidence)
628
+ /**
629
+ * THE DECLARED RETRACTION (slice 8, extremum kinds only): the covered range
630
+ * RETRACTED the extremum (a contributing row left/worsened — the non-monotone
631
+ * case), so `value` is the shard's SUFFIX RE-DERIVATION (its maintained
632
+ * projection extremum at `toOid`), not an extremize over `prev` + candidates.
633
+ * The plan REFUSES an undeclared non-monotone chain (a silent retraction can
634
+ * never fold); a declared one folds as a frontier-pinned, deep-verifiable
635
+ * fact — exactly the trust-chain posture of the strike-bearing close (§5.5).
636
+ */
637
+ rederived: z.boolean().optional(),
638
+ });
639
+
640
+ const summaryEventSchema = z.object({
641
+ oid: z.string().min(1), // the shard commit (content address — deep-verify resolves it)
642
+ intentId: z.string(),
643
+ // Additive kinds: `delta` is the increment. EXTREMUM kinds (slice 8): `delta`
644
+ // is the POST-INTENT ABSOLUTE extremum of the bucket (null = the group emptied)
645
+ // — the evidence chain the plan walks for monotonicity. Null on an additive
646
+ // kind refuses in the plan (typed).
647
+ deltas: z
648
+ .array(z.object({ readId: z.string().min(1), group: z.string(), delta: z.number().int().nullable() }))
649
+ .max(64),
650
+ });
651
+
652
+ export const nomosPropagateSummary = directive("nomosPropagateSummary")
653
+ .ensures(NomosSummarySubtotal)
654
+ .payload(
655
+ z.object({
656
+ shard: z.string().regex(/^s\d+$/),
657
+ fromOid: z.string(), // "" = the shard's first delta (a virgin coordinator record)
658
+ toOid: z.string().min(1),
659
+ readManifestHash: z.string(),
660
+ propagatedAt: z.string(),
661
+ subtotals: z.array(summarySubtotalSchema).min(1).max(1024),
662
+ events: z.array(summaryEventSchema).max(4096),
663
+ }),
664
+ )
665
+ .plan((p) => {
666
+ // ── THE GATE RECOMPUTATION, arithmetic half (ruling 4 — day one, in the law) ──
667
+ // Pure over the payload. ADDITIVE kinds (count/sum/rows): every claimed absolute
668
+ // must equal the claimed previous plus the carried events' deltas. EXTREMUM
669
+ // kinds (slice 8 — min/max): the carried events are POST-INTENT ABSOLUTES; the
670
+ // plan walks the chain prev → e1 → … → en and verifies EXTREMIZE-ON-MONOTONE
671
+ // (every step only ever improves the extremum, and the final step IS the
672
+ // claim); a NON-MONOTONE step (a retraction — the extremum's row left or
673
+ // worsened) folds ONLY when the subtotal DECLARES it (`rederived: true`, the
674
+ // shard's suffix re-derivation) — silent retraction is a typed refusal, and a
675
+ // declared one stays deep-verifiable (mount the shard, recount, byte-compare).
676
+ // Every carried delta must land in a claimed subtotal (no hidden effects). The
677
+ // held-state half — fromOid == the recorded frontier, claimed prev == the held
678
+ // subtotal — is the `nomosSummaryGate` invariant's `pre:` reads. Together:
679
+ // held + carried evidence ⇒ claimed, or refuse.
680
+ if (p.fromOid === p.toOid) {
681
+ throw new Error(`summary-empty: fromOid and toOid are both '${p.toOid}' — an empty range propagates nothing`);
682
+ }
683
+ const isExtremum = (kind: string) => kind === "min" || kind === "max";
684
+ const claimed = new Map<
685
+ string,
686
+ { kind: string; prev: number | null; value: number | null; sum: number; running: number | null; touched: boolean; nonMonotone: boolean }
687
+ >();
688
+ for (const s of p.subtotals) {
689
+ const key = summaryBucket(s.readId, s.group);
690
+ if (claimed.has(key)) throw new Error(`summary-duplicate: subtotal (${s.readId}, '${s.group}') claimed twice`);
691
+ if (!isExtremum(s.kind) && (s.prev === null || s.value === null)) {
692
+ throw new Error(`summary-null:${s.readId}: a '${s.kind}' subtotal carries null — null is the extremum kinds' empty-group vocabulary, never an additive value`);
693
+ }
694
+ claimed.set(key, { kind: s.kind, prev: s.prev, value: s.value, sum: 0, running: s.prev, touched: false, nonMonotone: false });
695
+ }
696
+ for (const ev of p.events) {
697
+ for (const d of ev.deltas) {
698
+ const c = claimed.get(summaryBucket(d.readId, d.group));
699
+ if (c === undefined) {
700
+ throw new Error(
701
+ `summary-incomplete: event ${ev.oid.slice(0, 10)} carries a delta for (${d.readId}, '${d.group}') ` +
702
+ `but no subtotal claims that group — every touched group must carry its absolute`,
703
+ );
704
+ }
705
+ if (isExtremum(c.kind)) {
706
+ // The carried value is the post-intent ABSOLUTE; monotone = it only improves.
707
+ const v = d.delta;
708
+ const improves =
709
+ v !== null && (c.running === null || (c.kind === "min" ? v <= c.running : v >= c.running));
710
+ if (!improves && !(v === null && c.running === null)) c.nonMonotone = true;
711
+ c.running = v;
712
+ c.touched = true;
713
+ } else {
714
+ if (d.delta === null) {
715
+ throw new Error(`summary-null:${d.readId}: a '${c.kind}' event delta is null — only extremum kinds carry the empty-group null`);
716
+ }
717
+ c.sum += d.delta;
718
+ }
719
+ }
720
+ }
721
+ for (const s of p.subtotals) {
722
+ const c = claimed.get(summaryBucket(s.readId, s.group))!;
723
+ if (isExtremum(s.kind)) {
724
+ const final = c.touched ? c.running : c.prev;
725
+ if (final !== s.value) {
726
+ throw new Error(
727
+ `summary-mismatch: (${s.readId}, '${s.group}') claims ${s.value === null ? "null" : s.value} but the ` +
728
+ `carried evidence chain ends at ${final === null ? "null" : final} — the evidence does not reproduce the claim`,
729
+ );
730
+ }
731
+ if (c.nonMonotone && s.rederived !== true) {
732
+ throw new Error(
733
+ `extremum-retraction:${s.readId}:${s.group}: the covered range retracts the ${s.kind} (a non-monotone ` +
734
+ `step) — a silent retraction never folds. Re-derive the absolute from the shard's projection and ` +
735
+ `declare it (rederived: true); deep-verify remains the recourse`,
736
+ );
737
+ }
738
+ } else if ((s.prev as number) + c.sum !== s.value) {
739
+ throw new Error(
740
+ `summary-mismatch: (${s.readId}, '${s.group}') claims ${s.value} but prev ${s.prev} + ` +
741
+ `carried deltas ${c.sum} = ${(s.prev as number) + c.sum} — the evidence does not reproduce the claim`,
742
+ );
743
+ }
744
+ }
745
+ const ops = [];
746
+ for (const s of p.subtotals) {
747
+ const row = instance(NomosSummarySubtotal, summarySubtotalRowId(p.shard, s.readId, s.group));
748
+ ops.push(
749
+ withMarker(set(row, "readId", s.readId), "ensures"),
750
+ set(row, "group", s.group),
751
+ set(row, "shard", p.shard),
752
+ set(row, "kind", s.kind),
753
+ set(row, "bucket", summaryBucket(s.readId, s.group)),
754
+ set(row, "value", s.value ?? 0),
755
+ set(row, "frontier", p.toOid),
756
+ // EXTREMUM kinds carry the emptiness FLAG (0 is a legitimate extremum);
757
+ // additive rows never set the field — byte-identical to the slice-3 shape.
758
+ ...(isExtremum(s.kind) ? [set(row, "empty", s.value === null ? 1 : 0)] : []),
759
+ );
760
+ }
761
+ const wm = instance(NomosSummaryFrontier, summaryFrontierRowId(p.shard));
762
+ ops.push(
763
+ withMarker(set(wm, "shard", p.shard), "ensures"),
764
+ set(wm, "frontier", p.toOid),
765
+ set(wm, "readManifestHash", p.readManifestHash),
766
+ set(wm, "updatedAt", p.propagatedAt),
767
+ );
768
+ return ops;
769
+ });
770
+
771
+ // ── helpers ───────────────────────────────────────────────────────────────────────────
772
+
773
+ const pascal = (s: string) =>
774
+ s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase());
775
+ const camel = (s: string) => {
776
+ const p = pascal(s);
777
+ return p.length ? p[0]!.toLowerCase() + p.slice(1) : p;
778
+ };
779
+
780
+ /** The derived placement directive's id for a packed axis type (`site` → `birthSite`). */
781
+ export function placementDirectiveId(axisType: string): string {
782
+ return `birth${pascal(axisType)}`;
783
+ }
784
+
785
+ /** The derived placement payload's home-key field for a packed axis (`site` → `siteId`). */
786
+ export function placementHomeKeyField(axisType: string): string {
787
+ return `${camel(axisType)}Id`;
788
+ }
789
+
790
+ // ── THE ROUTE TAG, engine-side (the §4 self-routing mint — pure JS, no wasm change) ──
791
+ //
792
+ // sha256 over UTF-8, byte-for-byte the client/edge tag (`workspace_routing.ts`
793
+ // `routeTagHexOfHomeKey`): the first 48 bits of sha256("nomos-route:" + homeKey) as
794
+ // 12 lowercase hex chars. Compact, dependency-free, deterministic — it must run
795
+ // inside the sealed QuickJS sandbox (no crypto, no node builtins).
796
+
797
+ const SHA256_K = [
798
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
799
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
800
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
801
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
802
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
803
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
804
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
805
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
806
+ ];
807
+
808
+ function utf8Bytes(s: string): number[] {
809
+ const out: number[] = [];
810
+ for (let i = 0; i < s.length; i++) {
811
+ let c = s.charCodeAt(i);
812
+ if (c < 0x80) out.push(c);
813
+ else if (c < 0x800) out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
814
+ else if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) {
815
+ const lo = s.charCodeAt(i + 1);
816
+ if (lo >= 0xdc00 && lo <= 0xdfff) {
817
+ c = 0x10000 + ((c - 0xd800) << 10) + (lo - 0xdc00);
818
+ out.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
819
+ i++;
820
+ } else out.push(0xef, 0xbf, 0xbd);
821
+ } else out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
822
+ }
823
+ return out;
824
+ }
825
+
826
+ /** sha256 hex of a string's UTF-8 bytes — pure, sandbox-safe, deterministic. */
827
+ export function sha256HexSync(text: string): string {
828
+ const msg = utf8Bytes(text);
829
+ const bitLen = msg.length * 8;
830
+ msg.push(0x80);
831
+ while (msg.length % 64 !== 56) msg.push(0);
832
+ // 64-bit big-endian length (length < 2^53 — exact in float)
833
+ const hi = Math.floor(bitLen / 0x100000000);
834
+ msg.push((hi >>> 24) & 0xff, (hi >>> 16) & 0xff, (hi >>> 8) & 0xff, hi & 0xff);
835
+ msg.push((bitLen >>> 24) & 0xff, (bitLen >>> 16) & 0xff, (bitLen >>> 8) & 0xff, bitLen & 0xff);
836
+ const h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
837
+ const w = new Array<number>(64);
838
+ const rotr = (x: number, n: number) => (x >>> n) | (x << (32 - n));
839
+ for (let off = 0; off < msg.length; off += 64) {
840
+ for (let i = 0; i < 16; i++) {
841
+ w[i] = ((msg[off + 4 * i]! << 24) | (msg[off + 4 * i + 1]! << 16) | (msg[off + 4 * i + 2]! << 8) | msg[off + 4 * i + 3]!) >>> 0;
842
+ }
843
+ for (let i = 16; i < 64; i++) {
844
+ const s0 = rotr(w[i - 15]!, 7) ^ rotr(w[i - 15]!, 18) ^ (w[i - 15]! >>> 3);
845
+ const s1 = rotr(w[i - 2]!, 17) ^ rotr(w[i - 2]!, 19) ^ (w[i - 2]! >>> 10);
846
+ w[i] = (w[i - 16]! + s0 + w[i - 7]! + s1) >>> 0;
847
+ }
848
+ let [a, b, c, d, e, f, g, hh] = h as [number, number, number, number, number, number, number, number];
849
+ for (let i = 0; i < 64; i++) {
850
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
851
+ const ch = (e & f) ^ (~e & g);
852
+ const t1 = (hh + S1 + ch + SHA256_K[i]! + w[i]!) >>> 0;
853
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
854
+ const maj = (a & b) ^ (a & c) ^ (b & c);
855
+ const t2 = (S0 + maj) >>> 0;
856
+ hh = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0;
857
+ }
858
+ h[0] = (h[0]! + a) >>> 0; h[1] = (h[1]! + b) >>> 0; h[2] = (h[2]! + c) >>> 0; h[3] = (h[3]! + d) >>> 0;
859
+ h[4] = (h[4]! + e) >>> 0; h[5] = (h[5]! + f) >>> 0; h[6] = (h[6]! + g) >>> 0; h[7] = (h[7]! + hh) >>> 0;
860
+ }
861
+ return h.map((x) => x.toString(16).padStart(8, "0")).join("");
862
+ }
863
+
864
+ /** The 48-bit route tag of a home key — agrees byte-for-byte with the client/edge tag. */
865
+ export function routeTagHexSync(homeKey: string): string {
866
+ return sha256HexSync(`nomos-route:${homeKey}`).slice(0, 12);
867
+ }
868
+
869
+ /** The 48-bit tag slot of a minted id (the UUIDv7 leading 12 hex), or undefined. */
870
+ function tagSlotOfMintedId(id: string): string | undefined {
871
+ const i = id.indexOf("_");
872
+ if (i <= 0) return undefined;
873
+ const body = id.slice(i + 1).replace(/-/g, "").toLowerCase();
874
+ return /^[0-9a-f]{32}$/.test(body) ? body.slice(0, 12) : undefined;
875
+ }
876
+
877
+ /**
878
+ * ROUTE-TAGGED IN-PLAN MINTS (§4, the slice-2 boundary closed): wrap the sealed
879
+ * sandbox's `globalThis.plan` and `globalThis.nomos.mint` so that, WHILE a routed
880
+ * directive's plan runs, an in-plan `create(Agg)` mint of a PACKED-HOMED aggregate
881
+ * folds the intent's home ROUTE TAG into the UUIDv7 timestamp slot. Called by the
882
+ * GENERATED ENTRY of a taxonomy-bearing package, at lump top-level eval — BEFORE the
883
+ * engine freezes `globalThis` (the same window `registerEngine` uses for its own
884
+ * assignments). Determinism: the tagged mint consumes the SAME 19 captured rng draws
885
+ * a plain mint consumes and reads no clock — a replayed plan re-mints byte-identical
886
+ * ids. Outside the sealed sandbox (compile-lane imports, tests) this is a no-op.
887
+ */
888
+ export function installRouteTaggedMint(spec: ShardingLawSpec): void {
889
+ const routes = new Map((spec.routes ?? []).map((r) => [r.directive, r]));
890
+ const homes = spec.homes ?? {};
891
+ if (routes.size === 0 || Object.keys(homes).length === 0) return;
892
+ const g = globalThis as {
893
+ plan?: (job: unknown) => unknown;
894
+ nomos?: { mint(typeTag: string): string; [k: string]: unknown };
895
+ __ports?: { rng(): number };
896
+ };
897
+ const prevPlan = g.plan;
898
+ const baseNomos = g.nomos;
899
+ const ports = g.__ports;
900
+ if (typeof prevPlan !== "function" || !baseNomos || typeof baseNomos.mint !== "function" || !ports) {
901
+ return; // not the sealed sandbox — nothing to install
902
+ }
903
+ let currentTag: string | null = null;
904
+ const nib = () => Math.floor(ports.rng() * 16).toString(16);
905
+ const nibs = (n: number) => { let s = ""; for (let i = 0; i < n; i++) s += nib(); return s; };
906
+ const taggedMint = (typeTag: string): string => {
907
+ if (currentTag === null || homes[String(typeTag)] === undefined) return baseNomos.mint(typeTag);
908
+ const tg = currentTag;
909
+ const variant = "89ab".charAt(Math.floor(ports.rng() * 4));
910
+ return (
911
+ String(typeTag) + "_" + tg.slice(0, 8) + "-" + tg.slice(8, 12) + "-7" + nibs(3) +
912
+ "-" + variant + nibs(3) + "-" + nibs(12)
913
+ );
914
+ };
915
+ g.nomos = Object.freeze({ ...baseNomos, mint: taggedMint });
916
+ g.plan = (job: unknown) => {
917
+ const intent = (job as { intent?: { directiveId?: string; payload?: Record<string, unknown> } } | null)?.intent ?? {};
918
+ const route = typeof intent.directiveId === "string" ? routes.get(intent.directiveId) : undefined;
919
+ currentTag = null;
920
+ if (route !== undefined) {
921
+ const v = intent.payload?.[route.key];
922
+ if (typeof v === "string" && v.length > 0) {
923
+ currentTag = route.via === "axis" ? routeTagHexSync(v) : (tagSlotOfMintedId(v) ?? null);
924
+ }
925
+ }
926
+ try {
927
+ return prevPlan(job);
928
+ } finally {
929
+ currentTag = null;
930
+ }
931
+ };
932
+ }
933
+
934
+ /**
935
+ * Build the derived sharding-law module for a taxonomy. Deterministic in `spec`
936
+ * (the generated entry calls it with compile-derived literals, so the lump bytes —
937
+ * and the domain hash — move iff the taxonomy moves).
938
+ */
939
+ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown> {
940
+ const mod: Record<string, unknown> = {
941
+ NomosShardAssignment,
942
+ NomosShardIdentity,
943
+ NomosHomeReceipt,
944
+ NomosShardRegistry,
945
+ NomosShardPolicy,
946
+ NomosSummarySubtotal,
947
+ NomosSummaryFrontier,
948
+ NomosDeepVerify,
949
+ NomosCheckpointSeal,
950
+ nomosDeclareShardIdentity,
951
+ nomosReceiveAssignment,
952
+ nomosOpenShard,
953
+ nomosSetShardPolicy,
954
+ nomosPropagateSummary,
955
+ nomosAmendSummaryFrontier,
956
+ nomosRecordDeepVerify,
957
+ nomosCheckpointSeal,
958
+ nomosSplitShard,
959
+ nomosSealHandoff,
960
+ nomosSealHome,
961
+ nomosShardAssignmentByHomeKey: query("nomosShardAssignmentByHomeKey")
962
+ .key("homeKey")
963
+ .returns(NomosShardAssignment),
964
+ nomosShardIdentityByScope: query("nomosShardIdentityByScope")
965
+ .key("scope")
966
+ .returns(NomosShardIdentity),
967
+ nomosSummarySubtotalByShard: query("nomosSummarySubtotalByShard")
968
+ .key("shard")
969
+ .returns(NomosSummarySubtotal),
970
+ nomosShardRegistryByStatus: query("nomosShardRegistryByStatus")
971
+ .key("status")
972
+ .returns(NomosShardRegistry),
973
+ // THE ESTATE TOTAL (§5.1): an ORDINARY maintained sum over committed subtotal
974
+ // rows, grouped by `bucket` = readId group — O(1) by #31. A scoped read's
975
+ // estate value = the coordinator's local tally + this sum at its bucket.
976
+ nomosEstateSummary: sum("nomosEstateSummary", "value").of(NomosSummarySubtotal).by("bucket"),
977
+ // THE ESTATE EXTREMUMS (§5.2, slice 8): ORDINARY maintained min/max over the
978
+ // SAME committed subtotal rows, by bucket — extremums over disjoint shards
979
+ // compose by extremizing per-shard extremums, so the estate value is O(1) off
980
+ // the projection's long-maintained extrema lane (the #47 RPC reads it). Rows
981
+ // flagged `empty:1` (a shard whose group has no contributing member) are
982
+ // EXCLUDED — an empty group has no extremum, and 0 is a legitimate value.
983
+ // Buckets partition by readId, so additive rows never pollute an extremum
984
+ // read's bucket. A scoped min/max's estate value = extremize(the
985
+ // coordinator's local extremum, this read at its bucket).
986
+ nomosEstateExtremumMin: min("nomosEstateExtremumMin", "value")
987
+ .of(NomosSummarySubtotal)
988
+ .where((p) => p.field("empty").ne(1))
989
+ .by("bucket"),
990
+ nomosEstateExtremumMax: max("nomosEstateExtremumMax", "value")
991
+ .of(NomosSummarySubtotal)
992
+ .where((p) => p.field("empty").ne(1))
993
+ .by("bucket"),
994
+ };
995
+
996
+ // ── §5.2 step 1+2 — THE SUMMARY GATE (held state versus the claims, via `pre:`) ──
997
+ // The plan above already proved claimed = claimedPrev + Σ carried deltas (pure
998
+ // arithmetic). This invariant pins the claims to HELD law-state, read PRE-APPLY
999
+ // (the executor's `pre:` namespace — the plan overwrites these very rows):
1000
+ // * contiguity — fromOid must equal the recorded per-shard frontier (a gap,
1001
+ // reorder, or fork refuses typed `frontier-gap:<expected>`; the shard re-emits
1002
+ // from there — the suffix re-derivation lane);
1003
+ // * recomputation — every claimed `prev` must equal the held subtotal value
1004
+ // (`summary-mismatch:…`): held + carried events ⇒ claimed, byte-compared.
1005
+ mod["nomosSummaryGate_nomosPropagateSummary"] = workspaceInvariant("nomosSummaryGate:nomosPropagateSummary")
1006
+ .on("nomosPropagateSummary")
1007
+ .reads(({ intent }) => {
1008
+ const shard = String(intent["shard"] ?? "");
1009
+ const subtotals = Array.isArray(intent["subtotals"]) ? (intent["subtotals"] as { readId?: unknown; group?: unknown }[]) : [];
1010
+ return [
1011
+ refAs("frontier", "NomosSummaryFrontier", `pre:${summaryFrontierRowId(shard)}`),
1012
+ ...subtotals.map((s, i) =>
1013
+ refAs(`prev${i}`, "NomosSummarySubtotal", `pre:${summarySubtotalRowId(shard, String(s.readId ?? ""), String(s.group ?? ""))}`),
1014
+ ),
1015
+ ];
1016
+ })
1017
+ .assert((snapshots, ctx) => {
1018
+ const intent = ctx?.intent ?? {};
1019
+ const held = snapshots["frontier"]?.["frontier"];
1020
+ const heldFrontier = typeof held === "string" ? held : "";
1021
+ const fromOid = typeof intent["fromOid"] === "string" ? (intent["fromOid"] as string) : "";
1022
+ if (fromOid !== heldFrontier) {
1023
+ return { reject: `frontier-gap:${heldFrontier === "" ? "genesis" : heldFrontier}` };
1024
+ }
1025
+ const subtotals = Array.isArray(intent["subtotals"]) ? (intent["subtotals"] as { readId?: unknown; group?: unknown; prev?: unknown; kind?: unknown }[]) : [];
1026
+ for (let i = 0; i < subtotals.length; i++) {
1027
+ const s = subtotals[i]!;
1028
+ const heldRow = snapshots[`prev${i}`] ?? {};
1029
+ const heldRaw = heldRow["value"];
1030
+ if (s.kind === "min" || s.kind === "max") {
1031
+ // EXTREMUM kinds (slice 8): the held prev is NULL when no row exists yet
1032
+ // OR the held row is flagged empty — null is vocabulary, never 0.
1033
+ const heldValue: number | null =
1034
+ typeof heldRaw !== "number" || heldRow["empty"] === 1 ? null : heldRaw;
1035
+ const claimedPrev = s.prev === null ? null : typeof s.prev === "number" ? s.prev : NaN;
1036
+ if (claimedPrev !== heldValue) {
1037
+ return {
1038
+ reject: `summary-mismatch:${String(s.readId)}:held:${heldValue === null ? "null" : heldValue}:claimedPrev:${String(s.prev)}`,
1039
+ };
1040
+ }
1041
+ continue;
1042
+ }
1043
+ const heldValue = typeof heldRaw === "number" ? heldRaw : 0;
1044
+ const claimedPrev = typeof s.prev === "number" ? s.prev : NaN;
1045
+ if (claimedPrev !== heldValue) {
1046
+ return {
1047
+ reject: `summary-mismatch:${String(s.readId)}:held:${heldValue}:claimedPrev:${String(s.prev)}`,
1048
+ };
1049
+ }
1050
+ }
1051
+ return { accept: true };
1052
+ });
1053
+
1054
+ // ── §5.5 — THE CHECKPOINT GATE (slice 4, ruling 1): a seal's claims are judged
1055
+ // against HELD law-state, pre-apply (the plan overwrites the head pointer):
1056
+ // * IDEMPOTENT RE-OFFER — a seal row already held with the SAME canonical
1057
+ // frontiers + state hash re-folds byte-identically (accept), even after
1058
+ // the lane moved on; DIFFERENT content under a held sequence refuses
1059
+ // typed `seal-exists:<seq>`;
1060
+ // * FRONTIER PINNING — every claimed frontier must equal the recorded
1061
+ // per-shard watermark (`seal-frontier-mismatch:<shard>:<held>`): a seal
1062
+ // can never bind a frontier the delta lane never committed;
1063
+ // * SEQUENCING — seq must be the held head + 1 (`seal-replay:<expected>`):
1064
+ // no skipped or replayed sequence points. ──
1065
+ mod["nomosCheckpointGate_nomosCheckpointSeal"] = workspaceInvariant("nomosCheckpointGate:nomosCheckpointSeal")
1066
+ .on("nomosCheckpointSeal")
1067
+ .reads(({ intent }) => {
1068
+ const frontiers = Array.isArray(intent["frontiers"]) ? (intent["frontiers"] as { shard?: unknown }[]) : [];
1069
+ const seq = typeof intent["seq"] === "number" ? (intent["seq"] as number) : "?";
1070
+ return [
1071
+ refAs("head", "NomosCheckpointSeal", `pre:${NOMOS_CHECKPOINT_HEAD_ID}`),
1072
+ refAs("own", "NomosCheckpointSeal", `pre:${checkpointSealRowId(seq)}`),
1073
+ ...frontiers.map((f, i) =>
1074
+ refAs(`front${i}`, "NomosSummaryFrontier", `pre:${summaryFrontierRowId(String(f?.shard ?? ""))}`),
1075
+ ),
1076
+ ];
1077
+ })
1078
+ .assert((snapshots, ctx) => {
1079
+ const intent = ctx?.intent ?? {};
1080
+ const claimedSeq = typeof intent["seq"] === "number" ? (intent["seq"] as number) : NaN;
1081
+ const frontiers = Array.isArray(intent["frontiers"])
1082
+ ? (intent["frontiers"] as { shard?: unknown; frontier?: unknown }[])
1083
+ : [];
1084
+ const canonical = canonicalSealFrontiers(
1085
+ frontiers.map((f) => ({ shard: String(f?.shard ?? ""), frontier: String(f?.frontier ?? "") })),
1086
+ );
1087
+ // 1. the idempotent re-offer: the SAME seal re-folds; different content refuses.
1088
+ const own = snapshots["own"] ?? {};
1089
+ if (typeof own["seq"] === "number") {
1090
+ return own["stateHash"] === intent["stateHash"] && own["frontiers"] === canonical
1091
+ ? { accept: true }
1092
+ : { reject: `seal-exists:${String(claimedSeq)}` };
1093
+ }
1094
+ // 2. every claimed frontier must be the HELD watermark.
1095
+ for (let i = 0; i < frontiers.length; i++) {
1096
+ const f = frontiers[i] ?? {};
1097
+ const heldRaw = snapshots[`front${i}`]?.["frontier"];
1098
+ const held = typeof heldRaw === "string" ? heldRaw : "";
1099
+ if (held === "" || held !== f.frontier) {
1100
+ return { reject: `seal-frontier-mismatch:${String(f.shard)}:${held === "" ? "genesis" : held}` };
1101
+ }
1102
+ }
1103
+ // 3. the next sequence point, exactly.
1104
+ const headSeq = typeof (snapshots["head"] ?? {})["seq"] === "number" ? ((snapshots["head"] ?? {})["seq"] as number) : 0;
1105
+ if (claimedSeq !== headSeq + 1) return { reject: `seal-replay:${headSeq + 1}` };
1106
+ return { accept: true };
1107
+ });
1108
+
1109
+ // ── §6 — THE MOVE GATE (slice 5): the lifecycle flips are judged against the HELD
1110
+ // rows (`pre:` reads — the plans overwrite them). A split may only move ACTIVE
1111
+ // homes it actually holds on the named source; a handoff seal may only land on
1112
+ // rows mid-move to exactly its target, with the map version bumped exactly once.
1113
+ // Every refusal is typed with the held state — the named remedy. ──
1114
+ const moveReads = ({ intent }: { intent: Record<string, unknown> }) => {
1115
+ const homeKeys = Array.isArray(intent["homeKeys"]) ? (intent["homeKeys"] as unknown[]) : [];
1116
+ return homeKeys.map((k, i) =>
1117
+ refAs(`held${i}`, "NomosShardAssignment", `pre:${shardAssignmentRowId(String(k ?? ""))}`),
1118
+ );
1119
+ };
1120
+ mod["nomosMoveGate_nomosSplitShard"] = workspaceInvariant("nomosMoveGate:nomosSplitShard")
1121
+ .on("nomosSplitShard")
1122
+ .reads(moveReads)
1123
+ .assert((snapshots, ctx) => {
1124
+ const intent = ctx?.intent ?? {};
1125
+ const fromShard = String(intent["fromShard"] ?? "");
1126
+ const homeKeys = Array.isArray(intent["homeKeys"]) ? (intent["homeKeys"] as unknown[]) : [];
1127
+ for (let i = 0; i < homeKeys.length; i++) {
1128
+ const held = snapshots[`held${i}`] ?? {};
1129
+ const heldShard = held["shard"];
1130
+ if (typeof heldShard !== "string" || heldShard.length === 0) {
1131
+ return { reject: `split-unplaced:${String(homeKeys[i])}` };
1132
+ }
1133
+ if (heldShard !== fromShard) return { reject: `wrong-source:${heldShard}` };
1134
+ const status = held["status"];
1135
+ if (typeof status === "string" && status !== "" && status !== "active") {
1136
+ return { reject: `not-active:${status}` };
1137
+ }
1138
+ }
1139
+ return { accept: true };
1140
+ });
1141
+ mod["nomosMoveGate_nomosSealHandoff"] = workspaceInvariant("nomosMoveGate:nomosSealHandoff")
1142
+ .on("nomosSealHandoff")
1143
+ .reads(moveReads)
1144
+ .assert((snapshots, ctx) => {
1145
+ const intent = ctx?.intent ?? {};
1146
+ const toShard = String(intent["toShard"] ?? "");
1147
+ const homeKeys = Array.isArray(intent["homeKeys"]) ? (intent["homeKeys"] as unknown[]) : [];
1148
+ const claimedVersion = typeof intent["mapVersion"] === "number" ? (intent["mapVersion"] as number) : NaN;
1149
+ for (let i = 0; i < homeKeys.length; i++) {
1150
+ const held = snapshots[`held${i}`] ?? {};
1151
+ const target = movingTargetOf(held["status"]);
1152
+ if (target === undefined) return { reject: `not-moving:${String(held["status"] ?? "unplaced")}` };
1153
+ if (target !== toShard) return { reject: `wrong-target:${target}` };
1154
+ const heldVersion = typeof held["mapVersion"] === "number" ? (held["mapVersion"] as number) : 1;
1155
+ if (claimedVersion !== heldVersion + 1) return { reject: `bad-map-version:${heldVersion + 1}` };
1156
+ }
1157
+ return { accept: true };
1158
+ });
1159
+
1160
+ // ── THE HOMING INVARIANT, per routed `via:"axis"` directive (#41 — slice 3a;
1161
+ // slice 3: judged against the shard's OWN ASSIGNMENT RECEIPTS, not a static
1162
+ // formula — least-loaded placement is law-state, never a pure function) ──
1163
+ // No identity declared (label absent/"") ⇒ vacuously holds — the coordinator, a
1164
+ // direct unsharded workspace, and every pre-#41 chain judge exactly as before (the
1165
+ // edge bailiff stays the outer guard there). A `via:"id"` misroute needs no invariant:
1166
+ // its target does not exist on the wrong shard, which the referential gate refuses.
1167
+ const axisByType = new Map(spec.axes.map((a) => [a.axisType, a]));
1168
+ for (const route of spec.routes ?? []) {
1169
+ if (route.via !== "axis") continue;
1170
+ const axis = axisByType.get(route.home);
1171
+ if (axis === undefined) {
1172
+ throw new Error(
1173
+ `workspace-sharding: route for directive '${route.directive}' homes on '${route.home}' ` +
1174
+ `but no packed axis of that type exists — the taxonomy and the routes disagree. ` +
1175
+ `Recompile from one source (nomos-compile derives both).`,
1176
+ );
1177
+ }
1178
+ const keyField = route.key;
1179
+ mod[`nomosWrongHome_${route.directive}`] = workspaceInvariant(`nomosWrongHome:${route.directive}`)
1180
+ .on(route.directive)
1181
+ .reads(({ intent }) => [
1182
+ refAs("shardIdentity", "NomosShardIdentity", NOMOS_SHARD_IDENTITY_ID),
1183
+ refAs("receipt", "NomosHomeReceipt", homeReceiptRowId(String(intent[keyField] ?? ""))),
1184
+ ])
1185
+ .assert((snapshots, ctx) => {
1186
+ const identity = snapshots["shardIdentity"] ?? {};
1187
+ const label = identity["label"];
1188
+ // No declared identity (or the coordinator's "") ⇒ this workspace makes no
1189
+ // homing claim ⇒ the invariant holds vacuously.
1190
+ if (typeof label !== "string" || label.length === 0) return { accept: true };
1191
+ const homeKey = ctx?.intent?.[keyField];
1192
+ if (typeof homeKey !== "string" || homeKey.length === 0) {
1193
+ // A routed directive whose home field is absent is an unroutable write —
1194
+ // refuse with the named remedy (the field is required by the route law).
1195
+ return { reject: `wrong-home:unroutable:${keyField}` };
1196
+ }
1197
+ const receipt = snapshots["receipt"] ?? {};
1198
+ const receiptShard = receipt["shard"];
1199
+ if (typeof receiptShard !== "string" || receiptShard.length === 0) {
1200
+ // No receipt: this chain has never been assigned the home. The typed remedy
1201
+ // points at the receipt leg; the edge bailiff (coordinator-sighted) NAMES
1202
+ // the correct workspace on the same refusal lane.
1203
+ return { reject: `wrong-home:unassigned:${homeKey}` };
1204
+ }
1205
+ return receiptShard === label
1206
+ ? { accept: true }
1207
+ : { reject: `wrong-home:${receiptShard}` };
1208
+ });
1209
+ }
1210
+
1211
+ // ── THE RECEIPT'S OWN HOMING (a receipt claiming a foreign shard never folds) ──
1212
+ mod["nomosReceiptHome_nomosReceiveAssignment"] = workspaceInvariant("nomosReceiptHome:nomosReceiveAssignment")
1213
+ .on("nomosReceiveAssignment")
1214
+ .reads(() => [refAs("shardIdentity", "NomosShardIdentity", NOMOS_SHARD_IDENTITY_ID)])
1215
+ .assert((snapshots, ctx) => {
1216
+ const label = snapshots["shardIdentity"]?.["label"];
1217
+ if (typeof label !== "string" || label.length === 0) return { accept: true };
1218
+ const claimed = ctx?.intent?.["shard"];
1219
+ return claimed === label ? { accept: true } : { reject: `wrong-home:receipt:${String(claimed)}` };
1220
+ });
1221
+
1222
+ for (const axis of spec.axes) {
1223
+ if (!Number.isInteger(axis.shardCount) || axis.shardCount < 1) {
1224
+ throw new Error(
1225
+ `workspace-sharding: axis '${axis.axisType}' has shardCount ${axis.shardCount} — ` +
1226
+ `declare the initial open-shard count as .pool(n) on the parent workspaceType.`,
1227
+ );
1228
+ }
1229
+ const keyField = placementHomeKeyField(axis.axisType);
1230
+ const axisType = axis.axisType;
1231
+ const dirId = placementDirectiveId(axis.axisType);
1232
+ // THE PLACEMENT (slice 3 — least-loaded): the shard rides IN the payload (picked
1233
+ // by the dispatching client/worker over committed load subtotals — law-state,
1234
+ // never a live read a plan takes); the row id is the home key (ONE active
1235
+ // assignment per home, by construction); a conflicting re-offer refuses typed.
1236
+ mod[dirId] = directive(dirId)
1237
+ .ensures(NomosShardAssignment)
1238
+ .payload(
1239
+ z.object({
1240
+ [keyField]: z.string().min(1),
1241
+ shard: z.string().regex(/^s\d+$/),
1242
+ placedAt: z.string(),
1243
+ }),
1244
+ )
1245
+ .plan((p: Record<string, string>) => {
1246
+ const homeKey = p[keyField]!;
1247
+ const row = instance(NomosShardAssignment, shardAssignmentRowId(homeKey));
1248
+ return [
1249
+ withMarker(set(row, "homeType", axisType), "ensures"),
1250
+ set(row, "homeKey", homeKey),
1251
+ set(row, "shard", p.shard!),
1252
+ set(row, "mapVersion", 1),
1253
+ set(row, "status", "active"),
1254
+ set(row, "placedAt", p.placedAt!),
1255
+ ];
1256
+ });
1257
+ // PLACEMENT UNIQUENESS (task #42 item 5): an idempotent re-offer (same shard)
1258
+ // re-folds the same row; a CONFLICTING one refuses typed with the held shard.
1259
+ // THE MOVE-LANE GUARD (slice 5): a re-offer may only reproduce a FRESH placement
1260
+ // byte-for-byte — a home mid-move refuses `home-moving:<target>` (park, retry),
1261
+ // and a home the §6 lane has ever moved (mapVersion > 1) refuses
1262
+ // `placement-exists:<heldShard>` (the plan would regress status/mapVersion to
1263
+ // their birth values; the routed client answers such re-offers from the held
1264
+ // row WITHOUT authoring).
1265
+ mod[`nomosPlacementUnique_${dirId}`] = workspaceInvariant(`nomosPlacementUnique:${dirId}`)
1266
+ .on(dirId)
1267
+ .reads(({ intent }) => [
1268
+ refAs("held", "NomosShardAssignment", `pre:${shardAssignmentRowId(String(intent[keyField] ?? ""))}`),
1269
+ ])
1270
+ .assert((snapshots, ctx) => {
1271
+ const held = snapshots["held"] ?? {};
1272
+ const heldShard = held["shard"];
1273
+ if (typeof heldShard !== "string" || heldShard.length === 0) return { accept: true };
1274
+ const target = movingTargetOf(held["status"]);
1275
+ if (target !== undefined) return { reject: `home-moving:${target}` };
1276
+ const heldVersion = typeof held["mapVersion"] === "number" ? (held["mapVersion"] as number) : 1;
1277
+ const offered = ctx?.intent?.["shard"];
1278
+ return offered === heldShard && heldVersion === 1
1279
+ ? { accept: true } // the idempotent re-offer — same home, same shard, fresh era
1280
+ : { reject: `placement-exists:${heldShard}` };
1281
+ });
1282
+ }
1283
+ return mod;
1284
+ }