@githolon/runtime 0.112.8 → 0.112.11

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.
@@ -6587,7 +6587,8 @@ var NomosRuntimeComponent = (() => {
6587
6587
  }
6588
6588
  }));
6589
6589
  if (response.outcome.case === "failure") {
6590
- throw new Error(`${response.outcome.value.code}: ${response.outcome.value.message}`);
6590
+ throwUnnamedHistoryRequirement(response.outcome.value, mount.workspace);
6591
+ throwKernelFailure(response.outcome.value);
6591
6592
  }
6592
6593
  if (response.outcome.case !== "outbox") {
6593
6594
  throw new Error(`kernel returned '${response.outcome.case || "empty"}' while reading correspondence`);
@@ -6616,7 +6617,8 @@ var NomosRuntimeComponent = (() => {
6616
6617
  }
6617
6618
  }));
6618
6619
  if (response.outcome.case === "failure") {
6619
- throw new Error(`${response.outcome.value.code}: ${response.outcome.value.message}`);
6620
+ throwUnnamedHistoryRequirement(response.outcome.value, mount.workspace);
6621
+ throwKernelFailure(response.outcome.value);
6620
6622
  }
6621
6623
  if (response.outcome.case !== "outbox") {
6622
6624
  throw new Error(
@@ -9574,19 +9576,19 @@ var NomosRuntimeComponent = (() => {
9574
9576
 
9575
9577
  // ../src/runtime/workspace_naming.ts
9576
9578
  var WORKSPACE_NAME_SEP = "";
9577
- var keyedBirthNameOf = (namespace, key) => `${namespace}-${sha256Hex(new TextEncoder().encode(namespace + WORKSPACE_NAME_SEP + key)).slice(0, 40)}`;
9579
+ var keyedBirthNameOf = (namespace2, key) => `${namespace2}-${sha256Hex(new TextEncoder().encode(namespace2 + WORKSPACE_NAME_SEP + key)).slice(0, 40)}`;
9578
9580
  var RECEIVING_GROUP_NAME_PREFIX = "nomos-rg-";
9579
9581
  var receivingGroupNameOf = (principal) => `${RECEIVING_GROUP_NAME_PREFIX}${sha256Hex(new TextEncoder().encode(principal)).slice(0, 40)}`;
9580
- var isKeyedBirthOf = (namespace, workspace) => workspace.startsWith(`${namespace}-`);
9582
+ var isKeyedBirthOf = (namespace2, workspace) => workspace.startsWith(`${namespace2}-`);
9581
9583
  var WORKSPACE_NAME_REQUEST_INVALID = "nomos.runtime.workspace_name_request_invalid";
9582
9584
  function resolveWorkspaceName(command) {
9583
9585
  switch (command.birth.case) {
9584
9586
  case "keyed": {
9585
- const { namespace, key } = command.birth.value;
9586
- if (namespace.length === 0 || key.length === 0) {
9587
+ const { namespace: namespace2, key } = command.birth.value;
9588
+ if (namespace2.length === 0 || key.length === 0) {
9587
9589
  throw new Error(`${WORKSPACE_NAME_REQUEST_INVALID}: a keyed birth name needs both its namespace and its key`);
9588
9590
  }
9589
- return create(ResolvedWorkspaceNameSchema, { workspace: keyedBirthNameOf(namespace, key) });
9591
+ return create(ResolvedWorkspaceNameSchema, { workspace: keyedBirthNameOf(namespace2, key) });
9590
9592
  }
9591
9593
  case "receivingGroup": {
9592
9594
  const { principal } = command.birth.value;
@@ -11629,6 +11631,132 @@ var NomosRuntimeComponent = (() => {
11629
11631
  }
11630
11632
  };
11631
11633
 
11634
+ // ../src/runtime/peer_desk_store.ts
11635
+ var recordId = (namespace2, key) => `${namespace2.length}:${namespace2}${key}`;
11636
+ var copy2 = (record) => ({
11637
+ ...record,
11638
+ value: record.value.slice()
11639
+ });
11640
+ var MemoryPeerDeskStore = class {
11641
+ #records = /* @__PURE__ */ new Map();
11642
+ list(namespace2) {
11643
+ return [...this.#records.values()].filter((record) => record.namespace === namespace2).sort((left, right) => left.key.localeCompare(right.key)).map(copy2);
11644
+ }
11645
+ compareExchange(namespace2, key, expectedRevision, value) {
11646
+ const id = recordId(namespace2, key);
11647
+ const current = this.#records.get(id);
11648
+ if (current?.revision !== expectedRevision) {
11649
+ throw new Error(
11650
+ `peer desk record '${namespace2}/${key}' changed at revision ${current?.revision ?? "absent"}`
11651
+ );
11652
+ }
11653
+ if (value === void 0) {
11654
+ this.#records.delete(id);
11655
+ return void 0;
11656
+ }
11657
+ const next = Object.freeze({
11658
+ namespace: namespace2,
11659
+ key,
11660
+ revision: (current?.revision ?? 0n) + 1n,
11661
+ value: value.slice()
11662
+ });
11663
+ this.#records.set(id, next);
11664
+ return copy2(next);
11665
+ }
11666
+ };
11667
+
11668
+ // ../src/runtime/custody_receipts.ts
11669
+ var namespace = "nomos.peer-desk.custody-receipts/v1";
11670
+ var encoder = new TextEncoder();
11671
+ var decoder = new TextDecoder();
11672
+ var MAX_BASES = 256;
11673
+ var MAX_RECEIPTS = 4096;
11674
+ var CustodyReceipts = class {
11675
+ #store;
11676
+ #records = /* @__PURE__ */ new Map();
11677
+ #journals = /* @__PURE__ */ new Map();
11678
+ #receipts = /* @__PURE__ */ new Map();
11679
+ constructor(store = new MemoryPeerDeskStore()) {
11680
+ this.#store = store;
11681
+ for (const record of store.list(namespace)) {
11682
+ this.#records.set(record.key, record);
11683
+ try {
11684
+ if (record.key.startsWith("receipt:")) {
11685
+ const newline = record.value.indexOf(10);
11686
+ if (newline < 0) continue;
11687
+ const receipt = fromBinary(CustodyIntentAdmissionSchema, record.value.subarray(newline + 1));
11688
+ const header2 = JSON.parse(decoder.decode(record.value.subarray(0, newline)));
11689
+ if (receipt.outcome.case === "admitted" && typeof header2.generation === "string" && typeof header2.head === "string") this.#receipts.set(record.key, { ...header2, receipt });
11690
+ continue;
11691
+ }
11692
+ if (!record.key.startsWith("head:")) continue;
11693
+ const value = JSON.parse(decoder.decode(record.value));
11694
+ if (typeof value.head !== "string" || typeof value.generation !== "string" || !Array.isArray(value.bases) || !Array.isArray(value.keys) || value.bases.length > MAX_BASES || value.keys.length > MAX_RECEIPTS) continue;
11695
+ if (!value.bases.every((base) => typeof base === "string") || !value.keys.every((key) => typeof key === "string")) continue;
11696
+ this.#journals.set(record.key.slice(5), value);
11697
+ } catch {
11698
+ }
11699
+ }
11700
+ }
11701
+ covers(workspace, current, base) {
11702
+ const value = this.#journals.get(workspace);
11703
+ return value?.head === current && value.bases.includes(base);
11704
+ }
11705
+ receipt(workspace, current, change) {
11706
+ const value = this.#journals.get(workspace);
11707
+ if (value?.head !== current) return void 0;
11708
+ const stored = this.#receipts.get(`receipt:${workspace}:${sha256Hex(change)}`);
11709
+ if (stored?.generation !== value.generation) return void 0;
11710
+ if (stored.head !== value.head && !value.bases.includes(stored.head)) return void 0;
11711
+ return fromBinary(CustodyIntentAdmissionSchema, toBinary(CustodyIntentAdmissionSchema, stored.receipt));
11712
+ }
11713
+ record(workspace, before, after, receipts) {
11714
+ const old = this.#journals.get(workspace);
11715
+ const journalKey = `head:${workspace}`;
11716
+ const value = old?.head === before ? { ...old, head: after, bases: [...old.bases], keys: [...old.keys] } : { head: after, bases: [], keys: [], generation: String((this.#records.get(journalKey)?.revision ?? 0n) + 1n) };
11717
+ if (before && before !== after) value.bases.push(before);
11718
+ value.bases = [...new Set(value.bases)].slice(-MAX_BASES);
11719
+ const keys = new Set(value.keys);
11720
+ const discarded = new Set(old?.generation !== value.generation ? old?.keys : []);
11721
+ for (const { change, receipt } of receipts) {
11722
+ if (receipt.outcome.case !== "admitted") continue;
11723
+ const key = `receipt:${workspace}:${sha256Hex(change)}`;
11724
+ keys.delete(key);
11725
+ keys.add(key);
11726
+ discarded.delete(key);
11727
+ const header2 = encoder.encode(`${JSON.stringify({ generation: value.generation, head: after })}
11728
+ `);
11729
+ const body = toBinary(CustodyIntentAdmissionSchema, receipt);
11730
+ const bytes = new Uint8Array(header2.length + body.length);
11731
+ bytes.set(header2);
11732
+ bytes.set(body, header2.length);
11733
+ const record2 = this.#store.compareExchange(namespace, key, this.#records.get(key)?.revision, bytes);
11734
+ if (record2) this.#records.set(key, record2);
11735
+ this.#receipts.set(key, {
11736
+ generation: value.generation,
11737
+ head: after,
11738
+ receipt: fromBinary(CustodyIntentAdmissionSchema, body)
11739
+ });
11740
+ }
11741
+ const ordered = [...keys];
11742
+ for (const key of ordered.slice(0, -MAX_RECEIPTS)) discarded.add(key);
11743
+ value.keys = ordered.slice(-MAX_RECEIPTS);
11744
+ for (const key of discarded) {
11745
+ this.#store.compareExchange(namespace, key, this.#records.get(key)?.revision, void 0);
11746
+ this.#records.delete(key);
11747
+ this.#receipts.delete(key);
11748
+ }
11749
+ const record = this.#store.compareExchange(
11750
+ namespace,
11751
+ journalKey,
11752
+ this.#records.get(journalKey)?.revision,
11753
+ encoder.encode(JSON.stringify(value))
11754
+ );
11755
+ if (record) this.#records.set(journalKey, record);
11756
+ this.#journals.set(workspace, value);
11757
+ }
11758
+ };
11759
+
11632
11760
  // ../src/runtime/custodian_placement.ts
11633
11761
  var CustodianPlacement = class {
11634
11762
  #kernel;
@@ -11729,14 +11857,16 @@ var NomosRuntimeComponent = (() => {
11729
11857
  var CustodyAdmissionDesk = class {
11730
11858
  #kernel;
11731
11859
  #custody;
11732
- constructor(kernel, custody) {
11860
+ #receipts;
11861
+ constructor(kernel, custody, receipts = new CustodyReceipts()) {
11733
11862
  this.#kernel = kernel;
11734
11863
  this.#custody = custody;
11864
+ this.#receipts = receipts;
11735
11865
  }
11736
11866
  admit(request, command) {
11737
- const session = request.target?.session;
11738
- if (!session) throw new Error("custody admission requires an addressed runtime session");
11739
- const mount = this.#custody.mountForSession(session);
11867
+ const workspace = request.target?.workspace;
11868
+ if (!workspace) throw new Error("custody admission requires an addressed workspace");
11869
+ const mount = this.#custody.mountForWorkspace(workspace);
11740
11870
  if (command.evidencePack.byteLength > 0) {
11741
11871
  this.#kernel.applyPack(mount, request.requestId, command.evidencePack);
11742
11872
  }
@@ -11747,21 +11877,42 @@ var NomosRuntimeComponent = (() => {
11747
11877
  const births = [];
11748
11878
  const admittedBase = this.#kernel.head(mount, request.requestId) ?? "";
11749
11879
  if (command.fastForwardOnly && offers.length > 0 && (command.baseHead ?? "") !== admittedBase) {
11880
+ for (const offer of offers) {
11881
+ const receipt = this.#receipts.receipt(mount.workspace, admittedBase, offer.changeUsda);
11882
+ if (!receipt) continue;
11883
+ receipt.commitOid = offer.commitOid;
11884
+ const records = sessions.get(offer.session) ?? [];
11885
+ records.push(receipt);
11886
+ sessions.set(offer.session, records);
11887
+ }
11888
+ let delta = custodyReturnDelta(
11889
+ this.#kernel,
11890
+ mount,
11891
+ request.requestId,
11892
+ command.baseHead,
11893
+ admittedBase,
11894
+ admittedBase,
11895
+ command.observedHead
11896
+ );
11897
+ if (command.baseHead && command.observedHead !== admittedBase && this.#receipts.covers(mount.workspace, admittedBase, command.baseHead)) {
11898
+ try {
11899
+ delta = {
11900
+ resultBase: command.baseHead,
11901
+ resultPack: this.#kernel.packDelta(mount, request.requestId, admittedBase, command.baseHead),
11902
+ recoveryRequired: false
11903
+ };
11904
+ } catch {
11905
+ }
11906
+ }
11750
11907
  return create(CustodyAdmissionSchema, {
11751
11908
  main: admittedBase,
11752
- ...custodyReturnDelta(
11753
- this.#kernel,
11754
- mount,
11755
- request.requestId,
11756
- command.baseHead,
11757
- admittedBase,
11758
- admittedBase,
11759
- command.observedHead
11760
- )
11909
+ sessions: [...sessions].map(([session, intents]) => create(CustodySessionAdmissionSchema, { session, intents })),
11910
+ ...delta
11761
11911
  });
11762
11912
  }
11763
11913
  let main = admittedBase;
11764
11914
  const heldSessions = /* @__PURE__ */ new Map();
11915
+ const receipts = [];
11765
11916
  for (const offer of offers) {
11766
11917
  const heldBehind = heldSessions.get(offer.session);
11767
11918
  const verdict = heldBehind === void 0 ? this.#offer(request, mount, offer) : create(OfferResponseSchema, { outcome: { case: "blockedOnDependency", value: {
@@ -11771,7 +11922,9 @@ var NomosRuntimeComponent = (() => {
11771
11922
  heldSessions.set(offer.session, verdict.outcome.value.offerId || offer.commitOid);
11772
11923
  }
11773
11924
  const records = sessions.get(offer.session) ?? [];
11774
- records.push(this.#record(offer, verdict));
11925
+ const receipt = this.#record(offer, verdict);
11926
+ records.push(receipt);
11927
+ receipts.push({ change: offer.changeUsda, receipt });
11775
11928
  sessions.set(offer.session, records);
11776
11929
  if (verdict.outcome.case === "admitted") {
11777
11930
  main = verdict.outcome.value.head;
@@ -11787,6 +11940,9 @@ var NomosRuntimeComponent = (() => {
11787
11940
  }
11788
11941
  }
11789
11942
  }
11943
+ if (receipts.some((entry) => entry.receipt.outcome.case === "admitted")) {
11944
+ this.#receipts.record(mount.workspace, admittedBase, main, receipts);
11945
+ }
11790
11946
  return create(CustodyAdmissionSchema, {
11791
11947
  main,
11792
11948
  sessions: [...sessions].map(
@@ -14745,8 +14901,8 @@ var NomosRuntimeComponent = (() => {
14745
14901
 
14746
14902
  // ../src/runtime/preserved_copies_desk.ts
14747
14903
  var PRESERVED_COPIES_NAMESPACE = "nomos.peer-desk.preserved-local-copies/v1";
14748
- var encoder = new TextEncoder();
14749
- var decoder = new TextDecoder();
14904
+ var encoder2 = new TextEncoder();
14905
+ var decoder2 = new TextDecoder();
14750
14906
  var PreservedCopiesDesk = class {
14751
14907
  #store;
14752
14908
  constructor(store) {
@@ -14755,7 +14911,7 @@ var NomosRuntimeComponent = (() => {
14755
14911
  list(workspace) {
14756
14912
  const record = this.#store.list(PRESERVED_COPIES_NAMESPACE).find((entry) => entry.key === workspace);
14757
14913
  if (!record) return [];
14758
- const values = JSON.parse(decoder.decode(record.value));
14914
+ const values = JSON.parse(decoder2.decode(record.value));
14759
14915
  if (!Array.isArray(values)) {
14760
14916
  throw new Error(`corrupt preserved-copy record for '${workspace}' at revision ${record.revision}`);
14761
14917
  }
@@ -14769,7 +14925,7 @@ var NomosRuntimeComponent = (() => {
14769
14925
  PRESERVED_COPIES_NAMESPACE,
14770
14926
  copy3.workspace,
14771
14927
  current?.revision,
14772
- encoder.encode(`[${next.map((entry) => toJsonString(PreservedLocalCopySchema, entry)).join(",")}]`)
14928
+ encoder2.encode(`[${next.map((entry) => toJsonString(PreservedLocalCopySchema, entry)).join(",")}]`)
14773
14929
  );
14774
14930
  if (!stored) throw new Error(`preserved copy of '${copy3.workspace}' was not recorded`);
14775
14931
  }
@@ -14810,9 +14966,9 @@ var NomosRuntimeComponent = (() => {
14810
14966
  ]);
14811
14967
  var SEAT_PREFIX = "nomos-office-seat/v1:";
14812
14968
  var SEAT_DIGEST_HEX = 64;
14813
- var encoder2 = new TextEncoder();
14814
- var decoder2 = new TextDecoder();
14815
- var header = (digest) => encoder2.encode(`${SEAT_PREFIX}${digest}
14969
+ var encoder3 = new TextEncoder();
14970
+ var decoder3 = new TextDecoder();
14971
+ var header = (digest) => encoder3.encode(`${SEAT_PREFIX}${digest}
14816
14972
  `);
14817
14973
  var QuarantineReason = Object.freeze({
14818
14974
  /** Another office's seat: a copied state directory, or a store lifted from another peer. */
@@ -14823,7 +14979,7 @@ var NomosRuntimeComponent = (() => {
14823
14979
  function readSeal(value) {
14824
14980
  const headerLength = SEAT_PREFIX.length + SEAT_DIGEST_HEX + 1;
14825
14981
  if (value.byteLength < headerLength) return void 0;
14826
- const text = decoder2.decode(value.subarray(0, headerLength));
14982
+ const text = decoder3.decode(value.subarray(0, headerLength));
14827
14983
  if (!text.startsWith(SEAT_PREFIX) || !text.endsWith("\n")) return void 0;
14828
14984
  const seat = text.slice(SEAT_PREFIX.length, headerLength - 1);
14829
14985
  if (!/^[0-9a-f]{64}$/.test(seat)) return void 0;
@@ -14847,44 +15003,44 @@ var NomosRuntimeComponent = (() => {
14847
15003
  this.#seat = seat;
14848
15004
  this.#report = report;
14849
15005
  }
14850
- list(namespace) {
14851
- if (LOCAL_ONLY_NAMESPACES.has(namespace)) return this.#inner.list(namespace);
15006
+ list(namespace2) {
15007
+ if (LOCAL_ONLY_NAMESPACES.has(namespace2)) return this.#inner.list(namespace2);
14852
15008
  const kept = [];
14853
- for (const record of this.#inner.list(namespace)) {
15009
+ for (const record of this.#inner.list(namespace2)) {
14854
15010
  const sealed = readSeal(record.value);
14855
15011
  if (sealed?.seat === this.#seat.digest) {
14856
15012
  kept.push({ ...record, value: sealed.value.slice() });
14857
15013
  continue;
14858
15014
  }
14859
- this.#quarantine(namespace, record, sealed?.seat);
15015
+ this.#quarantine(namespace2, record, sealed?.seat);
14860
15016
  }
14861
15017
  return kept;
14862
15018
  }
14863
- compareExchange(namespace, key, expectedRevision, value) {
14864
- if (LOCAL_ONLY_NAMESPACES.has(namespace)) {
14865
- return this.#inner.compareExchange(namespace, key, expectedRevision, value);
15019
+ compareExchange(namespace2, key, expectedRevision, value) {
15020
+ if (LOCAL_ONLY_NAMESPACES.has(namespace2)) {
15021
+ return this.#inner.compareExchange(namespace2, key, expectedRevision, value);
14866
15022
  }
14867
15023
  const stored = this.#inner.compareExchange(
14868
- namespace,
15024
+ namespace2,
14869
15025
  key,
14870
15026
  expectedRevision,
14871
15027
  value === void 0 ? void 0 : seal(this.#seat.digest, value)
14872
15028
  );
14873
15029
  if (stored === void 0) return void 0;
14874
15030
  const sealed = readSeal(stored.value);
14875
- if (!sealed) throw new Error(`peer desk record '${namespace}/${key}' lost its office seat`);
15031
+ if (!sealed) throw new Error(`peer desk record '${namespace2}/${key}' lost its office seat`);
14876
15032
  return { ...stored, value: sealed.value.slice() };
14877
15033
  }
14878
15034
  /** Preserve the foreign record under the quarantine namespace, then free the slot. */
14879
- #quarantine(namespace, record, recordedSeat) {
14880
- const key = `${namespace}\0${record.key}\0${record.revision}`;
15035
+ #quarantine(namespace2, record, recordedSeat) {
15036
+ const key = `${namespace2}\0${record.key}\0${record.revision}`;
14881
15037
  try {
14882
15038
  this.#inner.compareExchange(QUARANTINED_BINDINGS_NAMESPACE, key, void 0, record.value);
14883
15039
  } catch {
14884
15040
  }
14885
- this.#inner.compareExchange(namespace, record.key, record.revision, void 0);
15041
+ this.#inner.compareExchange(namespace2, record.key, record.revision, void 0);
14886
15042
  this.#report(create(QuarantinedBindingSchema, {
14887
- namespace,
15043
+ namespace: namespace2,
14888
15044
  key: record.key,
14889
15045
  reason: recordedSeat === void 0 ? QuarantineReason.Unseated : QuarantineReason.ForeignSeat,
14890
15046
  recordedSeat: recordedSeat ?? "",
@@ -14934,6 +15090,7 @@ var NomosRuntimeComponent = (() => {
14934
15090
  const undiscovered = /* @__PURE__ */ new Set();
14935
15091
  for (const restored of this.#follows.recover()) {
14936
15092
  following.set(restored.workspace, restored.delayMillis);
15093
+ followed.add(restored.workspace);
14937
15094
  }
14938
15095
  if (!this.#bootstrapped) {
14939
15096
  this.#bootstrapped = true;
@@ -14971,6 +15128,7 @@ var NomosRuntimeComponent = (() => {
14971
15128
  stopped.delete(workspace);
14972
15129
  } else if (reconciliation.changed) {
14973
15130
  following.delete(workspace);
15131
+ followed.delete(workspace);
14974
15132
  stopped.add(workspace);
14975
15133
  }
14976
15134
  } catch (error) {
@@ -15299,7 +15457,7 @@ var NomosRuntimeComponent = (() => {
15299
15457
  wasResident: this.kernel.isResident(binding.workspace) !== false
15300
15458
  };
15301
15459
  this.#links.set(binding.workspace, link);
15302
- if (link.wasResident) this.#open(link);
15460
+ if (!link.publisher || link.wasResident) this.#open(link);
15303
15461
  }
15304
15462
  changed(workspace) {
15305
15463
  const link = this.#links.get(workspace);
@@ -15326,6 +15484,9 @@ var NomosRuntimeComponent = (() => {
15326
15484
  this.#effect(link, { case: "send", value: bytes });
15327
15485
  }
15328
15486
  export(binding, base, requestId) {
15487
+ if (this.kernel.isResident(binding.workspace) === false) {
15488
+ return create(CustodyOfferBatchRequestSchema, { protocolVersion: 2 });
15489
+ }
15329
15490
  const mount = workspaceMount(binding.workspace);
15330
15491
  const head = this.kernel.head(mount, requestId);
15331
15492
  if (!base || !head || base === head) return create(CustodyOfferBatchRequestSchema, { protocolVersion: 2 });
@@ -15386,6 +15547,7 @@ var NomosRuntimeComponent = (() => {
15386
15547
  }
15387
15548
  if (frame.message.case === "frontier") {
15388
15549
  this.#announced(link, frame.message.value.head);
15550
+ if (this.kernel.isResident(link.binding.workspace) === false) return;
15389
15551
  if (!frame.message.value.response) {
15390
15552
  this.#send(link, { case: "frontier", value: {
15391
15553
  head: this.kernel.head(workspaceMount(link.binding.workspace), ++this.#requestId) ?? "",
@@ -15402,6 +15564,10 @@ var NomosRuntimeComponent = (() => {
15402
15564
  if (frame.message.case !== "offers") return;
15403
15565
  const batch = frame.message.value;
15404
15566
  if (batch.protocolVersion !== 2 || !batch.requestId || batch.source.case !== "inline" || !batch.source.value.offers.length) return;
15567
+ if (this.kernel.isResident(link.binding.workspace) === false) {
15568
+ this.remoteAdvanced?.(link.binding.workspace);
15569
+ return;
15570
+ }
15405
15571
  if ([...this.#pending.values()].some((pending) => pending.link === link && pending.bytes.length === bytes.length && pending.bytes.every((byte, index) => byte === bytes[index]))) return;
15406
15572
  if (this.#pending.size >= 32) return;
15407
15573
  this.#pending.set(request.requestId, { link, bytes });
@@ -15409,7 +15575,16 @@ var NomosRuntimeComponent = (() => {
15409
15575
  protocolVersion: 2,
15410
15576
  requestId: request.requestId,
15411
15577
  target: { workspace: link.binding.workspace, session: link.binding.replicaId },
15412
- command: { case: "admitCustodyOffers", value: create(CustodyOfferBatchRequestSchema, { ...batch, baseHead: void 0 }) }
15578
+ // HTTP sender reconciliation controls are not part of a live proposal.
15579
+ // Keeping fastForwardOnly while removing baseHead silently skips every
15580
+ // offer against a nonempty local head. The Kernel judges sealed causality;
15581
+ // live receivers neither ask for a return archive nor confirm the sender.
15582
+ command: { case: "admitCustodyOffers", value: create(CustodyOfferBatchRequestSchema, {
15583
+ ...batch,
15584
+ baseHead: void 0,
15585
+ observedHead: void 0,
15586
+ fastForwardOnly: false
15587
+ }) }
15413
15588
  });
15414
15589
  }
15415
15590
  /** Upstream news is a frontier this replica has NEVER seen: neither its own head nor the
@@ -15418,6 +15593,10 @@ var NomosRuntimeComponent = (() => {
15418
15593
  #announced(link, head) {
15419
15594
  if (!head || link.lastAnnounced === head) return;
15420
15595
  link.lastAnnounced = head;
15596
+ if (this.kernel.isResident(link.binding.workspace) === false) {
15597
+ this.remoteAdvanced?.(link.binding.workspace);
15598
+ return;
15599
+ }
15421
15600
  const mount = workspaceMount(link.binding.workspace);
15422
15601
  if (head === this.kernel.head(mount, ++this.#requestId) || head === this.kernel.resolveRef(mount, ++this.#requestId, "refs/remotes/nomos/main")) return;
15423
15602
  this.remoteAdvanced?.(link.binding.workspace);
@@ -15427,6 +15606,10 @@ var NomosRuntimeComponent = (() => {
15427
15606
  #placementAnnounced(link, frontier) {
15428
15607
  if (!frontier || link.lastPlacementAnnounced === frontier) return;
15429
15608
  link.lastPlacementAnnounced = frontier;
15609
+ if (this.kernel.isResident(link.binding.workspace) === false) {
15610
+ this.placementAdvanced?.(link.binding.workspace);
15611
+ return;
15612
+ }
15430
15613
  const mount = workspaceMount(link.binding.workspace);
15431
15614
  if (frontier === this.kernel.resolveRef(mount, ++this.#requestId, PLACEMENT_REF2)) return;
15432
15615
  this.placementAdvanced?.(link.binding.workspace);
@@ -15443,11 +15626,13 @@ var NomosRuntimeComponent = (() => {
15443
15626
  disconnect(workspace) {
15444
15627
  const link = this.#links.get(workspace);
15445
15628
  if (!link) return;
15629
+ if (link.state === "open") this.linkClosed?.(workspace);
15446
15630
  this.#effect(link, { case: "close", value: true });
15447
15631
  this.#links.delete(workspace);
15448
15632
  for (const [id, pending] of this.#pending) if (pending.link === link) this.#pending.delete(id);
15449
15633
  }
15450
15634
  #hello(link) {
15635
+ if (this.kernel.isResident(link.binding.workspace) === false) return;
15451
15636
  const head = this.kernel.head(workspaceMount(link.binding.workspace), ++this.#requestId);
15452
15637
  this.#send(link, { case: "frontier", value: { head: head ?? "" } });
15453
15638
  }
@@ -15455,19 +15640,20 @@ var NomosRuntimeComponent = (() => {
15455
15640
  const now = this.clock.monotonicNanoseconds();
15456
15641
  let wake;
15457
15642
  for (const link of this.#links.values()) {
15458
- if (this.kernel.isResident(link.binding.workspace) === false) {
15643
+ const resident = this.kernel.isResident(link.binding.workspace) !== false;
15644
+ if (link.publisher && !resident) {
15459
15645
  if (link.wasResident) this.disconnect(link.binding.workspace);
15460
15646
  else wake = wake === void 0 || wake > 250n ? 250n : wake;
15461
15647
  continue;
15462
15648
  }
15463
- link.wasResident = true;
15649
+ if (resident) link.wasResident = true;
15464
15650
  if (link.state === "opening" && now >= link.due) {
15465
15651
  this.#effect(link, { case: "close", value: true });
15466
15652
  link.state = "closed";
15467
15653
  }
15468
15654
  if (link.state === "closed" && now >= link.due) this.#open(link);
15469
15655
  if (link.state === "open") {
15470
- if (link.dirty) {
15656
+ if (link.dirty && resident) {
15471
15657
  link.dirty = false;
15472
15658
  try {
15473
15659
  link.published ??= link.publisher ? this.kernel.head(workspaceMount(link.binding.workspace), ++this.#requestId) : this.kernel.resolveRef(workspaceMount(link.binding.workspace), ++this.#requestId, "refs/remotes/nomos/main");
@@ -15486,8 +15672,7 @@ var NomosRuntimeComponent = (() => {
15486
15672
  return wake;
15487
15673
  }
15488
15674
  close() {
15489
- for (const link of this.#links.values()) this.#effect(link, { case: "close", value: true });
15490
- this.#links.clear();
15675
+ for (const workspace of [...this.#links.keys()]) this.disconnect(workspace);
15491
15676
  this.#pending.clear();
15492
15677
  }
15493
15678
  };
@@ -15616,9 +15801,9 @@ var NomosRuntimeComponent = (() => {
15616
15801
  command.namespace === void 0 ? {} : { "custody.children_namespace": command.namespace },
15617
15802
  () => this.#kernel.childWorkspaces(mount, request.requestId)
15618
15803
  );
15619
- const namespace = command.namespace;
15620
- const selected = namespace === void 0 ? result.children : result.children.filter(
15621
- (child) => child.status === ChildWorkspaceLifecycleStatus.ACTIVE && isKeyedBirthOf(namespace, child.workspace)
15804
+ const namespace2 = command.namespace;
15805
+ const selected = namespace2 === void 0 ? result.children : result.children.filter(
15806
+ (child) => child.status === ChildWorkspaceLifecycleStatus.ACTIVE && isKeyedBirthOf(namespace2, child.workspace)
15622
15807
  );
15623
15808
  return create(ChildWorkspaceListSchema, {
15624
15809
  children: selected.map((child) => create(ChildWorkspaceSchema, {
@@ -15980,40 +16165,6 @@ var NomosRuntimeComponent = (() => {
15980
16165
  }
15981
16166
  };
15982
16167
 
15983
- // ../src/runtime/peer_desk_store.ts
15984
- var recordId = (namespace, key) => `${namespace.length}:${namespace}${key}`;
15985
- var copy2 = (record) => ({
15986
- ...record,
15987
- value: record.value.slice()
15988
- });
15989
- var MemoryPeerDeskStore = class {
15990
- #records = /* @__PURE__ */ new Map();
15991
- list(namespace) {
15992
- return [...this.#records.values()].filter((record) => record.namespace === namespace).sort((left, right) => left.key.localeCompare(right.key)).map(copy2);
15993
- }
15994
- compareExchange(namespace, key, expectedRevision, value) {
15995
- const id = recordId(namespace, key);
15996
- const current = this.#records.get(id);
15997
- if (current?.revision !== expectedRevision) {
15998
- throw new Error(
15999
- `peer desk record '${namespace}/${key}' changed at revision ${current?.revision ?? "absent"}`
16000
- );
16001
- }
16002
- if (value === void 0) {
16003
- this.#records.delete(id);
16004
- return void 0;
16005
- }
16006
- const next = Object.freeze({
16007
- namespace,
16008
- key,
16009
- revision: (current?.revision ?? 0n) + 1n,
16010
- value: value.slice()
16011
- });
16012
- this.#records.set(id, next);
16013
- return copy2(next);
16014
- }
16015
- };
16016
-
16017
16168
  // ../src/runtime/correspondence_recovery.ts
16018
16169
  var CorrespondenceRecoveryWork = class {
16019
16170
  workId;
@@ -16392,6 +16543,7 @@ var NomosRuntimeComponent = (() => {
16392
16543
  #offerSequence = 0;
16393
16544
  #lastStep;
16394
16545
  #probeOnly;
16546
+ #liveConnected;
16395
16547
  /** Set when this round adopted the upstream's own head as its base (see #establishBase). */
16396
16548
  #establishedBase;
16397
16549
  #receiveState;
@@ -16424,6 +16576,7 @@ var NomosRuntimeComponent = (() => {
16424
16576
  this.#directoryAnswered = args.directoryAnswered;
16425
16577
  this.#placementFetched = args.placementFetched;
16426
16578
  this.#probeOnly = args.probeOnly ?? false;
16579
+ this.#liveConnected = args.liveConnected === true;
16427
16580
  this.#receiveState = args.receiveState;
16428
16581
  this.#completeHistory = args.completeHistory === true;
16429
16582
  this.#repairMissingCommit = args.repairMissingCommit === true;
@@ -16494,6 +16647,9 @@ var NomosRuntimeComponent = (() => {
16494
16647
  #openRound() {
16495
16648
  if (this.#receiveState || this.#replaceLocalHead !== void 0) return this.#leaseEffect();
16496
16649
  if (!this.#localHead) return this.#leaseEffect();
16650
+ if (this.#liveConnected && !this.#probeOnly && this.#holdsLocalWork() && this.#previousHead) {
16651
+ return this.#offerOrReconcile();
16652
+ }
16497
16653
  const requestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:frontier`;
16498
16654
  return this.#beginOffer(encodeInlineCustodyOffer({
16499
16655
  session: this.#binding.replicaId,
@@ -16725,6 +16881,18 @@ var NomosRuntimeComponent = (() => {
16725
16881
  return this.#finishOfferOutcome(false);
16726
16882
  }
16727
16883
  const admission = outcome.value;
16884
+ globalThis.__nomosSyncDiagnostic?.("sync.admission", {
16885
+ workspace: this.#mount.workspace,
16886
+ phase: state.phase,
16887
+ local: this.#localHead,
16888
+ tracked: this.#previousHead,
16889
+ remote: admission.main,
16890
+ sealed: admission.sealedMain,
16891
+ resultBase: admission.resultBase,
16892
+ resultBytes: admission.resultPack.byteLength,
16893
+ recoveryRequired: admission.recoveryRequired,
16894
+ summary: summarizeCustodyAdmission(admission)
16895
+ });
16728
16896
  this.#desiredHead = admission.main || void 0;
16729
16897
  if (!this.#desiredHead) return this.#transportFailure("resident peer returned no admitted frontier", false);
16730
16898
  this.#candidateDurable = admission.sealedMain === this.#desiredHead;
@@ -17341,6 +17509,19 @@ var NomosRuntimeComponent = (() => {
17341
17509
  return step;
17342
17510
  }
17343
17511
  #finish(value) {
17512
+ globalThis.__nomosSyncDiagnostic?.("sync.finished", {
17513
+ workspace: value.workspace,
17514
+ previous: value.previousHead,
17515
+ current: value.currentHead,
17516
+ stillAhead: value.stillAhead,
17517
+ deferred: String(value.deferred),
17518
+ admitted: String(value.admitted),
17519
+ reconnect: value.reconnectRequired,
17520
+ directOfferDeferred: value.directOfferDeferred,
17521
+ pushFailed: value.pushFailed,
17522
+ failure: value.pushError?.message,
17523
+ wallMs: elapsedMillis(this.#clock, this.#started)
17524
+ });
17344
17525
  this.#state = { phase: WorkspaceSyncPhase.Done };
17345
17526
  this.#current = void 0;
17346
17527
  return this.#remember({ case: "synced", value });
@@ -17396,6 +17577,10 @@ var NomosRuntimeComponent = (() => {
17396
17577
  this.#plan = [this.#args.root];
17397
17578
  return this.#advance();
17398
17579
  }
17580
+ if (this.#args.liveConnected && this.#args.kernel.resolveRef(this.#args.root.mount, this.#args.requestId, trackedMain2)) {
17581
+ this.#plan = this.#planDependencies();
17582
+ return this.#advance();
17583
+ }
17399
17584
  this.#current = this.#work(this.#args.root, `${this.workId}:frontier`, true);
17400
17585
  return this.#accept(this.#current.start());
17401
17586
  }
@@ -18581,6 +18766,7 @@ var NomosRuntimeComponent = (() => {
18581
18766
  #onSyncCompleted;
18582
18767
  #onPlacementFetched;
18583
18768
  #onShallowMount;
18769
+ #onOutboxHistoryRequired;
18584
18770
  #remoteOffers = new CorrespondenceWorkRegistry();
18585
18771
  #recoveries = new CorrespondenceWorkRegistry();
18586
18772
  #deliveries = new CorrespondenceWorkRegistry();
@@ -18627,6 +18813,7 @@ var NomosRuntimeComponent = (() => {
18627
18813
  this.#onSyncCompleted = args.onSyncCompleted;
18628
18814
  this.#onPlacementFetched = args.onPlacementFetched;
18629
18815
  this.#onShallowMount = args.onShallowMount;
18816
+ this.#onOutboxHistoryRequired = args.onOutboxHistoryRequired;
18630
18817
  this.#clock = args.clock;
18631
18818
  this.#outboxClock = new OutboxObservationClock(args.clock, args.peerIdentity);
18632
18819
  this.#executesCustodianObligations = args.executesCustodianObligations ?? false;
@@ -18790,6 +18977,15 @@ var NomosRuntimeComponent = (() => {
18790
18977
  this.#followLane.dependencyAdvanced(workspace);
18791
18978
  this.reportFollows();
18792
18979
  }
18980
+ /** Received custody can release correspondence and foreign-read dependencies,
18981
+ * but is not newly authored work to send back to its upstream. Recovery is
18982
+ * driven separately by missing history, reconnects and provider announcements.
18983
+ * In particular an optimistic relay is not a durable upstream receipt. */
18984
+ receivedFrontierChanged(workspace) {
18985
+ this.wakeOutbox(workspace);
18986
+ this.#followLane.dependencyAdvanced(workspace);
18987
+ this.reportFollows();
18988
+ }
18793
18989
  /**
18794
18990
  * Tell a workspace's sessions what its durable follow ALREADY knows.
18795
18991
  *
@@ -19026,8 +19222,16 @@ var NomosRuntimeComponent = (() => {
19026
19222
  )
19027
19223
  );
19028
19224
  } catch (error) {
19225
+ if (error instanceof KernelHistoryRequired && this.#onOutboxHistoryRequired) {
19226
+ const retry = this.#onOutboxHistoryRequired(workspace, binding.credentialCapability);
19227
+ this.#outboxDueAt.set(workspace, now + Math.max(250, retry));
19228
+ return { nextAfterMillis: 0 };
19229
+ }
19029
19230
  const format = localCopyFormatRefusal(error);
19030
- if (format === void 0) throw error;
19231
+ if (format === void 0) {
19232
+ this.#outboxDueAt.set(workspace, now + 3e4);
19233
+ return { nextAfterMillis: 0 };
19234
+ }
19031
19235
  this.#readability.mark(workspace, void 0, format);
19032
19236
  this.#outboxDueAt.delete(workspace);
19033
19237
  return { nextAfterMillis: 0 };
@@ -19307,6 +19511,7 @@ var NomosRuntimeComponent = (() => {
19307
19511
  requestId: request.requestId,
19308
19512
  workId: id,
19309
19513
  probeOnly,
19514
+ liveConnected: this.#openLinks.has(target3.mount.workspace),
19310
19515
  ...beforeConverge === void 0 ? {} : { beforeConverge },
19311
19516
  ...target3.receiveState === void 0 ? {} : { receiveState: target3.receiveState },
19312
19517
  ...target3.offerHead === void 0 ? {} : { offerHead: target3.offerHead },
@@ -19334,6 +19539,7 @@ var NomosRuntimeComponent = (() => {
19334
19539
  root,
19335
19540
  work,
19336
19541
  outbound: command.admit,
19542
+ liveConnected: this.#openLinks.has(mount.workspace),
19337
19543
  dependency: (workspace, source, acquire2 = false) => {
19338
19544
  if (this.#roundInFlight(workspace, workId2)) return void 0;
19339
19545
  const existing = this.#follows.bindingForWorkspace(workspace);
@@ -20855,15 +21061,15 @@ var NomosRuntimeComponent = (() => {
20855
21061
  var HISTORY_ACQUISITION_NAMESPACE = "nomos.peer-desk.history-acquisition/v1";
20856
21062
  var HISTORY_BACKGROUND_START_MILLIS = 1e4;
20857
21063
  var SLICE_ANSWER_MILLIS = 5 * 6e4;
20858
- var encoder3 = new TextEncoder();
20859
- var decoder3 = new TextDecoder();
21064
+ var encoder4 = new TextEncoder();
21065
+ var decoder4 = new TextDecoder();
20860
21066
  var workId = (workspace) => `history-acquisition:${workspace}`;
20861
21067
  var effectId = (workspace, attempt) => `history-acquisition:${workspace}:${HistoryAcquisitionPhase.Acquiring}:${attempt}`;
20862
21068
  var encode2 = (work) => {
20863
21069
  if (!work.credentialCapability) {
20864
21070
  throw new Error(`history acquisition for '${work.workspace}' has not restored its credential capability`);
20865
21071
  }
20866
- return encoder3.encode(JSON.stringify({
21072
+ return encoder4.encode(JSON.stringify({
20867
21073
  version: 2,
20868
21074
  workspace: work.workspace,
20869
21075
  credentialCapability: work.credentialCapability,
@@ -20872,7 +21078,7 @@ var NomosRuntimeComponent = (() => {
20872
21078
  };
20873
21079
  var upgraded = (state) => state.case === "backoff" ? { case: HistoryAcquisitionPhase.Paced, attempt: state.attempt, delayMillis: state.delayMillis, failed: true } : state;
20874
21080
  var decode2 = (record) => {
20875
- const value = JSON.parse(decoder3.decode(record.value));
21081
+ const value = JSON.parse(decoder4.decode(record.value));
20876
21082
  if (!value || typeof value !== "object") throw corrupt2(record, "not an object");
20877
21083
  const candidate = value;
20878
21084
  if (candidate.version !== 1 && candidate.version !== 2 || candidate.workspace !== record.key || !candidate.state) {
@@ -20935,6 +21141,21 @@ var NomosRuntimeComponent = (() => {
20935
21141
  this.#ensure(workspace, credentialCapability);
20936
21142
  this.#dueAt.set(workspace, this.#now() + HISTORY_BACKGROUND_START_MILLIS);
20937
21143
  }
21144
+ /** A background kernel read needs an omitted ancestor now. Queue one bounded
21145
+ * slice on the existing history lane; a failed slice keeps its retry pace. */
21146
+ requestFromBackground(workspace, credentialCapability) {
21147
+ if (!workspace) throw new Error("kernel history requirement names no workspace");
21148
+ this.#ensure(workspace, credentialCapability);
21149
+ const work = this.#get(workspace);
21150
+ const now = this.#now();
21151
+ if (work.state.case === HistoryAcquisitionPhase.Paced && work.state.failed) {
21152
+ const due2 = this.#dueAt.get(workspace) ?? now + work.state.delayMillis;
21153
+ this.#dueAt.set(workspace, due2);
21154
+ if (due2 > now) return due2 - now;
21155
+ }
21156
+ this.#dueAt.set(workspace, now);
21157
+ return 250;
21158
+ }
20938
21159
  /** An operation needs history this custody lacks: its next slice now, joining one in flight. */
20939
21160
  begin(workspace, credentialCapability) {
20940
21161
  if (!workspace) throw new Error("kernel history requirement names no workspace");
@@ -21243,7 +21464,7 @@ var NomosRuntimeComponent = (() => {
21243
21464
  this.#trace,
21244
21465
  this.#readability
21245
21466
  );
21246
- this.#custodyAdmission = new CustodyAdmissionDesk(this.#kernel, this.#custody);
21467
+ this.#custodyAdmission = new CustodyAdmissionDesk(this.#kernel, this.#custody, new CustodyReceipts(seatedStore));
21247
21468
  this.#queries = new KernelQueries(this.#kernel, this.#credentials);
21248
21469
  this.#follows = new ReplicaFollowController(
21249
21470
  new ReplicaFollowDesk(seatedStore),
@@ -21405,6 +21626,7 @@ var NomosRuntimeComponent = (() => {
21405
21626
  // Lazy history: a head-only mount materialises its history in the background, never on
21406
21627
  // the path of an operation (architecture/lazy_history.md).
21407
21628
  onShallowMount: (workspace, credentialCapability) => this.#historyAcquisition.materialise(workspace, credentialCapability),
21629
+ onOutboxHistoryRequired: (workspace, credentialCapability) => this.#historyAcquisition.requestFromBackground(workspace, credentialCapability),
21408
21630
  onSyncCompleted: (session, event) => {
21409
21631
  if (event.result?.workspace) {
21410
21632
  this.#replicaInventory.placementMayHaveChanged(event.result.workspace);
@@ -21871,8 +22093,8 @@ var NomosRuntimeComponent = (() => {
21871
22093
  this.#replicaInventory.placementMayHaveChanged(workspace);
21872
22094
  }
21873
22095
  }
21874
- /** Execute a typed paper after the access desk; never skip lifecycle or
21875
- * session-binding checks merely because its caller is another local desk. */
22096
+ /** Execute a typed paper after the access desk. Custody admission addresses a
22097
+ * workspace by replica identity; application calls retain session binding. */
21876
22098
  #dispatchRequest(request) {
21877
22099
  validateRuntimeV2Request(request);
21878
22100
  try {
@@ -21886,18 +22108,20 @@ var NomosRuntimeComponent = (() => {
21886
22108
  if (!request.target.workspace || !request.target.session) {
21887
22109
  throw new Error("addressed runtime request requires workspace and session");
21888
22110
  }
21889
- const binding = this.#sessions.bindingIfPresent(request.target.session);
21890
- if (binding !== void 0) {
21891
- if (binding.workspace !== request.target.workspace) {
21892
- throw new Error(
21893
- `session '${request.target.session}' is bound to workspace '${binding.workspace}', not '${request.target.workspace}'`
21894
- );
22111
+ if (request.command.case !== "admitCustodyOffers") {
22112
+ const binding = this.#sessions.bindingIfPresent(request.target.session);
22113
+ if (binding !== void 0) {
22114
+ if (binding.workspace !== request.target.workspace) {
22115
+ throw new Error(
22116
+ `session '${request.target.session}' is bound to workspace '${binding.workspace}', not '${request.target.workspace}'`
22117
+ );
22118
+ }
22119
+ } else {
22120
+ this.#bindInteraction(create(BindSessionRequestSchema, {
22121
+ session: request.target.session,
22122
+ workspace: request.target.workspace
22123
+ }));
21895
22124
  }
21896
- } else {
21897
- this.#bindInteraction(create(BindSessionRequestSchema, {
21898
- session: request.target.session,
21899
- workspace: request.target.workspace
21900
- }));
21901
22125
  }
21902
22126
  }
21903
22127
  switch (request.command.case) {
@@ -22094,7 +22318,7 @@ var NomosRuntimeComponent = (() => {
22094
22318
  ReadMaintenanceCause.ADMITTED_OFFER
22095
22319
  );
22096
22320
  this.#subscriptions.refreshWorkspace(request.target.workspace);
22097
- this.#correspondence.workspaceFrontierChanged(request.target.workspace);
22321
+ this.#correspondence.receivedFrontierChanged(request.target.workspace);
22098
22322
  }
22099
22323
  return this.#birthCustodyResponse(
22100
22324
  request,