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:
araharah-executor
2026-10-24 13:56:18 +08:00
parent 020eb92058
commit ea9584d163
5 changed files with 666 additions and 84 deletions
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env node
/*
* upload-road-graph.mjs — put the built road graph on R2 (D-01, fork path).
* ---------------------------------------------------------------------------
* A MAINTAINER-TIME REPO TOOL — NOT a runtime dependency; nothing in bot/src,
* web/src, or routing/src imports it. It shells the wrangler CLI (already the
* repo's deploy tool) so a fork needs zero new installs:
*
* npx wrangler r2 object put araharah-graphs/kl-roadgraph.v1.bin
* --file routing/data/kl-roadgraph.v1.bin --remote
*
* THE FORK CONTRACT (docs/ROAD-GRAPH.md is the prose): your graph, YOUR
* bucket. Create it first (`npx wrangler r2 bucket create araharah-graphs`
* — or your own name, and point bot/wrangler.jsonc's ROAD_GRAPH binding at
* it), build the binary with build-road-graph.mjs, then run this. The
* Worker side reads the binding, never a bucket name — the name here and in
* wrangler.jsonc must simply agree.
*
* PRE-FLIGHT: the binary must EXIST and clear the enforced 1 MB floor (the
* 80 MB ceiling is build-road-graph.mjs's gate — this script re-checks both
* cheaply so a stale artifact never uploads silently).
*
* Usage:
* node routing/scripts/upload-road-graph.mjs
*
* Exit codes: 0 uploaded; 1 pre-flight failure or wrangler failure.
*/
import { execFileSync } from "node:child_process";
import { existsSync, statSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const here = dirname(fileURLToPath(import.meta.url)); // routing/scripts
const GRAPH_PATH = resolve(here, "..", "data", "kl-roadgraph.v1.bin");
/** Must agree with bot/wrangler.jsonc's ROAD_GRAPH binding bucket_name. */
const BUCKET = "araharah-graphs";
/** Must agree with worker-router.ts's GRAPH_KEY. */
const KEY = "kl-roadgraph.v1.bin";
/** The enforced floor — an at/below-floor file is a broken build, never an upload. */
const MIN_BINARY_BYTES = 1 * 1024 * 1024;
const MAX_BINARY_BYTES = 80 * 1024 * 1024;
function fail(message) {
console.error(`upload-road-graph: ${message}`);
process.exit(1);
}
if (!existsSync(GRAPH_PATH)) {
fail(`no graph at ${GRAPH_PATH} — build it first: node routing/scripts/build-road-graph.mjs`);
}
const bytes = statSync(GRAPH_PATH).size;
if (bytes <= MIN_BINARY_BYTES || bytes > MAX_BINARY_BYTES) {
fail(
`${GRAPH_PATH} is ${bytes} bytes — outside the enforced 1-80 MB envelope (D-01). ` +
`Rebuild and heed the stats gate: node routing/scripts/build-road-graph.mjs`,
);
}
console.log(`uploading ${GRAPH_PATH} (${(bytes / (1024 * 1024)).toFixed(1)} MB) -> r2://${BUCKET}/${KEY}`);
try {
execFileSync(
"npx",
["wrangler", "r2", "object", "put", `${BUCKET}/${KEY}`, "--file", GRAPH_PATH, "--remote", "--content-type", "application/octet-stream"],
{ stdio: "inherit" },
);
} catch (err) {
fail(
`wrangler failed (${err.status ?? "no status"}). If the bucket does not exist yet: ` +
`npx wrangler r2 bucket create ${BUCKET} — and if R2 is not enabled on the account, ` +
`activate it once in the Cloudflare dashboard (R2 -> Get started).`,
);
}
console.log(`uploaded r2://${BUCKET}/${KEY}`);
console.log("next: npm run dev -w bot # /api/route loads it through the ROAD_GRAPH binding (memoized per isolate)");
console.log(`verify: npx wrangler r2 object get ${BUCKET}/${KEY} --file /tmp/opencode/graph-roundtrip.bin --remote && node routing/scripts/build-road-graph.mjs --check /tmp/opencode/graph-roundtrip.bin`);
process.exit(0);