@run402/sdk 4.61.1 → 4.62.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.
@@ -1101,6 +1101,11 @@ export function createGitvaultHttpTransport(client, options = {}) {
1101
1101
  },
1102
1102
  getVaultRecord: ({ repo_id }) => client.request(base(repo_id), { context: "reading the gitvault record" }),
1103
1103
  getState: ({ repo_id, since }) => getVaultStateOut(repo_id, since),
1104
+ openCompactionGrant: ({ repo_id }) => client.request(`${base(repo_id)}/compaction-grant`, { method: "POST", context: "opening the gitvault compaction headroom grant" }),
1105
+ closeCompactionGrant: async ({ repo_id }) => {
1106
+ const r = await client.request(`${base(repo_id)}/compaction-grant`, { method: "DELETE", context: "closing the gitvault compaction headroom grant" });
1107
+ return { closed: r.closed === true };
1108
+ },
1104
1109
  findVaultByProject: ({ project_id }) => client.request(`/gitvault/v1/vaults?project_id=${encodeURIComponent(project_id)}`, { context: "resolving the project's gitvault" }),
1105
1110
  findVaultByRepo: ({ org_slug, repo_name }) => client.request(`/gitvault/v1/vaults?repo=${encodeURIComponent(`${org_slug}/${repo_name}`)}`, { context: "resolving the gitvault by repo address" }),
1106
1111
  listOrgEncryptionKeys: ({ org_id }) => client.request(`/orgs/v1/${encodeURIComponent(org_id)}/encryption-keys`, { context: "reading the org encryption-key directory" }),
@@ -1318,6 +1323,35 @@ async function writeGitvaultRestoreMarker(targetRepoDir, generation, headSha256)
1318
1323
  await hardenedGit(targetRepoDir, ["config", "--local", GITVAULT_RESTORE_MARKER_GENERATION_KEY, generation]);
1319
1324
  await hardenedGit(targetRepoDir, ["config", "--local", GITVAULT_RESTORE_MARKER_SHA256_KEY, headSha256]);
1320
1325
  }
1326
+ // ─── auto-gc cadence threshold (gitvault-checkpoint-cadence design D1) ───────
1327
+ //
1328
+ // `auto_gc_generations` rides the SAME local-git-config mechanism as the
1329
+ // restore marker above — a per-CHECKOUT knob, exactly like git's own
1330
+ // `gc.auto` (`git config gc.auto`), not a server-side vault policy. Read
1331
+ // fresh every push (a cheap local read, never network); `0` disables;
1332
+ // absent reads as the default.
1333
+ const GITVAULT_AUTO_GC_GENERATIONS_KEY = "r402.autoGcGenerations";
1334
+ /** Default `auto_gc_generations` — see the change proposal's rationale (≈2-3s over the fresh-checkpoint floor at this backlog, a busy repo compacts roughly once per few dozen pushes). */
1335
+ export const GITVAULT_AUTO_GC_GENERATIONS_DEFAULT = 32;
1336
+ /**
1337
+ * Read this checkout's auto-gc threshold, or the default when unset or
1338
+ * unparseable. Never throws — a corrupt local config value degrades to the
1339
+ * default rather than blocking a push's own auto-gc check.
1340
+ */
1341
+ export async function readGitvaultAutoGcThreshold(targetRepoDir) {
1342
+ const raw = await readLocalGitConfigValue(targetRepoDir, GITVAULT_AUTO_GC_GENERATIONS_KEY);
1343
+ if (raw === null)
1344
+ return GITVAULT_AUTO_GC_GENERATIONS_DEFAULT;
1345
+ const trimmed = raw.trim();
1346
+ if (!/^\d+$/.test(trimmed))
1347
+ return GITVAULT_AUTO_GC_GENERATIONS_DEFAULT;
1348
+ const n = Number.parseInt(trimmed, 10);
1349
+ return Number.isSafeInteger(n) && n >= 0 ? n : GITVAULT_AUTO_GC_GENERATIONS_DEFAULT;
1350
+ }
1351
+ /** Set this checkout's auto-gc threshold. `0` disables auto-gc entirely. */
1352
+ export async function writeGitvaultAutoGcThreshold(targetRepoDir, generations) {
1353
+ await hardenedGit(targetRepoDir, ["config", "--local", GITVAULT_AUTO_GC_GENERATIONS_KEY, String(generations)]);
1354
+ }
1321
1355
  /** A transport-agnostic view of git ops the publication needs (the local repository). */
1322
1356
  export class GitvaultVault {
1323
1357
  keystore;
@@ -3578,13 +3612,28 @@ export class GitvaultVault {
3578
3612
  * verification and retained-refs reconciliation run UNCHANGED on both
3579
3613
  * paths; the marker only advances after they both succeed.
3580
3614
  */
3581
- async restoreObjectsInto(targetRepoDir) {
3615
+ async restoreObjectsInto(targetRepoDir, reuse) {
3582
3616
  // gitvault-delta-fetch: the marker is read BEFORE materialize so the
3583
3617
  // state read can carry THIS git dir's applied position as `since` —
3584
3618
  // see tryStateFastPath's own comment on why the pin is the wrong
3585
3619
  // position for a standing clone.
3620
+ //
3621
+ // gitvault-session-state-reuse: `reuse` lets a caller that ALREADY
3622
+ // materialized state for this same target directory — using the SAME
3623
+ // marker `since` this method would itself read — hand that response
3624
+ // in directly, skipping this method's own state read entirely (the
3625
+ // remote-helper session's `list` phase is the caller: see `runFetch`
3626
+ // in `cli/lib/remote-helper-session.mjs`). The marker below is read
3627
+ // regardless (a cheap local `git config` read, never a network call)
3628
+ // and compared against `reuse.marker`: only an EXACT match (both
3629
+ // `null`, or both present with equal `generation`/`head_sha256`) is
3630
+ // trusted — any mismatch (a different push admitted in this same
3631
+ // session, a stale handoff) falls through to this method's own
3632
+ // `materialize()` exactly as if `reuse` had never been passed. A wrong
3633
+ // reuse therefore costs one extra read, never a wrong result.
3586
3634
  const marker = await readGitvaultRestoreMarker(targetRepoDir);
3587
- const newest = await this.materialize({ ...(marker ? { deltaSince: marker.generation } : {}) });
3635
+ const markerMatches = (a, b) => a === null ? b === null : b !== null && a.generation === b.generation && a.head_sha256 === b.head_sha256;
3636
+ const newest = reuse && markerMatches(reuse.marker, marker) ? reuse.state : await this.materialize({ ...(marker ? { deltaSince: marker.generation } : {}) });
3588
3637
  if (!newest.head) {
3589
3638
  const retained_refs = await reconcileRetainedTipRefs(targetRepoDir, { refs: {}, roots: [], head_target: newest.head_target });
3590
3639
  return { refs: {}, head_target: newest.head_target, generation: newest.generation, retained_refs };
@@ -3844,6 +3893,15 @@ export class GitvaultVault {
3844
3893
  await writeGitvaultRestoreMarker(targetRepoDir, newest.generation, newest.head_sha256);
3845
3894
  return { refs: newest.refs, head_target: newest.head_target, generation: newest.generation, retained_refs };
3846
3895
  }
3896
+ // ── compaction headroom grant (gitvault-checkpoint-cadence design D3) ──
3897
+ /** Thin passthrough to the transport — see {@link GitvaultTransport.openCompactionGrant}. */
3898
+ async openCompactionGrant() {
3899
+ return this.transport.openCompactionGrant({ repo_id: this.repoId });
3900
+ }
3901
+ /** Thin passthrough to the transport — see {@link GitvaultTransport.closeCompactionGrant}. Always safe best-effort; never throws by construction of the route (idempotent). */
3902
+ async closeCompactionGrant() {
3903
+ return this.transport.closeCompactionGrant({ repo_id: this.repoId });
3904
+ }
3847
3905
  }
3848
3906
  /** §4.7 cross-field equality: covers_through agree; the claim set's ordered pack ids/hashes/sizes/total equal the manifest's (shared stored fields only). */
3849
3907
  export function checkClaimSetEquality(claimSet, manifest, headCoversThrough) {