The monorepo's IOReport energy backends now decline unless the CPU energy rail keeps advancing: on macOS 27 an unprivileged reader sees the CPU rails frozen or republished only in rare lumps, so a meter that trusted a present counter reported CPU work as GPU-only energy, or as zero, while tagging it a reading. The owner's decision is that a vendor energy counter is a real reading (the trust gap is provenance, not accuracy), so a LIVE reading is graded as a hardware reading, and every spec that grades vendor counters now carries the same rule: A reading from a vendor energy counter (RAPL, NVML, Apple IOReport, ...) MAY be graded as a hardware reading only while the counter is demonstrably live for the domain the figure claims: the implementation MUST have observed the counter covering that domain advance under load (for Apple IOReport: the CPU energy rail itself advancing in at least 5 sample intervals, in at least 4 of every 5, over at least 300 ms while the probing thread burns CPU). A counter that is present but not live - frozen, or republished in lumps - MUST NOT produce a hardware-graded figure; the implementation falls back to its estimator or reports the work unmetered, and SHOULD record why. A figure MUST NOT claim coverage of a domain its counter does not read; where the format has a coverage field, such a figure is Partial. Joule Code - spec/receipt.md §2 (REQUIRED): HwShunt now reads "an external shunt, or a vendor energy counter (RAPL, NVML, Apple SMC/IOReport) while that counter is live"; the rule above is added verbatim, and in this receipt the hardware grade is a present measured_uj and the HwShunt tag, so a counter that is not live supplies neither. The §1 comment and §5 say "live". - SPEC.md §6: the provenance definition now names IOReport and NVML beside RAPL/SMC, measured_uj comes only from an interface live for the domain it claims, and a paragraph states the MAY/MUST NOT, pointing to receipt.md §2 for the test. The §9 readiness row says "live" and "not live". - ROADMAP.md v0.2 "Measured energy": a vendor counter feeds the HwShunt path only while live; where it is present but not live (IOReport's CPU rails to an unprivileged reader on macOS 27) the receipt stays on the estimator. - examples/l3-jouleclaw/README.md: the claim that the receipt is "a real local measurement ... (HwShunt on RAPL/NVML hosts; Estimator on Apple Silicon until IOReport is wired)" was wrong both ways: run.rs wires only Apple Silicon's counter, and other hosts fall to the token Estimator. It now says so, that the counter's own tag is carried, and that HwShunt needs a live counter. - joule-code-receipt: the Provenance::HwShunt doc comment says the same. - CHANGELOG.md [Unreleased] ### Changed: the rule; no wire or vector change. Sense - SPEC.md §7 (REQUIRED): measured_uj comes only from a live interface; the rule is added verbatim. The Sandbox PowerMeter was described as "(real RAPL/IOReport)"; it is now described as grading a reading HwShunt only while its counter is live and falling back to its estimator otherwise, which is what origin/sandbox-meter/cpu-rail-liveness implements. §10's Sandbox edge says the same in brief. - sense-schema: the HwShunt, Energy and Energy::measured doc comments say a vendor counter is a hardware reading only while live. - Sense keeps no CHANGELOG, so none is added. Other specs that grade vendor counters - map/spec/receipt.md §2: the HwShunt row read "RAPL/SMC/shunt"; it now names a counter "while it is live", and the rule is added verbatim, noting that Receipt::seal cannot check liveness (the meter's duty, upstream). map/SPEC.md §6 states the MUST NOT and points there. CHANGELOG.md gains an [Unreleased] ### Changed entry; no wire or vector change. - arl/ARL-S.md, Telemetry: physical telemetry is what gates tiers above 0, and it said energy is "measured directly via RAPL ... NVML". The rule is added in ARL-S's own terms and lower-case keywords (ARL uses no RFC 2119 capitals): a counter that is not live does not produce physical telemetry, the energy it would have covered is reported unmetered, and the Supervisor must have observed the counter advance. No version bump. - smart-byte treatise §8.6: "Where RAPL or equivalent hardware energy counters are available, the measurement is read from the hardware" now requires the counter to be demonstrably live, and a frozen or lump- republished counter yields the same honest zero as an absent one. Text and doc comments only: no code path, wire format, or conformance vector changes. joule-code-rs (joule-code-receipt, joule-code-conformance) and sense-rs (sense-schema, sense-conformance) build and test clean, their rustdoc builds under -D warnings, map-rs (map-receipt, map-conformance) tests pass, `joule-code-conformance verify ../conformance/v1` passes 24/0/0, the no-vanity-metrics check passes, and every relative link in the changed Markdown resolves. Co-Authored-By: Claude Opus 5.5 <[email protected]>
130 lines
12 KiB
Markdown
130 lines
12 KiB
Markdown
# Map v0.1 — A Carrier for Place
|
|
|
|
Status: **Draft Standard**. Reference implementation: [`map-rs/`](map-rs/) (Rust workspace, Apache-2.0). Specification text: CC-BY-4.0.
|
|
|
|
The keywords **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **MAY**, and **OPTIONAL** are to be interpreted as described in RFC 2119.
|
|
|
|
---
|
|
|
|
## 1. Scope and doctrine
|
|
|
|
Map specifies how a region of the world — at a place, an altitude, an instant, and a fidelity — is identified, addressed, resolved through an energy-tiered fidelity cascade, and reported with its energy cost. It is the **geospatial cargo class** of the Transaction Science substrate. Where [Smart Byte](../smart-byte/) carries value, [Joule Code](../joule-code/) carries coding work, and [EOC](../eoc/) carries compute, Map carries **place**.
|
|
|
|
The **doctrine** is one sentence: *the map is a file, not a service.* Three normative consequences follow.
|
|
|
|
### 1.1 The tile is the cargo (REQUIRED)
|
|
|
|
The unit of place is a **tile**: a content-addressed, signed unit of the world at a single 4D cell, with its energy cost intrinsic. A tile is not an image and not an API response. It is a vesicle carrying exactly five things — identity (the BLAKE3 content address of its canonical serialization), a cell key (where/when/how-detailed it is), provenance (the prior tileset root it was published under, and the author key that signed it), cargo (the payload — terrain, a field, vector features, or splats), and a two-part joule cost (`measured + estimated`, honestly tagged).
|
|
|
|
### 1.2 The cascade (REQUIRED)
|
|
|
|
A conforming resolver **MUST** resolve every render request through an explicit energy-tiered **fidelity cascade**, **MUST** return the highest tier that is at or below the requested fidelity *and* holds data for the cell, and **MUST** fall back toward the base floor — which always closes the request. The tiers are energy-ordered:
|
|
|
|
| Tier | Wire tag | Named tag | Joule class | Payload for a render request |
|
|
|------|----------|------------|-----------------|-------------------------------------------------------------------------|
|
|
| F0 | `F0` | Cache | picojoules | content-addressed tile already resolved in the local store (OPFS) |
|
|
| F1 | `F1` | Base | microjoules | terrain DEM + coastlines + borders — the universal floor, always present |
|
|
| F2 | `F2` | Field | sub-millijoules | raster + scalar/vector field tiles (imagery, weather grids, wind) |
|
|
| F3 | `F3` | Feature | millijoules | vector feature tiles (roads, POIs, labels, airspace) + live entities |
|
|
| F4 | `F4` | Photoreal | joules | 3D Gaussian splats — dense, sparse-coverage; the escape hatch |
|
|
|
|
A resolver **MUST NOT** return a tier above the requested fidelity. A resolver **MUST NOT** fail a render request for a covered region: F1 **SHALL** be defined everywhere on the datum, so graceful fallback is a property of the standard, not of an implementation. A resolver **MUST** record in the receipt the tier that actually closed the request. F0 is the cache short-circuit and **MAY** close any request whose cell key was previously resolved.
|
|
|
|
> The cascade is inverted relative to compute cascades (JouleClaw, Joule Code) only in *intent*: there the cheapest sufficient tier wins; here the highest *available* tier at or below the request wins, with the cheap floor as the guaranteed fallback. The shared invariant is identical — **stop at the first tier that closes the request, and record it.**
|
|
|
|
### 1.3 The receipt (REQUIRED)
|
|
|
|
Every resolve/decode operation **MUST** report a two-part energy cost in integer microjoules — `measured + estimated` — with a `Provenance` tag (`HwShunt | ModelBased | Estimator`) on every reading, sealed as a **JCR-1** envelope (canonical det-CBOR + COSE_Sign1/EdDSA — the family's shared signed-receipt format). A `measured` figure **MUST** originate from a hardware energy interface; the spec never claims an accuracy the platform cannot deliver.
|
|
|
|
---
|
|
|
|
## 2. The cell key
|
|
|
|
A tile is addressed by a 4D **cell key** with four orthogonal axes (`map-schema::CellKey`):
|
|
|
|
```
|
|
CellKey {
|
|
s2: S2CellId, // horizontal position + extent (hierarchical, levels 0..30)
|
|
alt: AltBand, // altitude band index (signed; band 0 = datum surface)
|
|
time: TimeBucket, // half-open [start, start+width) interval; bucket 0 = atemporal
|
|
lod: Lod, // 0..=MAX detail level within the tier
|
|
}
|
|
```
|
|
|
|
- **Horizontal (REQUIRED).** The `s2` axis **MUST** be an S2 cell identifier. S2 is a hierarchical, space-filling (Hilbert-ordered) decomposition of the sphere: adjacent cells are adjacent in the integer key space, which is what makes a tileset range-requestable (§4). An implementation **MUST** treat the S2 level as the horizontal LOD.
|
|
- **Vertical (REQUIRED).** The `alt` axis **MUST** be a signed band index against a declared band schedule (`map-schema::AltSchedule`). Band 0 is the datum surface; negative bands are sub-surface; positive bands are altitude. Atmospheric and orbital payloads occupy positive bands; bathymetry occupies negative bands. A schedule's `width_m` **MAY** be `+∞` — a single unbounded surface band, the 2.5D case; JSON has no infinity, so its JSON form is `null`, which **MUST** be read back as `+∞`, while the canonical CBOR (§3) carries the float itself.
|
|
- **Temporal (REQUIRED).** The `time` axis **MUST** be a `TimeBucket`: a half-open interval anchored to TAI. Bucket 0 is reserved for **atemporal** tiles (a coastline, a border). A weather forecast cycle, an AIRAC navigation cycle, and an ADS-B snapshot are distinct, non-zero buckets. The temporal axis is first-class: a "map of Tuesday at 14:00Z" is a different, equally addressable region from "the same place on Wednesday."
|
|
- **Detail (REQUIRED).** The `lod` axis is the within-tier detail level, independent of the S2 level (which is the horizontal extent). A splat tile at S2 level 20 **MAY** carry LODs 0..N of progressively denser Gaussians.
|
|
|
|
Two cell keys are equal iff all four axes are equal. The canonical serialization of a `CellKey` (§3) is deterministic, so its BLAKE3 hash is a stable join key across implementations.
|
|
|
|
---
|
|
|
|
## 3. Canonical serialization and content addressing
|
|
|
|
Every Map structure that is hashed or signed **MUST** be serialized with the family's deterministic CBOR profile (det-CBOR: definite-length, canonically-ordered keys, no floats where an integer suffices). The **content id** of a tile is `blake3(canonical_serialization)` (`map-schema::ContentId`, 32 bytes). Content ids are the only cross-reference Map uses internally: a tileset directory maps cell keys to content ids; a manifest commits to content ids; a receipt references the content ids it resolved.
|
|
|
|
A tile is **immutable**: changing any byte changes its content id and therefore its identity. There is no in-place edit. Revision happens at the tileset level (§5).
|
|
|
|
---
|
|
|
|
## 4. The tileset (REQUIRED)
|
|
|
|
A **tileset** is the on-disk, range-requestable archive — the "it's just a file" deliverable. A conforming tileset is a single addressable blob on dumb storage (object store, static host, OPFS) with three parts:
|
|
|
|
1. A **header** with a magic tag, the standard version, the datum, the alt schedule, and the byte offset of the directory.
|
|
2. A **directory** mapping `CellKey → (byte_offset, byte_len, content_id)` for every tile in the set, **MUST** be ordered by the S2 (Hilbert) key so that the tiles of one viewport occupy a near-contiguous byte range. A reader **MUST** be able to fetch the directory, compute the cell keys a viewport needs, and issue HTTP range requests for only those byte ranges — no server-side logic, no per-request billing.
|
|
3. The **tile payloads**, content-addressable and independently decodable.
|
|
|
|
A reader **MUST NOT** be required to download the whole tileset to render a viewport. A reader **MUST** verify that each fetched payload hashes to the `content_id` the directory claims.
|
|
|
|
---
|
|
|
|
## 5. The manifest and signed transitions (REQUIRED)
|
|
|
|
A tileset's authenticity rests on a signed **manifest** (`map-schema::Manifest`): a commitment to the directory root (a BLAKE3 Merkle root over the sorted `CellKey → content_id` pairs), the publisher's Ed25519 public key, the standard version, and the cell-key bounds of the set. The manifest **MUST** be signed (Ed25519 over the det-CBOR of the manifest body).
|
|
|
|
A **revision** is a signed transition `prev_root → new_root` (`map-schema::Revision`): a new manifest that names the root it supersedes and is signed by the same publisher key (or a successor key it endorses). Revisions are **append-only and dated** (each carries a TAI timestamp). A new weather forecast hour, a new 28-day AIRAC cycle, and a re-flight of a corridor are revisions — never in-place mutations. A reader **MAY** pin any historical root and **MUST** be able to verify a revision chain back to a trusted root without contacting the publisher.
|
|
|
|
This is the "works forever" guarantee made normative: a newsroom's election map, an archived flight plan, or a forecast cycle is a signed, dated file whose validity does not depend on any service remaining online.
|
|
|
|
---
|
|
|
|
## 6. Energy and the receipt (REQUIRED)
|
|
|
|
The receipt is the family's JCR-1 envelope (`map-receipt`). A receipt **MUST** carry:
|
|
|
|
- the `CellKey` and `content_id` of the tile(s) it covers;
|
|
- the cascade `Tier` that closed the request;
|
|
- an `Energy` record: `measured_uj: Option<u64>`, `estimated_uj: u64`, `provenance: Provenance`;
|
|
- the publisher/resolver identity and an Ed25519 signature (COSE_Sign1/EdDSA over canonical det-CBOR).
|
|
|
|
The provenance coupling is normative: `HwShunt` **REQUIRES** a present `measured_uj`; `Estimator` **MUST** leave it absent; `ModelBased` **MAY** include it. A session's total cost is the sum of its tile receipts. A resolver **MUST NOT** report a `measured` figure it did not measure. A vendor energy counter (RAPL, NVML, Apple IOReport, ...) grades a figure `HwShunt` only while it is demonstrably live for the domain the figure claims; a counter that is present but not live — frozen, or republished in lumps — **MUST NOT** produce one ([`spec/receipt.md`](spec/receipt.md) §2).
|
|
|
|
A reference (non-normative) estimator orders the tiers by joule class: a cache hit is picojoules; a base/terrain tile is microjoules; a field tile is sub-millijoules; a feature/entity tile is millijoules; a splat tile is joules. The standard is that the figure is reported and honestly tagged — not the constant.
|
|
|
|
---
|
|
|
|
## 7. No capture (REQUIRED)
|
|
|
|
Map carries no protocol-level token. Reading a tileset **MUST** require nothing but the bytes and the publisher's public key. A conforming reader performs its core function — resolve a cell, verify it, render it, meter it — against static storage, with no contact with any single company's servers as a condition of operation. Transaction Science is one steward: it publishes the spec, ships the reference implementation, and operates optional commercial services (capture pipelines, hosted tilesets, a registry) around the substrate — not its proprietor.
|
|
|
|
---
|
|
|
|
## 8. Conformance
|
|
|
|
An implementation is **conformant** if it round-trips the vectors in [`conformance/v1/`](conformance/v1/):
|
|
|
|
- **C1 — cell key.** Canonical serialization and `ContentId` of the cell-key vectors match byte-for-byte.
|
|
- **C2 — tileset directory.** A reader resolves the correct byte range for a viewport over the sample tileset and verifies every payload hash.
|
|
- **C3 — manifest & revision.** Signature verification and revision-chain validation against the sample roots; a tampered directory root **MUST** fail.
|
|
- **C4 — cascade.** For each render-request vector, the resolver closes at the specified tier and falls back to F1 where higher tiers are absent.
|
|
- **C5 — receipt.** JCR-1 receipts seal, verify, and enforce the provenance ↔ measured coupling; vectors are interoperable with the family's existing JCR-1 verifiers.
|
|
|
|
A negative pack (`C-neg`) covers tampered payloads, an out-of-order directory, a revision signed by an unendorsed key, and a receipt claiming `HwShunt` with no `measured_uj`.
|
|
|
|
---
|
|
|
|
## 9. Non-goals
|
|
|
|
Map is a **substrate for read-heavy, published geospatial data**. It is explicitly **not** a live geocoding/routing *service* (the reference ships `map-route` as a local capability over a tileset, not a hosted API), and it is **not** a certified primary navigation source. Air-navigation payloads are for **situational awareness, planning, and simulation only** — never a DO-178C/TSO-certified source of primary flight guidance. A conforming flight-nav tileset **MUST** carry this disclaimer in its manifest metadata.
|