feat(05-01): RoutingSource contract + day-one route model + fixture seed schema

- types.ts: the D-01 backend-ladder seam (fixture -> wasm-router -> valhalla),
  D-03 null contract documented on the interface, D-07 day-one fields
  (RouteStep typed + RouteGeometry.cumulativeDistanceM, GUID-05/06 obligation)
- route-fixtures.json: committed EMPTY seed with the ODbL header
  (license/attribution/notice, T-05-06) — plan 05-02 populates it
This commit is contained in:
araharah-executor
2026-10-24 04:59:45 +08:00
parent 69b6a20350
commit c92a955272
2 changed files with 141 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"_license": "ODbL-1.0",
"_attribution": "© OpenStreetMap contributors",
"_notice": "Route geometry © OpenStreetMap contributors, precomputed from the public OSRM demo server (router.project-osrm.org) at build time between fixed public landmark coordinates. ODbL v1.0 — https://opendatacommons.org/licenses/odbl/1-0/",
"_source": "OSRM demo server (router.project-osrm.org) — build-time fetch, never a runtime dependency",
"version": 1,
"originToleranceM": 200,
"pairs": []
}
+132
View File
@@ -0,0 +1,132 @@
/*
* types.ts — the routing contracts (D-01, D-03, D-05, D-07, D-11).
* ----------------------------------------------------------------
* RoutePoint / RouteRequest / RouteResult / RoutingSource are the seam
* every backend implements: the build-time fixture corpus (backend #1,
* "fixture", this phase), the client-side WASM router (backend #2,
* "wasm-router" — OSS track, post-v1; origin+destination never touch a
* server), and Valhalla-on-VPS (backend #3, documented provision path,
* not shipped). The ladder is DOCUMENTED, not switchable: no env read,
* no config read, no dynamic backend registry (T-05-04) — the import
* site constructs the backend in code (plan 05-04).
*
* THE NULL CONTRACT IS LOAD-BEARING (D-03): route() resolving to NULL is
* the honest "router pending" state — an unknown destination, or an
* origin outside the fixture corpus's tolerance. Never a blank map,
* never a fake route, never a thrown error for a normal miss.
*
* No logging, no storage, no network (D-05/D-12/D-15 analog): request
* coordinates never leave the caller's frame.
*/
/** A WGS84 point. */
export interface RoutePoint {
lat: number;
lng: number;
}
/** A routing request. The origin is the live fix (memory only — GEO-03/D-07). */
export interface RouteRequest {
origin: RoutePoint;
/** The gazetteer id resolved by the Phase 4 destination resolver. */
destinationId: string;
}
/** Distance/duration summary. Distances in meters, durations in seconds. */
export interface RouteSummary {
distanceM: number;
durationS: number;
}
/**
* A maneuver step — DAY-ONE FIELD (D-07, GUID-05/06 upstream obligation).
* Fixture-world steps are ALWAYS empty arrays (D-10 defers instructions to
* Phase 6): an empty array is honest "not wired"; a fabricated step list
* would not be. Real step geometry arrives with backends #2/#3.
*/
export interface RouteStep {
distanceM?: number;
durationS?: number;
name?: string;
/** polyline6-encoded step geometry (backend #2/#3 populate this). */
geometry?: string;
}
/**
* Decoded geometry + the snapped-progress support field — DAY-ONE FIELD
* (D-07, GUID-05/06 upstream obligation): Phase 6's guidance walks a live
* fix along cumulativeDistanceM instead of re-deriving it per fix.
*/
export interface RouteGeometry {
points: RoutePoint[];
/** Same length as points; [0] === 0; non-decreasing. Meters along the line. */
cumulativeDistanceM: number[];
}
/** One routable alternative. options[0] of a result is the BEST route. */
export interface RouteOption {
geometry: RouteGeometry;
summary: RouteSummary;
steps: RouteStep[];
}
export interface RouteResult {
destinationId: string;
origin: RoutePoint;
/** 1..3 options, index 0 = best (order preserved from the backend). */
options: RouteOption[];
/** === the RoutingSource name that produced it. */
backend: string;
}
/**
* The pluggable routing seam (D-01). Backends: "fixture" (this phase) →
* "wasm-router" (OSS track, backend #2 — client-side, origin+destination
* never touch a server) → "valhalla" (VPS, backend #3). Backends #2/#3 do
* NOT ship in this phase — the interface exists so they slot in without a
* client rewrite, exactly parallel to SearchSource's ladder (D-03/D-04).
*
* THE NULL CONTRACT IS LOAD-BEARING (D-03): route() resolving to NULL is
* the honest "router pending" state — an unknown destination, or an origin
* outside the fixture corpus's tolerance. Never a blank map, never a fake
* route, never a thrown error for a normal miss.
*
* No logging, no storage, no network (D-05/D-12/D-15 analog): request
* coordinates never leave the caller's frame.
*/
export interface RoutingSource {
readonly name: string;
route(req: RouteRequest): Promise<RouteResult | null>;
}
/** One committed fixture route — the polyline6 string stored VERBATIM
* from the build-time OSRM demo response (RESEARCH Finding 1). */
export interface FixtureRoute {
/** polyline6, stored VERBATIM from the OSRM demo response. */
geometry: string;
distanceM: number;
durationS: number;
}
/** One golden origin→destination pair of the fixture corpus (D-02). */
export interface FixturePair {
/** "<origin-id>-to-<dest-id>", slug charset — also the ?fixpair= knob value. */
pairId: string;
origin: { lat: number; lng: number; label: string };
destinationId: string;
/** Best-first, 1..3 entries (OSRM demo returns best-first; preserved). */
routes: FixtureRoute[];
}
/** The committed dataset envelope (route-fixtures.json) — the ODbL
* attribution travels INSIDE the file, the gazetteer's three-surfaces
* pattern (T-05-06). Tests inject synthetic values of this shape. */
export interface RouteFixturesFile {
_license: string;
_attribution: string;
_notice: string;
_source: string;
version: number;
originToleranceM: number;
pairs: FixturePair[];
}