Skip to content

osm slab: hydrate from S3 onto the persistent volume, checksum-pinned - #112

Merged
AdaWorldAPI merged 2 commits into
mainfrom
claude/q2-osm-map-reencoding-56p5e2
Aug 11, 2026
Merged

osm slab: hydrate from S3 onto the persistent volume, checksum-pinned#112
AdaWorldAPI merged 2 commits into
mainfrom
claude/q2-osm-map-reencoding-56p5e2

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes the one item that blocked the POC from running anywhere but this sandbox, plus the plan-hygiene commit it was branched on.

"railway has exactly the same S3 variables like you" retired the blocker. Phase 4 was recorded as "verifiable only on deploy" because /volume01 doesn't exist here — but the volume is a path, and the part that needed proving (do these credentials reach that bucket, does a 1.29 GiB round trip work) is verifiable right now. OSM_SLAB_CACHE_DIR substitutes for the mount, so everything except the literal mount point is exercised locally.

Topology

Per the framing — "S3 → lancedb → volume01", "persist the lance file to volume01", "it's just a convenience to persist lance across rebuilds"S3 is the source of truth, the volume is a cache that survives container rebuilds. Deleting it costs a re-download and nothing else.

Three assumptions I had written down, all wrong

The earlier sketch claimed ~5 new deps, hand-rolled SigV4, and a sync→async conversion of open_slab(). Checked instead of repeated:

claimed measured
needs reqwest + a new HTTP stack object_store 0.13.2 is already in the graph via datafusion/lance — adding it direct pulls no new transitive tree
needs hand-rolled SigV4 AmazonS3Builder::from_env() reads it all, and aws_endpoint_url is an accepted alias for the endpoint key (builder.rs:498), so a non-AWS endpoint needs no special casing
open_slab() must become async main() is already async — hydrating there leaves the read path untouched and stops the first request paying for the download

Format-agnostic by construction

openstreetmap-website-rs states in its own Cargo.toml header: "no lance, no datafusion: the ABI row is a byte layout, not an engine" — and lance_abi was removed there (a27b06a) for doing a full-table materialising read. So this layer moves named objects onto the volume and verifies them; whether the artifact travels as .soa or as a Lance dataset is a separate decision it does not prejudge.

Worth carrying from that removal, since it names the right shape if the Lance path is taken later: "this crate already has fragment.rs (64k-row fragments, so a tile prefix lands in a fragment) and RowSlab::tile_range. The module used neither." object_store exposes get_range(location, Range<u64>) — exactly that range read.

Checksum pinning, on the cache hit too

SHA256SUMS is fetched from the same prefix; a missing entry is a refusal, not a warning. Verifying on a hit — not only after download — guards the half-written file left by a container killed mid-download, which is precisely the case a "we already have it" check waves through. Downloads land on .part and are renamed only after the hash matches.

Verified — all three paths, against the live bucket

path result
cold empty volume → downloaded 1,292,826,624 + 58,763,338 B, verified, mmap'd; serves 14/8802/5373 as 15,016 of 15,016 — identical to the local-file numbers
warm cache hit, both artifacts checksum-verified in ~0.8 s, no download
dirty 16 B corrupted mid-file → got=7247b2bb… want=d12bc8a1… detected, re-fetched, verified, no .part left behind

Both halves of the guard are proven on real data: silent on a clean cache, loud on a corrupt one.

⚠ Found while doing it — the server's tracing has never been emitted

The filter said cockpit_server=info, but the binary target is q2-cockpit, so module_path!() starts with q2_cockpit. Every tracing::* call in this binary was silently discarded. Noticed because the hydrate ran correctly and logged nothing — on a deploy that would have been 503s with no explanation at all. Both names are now listed so a rename of either cannot silence it again, and RUST_LOG wins when set, which is the operational control a deploy needs.

Bucket conventions — followed, not invented

The bucket already held 50+ MedCare-rs objects shaped <repo>/bakes/<version>/<artifact> + SHA256SUMS, with Lance datasets as .lance.tar. q2 uploads to q2/bakes/berlin-v1/ and touches nothing under MedCare-rs/.

key bytes
q2/bakes/berlin-v1/berlin.soa 1,292,826,624
q2/bakes/berlin-v1/berlin.books 58,763,338
q2/bakes/berlin-v1/SHA256SUMS 156

Also in this PR

The plan-hygiene commit this branched on: two follow-ups were checked off inside Phase 5's own list without closing their originals higher in the file, so a grep for open items reported the hip-cell comparison and the quadratic render as pending when both had shipped and been measured.

Verification

cargo test --bin q2-cockpit90 passed, 0 failed, 3 ignored.

⚠️ CI on this fork is skipped, not passing (if: github.repository == 'quarto-dev/q2'). Do not read the absence of red as a pass.

Still unverified: the literal /volume01 mount. Everything else in the chain is exercised above; first boot on Railway is what tests the mount point itself.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NMeiLmtDKhomJNSo2ecbJw

Summary by CodeRabbit

  • New Features

    • Added automatic startup hydration of required OSM map data from S3 into the local cache.
    • Existing valid local files are used first, while missing or invalid files are downloaded and verified.
    • Downloads are completed safely before becoming available to the server.
  • Bug Fixes

    • Prevented corrupted or incomplete map artifacts from being served.
    • Improved startup log visibility through configurable default logging.

Both were checked off in Phase 5's own list without closing their originals
higher in the file, so a grep for open items reported the hip-cell comparison
and the quadratic render as pending when both had shipped and been measured.
Same doc-drift the tesseract-rs D3/D4 case burned time on: the summary line
went stale the moment it was written because nothing re-checked it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMeiLmtDKhomJNSo2ecbJw
@cursor

cursor Bot commented Aug 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f0fa7501-5edb-4d9e-a30c-9802b7b5c7eb)

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dca918b6-c632-4039-8c2f-a6cc2b6fa21f

📥 Commits

Reviewing files that changed from the base of the PR and between c1940bf and 096248b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • claude-notes/plans/2026-08-08-osm-soa-cockpit-wiring.md
  • crates/cockpit-server/Cargo.toml
  • crates/cockpit-server/src/main.rs
  • crates/cockpit-server/src/osm_slab_hydrate.rs

📝 Walkthrough

Walkthrough

The plan records completed cockpit rendering work. The server now hydrates OSM slab artifacts from S3 into a volume cache, verifies SHA-256 checksums, and performs hydration before binding.

Changes

Cockpit completion records

Layer / File(s) Summary
Rendering completion records
claude-notes/plans/...
The plan records per-tile cell sampling and incremental feature painting results.

OSM slab hydration

Layer / File(s) Summary
Hydration contract and checksum handling
crates/cockpit-server/src/osm_slab_hydrate.rs, claude-notes/plans/...
The module defines local-path precedence, cache placement, checksum parsing, streamed hashing, and unit tests.
Cache verification and artifact downloads
crates/cockpit-server/src/osm_slab_hydrate.rs, claude-notes/plans/...
Cached artifacts are verified. Missing or invalid artifacts are streamed to temporary files and atomically renamed after checksum validation.
Startup integration and runtime configuration
crates/cockpit-server/Cargo.toml, crates/cockpit-server/src/main.rs
Startup runs hydration before binding, sets OSM_SLAB_PATH when successful, and uses environment-configurable logging.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ServerStartup
  participant ensure_slab_local
  participant S3
  participant VolumeCache
  ServerStartup->>ensure_slab_local: hydrate before server bind
  ensure_slab_local->>S3: fetch checksum manifest
  ensure_slab_local->>VolumeCache: validate cached slab files
  ensure_slab_local->>S3: download missing or invalid files
  S3-->>ensure_slab_local: streamed artifact data
  ensure_slab_local->>VolumeCache: atomically store verified files
  ensure_slab_local-->>ServerStartup: return local slab path or None
Loading

Possibly related PRs

  • AdaWorldAPI/q2#108: This change hydrates the OSM_SLAB_PATH artifacts introduced by that OSM slab integration.

Suggested reviewers: claude

Poem

A rabbit checks each checksum line,
Then hops through S3’s cloud design.
The slab lands safe in cache below,
Before the cockpit starts to show.
Local paths still lead the way—
Verified files greet the day.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Closes the one item that blocked the POC from running anywhere but this
sandbox. Operator: "railway has exactly the same S3 variables like you" — which
retired the blocker, because the volume is a PATH and the part that needed
proving (do these credentials reach that bucket, does a 1.29 GiB round trip
work) is verifiable right here. OSM_SLAB_CACHE_DIR substitutes for the mount.

TOPOLOGY, per the operator's framing ("S3 -> lancedb -> volume01", "persist the
lance file to volume01", "it's just a convenience to persist lance across
rebuilds"): S3 is the source of truth, the volume is a CACHE that survives
container rebuilds. Deleting it costs a re-download and nothing else.

THREE ASSUMPTIONS I HAD WRITTEN DOWN, ALL WRONG. The earlier sketch claimed
this needed ~5 new deps (reqwest+rustls, sha2, hex, chrono, tokio sync),
hand-rolled SigV4, and a sync->async conversion of open_slab(). Checked instead
of repeated:

  claimed                        measured
  needs a new HTTP stack         object_store 0.13.2 is ALREADY in the graph
                                 via datafusion/lance; no new transitive tree
  needs hand-rolled SigV4        AmazonS3Builder::from_env() reads it all, and
                                 `aws_endpoint_url` is an accepted alias for the
                                 endpoint key (builder.rs:498), so a non-AWS
                                 endpoint needs no special casing
  open_slab() must become async  main() is ALREADY async — hydrating there,
                                 before the listener binds, leaves the read path
                                 untouched and stops the first request paying
                                 for the download

FORMAT-AGNOSTIC BY CONSTRUCTION. openstreetmap-website-rs states in its own
Cargo.toml header: "no lance, no datafusion: the ABI row is a byte layout, not
an engine" — and lance_abi was REMOVED there (a27b06a) for doing a full-table
materialising read. So this layer moves named objects from a prefix onto the
volume and verifies them; whether the artifact travels as .soa or as a Lance
dataset is a separate decision it does not prejudge. (object_store exposes
get_range(location, Range<u64>) — the range read that removal asked for, if the
Lance path is taken later.)

CHECKSUM PINNING, ON THE CACHE HIT TOO. SHA256SUMS is fetched from the same
prefix; a missing entry is a refusal, not a warning. Verifying on a HIT — not
only after download — guards the half-written file left by a container killed
mid-download, exactly the case a "we already have it" check waves through.
Downloads land on .part and are renamed only after the hash matches.

VERIFIED, all three paths, against the live bucket:

  cold   empty volume -> downloaded 1,292,826,624 + 58,763,338 B, verified,
         mmap'd; serves 14/8802/5373 as 15,016 of 15,016 — identical to the
         local-file numbers
  warm   cache hit, both artifacts checksum-verified in ~0.8s, no download
  dirty  16 B corrupted mid-file -> got=7247b2bb… want=d12bc8a1… detected,
         re-fetched, verified, no .part left behind

Both halves of the guard are proven on real data: silent on a clean cache, loud
on a corrupt one.

FOUND WHILE DOING IT — the server's own tracing has never been emitted. The
filter said `cockpit_server=info`, but the binary TARGET is `q2-cockpit`, so
module_path!() starts with `q2_cockpit`. Every tracing::* call in this binary
was silently discarded. Noticed because the hydrate ran correctly and logged
nothing; on a deploy that would have been 503s with no explanation at all. Both
names are now listed so a rename of either cannot silence it again, and
RUST_LOG wins when set — the operational control a deploy needs.

Bucket conventions followed, not invented: the bucket already had 50+ MedCare-rs
objects shaped <repo>/bakes/<version>/<artifact> + SHA256SUMS. q2 uploads to
q2/bakes/berlin-v1/ and touches nothing under MedCare-rs/.

90 passed, 0 failed, 3 ignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMeiLmtDKhomJNSo2ecbJw
@AdaWorldAPI AdaWorldAPI changed the title plan: close two follow-ups that Phase 5 already did osm slab: hydrate from S3 onto the persistent volume, checksum-pinned Aug 11, 2026
@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review August 11, 2026 08:55
@AdaWorldAPI
AdaWorldAPI merged commit 1e30bd3 into main Aug 11, 2026
3 of 4 checks passed
@cursor

cursor Bot commented Aug 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_8de54985-c770-41fa-9bee-23560b548594)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants