refactor(import-metadata): sidecar-only; CSV loads via sync-metadata (SA-teardown-A) - #149
Conversation
…-metadata (SA-teardown-A) dataset-separation SA-teardown, part A. `levels sync-metadata` already applies the CSV columns by stable id with delete-safety (#146); reach.geom/reach.gradient_profile are EXCLUDED_COLUMNS, applied only from reaches.json/reaches-gradient.json. So make import_metadata.py a **sidecar-only** applier and standardize CSV loads on sync-metadata: - import_metadata.py: drop the full-CSV upsert path (and the `metadata_csv` import); it now applies only the geometry sidecars. No flags = both; --geom-only / --gradient-only = one. This removes the parallel-to-sync-metadata CSV channel that skipped #146's delete-safety (the #148 review's "optional hardening" — the unsafe channel is gone, not just gated). It stays the sanctioned sidecar applier (no refuse_configured_db), per the safety.py enumeration; deploy.sh 3.25/3.26 (--geom-only/--gradient-only) are unchanged, and 3.1 already does the CSV via sync-metadata. - Fresh-load / recovery / onboarding runbooks now run init-db --no-seed → sync-metadata → import_metadata (sidecars) → pipeline: CLAUDE.md, README.md, CONTRIBUTING.md, deploy/SETUP.md (§4 + the §-755 block), and docs/migrations.md's recovery runbook (incl. the --no-seed-rationale paragraph: the collision + FK semantics are sync-metadata's now, not import's). - Docstring sweep: safety.py (import_metadata has no full-CSV mode) and metadata_csv.py (its sole upsert caller is now sync-metadata). - Tests: the full-CSV round-trip tests now apply the CSV half via sync_metadata (new `_sync_csvs` helper writes the minimal contract); the geom/gradient sidecar tests are unchanged. Renamed the two that were import-CSV-specific (test_csv_apply_preserves_geom_absent_from_snapshot, test_resync_idempotent_across_pk_shapes). Verified on a copy of the live DB: init-db --no-seed → sync-metadata → import_metadata reproduces prod exactly (328 sources / 231 gauges / 432 reaches incl. all 432 geoms + 432 gradients) and builds. No schema change; no kayak_data change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Adversarial review findings:
What I verified:
|
Adversarial review — PR #149 (SA-teardown-A: import_metadata sidecar-only; CSV loads via sync-metadata)Reviewed on the live host. This implements the #148 review's "optional hardening" — removing Verdict: ready to merge. The code change is small and correct, the runtime behavior I can verify locally is unchanged, and all runbooks/deploy paths are consistent. One claim rests on the author's reproduction rather than mine (see Findings); it's low-risk for deploy and I note where I'd keep it as an acceptance step. What I verified
Findings1. [Low / informational] The "sync-metadata is a complete CSV load path from empty" claim rests on the author's real-DB reproduction, not mine. I confirmed the live DB has the target rows, but I did not independently run the 2. [Low] Fresh-load runbooks assume a 3. [Nit] No Scope / deployNo schema change, no migration, no |
Follow-up: Finding #1 independently verified (empty→full load)I reproduced the empty→full load myself on a scratch DB from the real Result — byte-for-byte reproduces prod:
So Finding #1 is resolved — |
…n setup runbooks (review) Address the two P2s (and the Low/Nit notes) from the #149 review. [P2] import_metadata.py reported success while applying ZERO sidecar rows. After A removed the CSV-load path, a no-flag run against an empty/wrong DB (the "ran before `levels sync-metadata`" mistake) applied 0/432 geoms + 0/432 gradients and exited 0 — invisible under `set -e`. Now `_apply_geom`/`_apply_gradient` return the count of snapshot ids that matched no reach row, and main() rolls back + exits non-zero when any are unmatched, with `--allow-missing-reaches` to opt into a deliberate partial apply. Verified: empty DB no-flag → exit 1, nothing applied (rolled back); --allow-missing-reaches → exit 0; full DB (after sync-metadata) → exit 0. Deploy 3.25/3.26 (--geom-only/--gradient-only against the full prod DB, after the 3.1 sync) all match, so this never false-positives on deploy. New test: a sidecar id with no reach row fails loud + rolls back; the flag allows the partial. [P2] Two setup runbooks ran `levels sync-metadata` without cloning kayak_data / setting DATASET_DIR, so they dead-ended at "csv dir does not exist: …/data/db": CONTRIBUTING.md (standalone dev quick-start) and deploy/SETUP.md's self-contained condensed install recap (its .env omitted DATASET_DIR and it cloned no metadata repo). Both now clone kayak_data + set DATASET_DIR before the load. (Pre-existing — import_metadata needed DATASET_DIR too — surfaced because A touches these blocks.) [Low] Note in the recovery runbook that sync-metadata refuses a `status: scaffold` dataset (--allow-scaffold to override; the real kayak_data is publishable). [Nit] CHANGELOG entry for the import_metadata sidecar-only behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Both P2s fixed in [P2]
Deploy 3.25/3.26 ( [P2] Setup runbooks ran [Low] recovery runbook now notes Full gate green: ruff/format (rc 0), mypy, |
|
Adversarial rereview of
The two previous P2s look resolved:
Verification run:
|
…locks (review) Address the #149 re-review P3. The condensed local-dev install recap (step 6) and the §4 "apply geometry by hand" snippet ran `python scripts/import_metadata.py` with a relative path while every other command in those blocks uses absolute paths (`/home/pat/.venv/bin/levels`, …) — so they read as self-contained but fail from outside /home/pat/kayak ("can't open file '.../scripts/import_metadata.py'"). Use the absolute `/home/pat/kayak/scripts/import_metadata.py` so both blocks are copy-paste-safe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Fixed the P3 in |
What
Dataset-separation SA-teardown, part A (the load-path slice). Makes
scripts/import_metadata.pya sidecar-only applier and routes all CSV metadata loads throughlevels sync-metadata.Why
levels sync-metadataalready applies the CSV columns by stable id with delete-safety (#146).reach.geom/reach.gradient_profileareEXCLUDED_COLUMNS— applied only fromreaches.json/reaches-gradient.json. Soimport_metadata.py's full-CSV upsert was a redundant parallel-to-sync-metadatachannel that skipped #146's delete-safety (the #148 review's "optional hardening"). This removes that channel entirely (not just gates it) and keeps the one thing onlyimport_metadatacan do: apply the geometry sidecars.This is the master-plan's "import_metadata → wrapper / standardize on sync-metadata", reconciled with the reality that
sync-metadatacan't apply the geom/gradient sidecars.Change
import_metadata.py— drop the full-CSV upsert path (+ the now-unusedmetadata_csvimport). No flags = apply both sidecars;--geom-only/--gradient-only= one. Still the sanctioned sidecar applier (norefuse_configured_db, per the SA-3safety.pyenumeration).deploy.sh3.25/3.26 (--geom-only/--gradient-only) unchanged; 3.1 already does CSV viasync-metadata.init-db --no-seed → sync-metadata → import_metadata (sidecars) → pipeline:CLAUDE.md,README.md,CONTRIBUTING.md,deploy/SETUP.md(§4 + the second install block), anddocs/migrations.md's recovery runbook (incl. the--no-seed-rationale paragraph — the collision + FK semantics aresync-metadata's now).safety.py(import_metadata has no full-CSV mode) +metadata_csv.py(sole upsert caller is nowsync-metadata).sync_metadata(new_sync_csvshelper writes the minimal contract); the geom/gradient sidecar tests are unchanged; the two import-CSV-specific tests renamed.Verification
init-db --no-seed→sync-metadata --csv-dir <kayak_data>→import_metadatareproduces prod exactly — 328 sources / 231 gauges / 328 gauge_source / 432 reaches, incl. all 432 geoms + 432 gradients — andbuildrenders. Proves sync-metadata is a complete CSV load path from empty.pytest -m "not slow"= 1402 passed, wheel-smoke,git diff --checkclean.CONTRIBUTING.md,docs/migrations.mdrecovery runbook) still prescribing the removed no-flag CSV load — fixed; round 2 caught one more paragraph in the same runbook and verified the rest of the sweep is complete.Scope / next
No schema change; no kayak_data change. Next: SA-teardown-B removes the snapshot script + systemd units +
hc_metadata_snapshotconfig +export_metadata→levels recover-metadata; then SA-teardown-C (operational) disables the prod timer and enables kayak_data branch protection.🤖 Generated with Claude Code