feat(06-04): R2 upload + A*-CPU measurement gate + the A1 ladder executed to its decision point
- upload-road-graph.mjs: wrangler-shelled maintainer tool (pre-flight 1-80MB, round-trip verify hint); bucket araharah-graphs NOT created — R2 is not yet enabled on the account (wrangler code 10042) — dashboard one-time activation is the phase's user_setup item - measure-route-cpu.mjs: 25-pair gate (corpus + seeded quadrant spread), hrtime around routeAstar, warm-up excluded, unreachable probes reported SEPARATELY from the routed p95, exits 1 over the 8ms budget - Rule 1 (measured): the class set lacked *_link ramps — the graph FRAGMENTED (7.7% of nodes in severed motorway islands; corpus pairs 'unreachable'). 5 link classes added -> 311325 nodes / 677068 edges / 14.7 MB, all landmarks in one component; enforced floor corrected 1KB -> 1MB (D-01) - ladder (a) drop tertiary+residential: p95 17.8ms but 12/25 pairs unreachable — REJECTED on correctness (D-03), reverted, ladder history recorded - ladder (b) bidirectional A* (BH, Ikeda potentials, reverse-CSR WeakMap): provably optimal (A1 625-pair sweep green), alternation balanced by heap size (raw-top comparison starves the backward side 19501:3), reduction signs in the standard feasible form. Measured: p95 ~38-93ms — NO GATE PASS. The potential split halves each side's steering, cancelling the halved radius - GATE FAIL is the honest verdict: ladder (c) — Workers Paid vs layout-v2 ALT (still $0) vs free-plan-with-degradation — requires maintainer sign-off; docs/ROAD-GRAPH.md carries the decision table and the fork contract
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
# The Road Graph (engine A's data) — build, host, and the laws
|
||||
|
||||
Engine A routes over a **CSR road-graph binary** built from OpenStreetMap and
|
||||
hosted on Cloudflare R2. The binary is a **build artifact, not a committed
|
||||
file** — forks build and upload their own. Everything needed is committed:
|
||||
two maintainer scripts and this doc. All of it is **$0** (Overpass public
|
||||
instance, R2 free tier, Workers free plan).
|
||||
|
||||
## The fork contract (your graph, your bucket)
|
||||
|
||||
1. **Create your bucket** (once, per Cloudflare account):
|
||||
|
||||
```
|
||||
npx wrangler r2 bucket create araharah-graphs
|
||||
```
|
||||
|
||||
If `wrangler` reports that R2 is not enabled, activate it once in the
|
||||
Cloudflare dashboard (**R2 → Get started**) — account-owner steps, then
|
||||
re-run the command. Use a different name if you like, but the name must
|
||||
agree with `bot/wrangler.jsonc`'s `ROAD_GRAPH` binding (`bucket_name`).
|
||||
|
||||
2. **Build the KL graph** (or your own region — tune `DRIVABLE_CLASSES` /
|
||||
the bbox in the script):
|
||||
|
||||
```
|
||||
node routing/scripts/build-road-graph.mjs
|
||||
```
|
||||
|
||||
Computes the bbox from the committed corpus (gazetteer + golden pairs,
|
||||
± 0.05°), fetches ONE Overpass extract (drivable classes only), encodes
|
||||
the pinned CSR binary through `encodeGraph`, and writes
|
||||
`routing/data/kl-roadgraph.v1.bin` — **only** after the encode round-trip
|
||||
and the size gate pass. Exit 1 leaves any previous binary untouched.
|
||||
|
||||
3. **Check the artifact** (decode + stats, no network):
|
||||
|
||||
```
|
||||
node routing/scripts/build-road-graph.mjs --check routing/data/kl-roadgraph.v1.bin
|
||||
```
|
||||
|
||||
4. **Upload to R2**:
|
||||
|
||||
```
|
||||
node routing/scripts/upload-road-graph.mjs
|
||||
# = npx wrangler r2 object put araharah-graphs/kl-roadgraph.v1.bin \
|
||||
# --file routing/data/kl-roadgraph.v1.bin --remote
|
||||
```
|
||||
|
||||
Verify the round-trip:
|
||||
|
||||
```
|
||||
npx wrangler r2 object get araharah-graphs/kl-roadgraph.v1.bin \
|
||||
--file /tmp/opencode/graph-roundtrip.bin --remote
|
||||
node routing/scripts/build-road-graph.mjs --check /tmp/opencode/graph-roundtrip.bin
|
||||
```
|
||||
|
||||
5. **Deploy the Worker** (`npm run deploy -w bot`). `wrangler.jsonc` binds
|
||||
the bucket as `ROAD_GRAPH`; `worker-router.ts` loads the object
|
||||
`kl-roadgraph.v1.bin` once per cold isolate (module-scope memo, shared
|
||||
with the cron arm) — 1 R2 GET, not per request.
|
||||
|
||||
**Dev note:** with no bucket (or an empty one), `/api/route` answers `200
|
||||
null` — the designed router-pending state (D-03). Dev and tests never need
|
||||
the real binary; tests use the committed mini-graph fixture.
|
||||
|
||||
## The envelope law (D-01)
|
||||
|
||||
| Gate | Value | Enforcement |
|
||||
|------|-------|-------------|
|
||||
| Ceiling | **≤ 80 MB** | enforced — build + upload + `--check` all exit 1 over it |
|
||||
| Floor | **> 1 MB** | enforced — a smaller "road graph" is a broken build |
|
||||
| Expected band | **30–80 MB** | advisory — a smaller build prints a WARNING (verify the class filter) and continues |
|
||||
|
||||
The binary lives in `routing/data/` and is gitignored (`*.bin`): it is
|
||||
ODbL-derived data, regenerated at will, never source.
|
||||
|
||||
## The CPU gate (A1) — OPEN: measured over the free-plan budget
|
||||
|
||||
Workers free plan: **10 ms CPU per request** (Error 1102 beyond it). The A\*
|
||||
cost over the REAL graph is measured, never hoped:
|
||||
|
||||
```
|
||||
node routing/scripts/measure-route-cpu.mjs
|
||||
```
|
||||
|
||||
25 pairs (golden-corpus routes + quadrant-sampled cross-city pairs; directed
|
||||
dead-end probes reported separately) timed around `routeAstar` over the real
|
||||
binary. **The gate is p95 ≤ 8 ms** (20% margin under the ceiling).
|
||||
|
||||
**Measured state (2026-10-24, the 311k-node linked graph): GATE FAIL.** The
|
||||
mitigation ladder was executed in order:
|
||||
|
||||
| Ladder step | Outcome |
|
||||
|-------------|---------|
|
||||
| full graph, unidirectional A\* | p95 ≈ 38 ms — FAIL |
|
||||
| (a) drop tertiary+residential, rebuild | p95 ≈ 18 ms — FAIL **and** 12/25 pairs became unreachable (the dropped classes are the fine mesh tying landmarks to arterials) — rejected on correctness, reverted |
|
||||
| (b) bidirectional A\* (BH, Ikeda potentials) | p95 ≈ 38 ms — FAIL: the potential split halves each side's heuristic steering, cancelling the halved search radius. Kept: sound (A1's 625-pair Dijkstra equivalence still passes) and better on short product routes |
|
||||
| (c) Workers Paid | **NOT applied — breaks the $0 law; needs a maintainer decision** |
|
||||
|
||||
The decision this gate now escalates: **(c1) Workers Paid ($5/mo — breaks
|
||||
$0)**, **(c2) a graph-layout v2 with ALT landmark preprocessing (still $0 at
|
||||
runtime, real build-time engineering)**, or **(c3) accept free-plan routing
|
||||
with honest degradation** (long reroutes may hit 1102; the client's
|
||||
`createHttpRoutingSource` then degrades to the router-pending state).
|
||||
Short product routes (2–8 km, the common reroute) measured 2–8 ms — inside
|
||||
budget; the failures concentrate on 20–30 km cross-metro routes.
|
||||
|
||||
## Licensing (the ODbL attribution)
|
||||
|
||||
The graph is derived from OpenStreetMap data —
|
||||
**© OpenStreetMap contributors, ODbL 1.0**
|
||||
(<https://opendatacommons.org/licenses/odbl/1-0/>), fetched via the public
|
||||
Overpass API. Hosting or redistributing the binary requires the same
|
||||
attribution (ODbL's attribution clause travels with derived databases). The
|
||||
route fixtures carry the same notice inside their committed envelope
|
||||
(`_license`/`_attribution`/`_notice`), and this doc is the notice surface for
|
||||
the hosted binary. Keep it intact in forks.
|
||||
|
||||
## Key facts
|
||||
|
||||
| What | Where |
|
||||
|------|-------|
|
||||
| Build tool (Overpass → CSR) | `routing/scripts/build-road-graph.mjs` |
|
||||
| Upload tool (wrangler shell) | `routing/scripts/upload-road-graph.mjs` |
|
||||
| CPU measurement gate | `routing/scripts/measure-route-cpu.mjs` |
|
||||
| Binary (gitignored artifact) | `routing/data/kl-roadgraph.v1.bin` |
|
||||
| R2 object key | `kl-roadgraph.v1.bin` (pinned in `worker-router.ts` as `GRAPH_KEY`) |
|
||||
| Binding | `ROAD_GRAPH` in `bot/wrangler.jsonc` (bucket, NEVER a KV namespace — GE5) |
|
||||
| Loader (isolate-memoized) | `bot/src/routing/worker-router.ts` → `loadGraph` |
|
||||
| Binary layout authority | `routing/src/graph.ts` (`encodeGraph`/`decodeGraph`, pinned CSR v1) |
|
||||
Reference in New Issue
Block a user