open-standards/map/SPEC.md
Transaction Science fef51e4ecd
Some checks failed
standards workspaces build + test / jouleclaw-rs (push) Has been cancelled
standards workspaces build + test / aac-rs (push) Has been cancelled
standards workspaces build + test / arl-rs (push) Has been cancelled
standards workspaces build + test / eoc-rs (push) Has been cancelled
standards workspaces build + test / jcp-rs (push) Has been cancelled
standards workspaces build + test / joule-code-rs (push) Has been cancelled
standards workspaces build + test / joule-ui-rs (push) Has been cancelled
standards workspaces build + test / joulecontract-rs (push) Has been cancelled
standards workspaces build + test / jouledesk-rs (push) Has been cancelled
standards workspaces build + test / joulehook-rs (push) Has been cancelled
standards workspaces build + test / map-rs (push) Has been cancelled
standards workspaces build + test / mesh-rs (push) Has been cancelled
standards workspaces build + test / openpay (push) Has been cancelled
standards workspaces build + test / proof-rs (push) Has been cancelled
standards workspaces build + test / sandbox-rs (push) Has been cancelled
standards workspaces build + test / sense-rs (push) Has been cancelled
standards workspaces build + test / smart-byte-rs (push) Has been cancelled
standards workspaces build + test / macOS energy backend (jcp-energy) (push) Has been cancelled
no-vanity-metrics / Scan committed Markdown for vanity metrics (push) Has been cancelled
map: an unbounded band width reads back from JSON, so the published tileset round-trips
AltSchedule::default() — the surface-only schedule, the common case — has width_m
= +inf. JSON has no infinity: serde_json wrote null and f64 refused it coming back,
so tileset_vectors.json could not be read into the reference types at all
("invalid type: null, expected f64") — the first thing a third party certifying
against the pack would try.

width_m now has one JSON form: +inf <-> null, in human-readable formats only. The
canonical CBOR every hash and signature covers still carries the float, so no
content id or signature moves and the published pack is byte-identical. NaN and
-inf, which mean nothing and have no JSON form, are refused rather than written
as a null that would read back as +inf. SPEC §2 states the rule.

The pack test that pinned the defect now asserts the round trip: the published
manifest reads back, verifies, re-serializes to the pack, equals the rebuilt
manifest, and the revision chains onto it. Falsified: without the adapter the
original error returns; writing null into CBOR too breaks the canonical-form test
and the published signatures.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 14:33:21 -04:00

12 KiB

Map v0.1 — A Carrier for Place

Status: Draft Standard. Reference implementation: 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 carries value, Joule Code carries coding work, and 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 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/:

  • 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.