Bundle Record and Download Metrics Plan¶
Status: proposed (2026-10-18). Governed by ADR 0008. Written against
main@ed2eedeand1.0@b6ce46e. Amended 2026-10-20 for ADR 0009 (R2 layout mirrors NCBI ClinVar): tasks 2–6 follow the new layout. Every live R2 write is a separately user-confirmed step.Superseded in part by the 2026-10-21 maintainer decisions. 1.1.0 is GA (ADR 0008 (a)); superseded lines are frozen and kept indefinitely with status
frozen(ADR 0008 (b)); retention is not a metrics question, only promotion is (ADR 0008 (f)); a content-only correction opens a new MINOR line (ADR 0009 §5). Where the body below mentionsLINE_EOL/eol,LINE_STATUS=superseded/SUPERSEDED_BY=2, "once GA is confirmed", the freeze-vs-window open point, or the retention and live-window triggers, those decisions replace it. The body is kept as written.For agentic workers: REQUIRED: Use superpowers:subagent-driven-development or superpowers:executing-plans. Steps use checkbox (
- [ ]) syntax.
Goal: Treat the JSON bundles as the public record. Measure downloads, keep every superseded generation,
let users identify the generation they hold, label Parquet as derived, and have a runbook ready for the first
v2/2.0/ line.
Tasks at a glance¶
| # | Task | When | Branch | Status |
|---|---|---|---|---|
| 1 | Daily download-stats collection | now | main |
this PR |
| 2 | 1.0.1 generation → frozen line v1/1.0/ |
before the 1.1.0 republish | — (R2 only) | archived; move to do |
| 3 | ADR 0009 layout; sha256, source_release, checkpoint_full, line status in manifests |
at the 1.1.0 republish | 1.0 (feat/r2-ncbi-layout) |
in progress |
| 4 | Layout and "Parquet is derived" doc notes | with task 3 | main (#146) |
in progress |
| 5 | Parquet rebuild tool (clinvar-gkm-parquet) |
after 1.1.0 | own repo | design (its PR #1) |
| 6 | v2/2.0/ line runbook |
before the first post-GA breaking change | main |
to do |
| 7 | Metrics review | 60 days after collection starts | — | scheduled |
1. Daily download-stats collection¶
Delivered with this plan:
src/scripts/collect-download-stats.shqueries Cloudflare GraphQLhttpRequestsAdaptiveGroupsfor one UTC day (default yesterday,--date YYYY-MM-DD) on hostdata.clinvar-gkm.org. It writesdate,path,status,requests,bytes,distinct_clientsto a CSV. Client IPs are used only in memory to count distinct clients; they are never stored.--bqalso loads the rows intoclingen-dev:clinvar_ingest.gkm_download_stats(created if missing; a re-run replaces that day's rows).CF_EXCLUDE_IPSleaves out maintainer IPs.src/scripts/summarize-download-stats.pyestimates complete downloads per bundle as 2xx bytes served ÷ the object's size, with separate tables for monthly fulls and deltas and a summary of every other file..github/workflows/download-stats.ymlruns daily at 03:23 UTC (and onworkflow_dispatchwith an optionaldate) and appends the day tostats/YYYY-MM.csvon the orphan branchops/download-stats.deploy-docs.ymlonly runs on pushes tomain, so these commits never deploy the site.
Steps after merge:
- [ ] Add the repository secrets
CF_ANALYTICS_TOKENandCF_ZONE_ID(and optionallyCF_EXCLUDE_IPS). - [ ] Backfill from 2026-10-14, the first day the custom domain served. Cloudflare keeps about 31 days, so
2026-10-14 must be collected by about 2026-11-13:
gh workflow run download-stats.yml -f date=2026-10-14, then 15, 16, 17 and 18. Start each run after the previous one finishes (gh run watch): the workflow's concurrency group keeps only one pending run and cancels an older pending one. - [ ] Check the first scheduled run created
ops/download-statswithstats/2026-10.csv.
2. 1.0.1 generation becomes the frozen line v1/1.0/¶
The 1.0.1 JSON bundles were copied to archive/1.0.1/ on 2026-10-18 (40 objects, 24.3 GB: 5 fulls including
00-latest, 16 deltas and manifests, an index.json / feed.xml snapshot), with a MANIFEST.json (sha256
per file) and a README.txt. Under ADR 0009 it
moves to the frozen line folder v1/1.0/, and archive/1.0.1/ is deleted. The 1.0.1 generation keeps no
Parquet.
- [ ] Confirm the archive holds the 4 dated monthly fulls, the 15 dated deltas and their manifests, and
record whether the
00-latestcopies are included. - [ ] Verify each
MANIFEST.jsonsha256 against the copied objects. - [ ] Move
archive/1.0.1/tov1/1.0/(dry run first, confirmed step), re-verify the sha256s, then deletearchive/1.0.1/and purge it from the CDN. TheMANIFEST.jsonpaths andREADME.txtnamev1/1.0/. - [ ] Record the frozen line in the 1.1.0 changelog entry and the completion notice: where it is, what it holds, and that it is the 1.0.1 generation.
3. Layout, manifest and index changes (on 1.0, at the 1.1.0 republish)¶
The publishing scripts are changed on feat/r2-ncbi-layout (base 1.0) to write the ADR 0009 layout under
v1/1.1/. What they must produce, for the docs and the gates:
Why sidecars, not object metadata: the uploaders copy every object of 2 GB or more (every monthly full
and its 00-latest pointer) with aws s3 cp --copy-props none, because R2 lacks GetObjectTagging. That copy
drops user metadata, so a checksum stored as metadata would be lost. A <file>.sha256 sidecar
(sha256sum -c format) survives every copy and every move.
- [ ] Monthlies at the line's top level:
clinvar-gkm_YYYY-MM.json.gz+.sha256, andclinvar-gkm_00-latest.json.gz+.sha256. The year rollover moves a year's monthlies and sidecars toarchive/YYYY/. - [ ] Weekly fulls in
weekly/:clinvar-gkm_YYYY-MMDD.json.gz+.sha256for every release, andclinvar-gkm_00-latest_weekly.json.gz+.sha256. The month rollover deletes the finished month's. - [ ] Deltas in
deltas/:clinvar-gkm-delta_YYYY-MMDD.json.gz, its.sha256and its manifest. The month rollover moves the finished month todeltas/YYYY-MM/; the year rollover moves those folders toarchive/YYYY/deltas/YYYY-MM/. - [ ] Delta manifest.
checkpoint_fullnames the official monthly that precedes the delta (today every manifest names2026-10, the newest monthly, so a replay can't find its base). Addbundle = {"path", "size", "sha256"}.build-delta-manifest.pystays BigQuery-only; the uploader patches the file fields. - [ ] Parquet in
parquet/: one dataset, overwritten every release, with a manifest file giving the build date, the source release and the tool version (source.jsonis replaced). No Parquet anywhere else. - [ ]
generate-r2-index.shwritesv1/1.1/index.json:- every monthly entry gets
sha256(from its sidecar;nullwhen there is none) andsource_release, the release the monthly was built from (ADR 0002); - every delta entry gets
"bundle": {"path", "size", "sha256"}and the path of its manifest, in whichever folder the delta now sits; - the weekly fulls, the
archive/YYYY/contents and the Parquet dataset are listed; - the Parquet entry gets
"derived": true, and the index a top-level"parquet_note": "Derived from the bundle; may be regenerated (ADR 0008)."; - top level:
"schema_version","status","superseded_by","eol", fromLINE_STATUS/SUPERSEDED_BY/LINE_EOLenv vars (defaultscurrent,null,null).
- every monthly entry gets
- [ ] Root manifest. Replace the hard-coded
"schema": "1.0.0"withSCHEMA_VERSIONread fromassemble-gkm-dicts.py(overridable by env). List each line folder (v1/1.1/current,v1/1.0/frozen withsuperseded_byandeol). It replaces the"archives"pointer toarchive/1.0.1/planned earlier. - [ ]
r2-readme.txt. Describe the layout, the rollovers, the.sha256sidecars and the Parquet note. - [ ] CDN (ADR 0005): point the
"Latest Parquet" cache rule at the
parquet/folder; purge old paths after every move and delete and the Parquet folder after every release; drop/archive/from the path allowlist after the 1.0.1 move. - [ ] Cleanup: delete the old
v1/datasets/,v1/deltas/and dated Parquet trees (confirmed step, dry run first, then purge). - [ ] Gate:
generate-r2-index.sh --dry-runoutput validates withjq; every monthly entry has a 64-hexsha256and asource_release; every delta'scheckpoint_fullnames the newest monthly whosesource_releaseis earlier than the delta'srelease.
4. Layout and "Parquet is derived" doc notes (on main, with task 3)¶
Done on PR #146 for the ADR 0009 layout:
- [ ]
docs/data-access/*(download, monthly full, weekly deltas, Parquet, release model, index): every path underv1/1.1/; the weekly-full deletion and how to rebuild a past week; the single Parquet dataset, its manifest file, and rebuilding past versions withclinvar-gkm-parquet(planned); a note on how the layout mirrors NCBI. - [ ] Delta replay recipe (
weekly-deltas.md): to rebuild week W, take the newest monthly whosesource_release≤ W, then apply the deltas whosereleaseis later than thatsource_releaseand ≤ W, checking the chain. The old recipe compared months and skipped same-month deltas. - [ ]
docs/assets/js/r2-browser.jsanddocs/assets/duckdb-console.htmlread the new index and Parquet paths. - [ ]
docs/reference/versioning.md. New section "The public record": bundles immutable from 1.1.0 except weekly fulls, frozen lines such asv1/1.0/, sha256 sidecars, how to check a local copy (sha256sum -c), Parquet is derived. Update the root-manifest example. Update the "Initial-stabilization period" admonition once GA is confirmed (ADR 0008 (a)). - [ ]
zensical build --clean --strictandpython src/scripts/check-site-links.pypass.
5. Parquet rebuild tool (clinvar-gkm-parquet)¶
ADR 0008 (e) needs a tool a user can run on a published bundle. It is
clinvar-gkm-parquet, a separate repo with its own
version cadence (design plan on its PR #1). Each tool version declares the bundle schema_versions it accepts
and the Parquet table spec it produces. Workstream 1 of the
simplification plan
then pins the tool so the pipeline and users build Parquet the same way.
Steps:
- [ ] Build the tool against the 1.1.0 BigQuery-exported Parquet as its oracle (
EXCEPT ALLin both directions = 0 rows per table, with the same schema). - [ ] The tool writes the Parquet manifest file (build date, source release, tool version) that the published
v1/1.1/parquet/dataset carries. - [ ] The tool reads inputs in the ADR 0009 layout: monthlies at the line's top level,
weekly/,deltas/with itsYYYY-MM/folders, andarchive/YYYY/. To rebuild a past week it uses the replay rule in task 4, usingcheckpoint_fulland each monthly'ssource_release. - [ ] ClinVar-GKM pins a tool version and the release builds
parquet/with it. - [ ] Docs: a "Rebuild Parquet from a bundle" page linked from the Parquet page.
- [ ] Decision: which bundles the tool accepts. Parquet published before the tool (1.0.x, and 1.1.0,
whose Parquet is built by BigQuery SQL) cannot be reproduced bit-for-bit. Options: the tool accepts only
1.1.0 bundles and later; or it also reads the 1.0.x section layout (
var*-proposition) inv1/1.0/and rebuilds it in the 1.1.0 table shape.
6. v2/2.0/ line runbook¶
A runbook for ADR 0008 (b), to write before the first breaking change after GA. It covers:
- [ ] The RFC (ADR 0004 §8) and its 4-week window.
- [ ] Building
v2/2.0/in full withR2_PREFIX=v2/2.0/(the uploaders andgenerate-r2-index.shread it;run-release.shneeds it passed through and a per-line stop rule). - [ ]
generate-r2-index.sh: the root manifest lists more than one line (today it hard-codes the singlev1entry);activebecomes["1", "2"]and then["2"]. - [ ] Freezing
v1/1.x/:LINE_STATUS=superseded,SUPERSEDED_BY=2,LINE_EOL=<date>written to the line'sindex.jsonand the root manifest; no more uploads to it. The same steps freeze a line folder when a new MINOR opens (ADR 0009 §1). - [ ] The switch-over notice: email list, Discussions Announcements, the feed, the changelog.
- [ ] CDN check: the ADR 0005 cache rules match by
00-latest/index.json/feed.xml, not byv1/, so they already coverv2/; the latest-Parquet rule must also matchv2/2.0/parquet/; verify withcf-cache-status. - [ ] Docs:
reference/versioning.md, the download pages and the console point at the current line. - [ ] Depends on ADR 0008's open point: freeze at the switch-over, or keep ADR 0004 §3's 6-month live window.
7. Metrics review (60 days)¶
- [ ] When: 60 days after the first collected day. With the backfill from 2026-10-14, that is about 2026-12-13.
- [ ] Run
summarize-download-stats.pyonstats/2026-10.csv…stats/2026-12.csvfromops/download-stats, per month. - [ ] Decide N for ADR 0008 (f), then apply the triggers: archive retention and promotion, and freeze vs parallel window for the next breaking change (ADR 0008 (b)).
- [ ] Decide whether to keep collecting daily and whether to load the CSVs into BigQuery (
--bq). - [ ] Move ADR 0008 from Proposed to Accepted (or amend it) with the numbers.