githolon 0.110.0 → 0.111.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.
@@ -12175,6 +12175,15 @@ var ReplicaFollowController = class {
12175
12175
  }
12176
12176
  if (result.stillAhead || result.pushFailed || result.directOfferDeferred || result.reconnectRequired) {
12177
12177
  if (result.directOfferDeferred) {
12178
+ if (!result.reconnectRequired) {
12179
+ this.#desk.transition(result.workspace, {
12180
+ case: ReplicaFollowEventKind.Failed,
12181
+ nowUnixMillis,
12182
+ failure: "remote workspace opening; retrying",
12183
+ retryWithinMillis: 5e3
12184
+ });
12185
+ return delayUntilDue(this.#desk.get(result.workspace), nowUnixMillis);
12186
+ }
12178
12187
  this.#desk.transition(result.workspace, {
12179
12188
  case: ReplicaFollowEventKind.DependencyDeferred,
12180
12189
  nowUnixMillis,
@@ -14076,6 +14085,34 @@ function resolveCustodyDisagreement(kernel, mount, requestId, policy, verdict) {
14076
14085
  import { create as create58, fromBinary as fromBinary23, toBinary as toBinary22 } from "@bufbuild/protobuf";
14077
14086
  var CUSTODY_OFFER_MEDIA_TYPE = "application/vnd.nomos.custody-offer-batch+protobuf";
14078
14087
  var CUSTODY_OFFER_RESULT_MEDIA_TYPE = "application/vnd.nomos.custody-offer-result+protobuf";
14088
+ function settleCustodyOfferExchange(awaiting, response) {
14089
+ if (response.status === 202 && response.contentType?.includes("application/json")) {
14090
+ try {
14091
+ const pending = JSON.parse(new TextDecoder().decode(response.body));
14092
+ if (pending?.retryable === true && typeof pending.preparing === "string") {
14093
+ return { case: "maintenance", kind: pending.preparing };
14094
+ }
14095
+ } catch {
14096
+ }
14097
+ }
14098
+ if (!response.contentType?.includes(CUSTODY_OFFER_RESULT_MEDIA_TYPE)) {
14099
+ return {
14100
+ case: "unexpected",
14101
+ status: response.status,
14102
+ ...response.contentType === void 0 ? {} : { contentType: response.contentType }
14103
+ };
14104
+ }
14105
+ const verdict = decodeCustodyOffer(response.body, awaiting.requestId);
14106
+ const receipt = custodyOfferReceipt(awaiting.requestId, verdict);
14107
+ switch (verdict.case) {
14108
+ case "admission":
14109
+ return { case: "admission", value: verdict.value, receipt, status: response.status };
14110
+ case "failure":
14111
+ return { case: "failure", value: verdict.value, receipt, status: response.status };
14112
+ case "blocked":
14113
+ return { case: "blocked", value: verdict.value, receipt, status: response.status };
14114
+ }
14115
+ }
14079
14116
  function encodeInlineCustodyOffer(args) {
14080
14117
  return toBinary22(CustodyOfferBatchRequestSchema, create58(CustodyOfferBatchRequestSchema, {
14081
14118
  protocolVersion: 2,
@@ -14202,6 +14239,7 @@ function summarizeCustodyAdmission(admission) {
14202
14239
  var PLACEMENT_REF4 = "refs/nomos/placement";
14203
14240
  var LAW_PRODUCTS_MEDIA_TYPE2 = "application/vnd.nomos.export-law-products-response+protobuf";
14204
14241
  var WorkspaceSyncPhase = Object.freeze({
14242
+ Idle: "idle",
14205
14243
  Probe: "probe",
14206
14244
  Offer: "offer",
14207
14245
  Lease: "lease",
@@ -14211,7 +14249,6 @@ var WorkspaceSyncPhase = Object.freeze({
14211
14249
  Done: "done"
14212
14250
  });
14213
14251
  var trackedMain = "refs/remotes/nomos/main";
14214
- var NO_COMMON_BASE = "this replica holds local history with no frontier its upstream ever confirmed; convergence needs a common admitted base \u2014 drop the local copy to refetch it, or re-discover a provider holding this lineage";
14215
14252
  var deferredRemote = "refs/remotes/nomos/deferred";
14216
14253
  var deferredLocal = "refs/nomos/sync-deferred-local";
14217
14254
  var judgedThisLifetime = /* @__PURE__ */ new Map();
@@ -14235,7 +14272,6 @@ var WorkspaceSyncWork = class {
14235
14272
  #rediscover;
14236
14273
  /** The route the directory's answer yields, recomputed once that answer is observed. */
14237
14274
  #reroute;
14238
- #placementFetch;
14239
14275
  #discoveredFrontier;
14240
14276
  #observePlacement;
14241
14277
  /** Ask the directory for the provider's current placement frontier this round although a
@@ -14244,19 +14280,14 @@ var WorkspaceSyncWork = class {
14244
14280
  #directoryAnswered;
14245
14281
  #placementFetched;
14246
14282
  /** A round which converged without leasing, finishing once placement alone is fetched. */
14247
- #afterPlacement;
14248
14283
  #leased = false;
14249
- #lease;
14250
14284
  #command;
14251
14285
  #requestId;
14252
14286
  #started;
14253
- #phase = WorkspaceSyncPhase.Lease;
14287
+ #state = { phase: WorkspaceSyncPhase.Idle };
14254
14288
  #localHead;
14255
14289
  #offerHead;
14256
14290
  #previousHead;
14257
- #fetchStarted;
14258
- #offerStarted;
14259
- #offerRequestId;
14260
14291
  #pushMillis = 0;
14261
14292
  #pushBytes = 0n;
14262
14293
  #desiredHead;
@@ -14351,7 +14382,7 @@ var WorkspaceSyncWork = class {
14351
14382
  throw new Error(`replica '${this.#binding.replicaId}' has no custody remote`);
14352
14383
  }
14353
14384
  if (this.#rediscover || this.#recheckPlacement || !this.#route?.placementObserved && !this.#route?.placementFrontier && !this.#kernel.resolveRef(this.#mount, this.#requestId, PLACEMENT_REF4)) {
14354
- this.#phase = WorkspaceSyncPhase.Discover;
14385
+ this.#state = { phase: WorkspaceSyncPhase.Discover };
14355
14386
  return this.#remember({
14356
14387
  case: "pending",
14357
14388
  value: create59(CorrespondencePendingSchema, {
@@ -14371,13 +14402,13 @@ var WorkspaceSyncWork = class {
14371
14402
  #openRound() {
14372
14403
  if (this.#receiveState || this.#replaceLocalHead !== void 0) return this.#leaseEffect();
14373
14404
  if (!this.#localHead) return this.#leaseEffect();
14374
- this.#offerRequestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:frontier`;
14405
+ const requestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:frontier`;
14375
14406
  return this.#beginOffer(encodeInlineCustodyOffer({
14376
14407
  session: this.#binding.replicaId,
14377
- requestId: this.#offerRequestId,
14408
+ requestId,
14378
14409
  observedHead: this.#localHead,
14379
14410
  intents: []
14380
- }), true);
14411
+ }), { case: "awaiting", requestId }, true);
14381
14412
  }
14382
14413
  #offerOrReconcile() {
14383
14414
  if (this.#command.admit && this.#localHead && this.#localHead !== this.#previousHead) {
@@ -14398,13 +14429,13 @@ var WorkspaceSyncWork = class {
14398
14429
  throw new Error(`kernel did not classify transport for intent '${unspecified.intentId}'`);
14399
14430
  }
14400
14431
  if (suffix.length > 0) {
14401
- this.#offerStarted = this.#clock.monotonicNanoseconds();
14402
- this.#offerRequestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}`;
14432
+ const requestId2 = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}`;
14433
+ const startedAt = this.#clock.monotonicNanoseconds();
14403
14434
  this.#offerAfterConverge = true;
14404
14435
  this.#rebaseFrom = this.#previousHead;
14405
14436
  return this.#beginOffer(encodeInlineCustodyOffer({
14406
14437
  session: this.#binding.replicaId,
14407
- requestId: this.#offerRequestId,
14438
+ requestId: requestId2,
14408
14439
  ...this.#previousHead === void 0 ? {} : { baseHead: this.#previousHead },
14409
14440
  observedHead: this.#localHead,
14410
14441
  intents: suffix,
@@ -14415,51 +14446,26 @@ var WorkspaceSyncWork = class {
14415
14446
  this.#previousHead
14416
14447
  ) : void 0,
14417
14448
  fastForwardOnly: true
14418
- }));
14449
+ }), { case: "awaiting", requestId: requestId2, startedAt });
14419
14450
  }
14420
14451
  }
14421
- this.#offerRequestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:reconcile`;
14452
+ const requestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:reconcile`;
14422
14453
  return this.#beginOffer(encodeInlineCustodyOffer({
14423
14454
  session: this.#binding.replicaId,
14424
- requestId: this.#offerRequestId,
14455
+ requestId,
14425
14456
  baseHead: this.#previousHead,
14426
14457
  observedHead: this.#localHead,
14427
14458
  intents: []
14428
- }));
14459
+ }), { case: "awaiting", requestId });
14429
14460
  }
14430
14461
  /**
14431
- * NO COMMON BASE — a round that cannot converge, and must not pay for history to find out.
14432
- *
14433
- * This replica holds a non-empty ledger but has never recorded a frontier its upstream
14434
- * confirmed (`refs/remotes/nomos/main` is absent). The kernel refuses convergence in that
14435
- * state ("a non-empty live ledger requires a common admitted base"), so every step after the
14436
- * O(1) frontier probe is wasted work — and it is not cheap waste: the fetch negotiates with
14437
- * no `have` (a full-history pack), and the dependency planner decodes EVERY intent from
14438
- * genesis and inspects every change, once per round. That is O(history) per follow round,
14439
- * repeated at every retry, for a round that can only end in a refusal.
14440
- *
14441
- * The one base this round can establish costs nothing and is established before this is
14442
- * asked: an upstream holding exactly this replica's head (the probe's answer) converges it
14443
- * and records the tracking frontier. Anything else needs the local copy to be dropped and
14444
- * refetched (an application decision, which preserves the copy), or a re-discovery that
14445
- * names a provider holding this lineage. Both are reported, at O(1), as this failure.
14446
- */
14447
- /**
14448
- * Establish this round's base when none is tracked, without walking any history it can avoid.
14449
- *
14450
- * The upstream just named its own frontier. If that commit is in THIS replica's lineage, it is
14451
- * a common admitted base by definition — both sides hold it — and adopting it turns the round
14452
- * into an ordinary fast-forward: the offer carries only the suffix above it, the fetch can name
14453
- * it as a truthful `have`, and convergence has the base the kernel requires. One commit walk
14454
- * (no change decoding, no pack, no network) decides it.
14455
- *
14456
- * If it is not in this replica's lineage, no base exists to establish and the round is refused
14457
- * (#noCommonBase) before it leases, fetches or plans anything.
14458
- *
14459
- * A kernel without the commit walk cannot be asked, so it keeps the previous behaviour.
14462
+ * Establish a cheap base when the provider's current tip is in our local chain.
14463
+ * A negative answer means the base is unknown: both sides may have advanced from
14464
+ * an earlier shared commit. Acquire the candidate and let the kernel discover
14465
+ * and judge that history. A tracking bookmark is not an admission prerequisite.
14460
14466
  */
14461
14467
  #establishBase(upstreamHead) {
14462
- if (!this.#noCommonBase() || !this.#kernel.historySummary) return true;
14468
+ if (!this.#missingTrackedBase() || !this.#kernel.historySummary) return true;
14463
14469
  const summary = this.#kernel.historySummary(this.#mount, this.#requestId, upstreamHead, this.#localHead);
14464
14470
  if (!summary.reachedBase) return false;
14465
14471
  this.#previousHead = upstreamHead;
@@ -14474,26 +14480,9 @@ var WorkspaceSyncWork = class {
14474
14480
  get establishedBase() {
14475
14481
  return this.#establishedBase;
14476
14482
  }
14477
- #noCommonBase() {
14483
+ #missingTrackedBase() {
14478
14484
  return this.#previousHead === void 0 && this.#localHead !== void 0 && this.#replaceLocalHead === void 0;
14479
14485
  }
14480
- #noCommonBaseResult() {
14481
- return this.#finish(create59(SyncWorkspaceResponseSchema, {
14482
- workspace: this.#mount.workspace,
14483
- ...this.#localHead === void 0 ? {} : { currentHead: this.#localHead },
14484
- stillAhead: true,
14485
- ...this.#offerReceipt === void 0 ? {} : { offerReceipt: this.#offerReceipt },
14486
- pushError: create59(RuntimeTransportFailureSchema, {
14487
- endpoint: this.#offerEndpoint(),
14488
- status: 409,
14489
- refused: true,
14490
- message: NO_COMMON_BASE
14491
- }),
14492
- timings: create59(SyncPhaseTimingsSchema, {
14493
- convergeMillis: elapsedMillis(this.#clock, this.#started)
14494
- })
14495
- }));
14496
- }
14497
14486
  #holdsLocalWork() {
14498
14487
  return this.#command.admit && !!this.#localHead && this.#localHead !== this.#previousHead;
14499
14488
  }
@@ -14501,18 +14490,18 @@ var WorkspaceSyncWork = class {
14501
14490
  #pullBeforeOffer() {
14502
14491
  this.#offerAfterConverge = true;
14503
14492
  this.#rebaseFrom = this.#previousHead;
14504
- this.#offerRequestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:pull-${this.#rebases}`;
14493
+ const requestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:pull-${this.#rebases}`;
14505
14494
  return this.#beginOffer(encodeInlineCustodyOffer({
14506
14495
  session: this.#binding.replicaId,
14507
- requestId: this.#offerRequestId,
14496
+ requestId,
14508
14497
  ...this.#previousHead === void 0 ? {} : { baseHead: this.#previousHead },
14509
14498
  observedHead: this.#localHead,
14510
14499
  intents: []
14511
- }));
14500
+ }), { case: "awaiting", requestId });
14512
14501
  }
14513
- #leaseEffect() {
14502
+ #leaseEffect(afterPlacement) {
14514
14503
  const token = this.#authToken();
14515
- this.#phase = WorkspaceSyncPhase.Lease;
14504
+ this.#state = { phase: WorkspaceSyncPhase.Lease, ...afterPlacement ? { afterPlacement } : {} };
14516
14505
  return this.#remember({
14517
14506
  case: "pending",
14518
14507
  value: create59(CorrespondencePendingSchema, {
@@ -14541,35 +14530,38 @@ var WorkspaceSyncWork = class {
14541
14530
  `workspace sync '${this.workId}' expected effect '${expected}', received '${receipt.effectId}'`
14542
14531
  );
14543
14532
  }
14533
+ const state = this.#state;
14544
14534
  const previousReceipt = this.#lastReceiptEffectId;
14545
14535
  this.#lastReceiptEffectId = receipt.effectId;
14546
14536
  try {
14547
14537
  if (receipt.outcome.case === "failure") {
14548
- if (this.#phase === WorkspaceSyncPhase.Discover) return this.#openRound();
14538
+ if (state.phase === WorkspaceSyncPhase.Discover) return this.#openRound();
14549
14539
  return this.#transportFailure(
14550
14540
  receipt.outcome.value.message,
14551
14541
  receipt.outcome.value.reason === PeerEffectFailureReason.OFFLINE
14552
14542
  );
14553
14543
  }
14554
- if (this.#phase === WorkspaceSyncPhase.Offer || this.#phase === WorkspaceSyncPhase.Probe) return this.#acceptOffer(receipt);
14555
- if (this.#phase === WorkspaceSyncPhase.Lease) return this.#acceptLease(receipt);
14556
- if (this.#phase === WorkspaceSyncPhase.Discover) return this.#acceptDiscovery(receipt);
14557
- if (this.#phase === WorkspaceSyncPhase.Placement) {
14558
- if (!this.#placementFetch || !this.#lease) throw new Error("placement transfer lost its lease");
14559
- this.#placementFetch.accept(receipt);
14544
+ if (state.phase === WorkspaceSyncPhase.Offer || state.phase === WorkspaceSyncPhase.Probe) {
14545
+ return this.#acceptOffer(receipt, state);
14546
+ }
14547
+ if (state.phase === WorkspaceSyncPhase.Lease) return this.#acceptLease(receipt, state);
14548
+ if (state.phase === WorkspaceSyncPhase.Discover) return this.#acceptDiscovery(receipt);
14549
+ if (state.phase === WorkspaceSyncPhase.Placement) {
14550
+ state.transfer.accept(receipt);
14560
14551
  this.#placementFetched?.();
14561
- if (this.#afterPlacement) return this.#finish(this.#afterPlacement);
14562
- return this.#beginMainFetch(this.#lease);
14552
+ if (state.afterPlacement) return this.#finish(state.afterPlacement);
14553
+ return this.#beginMainFetch(state.lease);
14563
14554
  }
14564
- if (this.#phase === WorkspaceSyncPhase.Fetch) return this.#acceptFetch(receipt);
14555
+ if (state.phase === WorkspaceSyncPhase.Fetch) return this.#acceptFetch(receipt, state);
14565
14556
  throw new Error(`workspace sync '${this.workId}' is already complete`);
14566
14557
  } catch (error) {
14567
14558
  this.#lastReceiptEffectId = previousReceipt;
14568
14559
  throw error;
14569
14560
  }
14570
14561
  }
14571
- #beginOffer(body, probe = false) {
14572
- this.#phase = probe ? WorkspaceSyncPhase.Probe : WorkspaceSyncPhase.Offer;
14562
+ #beginOffer(body, exchange, probe = false) {
14563
+ const phase = probe ? WorkspaceSyncPhase.Probe : WorkspaceSyncPhase.Offer;
14564
+ this.#state = { phase, exchange };
14573
14565
  const token = this.#authToken();
14574
14566
  this.#pushBytes += BigInt(body.byteLength);
14575
14567
  return this.#remember({
@@ -14579,7 +14571,7 @@ var WorkspaceSyncWork = class {
14579
14571
  effect: {
14580
14572
  case: "http",
14581
14573
  value: create59(PeerHttpEffectSchema, {
14582
- effectId: `${this.workId}:${this.#phase}`,
14574
+ effectId: `${this.workId}:${phase}`,
14583
14575
  method: PeerHttpMethod.POST,
14584
14576
  url: this.#offerEndpoint(),
14585
14577
  contentType: CUSTODY_OFFER_MEDIA_TYPE,
@@ -14596,28 +14588,30 @@ var WorkspaceSyncWork = class {
14596
14588
  })
14597
14589
  });
14598
14590
  }
14599
- #acceptOffer(receipt) {
14600
- this.#pushMillis = this.#offerStarted === void 0 ? 0 : elapsedMillis(this.#clock, this.#offerStarted);
14591
+ #acceptOffer(receipt, state) {
14592
+ const exchange = state.exchange;
14593
+ this.#pushMillis = exchange.startedAt === void 0 ? 0 : elapsedMillis(this.#clock, exchange.startedAt);
14601
14594
  if (receipt.outcome.case !== "response") {
14602
14595
  throw new Error("platform returned the wrong receipt for a custody offer");
14603
14596
  }
14604
- const response = receipt.outcome.value;
14605
- if (!response.contentType?.includes(CUSTODY_OFFER_RESULT_MEDIA_TYPE)) {
14597
+ const outcome = settleCustodyOfferExchange(exchange, receipt.outcome.value);
14598
+ if (outcome.case === "maintenance") {
14599
+ return this.#finishOfferOutcome(true, false);
14600
+ }
14601
+ if (outcome.case === "unexpected") {
14606
14602
  this.#pushError = create59(RuntimeTransportFailureSchema, {
14607
14603
  endpoint: this.#offerEndpoint(),
14608
- status: response.status,
14604
+ status: outcome.status,
14609
14605
  refused: false,
14610
- message: `Sync received an unexpected server response (HTTP ${response.status}; ${response.contentType || "no content type"}). No custody receipt was returned.`
14606
+ message: `Sync received an unexpected server response (HTTP ${outcome.status}; ${outcome.contentType || "no content type"}). No custody receipt was returned.`
14611
14607
  });
14612
14608
  return this.#finishOfferOutcome(false);
14613
14609
  }
14614
- if (!this.#offerRequestId) throw new Error("custody offer has no request identity");
14615
- const outcome = decodeCustodyOffer(response.body, this.#offerRequestId);
14616
- this.#offerReceipt = custodyOfferReceipt(this.#offerRequestId, outcome);
14610
+ this.#offerReceipt = outcome.receipt;
14617
14611
  if (outcome.case === "failure") {
14618
14612
  this.#pushError = create59(RuntimeTransportFailureSchema, {
14619
14613
  endpoint: this.#offerEndpoint(),
14620
- status: response.status,
14614
+ status: outcome.status,
14621
14615
  refused: false,
14622
14616
  message: `${outcome.value.code}: ${outcome.value.message}`
14623
14617
  });
@@ -14626,12 +14620,12 @@ var WorkspaceSyncWork = class {
14626
14620
  if (outcome.case === "blocked") {
14627
14621
  return this.#finishOfferOutcome(true);
14628
14622
  }
14629
- if (response.status < 200 || response.status >= 300) {
14623
+ if (outcome.status < 200 || outcome.status >= 300) {
14630
14624
  this.#pushError = create59(RuntimeTransportFailureSchema, {
14631
14625
  endpoint: this.#offerEndpoint(),
14632
- status: response.status,
14633
- refused: response.status === 422,
14634
- message: `custody offer returned an admission verdict with HTTP ${response.status}`
14626
+ status: outcome.status,
14627
+ refused: outcome.status === 422,
14628
+ message: `custody offer returned an admission verdict with HTTP ${outcome.status}`
14635
14629
  });
14636
14630
  return this.#finishOfferOutcome(false);
14637
14631
  }
@@ -14639,8 +14633,7 @@ var WorkspaceSyncWork = class {
14639
14633
  this.#desiredHead = admission.main || void 0;
14640
14634
  if (!this.#desiredHead) return this.#transportFailure("resident peer returned no admitted frontier", false);
14641
14635
  this.#candidateDurable = admission.sealedMain === this.#desiredHead;
14642
- if (this.#phase === WorkspaceSyncPhase.Probe && this.#desiredHead !== this.#localHead) {
14643
- if (!this.#establishBase(this.#desiredHead)) return this.#noCommonBaseResult();
14636
+ if (state.phase === WorkspaceSyncPhase.Probe && this.#desiredHead !== this.#localHead) {
14644
14637
  if (this.#alreadyJudged(this.#desiredHead)) {
14645
14638
  return this.#finish(create59(SyncWorkspaceResponseSchema, {
14646
14639
  workspace: this.#mount.workspace,
@@ -14652,6 +14645,22 @@ var WorkspaceSyncWork = class {
14652
14645
  offerReceipt: this.#offerReceipt
14653
14646
  }));
14654
14647
  }
14648
+ if (!this.#establishBase(this.#desiredHead)) {
14649
+ this.#offerAfterConverge = this.#command.admit;
14650
+ this.#rebaseFrom = void 0;
14651
+ return this.#leaseEffect();
14652
+ }
14653
+ if (this.#establishedBase && !this.#candidateDurable && this.#kernel.resolveRef(this.#mount, this.#requestId, trackedMain) === void 0) {
14654
+ return this.#finish(create59(SyncWorkspaceResponseSchema, {
14655
+ workspace: this.#mount.workspace,
14656
+ currentHead: this.#localHead,
14657
+ previousHead: this.#previousHead,
14658
+ stillAhead: true,
14659
+ directOfferDeferred: true,
14660
+ reconnectRequired: false,
14661
+ offerReceipt: this.#offerReceipt
14662
+ }));
14663
+ }
14655
14664
  if (this.#probeOnly) {
14656
14665
  this.#needsReconciliation = true;
14657
14666
  return this.#finish(create59(SyncWorkspaceResponseSchema, {
@@ -14697,11 +14706,11 @@ var WorkspaceSyncWork = class {
14697
14706
  return this.#converge(this.#desiredHead, admission.resultPack.byteLength, 0);
14698
14707
  }
14699
14708
  if (this.#desiredHead === this.#previousHead) return this.#converge(this.#desiredHead, 0, 0);
14700
- if (!this.#establishBase(this.#desiredHead)) return this.#noCommonBaseResult();
14709
+ this.#establishBase(this.#desiredHead);
14701
14710
  if (admission.recoveryRequired !== false) return this.#leaseEffect();
14702
14711
  return this.#transportFailure("resident peer supplied neither the missing delta nor a history gap", false);
14703
14712
  }
14704
- #acceptLease(receipt) {
14713
+ #acceptLease(receipt, state) {
14705
14714
  if (receipt.outcome.case !== "response") {
14706
14715
  throw new Error("platform returned the wrong receipt for a Git read lease");
14707
14716
  }
@@ -14717,21 +14726,25 @@ var WorkspaceSyncWork = class {
14717
14726
  this.#leased = true;
14718
14727
  const frontier = this.#placementFrontier();
14719
14728
  if (frontier) {
14720
- this.#placementFetch = new PlacementFetch(
14729
+ const transfer = new PlacementFetch(
14721
14730
  this.#kernel,
14722
14731
  this.#mount,
14723
14732
  this.#requestId,
14724
14733
  this.workId,
14725
14734
  frontier
14726
14735
  );
14727
- const pending = this.#placementFetch.start(lease);
14736
+ const pending = transfer.start(lease);
14728
14737
  if (pending) {
14729
- this.#phase = WorkspaceSyncPhase.Placement;
14730
- this.#lease = lease;
14738
+ this.#state = {
14739
+ phase: WorkspaceSyncPhase.Placement,
14740
+ transfer,
14741
+ lease,
14742
+ ...state.afterPlacement ? { afterPlacement: state.afterPlacement } : {}
14743
+ };
14731
14744
  return this.#remember({ case: "pending", value: pending });
14732
14745
  }
14733
14746
  }
14734
- if (this.#afterPlacement) return this.#finish(this.#afterPlacement);
14747
+ if (state.afterPlacement) return this.#finish(state.afterPlacement);
14735
14748
  return this.#beginMainFetch(lease);
14736
14749
  }
14737
14750
  /** The provider's placement frontier as this round knows it: what the directory named in this
@@ -14751,8 +14764,7 @@ var WorkspaceSyncWork = class {
14751
14764
  if (!new PlacementFetch(this.#kernel, this.#mount, this.#requestId, this.workId, frontier).wanted()) {
14752
14765
  return void 0;
14753
14766
  }
14754
- this.#afterPlacement = presented;
14755
- return this.#leaseEffect();
14767
+ return this.#leaseEffect(presented);
14756
14768
  }
14757
14769
  #acceptDiscovery(receipt) {
14758
14770
  this.#directoryAnswered?.();
@@ -14776,9 +14788,11 @@ var WorkspaceSyncWork = class {
14776
14788
  return this.#replaceLocalHead !== void 0 || this.#localHead === void 0 && this.#previousHead === void 0;
14777
14789
  }
14778
14790
  #beginMainFetch(lease) {
14779
- this.#phase = WorkspaceSyncPhase.Fetch;
14780
14791
  const lawProducts = this.#lawProductsAlongside();
14781
- this.#fetchStarted = this.#clock.monotonicNanoseconds();
14792
+ this.#state = {
14793
+ phase: WorkspaceSyncPhase.Fetch,
14794
+ startedAt: this.#clock.monotonicNanoseconds()
14795
+ };
14782
14796
  const haves = this.#replaceLocalHead !== void 0 ? this.#repairMissingCommit ? [] : [this.#kernel.resolveRef(this.#mount, this.#requestId, deferredRemote)].filter(
14783
14797
  (head) => !!head
14784
14798
  ) : [...new Set([
@@ -14809,7 +14823,7 @@ var WorkspaceSyncWork = class {
14809
14823
  })
14810
14824
  });
14811
14825
  }
14812
- #acceptFetch(receipt) {
14826
+ #acceptFetch(receipt, state) {
14813
14827
  if (receipt.outcome.case !== "gitFetch") {
14814
14828
  throw new Error("platform returned the wrong receipt for a Git object fetch");
14815
14829
  }
@@ -14819,7 +14833,7 @@ var WorkspaceSyncWork = class {
14819
14833
  if (fetched.pack.byteLength > 0) {
14820
14834
  this.#kernel.applyPack(this.#mount, this.#requestId, fetched.pack, fetched.head, fetched.shallow);
14821
14835
  }
14822
- const prefetchMillis = this.#fetchStarted === void 0 ? 0 : elapsedMillis(this.#clock, this.#fetchStarted);
14836
+ const prefetchMillis = elapsedMillis(this.#clock, state.startedAt);
14823
14837
  const candidateHead = this.#receiveState ? this.#kernel.resolveStatePosition?.(this.#mount, this.#requestId, fetched.head, this.#receiveState) : fetched.head;
14824
14838
  if (!candidateHead) {
14825
14839
  return this.#finish(create59(SyncWorkspaceResponseSchema, {
@@ -14915,7 +14929,7 @@ var WorkspaceSyncWork = class {
14915
14929
  }
14916
14930
  }
14917
14931
  #converge(candidateHead, receivedBytes, prefetchMillis) {
14918
- if (!this.#establishBase(candidateHead)) return this.#noCommonBaseResult();
14932
+ this.#establishBase(candidateHead);
14919
14933
  this.#beforeConverge?.(candidateHead, this.#previousHead);
14920
14934
  const convergeStarted = this.#clock.monotonicNanoseconds();
14921
14935
  let judged;
@@ -15170,11 +15184,11 @@ var WorkspaceSyncWork = class {
15170
15184
  pushBytes: this.#pushBytes,
15171
15185
  pullBytes: BigInt(receivedBytes),
15172
15186
  serverMillis: this.#admissionSummary?.serverMillis ?? 0,
15173
- offerInPush: this.#offerRequestId !== void 0
15187
+ offerInPush: this.#pushBytes > 0n
15174
15188
  })
15175
15189
  });
15176
15190
  }
15177
- #finishOfferOutcome(blocked) {
15191
+ #finishOfferOutcome(blocked, reconnectRequired = true) {
15178
15192
  return this.#finish(create59(SyncWorkspaceResponseSchema, {
15179
15193
  workspace: this.#mount.workspace,
15180
15194
  previousHead: this.#previousHead,
@@ -15182,7 +15196,7 @@ var WorkspaceSyncWork = class {
15182
15196
  admitted: BigInt(this.#admissionSummary?.admittedIntentIds.length ?? 0),
15183
15197
  refused: BigInt((this.#admissionSummary?.refusedMessages.length ?? 0) + this.#parkedLocal),
15184
15198
  deferred: BigInt(this.#admissionSummary ? this.#admissionSummary.blockedMessages.length + this.#admissionSummary.deadLetteredMessages.length + this.#admissionSummary.quarantinedMessages.length + this.#admissionSummary.deferredMessages.length : blocked ? 1 : 0),
15185
- reconnectRequired: true,
15199
+ reconnectRequired,
15186
15200
  stillAhead: this.#localHead !== this.#previousHead,
15187
15201
  directOfferDeferred: blocked,
15188
15202
  offerReceipt: this.#offerReceipt,
@@ -15196,15 +15210,16 @@ var WorkspaceSyncWork = class {
15196
15210
  }));
15197
15211
  }
15198
15212
  #transportFailure(message, offline, status = -1) {
15199
- const pushFailed = this.#phase === WorkspaceSyncPhase.Offer;
15213
+ const state = this.#state;
15214
+ const pushFailed = state.phase === WorkspaceSyncPhase.Offer;
15200
15215
  const failedEndpoint = this.#offerEndpoint();
15201
15216
  const failure2 = create59(RuntimeTransportFailureSchema, {
15202
15217
  endpoint: pushFailed ? failedEndpoint : this.#binding.cloud ?? "",
15203
15218
  status,
15204
15219
  message
15205
15220
  });
15206
- if (pushFailed) {
15207
- this.#pushMillis = this.#offerStarted === void 0 ? 0 : elapsedMillis(this.#clock, this.#offerStarted);
15221
+ if (state.phase === WorkspaceSyncPhase.Offer) {
15222
+ this.#pushMillis = state.exchange.startedAt === void 0 ? 0 : elapsedMillis(this.#clock, state.exchange.startedAt);
15208
15223
  this.#pushError = failure2;
15209
15224
  }
15210
15225
  return this.#finish(create59(SyncWorkspaceResponseSchema, {
@@ -15221,7 +15236,7 @@ var WorkspaceSyncWork = class {
15221
15236
  pushMillis: this.#pushMillis,
15222
15237
  pushBytes: this.#pushBytes,
15223
15238
  prefetchMillis: elapsedMillis(this.#clock, this.#started),
15224
- offerInPush: this.#offerRequestId !== void 0
15239
+ offerInPush: this.#pushBytes > 0n
15225
15240
  })
15226
15241
  }));
15227
15242
  }
@@ -15231,7 +15246,7 @@ var WorkspaceSyncWork = class {
15231
15246
  return step;
15232
15247
  }
15233
15248
  #finish(value) {
15234
- this.#phase = WorkspaceSyncPhase.Done;
15249
+ this.#state = { phase: WorkspaceSyncPhase.Done };
15235
15250
  this.#current = void 0;
15236
15251
  return this.#remember({ case: "synced", value });
15237
15252
  }
@@ -17359,13 +17374,21 @@ var CorrespondenceDesk = class {
17359
17374
  }
17360
17375
  #observeSyncResult(requestId, workId2, result) {
17361
17376
  const timings = result.timings;
17377
+ const receipt = result.offerReceipt?.outcome;
17378
+ const admission = receipt?.case === "admission" ? receipt.value : void 0;
17362
17379
  this.#events.instant("peer.follow.sync-result", {
17363
17380
  request: create64(RuntimeRequestSchema, { protocolVersion: 2, requestId }),
17364
17381
  workspace: result.workspace
17365
17382
  }, {
17366
17383
  "sync.workId": workId2,
17367
17384
  ...result.currentHead ? { "sync.head": result.currentHead } : {},
17385
+ ...result.previousHead ? { "sync.previousHead": result.previousHead } : {},
17386
+ ...admission?.main ? { "sync.providerHead": admission.main } : {},
17387
+ ...admission?.sealedMain ? { "sync.sealedHead": admission.sealedMain } : {},
17368
17388
  "sync.stillAhead": result.stillAhead,
17389
+ "sync.reconnectRequired": result.reconnectRequired,
17390
+ "sync.refused": result.refused,
17391
+ "sync.deferred": result.deferred,
17369
17392
  "sync.directOfferDeferred": result.directOfferDeferred,
17370
17393
  "sync.offline": result.offline,
17371
17394
  ...timings ? {