contract store · 10-store.md · 11-snapshot.md · 12-time.md · 13-replica.md · 14-drain.md · 15-endpoint.md · 16-collect.md · 17-merge.md · 18-pull.md · guide · card
Bucket pulls
A pull downloads the bucket into the store, verifying each object, and writes each table pointer last.
pull
Downloading bucket files, verifying digests, advancing table pointers and restoring named generations.
digest-mismatchtestA downloaded object whose digest differs from its manifest entry is discarded and raisesSyncObjectDigestMismatch; run states followrun-state-torninstead.Why
P4run-state-torntestA downloadednodes/<node-id>/run-state.jsondiffering from its listed digest is discarded with a warning naming the key, and the pull continues without it.Why
because an interrupted push leaves a run state its manifest never committedidentity-conflicttestA pull into a store holding a differentstore-identityraisesStoreIdentityConflictbefore downloading files.Why
because importing another UUID lets that store’s owner credential read local rowsconvergencetestA named key disappearing mid-download makes the pull re-fetch the manifest and retry the shortfall, up to 3 attempts.unconvergedtestExhausting those retries raisesSyncPullDidNotConverge, naming the key that kept moving, and writes no pointer.Why
P4unconverged-resumetestA pull refused underunconvergedkeeps verified objects, so the nextsync pulldownloads only keys whose digests still differ.transfer-widthtestA pull downloads with up to[sync] transfersconcurrent requests, default 16 requests, minimum 1, and writes every verified object before returning any refusal.Why
because one request per round trip makes a cold boot grow with object countdigest-recordtestPull and push reuse a file’s machine-local sha256 record while size, modification time, change time and inode match it, and hash the file otherwise.Why
because rehashing every held file on each sync grows with the storepointer-lasttestA pull writes a table’s pointer only once every Parquet part of the named snapshot exists locally, so no reader meets a pointer before its data.run-manifest-lasttestA pull writes a run’s_manifest.jsononly after every other listed object it downloads exists locally, so a concurrent read resolves no run missing parts.Why
because a read resolves every run whose_manifest.jsonexiststombstone-appliedtestPull deletes local keys tombstoned and absent from entries; a tombstoned key with no local copy goes unverified and unreported.Why
because a deletion that never reaches a consumer resurrects once its tombstone ages outcold-own-keystesttestA pull downloads each key its node owns that the store lacks andheld-recorddoes not list, so a replacement host or a widened table scope reusing the node id reads every such key.Why
because only a key the store held and lost is its own deletion; one it never held carries rows nothing local replacespointer-readtestPull and push read the pointer of each table the bucket manifest lists by its key,tables/<t>/_pointer.json, and list no table objects, so the read grows with the tables and not with their objects.Why
because a bucket gathers parts with every landing, and a pointer read listing them grows with the store’s historypointer-advancetestPull advances a local pointer whose snapshot the bucket pointer’s chain names, keeping one on the same snapshot at a higher fence, and replaces any other only underpointer-adopt, verifying every advancing table before writing any pointer.pointer-adopttesttestPull replaces a local pointer the bucket pointer’s chain does not name, whatever its fence, save one ahead whose ancestry names the bucket pointer’s snapshot, and warns oncelocal-fold-unrecoverableadmits its branch.Why
because a fold outside a lease writes no fence, and a local snapshot off the bucket’s chain misses every run the bucket’s head foldedlocal-fold-unrecoverabletestA branch back to the bucket’s chain or a root that folds a run neither on disk, listed in the bucket nor folded on that chain, or meets an absent manifest, raisesSyncLocalFoldUnrecoverablenaming the runs, and the pointer stays.Why
because those rows otherwise live only in the replaced snapshot’s parts, which collection deletes once no pointer names themschema-mergetestA pulledschema.jsondiffering from the local copy merges into it column by column throughincompatible‘s lattice rather than replacing it.Why
because rows landed locally carry columns the bucket’s copy may lacktable-scopetestcontextful sync pull --table <pattern>, repeatable, pulls only the tables its patterns cover throughone-matcher. Without the flag, a pull takes[sync] pull_tables, and without either, every table.Why
because a disposable disk serving one table otherwise downloads every table the bucket holdstables-emptytestA[sync] pull_tablesholding no pattern raisesSyncPullTablesEmptywhen the config is read;["*"]names every table.Why
because an empty list reads as no table to one operator and every table to anotherbefore-runtestWith[sync] pull_before_run = true,run start,pipeline run,mcpandservepull the tablestable-scopeselects before their first read, a failed pull stopping the command.Why
because a container on an empty disk otherwise serves without other nodes’ runsserve-refreshtestUnderbefore-run,servepulls again every 30 s and maps the new generation for reads starting after that pull; a failed refresh keeps serving the held generation and logs the failure.Why
because a separate query face otherwise serves its starting generation until restartgenerationtestcontextful sync pull --generation <N>readsgen-<N>.jsonin place of the bucket manifest, downloads its in-scope keys at their listed digests and writes exactly its pointers regardless of fence.generation-listtestcontextful sync generationslists the bucket’sgen-<N>.jsonfiles in ascending order withcommitted_at,committed_byand the project’s entry and tombstone counts, a missing stamp printing as-.Why
because generation numbers carry no timegeneration-absenttestA generation the bucket holds nogen-<N>.jsonfor raisesSyncGenerationAbsent, naming N and the newest generation, and writes nothing.Why
because a restore falling back to another generation reads a state nobody namedgeneration-divergedtestA local syncable file in scope that the generation omits makes generation pull raiseSyncGenerationDiverged, naming the file, and write nothing.Why
because a kept unlisted run adds rows the generation never heldgeneration-schematestA generation pull merges the bucket’s currentschema.jsonunderschema-merge, refusing nothing over a differing generation digest.Why
because the lattice keeps every column an earlier generation carriedgeneration-run-statetestGeneration pull exemptsnodes/<node-id>/run-state.jsonfromgeneration-diverged, keeping run states the generation omits; other unlisted files undernodes/meet that refusal.Why
because a run state summarizes history outside the store rootrun-state-cursortestAt run open, a run resumes from a pulled run state’s cursor when that pipeline and table’s commit marker is newer than every local one.Why
because a collected run takes its cursor out of the bucket, so a cold node re-reads the sourcecontrol-headtestPull downloads the bucket manifest’s project-scoped control head, snapshot and receipt ancestry at their listed digests. The local applied pointer stays unchanged forpulled-control.
lazy parts
A lazy store pulls metadata and fetches each data part and folded run manifest when a reader first needs it.
lazy-partstestWith[sync] lazy_parts = true, pull skips table Parquet data parts, which the store fetches whole on first read or fold use and verifies underdigest-mismatch.lazy-pointertestUnderlazy-parts,pointer-lastwaits for the named snapshot’s_manifest.jsonalone; a part’s digest comes from the bucket manifest when a read first fetches it.lazy-readytesttestUnderlazy-parts,before-runwaits for every in-scope object except data parts and the manifestslazy-folded-runsskips.lazy-folded-runstesttesttestUnderlazy-parts, pull skips each other node’s run_manifest.jsonthat the pointer’s snapshot chain folds, keeping its_group.json, and lists the skipped keys in the store’s.skipped-runs.json.husks-skippedtesttesttesttestUnderlazy-parts, pull skips each_manifest.jsonbeside a listed_collected.jsonwhere the pointer’s snapshot, once home, holds a valid folded-runs index and an ancestry reaching the local pointer, listing it in.skipped-runs.json.husks-fetchedtesttesttesttestA full chain walk, or one meeting no valid index, fetches the table’s skipped collected snapshot manifests through the part source at the first it reaches; local retention and a walk past a valid index end there.husks-stalenesstesttestA model’s staleness check reads its head snapshot, each input’s snapshots from the pointer down to the watermark snapshot, and the runs folded beneath throughfolded-index-walk, fetching a skipped manifest only on that path.skipped-recordtesttesttestEach pull clearing its refusals rewrites.skipped-runs.jsonto the manifests the bucket lists and the store lacks, inside its scope or outside it, a generation restore also keeping each entry outside its scope; an empty record leaves no file.Why
because a manifest the bucket stopped listing was collected, and an eager pull applies its tombstonefolded-runs-fetchedtesttesttestListing a table’s committed runs, or anas_ofread resolving before the pointer’s snapshot, fetches each skipped manifest through the part source withinmiss-wait‘s deadline; an unbounded read fetches none.folded-run-collectedtesttesttesttesttestA skipped manifest the part source cannot bring home refuses the read aspart-collecteddoes, save where the source answers it collected: an unbounded listing leaves it out, and a bounded read does when a kept snapshot folds its run.folded-runs-unreachabletestA store listing skipped manifests and opened without a part source raisesSyncFoldedRunsUnfetchedwhereverfolded-runs-fetchedorhusks-fetchedfetches, naming the table and a manifest.folded-runs-sourcetestEvery command opening a store whose[sync]declareslazy_partsregisters the bucket’s part source once per process, whether or not the command pulls.Why
because a command reading runs without pulling otherwise meetsfolded-runs-unreachablepart-cachetest[sync] part_cache_bytes, default 8 GiB, bounds locally fetched parts; above it, the cache deletes the least recently read part no statement or fold holds.part-accountingtestEach verified lazy download counts once towardpart-cacheupon reaching disk, despite other fetch failures. If the cache index is missing, reopening adopts receipt-listed parts without bucket reads or receipt-free tree scans.Why
because a partial acquisition and a lost index leave completed downloads subject to evictionpart-collectedtestA partlazy-partsneeds and the bucket lacks raisesSyncPartCollected, naming the part and snapshot; the next pull advances the pointer past that snapshot.Why
becausesync collectdeletes a tombstoned run’s parts while a container whose pointer predates the tombstone reads them
reland
A reland lands again the runs a refused pull names and moves the local pointer onto the bucket pointer’s snapshot.
relandtesttestcontextful sync reland <table>lands each runlocal-fold-unrecoverablenames again as a new run of this node, then writes the bucket pointer’s snapshot and fence as the local pointer, so the next pull proceeds.Why
because those rows otherwise wait on a landing by hand and a deleted pointer per tablereland-namedtesttestA refusal underlocal-fold-unrecoverablenaming runs namescontextful sync reland <table>as its remedy; one meeting an absent manifest names landing the rows by hand.Why
because an operator reads the remedy where the pull stopsreland-rowstesttestA relanded run holds the local snapshot’s rows whose_run_id, and_commit_seqand_ingested_atwhere the folded-runs index records them, match the run’s, committed at its instant with its site, author and taint.Why
because one run id repeats across nodes, and the original instant keeps a keyed read’s rankingreland-keyedtestA relanded run of a keyed table holds only the rows its snapshot kept, and the reland reports beside them the rows the folded-runs index records the run held.Why
because a later run in the branch superseded the rest, and that run folds again or is relanded tooreland-lineagetestA relanded run’s id isreland-and 16 hex digits of SHA-256 over the local snapshot id and the run key, and its manifest’srelanded_fromnames that key.Why
because a repeat then replays the committed run instead of landing its rows twicereland-ordertestA reland commits every relanded run and syncs its files to disk before writing the pointer; a failure before that write leaves the local pointer and the snapshot’s parts as they stood.Why
because the pointer write is the step after which no pointer names the snapshot’s partsreland-unneededtesttestA reland of a table holding no local pointer, against a bucket naming no snapshot, of a pointerpointer-advancekeeps or advances, or of a branch folding no unrecoverable run raisesSyncRelandUnneeded, writing nothing.Why
because with nothing stranded the pull already brings every row homereland-head-absenttestA reland while the bucket pointer’s snapshot lacks its manifest on disk, or a Parquet part outsidelazy-parts, raisesSyncRelandHeadAbsent, naming the snapshot; the refused pull brings it home.Why
because without the head’s chain on disk every run the head folds reads as unrecoverablereland-unknowntesttestA branch meeting an absent manifest, or a run whose rows no folded-runs row tells apart from another node’s run of its id, refuses the reland aslocal-fold-unrecoverabledoes, writing nothing.Why
because neither names the rows a relanded run holds
A pull converges on the bucket manifest and writes each table pointer last.
Scenarios
store.pull.control-head: WHEN a cold node pulls a signed control head, THEN its local applied pointer stays absent until reconciliation verifies the head.