@githolon/dsl 0.15.0 → 0.16.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/dart/lib/src/webview_bridge.dart +52 -0
- package/package.json +1 -1
- package/src/aggregate.ts +9 -1
- package/src/authoring.ts +10 -1
- package/src/codegen_dart.ts +22 -12
- package/src/codegen_dart_client.ts +54 -5
- package/src/codegen_ts.ts +32 -7
- package/src/compile_package_main.ts +90 -20
- package/src/ctx.ts +13 -0
- package/src/engine_entry.ts +22 -5
- package/src/fields.ts +44 -0
- package/src/flow_gate.ts +208 -0
- package/src/framework/capability.ts +19 -1
- package/src/framework/encryption_scope.ts +9 -1
- package/src/framework/genesis.ts +5 -1
- package/src/framework/relations.ts +23 -4
- package/src/framework/signing_identity.ts +1 -1
- package/src/framework/workspace_invariant.ts +29 -8
- package/src/framework/workspaces.ts +7 -0
- package/src/index.ts +24 -0
- package/src/manifest.ts +11 -17
- package/src/ops.ts +42 -1
- package/src/principal_evidence.ts +112 -0
- package/src/relation_sugar.ts +10 -4
- package/src/shape_compat.ts +208 -0
- package/src/usd.ts +131 -0
- package/src/usd_layers.ts +43 -0
- package/src/wire.ts +8 -0
- package/src/wire_encode.ts +75 -2
- package/src/workspace_sharding.ts +195 -17
- package/src/workspace_type.ts +1 -0
|
@@ -73,7 +73,10 @@ import { t } from "./fields.js";
|
|
|
73
73
|
import { Lww } from "./drivers.js";
|
|
74
74
|
import { directive } from "./directive.js";
|
|
75
75
|
import { set, strike, withMarker, type PlannedOp } from "./ops.js";
|
|
76
|
+
import { create } from "./authoring.js";
|
|
77
|
+
import { impureCapabilityAggregate } from "./framework/impure_capability.js";
|
|
76
78
|
import { query } from "./query.js";
|
|
79
|
+
import { read } from "./read.js";
|
|
77
80
|
import { sum } from "./sum.js";
|
|
78
81
|
import { min, max } from "./extremum.js";
|
|
79
82
|
import { refAs, workspaceInvariant } from "./framework/workspace_invariant.js";
|
|
@@ -156,6 +159,15 @@ export const checkpointSealRowId = (seq: number | string): string => `nomos-chec
|
|
|
156
159
|
/** The LATEST seal (the head pointer — same aggregate, well-known id; O(1) to find). */
|
|
157
160
|
export const NOMOS_CHECKPOINT_HEAD_ID = "nomos-checkpoint-seal:head";
|
|
158
161
|
|
|
162
|
+
/** THE MOVE OBLIGATION id (P6 — sharding orchestration as outbound offers): one obligation per
|
|
163
|
+
* (move, leg), keyed deterministically so re-entry is idempotent (the receipt-leg obligation re-folds
|
|
164
|
+
* to the same row, never a duplicate). `<from>-<to>` names the move; the leg names the step. */
|
|
165
|
+
export const moveObligationRowId = (fromShard: string, toShard: string, leg: string): string =>
|
|
166
|
+
`nomos-move-obligation:${fromShard}-${toShard}:${leg}`;
|
|
167
|
+
/** The move-leg sequence the coordinator emits as outbound offers (architecture/sharding_as_outbound_offers.md). */
|
|
168
|
+
export const MOVE_LEGS = ["receipt", "migrate", "seal", "open", "handoff"] as const;
|
|
169
|
+
export type MoveLeg = (typeof MOVE_LEGS)[number];
|
|
170
|
+
|
|
159
171
|
/** The estate-total bucket of a scoped read's group (`nomosEstateSummary` group key).
|
|
160
172
|
* `|` — printable (the kernel's canonical event decode refuses raw control bytes);
|
|
161
173
|
* unambiguous because a SCOPED read's id may not contain `|` (compile-refused);
|
|
@@ -201,6 +213,13 @@ export const NomosShardAssignment = aggregate("NomosShardAssignment", {
|
|
|
201
213
|
placedAt: t.string().merge(Lww),
|
|
202
214
|
});
|
|
203
215
|
|
|
216
|
+
/** The home's CURRENT assignment row, keyed by home key. Top-level so the §6 move directives can
|
|
217
|
+
* CAPTURED-READ it (P6 S3a — the map version is derived in law, not by the host). Also routed by
|
|
218
|
+
* the law module under the same id. */
|
|
219
|
+
export const nomosShardAssignmentByHomeKey = query("nomosShardAssignmentByHomeKey")
|
|
220
|
+
.key("homeKey")
|
|
221
|
+
.returns(NomosShardAssignment);
|
|
222
|
+
|
|
204
223
|
/**
|
|
205
224
|
* THE SHARD'S OWN IDENTITY — law-state in the shard's OWN chain (§3's "its assignment"
|
|
206
225
|
* fact, the slice-3a slice of it): which shard label this workspace IS. An Ensure
|
|
@@ -313,6 +332,13 @@ export const NomosSummarySubtotal = aggregate("NomosSummarySubtotal", {
|
|
|
313
332
|
empty: t.int().merge(Lww).optional(),
|
|
314
333
|
});
|
|
315
334
|
|
|
335
|
+
/** Per-(shard) committed subtotal rows, keyed by shard. Top-level so the derived placement directive can
|
|
336
|
+
* CAPTURED-READ each open shard's load (the `nomosShardRows` subtotal) to pick LEAST-LOADED IN THE PLAN
|
|
337
|
+
* (holon-native placement, P6 slice 3 — the host no longer picks). Also routed by the law module. */
|
|
338
|
+
export const nomosSummarySubtotalByShard = query("nomosSummarySubtotalByShard")
|
|
339
|
+
.key("shard")
|
|
340
|
+
.returns(NomosSummarySubtotal);
|
|
341
|
+
|
|
316
342
|
/** THE PER-SHARD DELTA FRONTIER watermark (§5.2 step 1 — contiguity law-state). */
|
|
317
343
|
export const NomosSummaryFrontier = aggregate("NomosSummaryFrontier", {
|
|
318
344
|
shard: t.string().merge(Lww),
|
|
@@ -394,7 +420,36 @@ export const nomosReceiveAssignment = directive("nomosReceiveAssignment")
|
|
|
394
420
|
// `shard = target` and the map version BUMPED (wrong-home refusals carry it;
|
|
395
421
|
// clients refresh and retry — §4's self-healing lane).
|
|
396
422
|
|
|
397
|
-
/**
|
|
423
|
+
/**
|
|
424
|
+
* THE MOVE OBLIGATION (P6 — sharding orchestration as outbound offers, slice S1): one
|
|
425
|
+
* impure-capability-shaped row per (move, leg), driven through the obligation lifecycle the
|
|
426
|
+
* KERNEL already enforces (`scan_requested` emits it on the outbox; `OrphanReceipt`/terminal-once
|
|
427
|
+
* gate its receipt). The coordinator EMITS the move as data; the bailiff delivers each leg + relays
|
|
428
|
+
* the shard's gate verdict as a receipt; each receipt mints the next leg (S2). It carries the
|
|
429
|
+
* `requested`-status lifecycle envelope (`scheduledAt`/`deadlineAt` are required ints — the reactor
|
|
430
|
+
* skips a row missing them) PLUS the move-leg params. S1 is ADDITIVE: `nomosSplitShard` mints the
|
|
431
|
+
* receipt-leg obligation ALONGSIDE the unchanged assignment flip; the host orchestration still runs
|
|
432
|
+
* the move (the obligation is observable on the outbox, not yet consumed). `architecture/sharding_as_outbound_offers.md`.
|
|
433
|
+
*/
|
|
434
|
+
export const NomosMoveObligation = impureCapabilityAggregate("NomosMoveObligation", {
|
|
435
|
+
// the move-leg params (the non-envelope fields scan_requested returns as the order's `params`):
|
|
436
|
+
fromShard: t.string().merge(Lww),
|
|
437
|
+
toShard: t.string().merge(Lww),
|
|
438
|
+
homeKeys: t.json().merge(Lww), // the moving home keys (the leg's work list)
|
|
439
|
+
leg: t.string().merge(Lww), // one of MOVE_LEGS
|
|
440
|
+
mapVersion: t.int().merge(Lww), // the law-derived map version (S3a), ferried leg-to-leg
|
|
441
|
+
// S3b — the target shard's ADMITTED set, accumulated migrate→seal (the cross-leg state seal-home
|
|
442
|
+
// needs to compute `missing` against the source chain). The LAW unions it (the adapter only ferries
|
|
443
|
+
// arrays it read off the obligation + the shard response — it never accumulates), so the host holds no
|
|
444
|
+
// cross-leg move state and an adapter restart re-derives everything from the on-chain obligation.
|
|
445
|
+
migratedIds: t.json().merge(Lww), // JSON string array; "[]" until the migrate leg's receipt
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
/** S1 receipt-leg obligation window (ms) — the leg's soft deadline past its scheduledAt. */
|
|
449
|
+
const MOVE_OBLIGATION_WINDOW_MS = 300_000;
|
|
450
|
+
|
|
451
|
+
/** Begin a move: flip each home's assignment to `moving:<toShard>` (gate-verified) AND mint the
|
|
452
|
+
* receipt-leg move obligation (P6 S1 — additive; observable on the outbox, not yet consumed). */
|
|
398
453
|
export const nomosSplitShard = directive("nomosSplitShard")
|
|
399
454
|
.mutates(NomosShardAssignment)
|
|
400
455
|
.payload(
|
|
@@ -403,6 +458,9 @@ export const nomosSplitShard = directive("nomosSplitShard")
|
|
|
403
458
|
toShard: z.string().regex(/^s\d+$/),
|
|
404
459
|
homeKeys: z.array(z.string().min(1)).min(1).max(64),
|
|
405
460
|
startedAt: z.string(),
|
|
461
|
+
// P6 S1: the caller-stamped epoch-ms the move begins (the obligation's scheduledAt). The
|
|
462
|
+
// timestamp rides IN the payload (the determinism doctrine — no clock in the plan).
|
|
463
|
+
scheduledAt: z.number().int().min(0),
|
|
406
464
|
}),
|
|
407
465
|
)
|
|
408
466
|
.plan((p) => {
|
|
@@ -412,10 +470,107 @@ export const nomosSplitShard = directive("nomosSplitShard")
|
|
|
412
470
|
if (new Set(p.homeKeys).size !== p.homeKeys.length) {
|
|
413
471
|
throw new Error("split-duplicate: a home key appears twice in one split");
|
|
414
472
|
}
|
|
415
|
-
|
|
473
|
+
const ops = p.homeKeys.flatMap((homeKey) => {
|
|
416
474
|
const row = instance(NomosShardAssignment, shardAssignmentRowId(homeKey));
|
|
417
475
|
return [set(row, "status", movingStatus(p.toShard))];
|
|
418
476
|
});
|
|
477
|
+
// P6 S3a — THE MAP VERSION IS DERIVED IN LAW, not by the host. CAPTURED-READ each moving home's
|
|
478
|
+
// CURRENT assignment (the pre-apply committed state; the gate re-derives at verify, CAS-refusing on
|
|
479
|
+
// drift) and bump: newMapVersion = max(1, max held) + 1 — the SAME arithmetic `_runSplitMigration`
|
|
480
|
+
// used to do in JS, now on the chain. The floor of 1 guarantees ≥2 (the handoff gate's minimum). The
|
|
481
|
+
// obligation carries it forward (nomosAdvanceMove ferries it leg-to-leg) so the host NEVER computes it.
|
|
482
|
+
let maxHeld = 1;
|
|
483
|
+
for (const homeKey of p.homeKeys) {
|
|
484
|
+
const rows = read(nomosShardAssignmentByHomeKey, { homeKey }) as Array<{ data?: { mapVersion?: number } }>;
|
|
485
|
+
for (const r of rows) {
|
|
486
|
+
const v = r?.data?.mapVersion;
|
|
487
|
+
if (typeof v === "number" && v > maxHeld) maxHeld = v;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const newMapVersion = maxHeld + 1;
|
|
491
|
+
// P6 S1/S2 — mint the receipt-leg obligation (the standard obligation API: `create` mints the id; its
|
|
492
|
+
// ops are ambient-harvested + merged with these returned ops). status:'requested' + the lifecycle
|
|
493
|
+
// envelope ⇒ the outbox (`scan_requested`) surfaces it as an outbound offer; the move-leg params ride
|
|
494
|
+
// as the order's params. Re-entry is idempotent via the MOVE GATE (a double `nomosSplitShard` refuses
|
|
495
|
+
// `not-active:moving:*` before this runs). The host orchestration is UNCHANGED — observable, not consumed.
|
|
496
|
+
create(NomosMoveObligation)
|
|
497
|
+
.set("requestedBy", "coordinator")
|
|
498
|
+
.set("requestedAt", p.scheduledAt)
|
|
499
|
+
.set("scheduledAt", p.scheduledAt)
|
|
500
|
+
.set("deadlineAt", p.scheduledAt + MOVE_OBLIGATION_WINDOW_MS)
|
|
501
|
+
.set("status", "requested")
|
|
502
|
+
.set("fromShard", p.fromShard)
|
|
503
|
+
.set("toShard", p.toShard)
|
|
504
|
+
.set("homeKeys", JSON.stringify(p.homeKeys)) // t.json() stores the stringified JSON
|
|
505
|
+
.set("leg", "receipt")
|
|
506
|
+
.set("mapVersion", newMapVersion)
|
|
507
|
+
.set("migratedIds", "[]"); // nothing migrated yet — the migrate leg's receipt accumulates it
|
|
508
|
+
return ops;
|
|
509
|
+
})
|
|
510
|
+
.reads(nomosShardAssignmentByHomeKey);
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* P6 S2 — THE MOVE RECEIPT (the leg sequence becomes LAW): a leg's receipt COMPLETES its obligation
|
|
514
|
+
* (a terminal status ⇒ the kernel's receipt gate enforces terminal-once + no-orphan, since
|
|
515
|
+
* NomosMoveObligation carries the full impure-capability envelope) AND MINTS THE NEXT leg's obligation —
|
|
516
|
+
* so the order receipt→migrate→seal→open→handoff lives on the chain, not in the host's `_runSplitMigration`
|
|
517
|
+
* JS. The bailiff offers this after delivering a leg + taking the shard's gate verdict (that wiring is S3;
|
|
518
|
+
* S2 drives it directly to prove the chain). The SEAL leg, when `missing` writes raced in on the source,
|
|
519
|
+
* re-mints `migrate` (the bounded re-migrate) instead of advancing to `open`. The LAST leg (handoff) mints
|
|
520
|
+
* nothing — the move is done. (architecture/sharding_as_outbound_offers.md)
|
|
521
|
+
*/
|
|
522
|
+
export const nomosAdvanceMove = directive("nomosAdvanceMove")
|
|
523
|
+
.mutates(NomosMoveObligation)
|
|
524
|
+
.payload(
|
|
525
|
+
z.object({
|
|
526
|
+
requestId: z.string().min(1), // the leg obligation being completed (its runtime-minted id)
|
|
527
|
+
leg: z.enum(MOVE_LEGS),
|
|
528
|
+
fromShard: z.string().regex(/^s\d+$/),
|
|
529
|
+
toShard: z.string().regex(/^s\d+$/),
|
|
530
|
+
homeKeys: z.array(z.string().min(1)).min(1).max(64),
|
|
531
|
+
mapVersion: z.number().int().min(2), // the law-derived version (S3a), ferried leg-to-leg
|
|
532
|
+
scheduledAt: z.number().int().min(0),
|
|
533
|
+
completedAt: z.number().int().min(0),
|
|
534
|
+
missing: z.array(z.string()).optional(), // seal leg: writes that raced in, still on the source
|
|
535
|
+
// S3b — the migrated-id ferry (the adapter passes through arrays it READ; the LAW unions them).
|
|
536
|
+
// `priorMigratedIds`: the current obligation's accumulated set (off the obligation). `migratedIds`:
|
|
537
|
+
// THIS leg's shard delta (off the migrate-in response; [] for non-migrate legs). The plan unions
|
|
538
|
+
// them onto the next obligation, so the seal leg's obligation carries the full admitted set.
|
|
539
|
+
priorMigratedIds: z.array(z.string()).default([]),
|
|
540
|
+
migratedIds: z.array(z.string()).default([]),
|
|
541
|
+
}),
|
|
542
|
+
)
|
|
543
|
+
.plan((p) => {
|
|
544
|
+
// THE RECEIPT — drive the current leg obligation TERMINAL (the kernel's terminal-once anchors on this
|
|
545
|
+
// terminal-status set; a second receipt on the same obligation refuses ReceiptOnTerminalObligation).
|
|
546
|
+
const cur = instance(NomosMoveObligation, p.requestId);
|
|
547
|
+
const ops = [
|
|
548
|
+
set(cur, "status", "completed"),
|
|
549
|
+
set(cur, "completedAt", p.completedAt),
|
|
550
|
+
set(cur, "closesObligation", p.requestId), // the closure link (the O(1) closure read)
|
|
551
|
+
];
|
|
552
|
+
// MINT THE NEXT LEG — the order is law. Seal + raced `missing` re-mints `migrate` (bounded re-migrate).
|
|
553
|
+
const idx = MOVE_LEGS.indexOf(p.leg);
|
|
554
|
+
let nextLeg: MoveLeg | undefined = MOVE_LEGS[idx + 1];
|
|
555
|
+
if (p.leg === "seal" && p.missing !== undefined && p.missing.length > 0) nextLeg = "migrate";
|
|
556
|
+
if (nextLeg !== undefined) {
|
|
557
|
+
// THE LAW UNIONS the migrated ids (the host never accumulates): the next obligation carries the
|
|
558
|
+
// accumulated admitted set forward, so the seal leg POSTs the full set + a re-migrate keeps growing it.
|
|
559
|
+
const accMigrated = [...new Set([...p.priorMigratedIds, ...p.migratedIds])].sort();
|
|
560
|
+
create(NomosMoveObligation)
|
|
561
|
+
.set("requestedBy", "coordinator")
|
|
562
|
+
.set("requestedAt", p.completedAt)
|
|
563
|
+
.set("scheduledAt", p.scheduledAt)
|
|
564
|
+
.set("deadlineAt", p.scheduledAt + MOVE_OBLIGATION_WINDOW_MS)
|
|
565
|
+
.set("status", "requested")
|
|
566
|
+
.set("fromShard", p.fromShard)
|
|
567
|
+
.set("toShard", p.toShard)
|
|
568
|
+
.set("homeKeys", JSON.stringify(p.homeKeys))
|
|
569
|
+
.set("leg", nextLeg)
|
|
570
|
+
.set("mapVersion", p.mapVersion) // carry the law-derived version forward (S3a)
|
|
571
|
+
.set("migratedIds", JSON.stringify(accMigrated)); // S3b — the accumulated admitted set
|
|
572
|
+
}
|
|
573
|
+
return ops;
|
|
419
574
|
});
|
|
420
575
|
|
|
421
576
|
/**
|
|
@@ -942,6 +1097,7 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
942
1097
|
NomosSummaryFrontier,
|
|
943
1098
|
NomosDeepVerify,
|
|
944
1099
|
NomosCheckpointSeal,
|
|
1100
|
+
NomosMoveObligation, // P6 S1 — the move-leg obligation `nomosSplitShard` emits
|
|
945
1101
|
nomosDeclareShardIdentity,
|
|
946
1102
|
nomosReceiveAssignment,
|
|
947
1103
|
nomosOpenShard,
|
|
@@ -951,17 +1107,14 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
951
1107
|
nomosRecordDeepVerify,
|
|
952
1108
|
nomosCheckpointSeal,
|
|
953
1109
|
nomosSplitShard,
|
|
1110
|
+
nomosAdvanceMove, // P6 S2 — the move receipt: completes a leg + mints the next
|
|
954
1111
|
nomosSealHandoff,
|
|
955
1112
|
nomosSealHome,
|
|
956
|
-
nomosShardAssignmentByHomeKey
|
|
957
|
-
.key("homeKey")
|
|
958
|
-
.returns(NomosShardAssignment),
|
|
1113
|
+
nomosShardAssignmentByHomeKey, // top-level (P6 S3a — captured-read by the move directives)
|
|
959
1114
|
nomosShardIdentityByScope: query("nomosShardIdentityByScope")
|
|
960
1115
|
.key("scope")
|
|
961
1116
|
.returns(NomosShardIdentity),
|
|
962
|
-
nomosSummarySubtotalByShard
|
|
963
|
-
.key("shard")
|
|
964
|
-
.returns(NomosSummarySubtotal),
|
|
1117
|
+
nomosSummarySubtotalByShard, // top-level (P6 slice 3 — captured-read by the placement directive)
|
|
965
1118
|
nomosShardRegistryByStatus: query("nomosShardRegistryByStatus")
|
|
966
1119
|
.key("status")
|
|
967
1120
|
.returns(NomosShardRegistry),
|
|
@@ -1228,27 +1381,52 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
1228
1381
|
// by the dispatching client/worker over committed load subtotals — law-state,
|
|
1229
1382
|
// never a live read a plan takes); the row id is the home key (ONE active
|
|
1230
1383
|
// assignment per home, by construction); a conflicting re-offer refuses typed.
|
|
1384
|
+
// HOLON-NATIVE PLACEMENT (P6 slice 3): the PLAN picks least-loaded — the host no longer chooses the
|
|
1385
|
+
// shard. `shard` is GONE from the payload. The plan CAPTURED-READS the home's held assignment (idempotent
|
|
1386
|
+
// reuse) and each open shard's committed `nomosShardRows` subtotal (least-loaded, ties → lowest ordinal),
|
|
1387
|
+
// both #58 declared reads the gate re-derives at verify (CAS — a load that moved refuses typed). Pure over
|
|
1388
|
+
// committed coordinator state ⇒ every peer places identically. (architecture/holon_native_routing.md)
|
|
1389
|
+
const pool = axis.shardCount;
|
|
1231
1390
|
mod[dirId] = directive(dirId)
|
|
1232
1391
|
.ensures(NomosShardAssignment)
|
|
1233
1392
|
.payload(
|
|
1234
1393
|
z.object({
|
|
1235
1394
|
[keyField]: z.string().min(1),
|
|
1236
|
-
shard: z.string().regex(/^s\d+$/),
|
|
1237
1395
|
placedAt: z.string(),
|
|
1238
1396
|
}),
|
|
1239
1397
|
)
|
|
1240
1398
|
.plan((p: Record<string, string>) => {
|
|
1241
1399
|
const homeKey = p[keyField]!;
|
|
1242
1400
|
const row = instance(NomosShardAssignment, shardAssignmentRowId(homeKey));
|
|
1401
|
+
// IDEMPOTENCY: a home already placed reuses ITS shard (re-offer is a no-op; the uniqueness
|
|
1402
|
+
// invariant guards a mid-move / moved home). A fresh home picks least-loaded over the open pool.
|
|
1403
|
+
const held = read(nomosShardAssignmentByHomeKey, { homeKey }) as Array<{ data?: { shard?: string } }>;
|
|
1404
|
+
const heldShard = held[0]?.data?.shard;
|
|
1405
|
+
let shard: string;
|
|
1406
|
+
if (typeof heldShard === "string" && heldShard) {
|
|
1407
|
+
shard = heldShard;
|
|
1408
|
+
} else {
|
|
1409
|
+
let best = "s0";
|
|
1410
|
+
let bestLoad = Infinity;
|
|
1411
|
+
for (let i = 0; i < pool; i++) {
|
|
1412
|
+
const label = `s${i}`;
|
|
1413
|
+
const subs = read(nomosSummarySubtotalByShard, { shard: label }) as Array<{ data?: { readId?: string; value?: number } }>;
|
|
1414
|
+
const sub = subs.find((r) => r?.data?.readId === NOMOS_SHARD_ROWS_READ);
|
|
1415
|
+
const load = typeof sub?.data?.value === "number" ? sub.data!.value! : 0;
|
|
1416
|
+
if (load < bestLoad) { best = label; bestLoad = load; } // strict < ⇒ ties keep the lowest ordinal
|
|
1417
|
+
}
|
|
1418
|
+
shard = best;
|
|
1419
|
+
}
|
|
1243
1420
|
return [
|
|
1244
1421
|
withMarker(set(row, "homeType", axisType), "ensures"),
|
|
1245
1422
|
set(row, "homeKey", homeKey),
|
|
1246
|
-
set(row, "shard",
|
|
1423
|
+
set(row, "shard", shard),
|
|
1247
1424
|
set(row, "mapVersion", 1),
|
|
1248
1425
|
set(row, "status", "active"),
|
|
1249
1426
|
set(row, "placedAt", p.placedAt!),
|
|
1250
1427
|
];
|
|
1251
|
-
})
|
|
1428
|
+
})
|
|
1429
|
+
.reads(nomosShardAssignmentByHomeKey, nomosSummarySubtotalByShard);
|
|
1252
1430
|
// PLACEMENT UNIQUENESS (task #42 item 5): an idempotent re-offer (same shard)
|
|
1253
1431
|
// re-folds the same row; a CONFLICTING one refuses typed with the held shard.
|
|
1254
1432
|
// THE MOVE-LANE GUARD (slice 5): a re-offer may only reproduce a FRESH placement
|
|
@@ -1262,17 +1440,17 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
1262
1440
|
.reads(({ intent }) => [
|
|
1263
1441
|
refAs("held", "NomosShardAssignment", `pre:${shardAssignmentRowId(String(intent[keyField] ?? ""))}`),
|
|
1264
1442
|
])
|
|
1265
|
-
.assert((snapshots
|
|
1443
|
+
.assert((snapshots) => {
|
|
1266
1444
|
const held = snapshots["held"] ?? {};
|
|
1267
1445
|
const heldShard = held["shard"];
|
|
1268
|
-
if (typeof heldShard !== "string" || heldShard.length === 0) return { accept: true };
|
|
1446
|
+
if (typeof heldShard !== "string" || heldShard.length === 0) return { accept: true }; // a virgin home — place it
|
|
1269
1447
|
const target = movingTargetOf(held["status"]);
|
|
1270
1448
|
if (target !== undefined) return { reject: `home-moving:${target}` };
|
|
1271
1449
|
const heldVersion = typeof held["mapVersion"] === "number" ? (held["mapVersion"] as number) : 1;
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1450
|
+
// The plan REUSES the held shard for an already-placed home (holon-native placement, slice 3), so a
|
|
1451
|
+
// re-offer to a FRESH placement (v1) is byte-identical — admit. A MOVED home (v>1) would regress its
|
|
1452
|
+
// version to 1, so it refuses typed (the routed client answers such re-offers from the held row).
|
|
1453
|
+
return heldVersion === 1 ? { accept: true } : { reject: `placement-exists:${heldShard}` };
|
|
1276
1454
|
});
|
|
1277
1455
|
}
|
|
1278
1456
|
return mod;
|
package/src/workspace_type.ts
CHANGED
|
@@ -367,6 +367,7 @@ export function deriveHoming(
|
|
|
367
367
|
"NomosShardAssignment", "NomosShardIdentity", "NomosHomeReceipt", "NomosShardRegistry",
|
|
368
368
|
"NomosShardPolicy", "NomosSummarySubtotal", "NomosSummaryFrontier", "NomosDeepVerify",
|
|
369
369
|
"NomosCheckpointSeal",
|
|
370
|
+
"NomosMoveObligation", // P6 — the move-leg obligation is coordinator law-state (no homing t.ref)
|
|
370
371
|
]);
|
|
371
372
|
const childIdsAll = new Set([...types.values()].flatMap((t) => [...t.childIds]));
|
|
372
373
|
const coordinators = [...types.values()]
|