fix(08-05): route-cpu gate law re-anchored to the post-ALT measured truth (escalation record — no retirement)
Task 3, re-scoped per the plan's honest-FAIL branch: the CPU verdict FAILED (the ladder ran to its documented stop), so the c3 class is NOT retired — the record instead carries the executed-and-measured ALT truth. - budget-gates.mjs: the verdict law documents the Phase-8 outcome (ALT EXECUTED, optimality green, CPU verdict FAIL 152.788/185.692 ms quoted with date; L=32 probe measured worse) — the stale 'next-milestone item' wording is gone; ROUTE_CPU_DRIFT_GUARD_MS re-anchored 200 → 400 (the post-ALT documented band outgrew the old guard; ≈2x the worst shipped- config run 185.692 ms); the guard stays a regression trip-wire, the 8 ms budget stays and its FAIL still prints every run - budget-audit.test.mjs: CPU3 pins the new law (evidence must name the EXECUTED-in-Phase-8 verdict and must NEVER say 'next-milestone'); 19/19 vectors ok (the count holds — no vector dropped) - docs/BUDGETS.md: route-cpu row carries the Phase-8 measured numbers + date; the c3 section is the honest post-ALT record (what changed, the quoted gate lines, the one-directional-Lipschitz wall, the open escalation) — staged as selective hunks; the tree's pre-existing doc-sync edits in other sections remain uncommitted, untouched grep law: zero 'next-milestone' hits in budget-gates.mjs + BUDGETS.md; ROUTE_CPU_DRIFT_GUARD_MS still exported and used.
This commit is contained in:
+82
-43
@@ -55,16 +55,16 @@ rounded away, never hidden in a footnote.
|
||||
|
||||
| Measurement | Current | Budget | Headroom | Evidence |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| A* CPU p95 (local ladder, full 25-pair sample) | 83.989 ms (runs today: 67.5–84.0 ms; documented band 38–93 ms) | 8 ms ladder budget (10 ms free ceiling) | **−76.0 ms** — the accepted c3 class (see below) | `route-cpu` |
|
||||
| Route payload (BA9 gz sum: fixtures + routing/route/guidance sources) | 79,815 gz bytes | 204,800 gz bytes (200 KiB) | +124,985 | `bundle-budget` |
|
||||
| Whole-app bundle (`web/dist/assets/*.js`, gz-summed) | 479,178 gz bytes (3 hashed assets) | 500,000 gz bytes | +20,822 | `bundle-budget` |
|
||||
| A* CPU p95 (local ladder, full 25-pair sample) | **152.788 ms** — measured 2026-10-25 on the Phase-8 ARG2 build (L=24, toTable-only ALT steering); n=23 routed (2 unreachable probes reported separately). The D-04 extended 100-pair verdict measured **185.692 ms** the same day (n=53). Not statically pinnable — re-run `npm run budget-audit` to refresh (shared host, run-to-run variance ≈ 2x). Pre-ALT historical band 38–93 ms; the L=32 ladder probe measured **182.725 ms** (25-route) / **204.499 ms** (extended) | 8 ms ladder budget (10 ms free ceiling) | **−144.8 ms** — the c3 class REMAINS (see below) | `route-cpu` (Phase-8 campaign) |
|
||||
| Route payload (BA9 gz sum: fixtures + routing/route/guidance sources) | 79,815 gz bytes — **recorded measurement, re-derived at `f018ab1`** as `route-fixtures.json` + every `*.ts` under `routing/src`, `web/src/route`, `web/src/guidance` (22 inputs, `zlib.gzipSync`); `route-fixtures.json` alone is 16,576 gz bytes. Re-run to refresh | 204,800 gz bytes (200 KiB) | +124,985 | `bundle-budget` |
|
||||
| Whole-app bundle (`web/dist/assets/*.js`, gz-summed) | 479,179 gz bytes (3 hashed assets; `zlib.gzipSync` — the gate's method; **recorded measurement, re-run to refresh**. A shell `gzip -c` sum of the same files measured 476,389 B in this session — a different tool, not the gate figure) | 500,000 gz bytes | +20,821 | `bundle-budget` |
|
||||
| Worker bundle (uncompressed) | not measured — `bot/dist` absent (wrangler builds in memory on deploy) | 64 MiB | n/a | `bundle-budget` |
|
||||
| KV writes — session mint (POST /api/session) | 2 writes (`session:<token>` + `chat:<chat_id>`), TTL 900 s | 1,000/day | see the daily estimate | `kv-writes` |
|
||||
| KV writes — location pin (chat flow) | 1 write (`loc:<chat_id>`), TTL 300 s | 1,000/day | see the daily estimate | `kv-writes` |
|
||||
| KV writes — guidance envelope (POST /api/guidance) | 1 write (`guidance:<id>`), TTL 7,200 s | 1,000/day | see the daily estimate | `kv-writes` |
|
||||
| KV writes — **cron guidance tick** | **0 writes** (source-proven) | 1,000/day | full | `kv-writes` |
|
||||
| KV writes — daily estimate (derived) | ≈ 4 writes per guided chat-session (2 mint + 1 envelope + 1 pin share; deletes excluded) — 1,000/day ≈ **250 guided chat-sessions/day**; private beta (≈ 5–10 drivers) is ≪ the ceiling | 1,000/day | large | derived (formula stated) |
|
||||
| KV list requests | **1,440/day derived** (60 s cron × 1 `list` per tick) | 1,000/day | **−440/day — OVER** (discovered finding; see below) | source derivation |
|
||||
| KV list requests | **720/day derived** (2-minute cron `*/2 * * * *` × 1 `list` per tick — the post-retune current state; see RESOLUTION) | 1,000/day | **+280/day — under** (the pre-retune `*/1` state was 1,440/day, −440/day over) | source derivation (`bot/wrangler.jsonc` + `bot/src/guidance/cron.ts`) |
|
||||
| Static asset cache headers (live) | not measured in this run — the deployed origin serves a bundle older than `web/dist` (the probe reads HTTP status first and reports deployment drift); the HTML entry observed revalidating: `cache-control: public, max-age=0, must-revalidate` | the `_headers` policy | — | `cache-headers` (--live) |
|
||||
| Subrequests per request class | see the dedicated table below | 50/request | ≥ +48 per class | `subrequests` |
|
||||
| Golden route corpus (`routing:corpus`) | exit 0 — corpus gate PASS, gz 16,576 B / 102,400 B budget | green (SC4.a) | — | `golden-corpus` |
|
||||
@@ -121,52 +121,91 @@ enforcement point for SC4.a / SC4.b):
|
||||
A failing corpus FAILS the gate carrying that child's own failure output verbatim — the
|
||||
record then cannot claim the corpora are green, because the gate just re-proved them.
|
||||
|
||||
## The KV list budget — a DISCOVERED over-ceiling finding (07-04)
|
||||
## The KV list budget — the 07-04 over-ceiling finding, RESOLVED by the launch-gate retune
|
||||
|
||||
The audit's source derivation found one over-ceiling number that the launch gate must see:
|
||||
The audit's source derivation found one over-ceiling number. It is **no longer the current
|
||||
state**: the launch gate resolved it by retuning the cron and the card cadence (the
|
||||
RESOLUTION section at the end of this record is authoritative). What follows keeps the
|
||||
finding auditable and states the post-retune arithmetic.
|
||||
|
||||
- **Current:** the cron trigger is `*/1 * * * *` (60 s scan, D-11's window) and every tick
|
||||
makes exactly one `env.chat_sessions.list(...)` call ⇒ **1,440 list requests/day derived**.
|
||||
- **Historical (pre-retune):** the cron trigger was `*/1 * * * *` (60 s scan, D-11's
|
||||
lower window edge) and every tick made exactly one `env.chat_sessions.list(...)` call ⇒
|
||||
**1,440 list requests/day derived**.
|
||||
- **Current (post-retune):** the trigger is `*/2 * * * *` (120 s ticks) in
|
||||
`bot/wrangler.jsonc` and every tick still makes exactly one `list` call ⇒ **720 list
|
||||
requests/day derived** — **+280/day headroom under the ceiling**. `CARD_CADENCE_S` in
|
||||
`bot/src/guidance/cron.ts` is `240` (a card window every 4 minutes; `SCAN_TICK_S` stays
|
||||
`60` as the window-width comparison, and `240 % 120 = 0` keeps the windows aligned to
|
||||
ticks).
|
||||
- **Ceiling:** the free plan allows **1,000 KV list requests/day**; beyond it, list
|
||||
operations fail with errors until the 00:00 UTC reset.
|
||||
- **Consequence:** every UTC day after ~16 h 40 m, the cron's KV list fails — the tick
|
||||
degrades (the list error surfaces as a waitUntil rejection; no cards fire until UTC
|
||||
midnight). Benign for a morning driver, not benign for an evening one.
|
||||
- **Why this plan does not auto-fix it:** the tick spacing is coupled into the due-card
|
||||
math — `projectTurnCards`' due rule is `nowSec % CARD_CADENCE_S < SCAN_TICK_S` with
|
||||
`CARD_CADENCE_S = 180` and `SCAN_TICK_S = 60`. Moving the trigger to 120 s (D-11's
|
||||
window upper edge, 720 lists/day) breaks the divisibility (`180 % 120 ≠ 0`) and retimes
|
||||
the designed 3-minute card cadence — a product-timing decision, not a config-only
|
||||
change.
|
||||
- **The maintainer's options:** (i) retune the pair (e.g. `CARD_CADENCE_S = 240` with a
|
||||
120 s tick ⇒ 720 lists/day, cards every 4 min) — a deliberate product decision with
|
||||
test updates in `bot/test/guidance-cron.test.mjs`; (ii) accept the documented
|
||||
degradation; (iii) Workers Paid — breaks the $0 law, requires sign-off. This is
|
||||
recorded as an open decision for the 07-06 launch gate.
|
||||
- **Consequence that no longer applies:** at 1,440/day every UTC day after ~16 h 40 m the
|
||||
cron's KV list would fail — the tick would degrade (the list error surfaces as a
|
||||
waitUntil rejection; no cards fire until UTC midnight), benign for a morning driver, not
|
||||
for an evening one. At 720/day the ceiling is never approached; this paragraph is kept
|
||||
as the record of why the retune mattered.
|
||||
- **Why it was a decision, not a config edit:** the tick spacing is coupled into the
|
||||
due-card math — `projectTurnCards`' due rule is `nowSec % CARD_CADENCE_S < SCAN_TICK_S`.
|
||||
At `CARD_CADENCE_S = 180`, moving the trigger to 120 s broke the divisibility
|
||||
(`180 % 120 ≠ 0`) and retimed the designed 3-minute card cadence, so the pair had to be
|
||||
retuned together — a product-timing decision.
|
||||
- **The decision taken:** option (i) — `CARD_CADENCE_S = 240` with a 120 s tick ⇒ 720
|
||||
lists/day, cards every 4 min — with the test updates in
|
||||
`bot/test/guidance-cron.test.mjs` (see RESOLUTION). Options (ii) accept the documented
|
||||
degradation and (iii) Workers Paid (breaks the $0 law) were not taken.
|
||||
- **Tooling caveat (as of this correction):** `scripts/budget-audit.mjs`'s printed
|
||||
comparison table still hard-codes the pre-retune `derived 1,440/day … OVER` row — the
|
||||
gate re-measures the write classes but does not re-derive lists/day from
|
||||
`bot/wrangler.jsonc`. The source derivation above (720/day) is authoritative for the
|
||||
budget; the printed row is a known stale line, not a second finding.
|
||||
|
||||
## Cross-metro CPU degradation (the c3 class)
|
||||
## Cross-metro CPU degradation (the c3 class — post-ALT state)
|
||||
|
||||
The honest escalation from Phase 6 (plan 06-04), restated as the record's CPU row:
|
||||
The record as of the Phase-8 campaign (2026-10-25). The class was NOT retired:
|
||||
the ALT lever was executed and honestly measured, and the verdict kept it.
|
||||
|
||||
- Short product routes (2–8 km) measure **2–8 ms** on a quiet isolate — inside the 8 ms
|
||||
ladder budget. The full 25-pair sample (the corpus demo-script pairs plus 10
|
||||
quadrant-sampled cross-city worst cases) measures **p95 38–93 ms** across runs — over
|
||||
budget, and this box's shared-load variance is ≈ 2x.
|
||||
- The mitigation ladder was exhausted WITHIN the $0 law: (a) tighten `DRIVABLE_CLASSES` +
|
||||
rebuild — **rejected on correctness** (12/25 pairs unreachable; the dropped classes are
|
||||
the fine mesh tying landmarks to the arterials), reverted; (b) bidirectional A* —
|
||||
**kept** (provably optimal, no worse on the p95 class, better on short product routes);
|
||||
(c) Workers Paid — **rejected** ($5/mo breaks the $0 law; needs maintainer sign-off).
|
||||
- The DECIDED posture (c3, now): cross-metro CPU exhaust → the Worker's 1102 path → the
|
||||
client's fail-honest null → the **router-pending state** — designed degradation, never
|
||||
a fabricated route. The committed upgrade lever is the **ALT landmark preprocessing**
|
||||
(build-time, still $0 at runtime) — a NEXT-MILESTONE item, not this phase.
|
||||
- Evidence: `routing/scripts/measure-route-cpu.mjs`'s LADDER HISTORY (in-file) and the
|
||||
06-04 plan summary (`.planning/phases/06-turn-by-turn-guidance-live-driving/06-04-SUMMARY.md`).
|
||||
- The `route-cpu` gate therefore compares the re-run p95 against a 200 ms DRIFT GUARD
|
||||
(≈ 2x the worst documented run): inside the band = the documented accepted state (the
|
||||
8 ms comparison is still printed, headroom negative); beyond it = uncharted regression
|
||||
and the gate FAILS.
|
||||
- **What changed (Phase 8):** the ARG2 graph carries build-time landmark
|
||||
tables (L=24 by the D-02 rule, quantum 0.1 s, coverage 0.980), the search
|
||||
steers with the toTable triangle bounds, and the D-03 optimality gate is
|
||||
GREEN — all 53 routed record pairs reproduce the frozen pre-ALT seconds
|
||||
within ±0.1% (max |Δ| 0.005%), verdict classes preserved.
|
||||
- **What the verdict measured (the quoted gate lines, 2026-10-25):**
|
||||
`GATE FAIL: p95 152.788 ms exceeds the 8 ms budget (A1)` (25-route CI
|
||||
sample, n=23) and `GATE FAIL: p95 185.692 ms exceeds the 8 ms budget —
|
||||
D-04 extended verdict` (100 coordinate-pinned cross-metro pairs, n=53).
|
||||
The pre-committed ladder ran to its documented stop: a `--landmarks 32`
|
||||
rebuild measured WORSE (`p95 182.725 ms` / `p95 204.499 ms`) — more
|
||||
anchors buy no pruning under the sound steering and cost per-node work.
|
||||
- **Why the fast configuration is gone (the honest wall):** the original
|
||||
four-family potential (toTable + fromTable bounds maxed together) returned
|
||||
SUBOPTIMAL routes on the real graph — the D-03 gate caught x038 at
|
||||
1234.6 s vs the 1214.8 s Dijkstra optimum (x045: 1475.5 vs 1460.6). The
|
||||
fromTable bound d̂(ℓ,t) − d̂(ℓ,v) is one-directional Lipschitz: its u-side
|
||||
term needs the reverse edge, which a oneway does not provide (the anchor
|
||||
reached one node 126.8 s faster than its own 1.49 s upstream neighbour).
|
||||
The shared Ikeda average p = (h_f − h_b)/2 serves BOTH search directions,
|
||||
so every family inside it must be two-sided; only the toTable pair (plus
|
||||
the haversine) is. Fixtures are bidirectional, which is why the suite
|
||||
never caught it — the D-03 gate did, and its A11/A12 pins now forbid the
|
||||
regression structurally.
|
||||
- **The DECIDED posture (unchanged in substance):** cross-metro CPU exhaust →
|
||||
the Worker's 1102 path → the client's fail-honest null → the
|
||||
**router-pending state** — designed degradation, never a fabricated route.
|
||||
- **The honest escalation (open, maintainer decisions):** direction-split
|
||||
potentials with a re-proven bidirectional meet rule (search-skeleton work —
|
||||
explicitly out of Phase 8's untouchable lines), Contraction Hierarchies
|
||||
(the Phase-6 deferral), or Workers Paid (rejected on the $0 law; reversing
|
||||
it needs maintainer sign-off). Nothing here promises routes in budget —
|
||||
only that the measurement is printed and the gate decides.
|
||||
- Evidence: `routing/scripts/measure-optimality-gate.mjs` (the D-03 record),
|
||||
`routing/scripts/measure-route-cpu.mjs`'s LADDER HISTORY (in-file), and
|
||||
the 08-05 plan summary
|
||||
(`.planning/phases/08-alt-landmark-preprocessing-routing-backend-v2/08-05-SUMMARY.md`).
|
||||
- The `route-cpu` gate compares the re-run p95 against a 400 ms DRIFT GUARD
|
||||
(≈ 2x the worst documented accepted run — re-anchored 2026-10-25 from 200,
|
||||
which the post-ALT measurements had outgrown): inside the band = the
|
||||
documented accepted state (the 8 ms comparison is still printed, headroom
|
||||
negative); beyond it = uncharted regression and the gate FAILS.
|
||||
|
||||
## Static assets
|
||||
|
||||
|
||||
Reference in New Issue
Block a user