You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stand-up of the SMG pyramid (`specs/avail-smg-pyramid.md`, done 2026-09-07 from `e`): 152 source days
backfilled, image `ctbk-engine:f5fefbe1` (job-def rev 12), Batch `-f` build → 224 shards registered in
D1, cover [2026-04-07, 2026-09-06).
Two engine-side surprises, both worked around here and documented in the spec:
- The engine's default `mem_budget` (70% of the cgroup limit) read the Fargate *host* (46 GB in a 32 GiB
container) → OOM at 96/307 windows. Every `engine submit` now passes `-b` (pyrmts spec filed).
- The Batch base image (pyrmts `ed50cdb`) predates open-period classification, so an uncapped fill
writes 0-row trailing shards over not-yet-existing days and then fails the strict source check.
New `ctbk gbfs engine sweep CUTOFF` deletes such shards from R2 + D1 + manifest; new
`ctbk gbfs engine adopt` records shards a dead job flushed but never wrote to the manifest;
`engine manifest -H N` dumps raw records.
Daily cadence (`gbfs-compact.yml`): a `ctbk gbfs smg backfill -C -k -t <day>` self-heal step (no-op
when nothing is missing) precedes the fill, and the fill is capped at `<day+1>T00:00` (`-r`) with
`-b 20g -w 1h`. `RECONCILE_PYRAMIDS` in the api worker now includes `smg-v1`, so the daily fill's new
shards self-register.
Spec also records the heartbeat era (2026-05-03): before it `no_poll` ≡ "unpolled or stale".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgLAzeRJeNeyizG4XHWRBK
Copy file name to clipboardExpand all lines: ctbk/gbfs_cli.py
+163-1Lines changed: 163 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -12,6 +12,7 @@
12
12
from __future__ importannotations
13
13
14
14
importjson
15
+
importre
15
16
importos
16
17
importsubprocess
17
18
importsys
@@ -1594,10 +1595,15 @@ def gbfs_engine_register(
1594
1595
1595
1596
@gbfs_engine.command('manifest', help='Summarize a build manifest (local path or `s3://` URL): shard counts per (tier, rung), period span; optionally diff key sets vs another manifest.')
raiseclick.ClickException(f'{key}: label {label!r} is not {shard}-aligned (period starts {p.start.isoformat()})')
1638
+
returntier, shard, p.start, p.end
1639
+
1640
+
1641
+
@gbfs_engine.command('sweep', help='Delete shards whose period extends past CUTOFF (UTC ISO) from all three stores — R2, the D1 `pyramid_shards` registry, and the prefix\'s manifest. Run after an UNCAPPED `engine submit -f` on an engine without open-period classification (base image < pyrmts 72f2552): the fill\'s expected cover reaches `now`, so shards spanning source days that don\'t exist yet build with 0 rows for those days (or partial rows, for a shard straddling the last real day) and read as "built" to every later fill. Then re-fill CAPPED: `engine submit -f -r /CUTOFF`. Candidates = manifest records ∪ R2 listing (keys missing from the manifest are reported).')
1642
+
@option('-C', '--config', 'config_name', default='smg-v1', show_default=True, help='Pyramid config basename (registry name and prefix default to it).')
1643
+
@option('-m', '--manifest', 'manifest_name', default='manifest.jsonl', show_default=True, help='Manifest object name under the prefix.')
1644
+
@option('-n', '--dry-run', is_flag=True, help='List affected shards; no deletes.')
@gbfs_engine.command('adopt', help='Adopt R2 shards that a build flushed but never recorded (the job died after the PUT — OOM, Spot reclaim): append them to the prefix\'s manifest (HEAD for bytes, streamed md5) and register them in D1. Default KEYS = every `.parquet` under the prefix missing from the manifest (what `sweep` reports as "not in manifest").')
1712
+
@option('-C', '--config', 'config_name', default='smg-v1', show_default=True, help='Pyramid config basename (registry name and prefix default to it).')
1713
+
@option('-m', '--manifest', 'manifest_name', default='manifest.jsonl', show_default=True, help='Manifest object name under the prefix.')
1714
+
@option('-n', '--dry-run', is_flag=True, help='List the shards that would be adopted; no writes.')
d1_query(f"INSERT OR REPLACE INTO pyramid_shards {cols} VALUES {', '.join(['(?, ?, ?, ?, ?, ?, ?)'] *len(new))}", params)
1778
+
err(f' registered {len(new)}')
1779
+
1780
+
1619
1781
@gbfs_r2.command('pq', help='Inspect a parquet object on R2: schema + row count; optionally head rows and row-group metadata (range reads, no full download).')
1620
1782
@option('-m', '--metadata', 'show_meta', is_flag=True, help='Row-group count/sizes and file metadata.')
1621
1783
@option('-n', '--head', 'head_n', type=int, default=0, help='Print the first N rows.')
0 commit comments