From d8e9b47f28267c08a8013d33fbf936e2eb00c450 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 10 Aug 2026 22:13:49 +0000
Subject: [PATCH 1/4] plan(osm): record the OGAR hot-plug surface and what it
means for q2
The OSM substrate is already plugged into OGAR USB-style: socket in
lance_graph_contract::hotplug, authority in ogar_vocab::geo_actions,
bridge via ogar_osm::plug_in, and the device is osm-soa-bake::capability
with a real dispatch arm per capability. Six capabilities are declared on
osm_node/osm_way, and two of them are things this endpoint already
touches: locate_tile IS slab.tile_range, and project_fields is the
masked projection.
Three consequences, recorded so a future session does not re-derive them:
q2 must not declare its own HOT_PLUG. GEO_EXPECTED_EXECUTORS is
["osm-soa-bake"] and resolve_hotplug checks the consumer name, so a plug
from cockpit-server earns UnexpectedConsumer by design. q2 calls an
already-activated device; it is not a second device.
Phase 1 needs no rework. Calling slab.tile_range directly is explicitly
sanctioned by capability.rs's own module doc -- the arms exist to make the
registration falsifiable, not to be a mandatory call path.
project_fields is a real gap, and it is the authorization one. The
endpoint returns decoded values with no mask; the declared capability is
surface-intersect-role and fail-closed, meaning an unauthorised position
is absent from the response rather than hidden by the caller. Moot for a
local cockpit, not moot for a deployment. Filed as a Phase 4 addendum
with the honest caveat that it needs a role source first -- until then
the endpoint should be described as unauthenticated rather than implying
a mask exists.
---
.../2026-08-08-osm-soa-cockpit-wiring.md | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/claude-notes/plans/2026-08-08-osm-soa-cockpit-wiring.md b/claude-notes/plans/2026-08-08-osm-soa-cockpit-wiring.md
index c97bce8f84..330d1817e7 100644
--- a/claude-notes/plans/2026-08-08-osm-soa-cockpit-wiring.md
+++ b/claude-notes/plans/2026-08-08-osm-soa-cockpit-wiring.md
@@ -358,6 +358,74 @@ because it means the slab was *never* V1; only q2's display key is.
(`RAILWAY_VOL` is set, the mount is Railway-side only), so this leg
is verifiable only on deploy — say so rather than claiming it works.
+## The OGAR plug-and-play surface (operator pointer, 2026-08-10)
+
+**The OSM substrate is already hot-plugged into OGAR, USB-style.** Worth
+recording because it changes Phase 4's design and it *validates* Phase 1's
+call style rather than condemning it.
+
+| USB role | Home | Surface |
+|---|---|---|
+| socket (agnostic, zero-dep) | `lance_graph_contract::hotplug` | `HotPlug { consumer, classids, covered }`, `CapabilityAuthority` |
+| authority (host) | `ogar_vocab::geo_actions` | the geo action table + `capability_registry::resolve_hotplug` |
+| bridge | `ogar_osm::plug_in` | socket ⇄ authority |
+| **device** | `osm-soa-bake::capability` | `Capability` enum, `activate()`, one real dispatch arm each |
+
+Declared capabilities, and what each already is in code:
+
+| capability | subject | primitive |
+|---|---|---|
+| `locate_point` | `osm_node` `0x0F01` | `tms::point_to_tiers` |
+| **`locate_tile`** | `osm_node` | **`slab.tile_range(z,x,y)` — what `/api/osm/features/:z/:x/:y` calls** |
+| `locate_fragment` | `osm_node` | `BoundaryIndex::locate_range` |
+| **`project_fields`** | `osm_node` | **`project::project(row, surface, role)` — the RBAC projection** |
+| `street_edges` | `osm_way` `0x0F02` | `street::edge_mask` |
+| `polyline_length` | `osm_way` | `geodesy::polyline_metres` |
+
+### Three consequences for q2 — read before touching the endpoint
+
+1. **q2 must NOT declare its own `HOT_PLUG`.**
+ `GEO_EXPECTED_EXECUTORS = ["osm-soa-bake"]`, and `resolve_hotplug`
+ checks the consumer name — a plug from `cockpit-server` earns
+ `HotplugDrift::UnexpectedConsumer`, by design. **q2 is a caller of an
+ already-activated device, not a second device.** The plug is the bake
+ crate's, and it is already shipped (`capability.rs`, with can-fire
+ halves proving the port rejects a wrong consumer and short coverage).
+
+2. **Calling `slab.tile_range(...)` directly is explicitly sanctioned**, so
+ Phase 1 needs no rework. `capability.rs` says so in its own module doc:
+ *"These are thin, on purpose … the dispatch surface exists so the
+ registration is checkable, not to add a layer: a caller that already has
+ the module in scope should keep calling `tms::point_to_tiers` directly."*
+ The arms exist to make the registration falsifiable, not to be a
+ mandatory call path.
+
+3. **`project_fields` is a real gap in the endpoint, and it is the
+ authorization one.** `/api/osm/features` currently decodes and returns
+ `lon/lat/entity_type/ordinal` with no mask applied. The declared
+ capability is `surface ∩ role` and is **fail-closed** — an unauthorised
+ position is *absent from the response*, not hidden client-side (the same
+ projection doctrine a2ui-rs enforces: RBAC happens before framing, and
+ pixels/JSON can't promise what the wire already leaked). For a
+ single-user local cockpit that is moot; for anything deployed it is the
+ difference between a demo and a surface with an access model.
+
+- [ ] Phase 4 addendum: route the feature response through
+ `project_fields(row, surface, role)` rather than hand-decoding, so
+ the endpoint inherits the fail-closed projection instead of
+ re-implementing (or silently skipping) it. Needs a role source —
+ until there is one, say plainly that the endpoint is unauthenticated
+ rather than implying a mask exists.
+
+**Anti-patterns this pins** (each burned upstream once, per
+`OGAR/.claude/knowledge/hotplug-consumer-migration.md`): no bespoke
+per-consumer plug mechanism; no shape ordinals in the classid low u16 (low
+= APP render prefix); no git deps on OGAR (path deps to the sibling — a
+git+branch dep writes a rev pin into `Cargo.lock`); no parallel registry
+beside `domain_tables()` — `ogar-osm` once shipped its own
+`OSM_CAPABILITIES` that verified itself against itself and passed while the
+real port answered `NoCapabilitiesFor(0x0F01)`.
+
## Notes
- Disk: the 1.2 GiB Berlin slab is a local dev artifact under `/tmp`, not
From a3fbc9f03246723a671c5c96dc0c85c4152c12d3 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 10 Aug 2026 22:44:37 +0000
Subject: [PATCH 2/4] osm cockpit: migrate the HHTL key from V1 (3-tier z=24)
to the V3 substrate
The cockpit's displayed address and the key RowSlab sorts rows by are now
the same key. They were not before: osm_tiles carried its own 3-tier,
z=24, un-flipped Morton key while the slab is keyed 4-tier, z=32,
Cesium-TMS-flipped. Every tier differed at every point tested -- Berlin
HEEL 0x624b vs 0xc8e1 -- so this was a different key, not a coarser one.
Done as a real TDD loop, since the environment can now link the test
binary (dropping debuginfo was what unblocked it):
RED hhtl_agrees_with_the_v3_substrate_oracle failed 25163 vs 51425
GREEN 14/14 osm tests after the migration
The test asserts equality against osm_soa_bake::tms::point_to_tiers -- the
implementation the slab is actually keyed on -- rather than asserting the
key "has four tiers", which would pass against a wrong key.
Implementation is mostly deletion. The local Mercator formula, the 24-bit
Morton interleave/deinterleave and the depth constant are now delegations
to osm_soa_bake::tms. Two implementations of one projection is precisely
how the display address and the row key drifted apart, so the duplicate is
gone rather than widened.
Two subtleties worth keeping:
- The TMS flip must happen at the tile's own zoom and THEN be left-aligned.
xyz_to_tms_y is 2^z-1-y, so flipping at z and shifting by 32-z yields the
minimum TMS row of the tile's z=32 range, which is the common Morton
prefix. Shifting first and flipping at 32 picks the opposite corner and
breaks the prefix property.
- Reading the bytes LEAF occupies as a tier, rather than as the head of the
old family:u24, IS the V3 content-blind reinterpretation.
TEST CHANGES -- two pre-existing tests encoded the V1 depth and were
re-pinned, not silently widened:
- hhtl_roundtrips_to_the_tile: z=24/3-tier/48-bit -> z=32/4-tier/64-bit,
and it now un-flips the recovered row before comparing. It also asserts
the stored row is genuinely flipped, so the test fails if tile_to_hhtl
ever stops applying the flip.
- over_depth_zoom_folds_to_its_native_ancestor: over-depth begins at 33,
not 25. It now also asserts z=25 is NOT folded, which the V1 form did.
- coarse_zoom_lives_in_heel_not_twig: strengthened to cover LEAF.
Panel gains a fourth tier cell; the overlay JS was re-checked with
node --check. The key-space warning in osm_features.rs was rewritten
rather than deleted -- it records why the two keys once differed, which is
the reason the equality test exists.
Verification: 14/14 osm tests green; 79/80 for the whole crate. The single
failure, osint_gotham::dual_use_facets_pack_into_the_value_tenant, was
confirmed pre-existing by stashing this change and re-running it on main's
state -- it fails identically there and touches nothing in this diff.
---
crates/cockpit-server/src/osm.rs | 20 ++-
crates/cockpit-server/src/osm_features.rs | 25 ++-
crates/cockpit-server/src/osm_tiles.rs | 210 +++++++++++++++-------
3 files changed, 169 insertions(+), 86 deletions(-)
diff --git a/crates/cockpit-server/src/osm.rs b/crates/cockpit-server/src/osm.rs
index 901f24a98d..e58ee90ba1 100644
--- a/crates/cockpit-server/src/osm.rs
+++ b/crates/cockpit-server/src/osm.rs
@@ -1,7 +1,7 @@
//! The `/OSM` cockpit — a self-contained slippy-map page over the OSM tile
//! material ([`crate::osm_tiles`]). The Geo-domain (`0x0F`) sibling of the FMA
//! body-helix cockpit: pan/zoom OSM raster tiles from the standard source, and
-//! read each tile's HHTL (HEEL/HIP/TWIG) key live from `/api/osm/locate`.
+//! read each tile's HHTL (HEEL/HIP/TWIG/LEAF) key live from `/api/osm/locate`.
//!
//! The page is a single inline HTML string (the `/mri` cockpit pattern): no
//! build step, no external JS. Tiles are `
` fetched directly from
@@ -36,10 +36,10 @@ const PAGE: &str = r##"
.row { display:flex; justify-content:space-between; padding:3px 0;
border-bottom:1px solid #161d27; }
.k { color:#6b7c93; } .v { color:#e6edf5; }
- .tier { display:grid; grid-template-columns:repeat(3,1fr); gap:8px; margin:12px 0; }
+ .tier { display:grid; grid-template-columns:repeat(4,1fr); gap:6px; margin:12px 0; }
.cell { background:#141b24; border:1px solid #223; border-radius:6px; padding:8px;
text-align:center; }
- .cell b { display:block; font-size:16px; color:#8fd6ff; }
+ .cell b { display:block; font-size:13px; color:#8fd6ff; }
.cell span { color:#6b7c93; font-size:11px; }
.ctl { position:absolute; left:12px; top:12px; display:flex; flex-direction:column;
gap:6px; z-index:5; }
@@ -77,12 +77,14 @@ const PAGE: &str = r##"
—HEEL
—HIP
—TWIG
+ —LEAF
tile source
—
A quadtree is a cascade:
- z/x/y Morton-interleaves into the three 16-bit HHTL tiers, so
- the map pyramid and the semantic address are one and the same.
+ z/x/y Morton-interleaves into the four 16-bit HHTL tiers
+ (GEO_V3_FACET rails 0–3), so the map pyramid and the slab's own
+ row key are one and the same address.