Lumen/IMPLEMENTATION-CONTRACT.md
Lumen Stage1 fcc18ddcc8
Some checks failed
ci / check (push) Has been cancelled
Checkpoint: current Lumen state
2026-09-23 18:58:21 -05:00

85 KiB
Raw Permalink Blame History

Lumen — Implementation Contract

  • Phase: Implementation Preparation — handoff from Architecture Validation to coding agent
  • Date: 2026-08-30
  • Validated architecture freeze: ARCHITECTURE-VALIDATION.md:1 (SPIKE-01…08 reconciled)
  • Source of truth chain: DISCOVERY.md → ARCHITECTURE-DESIGN.md + ARCHITECTURE-DECISIONS.md + ASSUMPTIONS-AND-OPEN-QUESTIONS.md → ARCHITECTURE-VALIDATION.md → this contract
  • Scope guard: No application code, no package.json, no node_modules, no PWA/DB/network implementation exists when this contract was written. A future coding agent must not reinterpret the architecture to simplify it.

0. How to read this contract

Every rule below traces to a validated ADR or spike. Where the contract says "normative" the rule was proven or hardened by a spike. Where it says "provisional" the architecture allows implementation to proceed but production still requires the physical-device or content checks listed in ARCHITECTURE-VALIDATION.md:4 and ASSUMPTIONS-AND-OPEN-QUESTIONS.md:9.

If an implementation conflict is found, stop and document it — do not silently change the architecture (RULES FOR FUTURE AI CODING AGENTS).


1. V1 scope

V1 ships an offline-first, mobile-first Progressive Web App for 300–500 attendees (DISCOVERY.md:103).

Included:

  • Four destinations: EMERGENCY · SCHEDULE · MAP · FESTIVAL (DISCOVERY.md:145 R-N1, ARCHITECTURE-DESIGN.md:207)
  • Emergency 3-tier model (floor + dataset section + reserved T3) (ARCHITECTURE-DECISIONS.md:272, SPIKE-06:12)
  • Schedule: browse, Now/Next, My Schedule (favorites), filters, search — all offline (DISCOVERY.md:162 R-S1…S5)
  • Map: raster WebP ≤2 levels + DOM POI overlay + Facilities list — all offline, no GPS in V1 (ARCHITECTURE-DECISIONS.md:315, SPIKE-03:58)
  • Festival info: structured blocks — all offline (DISCOVERY.md:182 R-F1)
  • Pull-only sync on open / online hint / manual (ARCHITECTURE-DECISIONS.md:406)
  • Honest readiness states: READY/PARTIAL/NOT_READY/RECOVERY/BASELINE_ONLY/FAILED (SPIKE-05:60)
  • Bootstrap L0–L2 preparation (SPIKE-07:31)
  • Time model: UTC + festival IANA zone + ClockService (ARCHITECTURE-DECISIONS.md:362)

Trace: ADR-001…ADR-010, ARCHITECTURE-DESIGN.md:25 executive summary.

2. Explicitly excluded functionality (V1)

Do not build these in V1 unless a later ADR explicitly approves them:

  • Mesh networking in any form (seam only, DISCOVERY.md:270 NR-1, ADR-012) — see §27
  • Native apps / App Store wrapping (NR-2, NR-3)
  • User accounts / cross-device sync of favorites (NR-4, NR-5; DISCOVERY.md:240 constraints 11–12)
  • Real-time collaboration/social (NR-6), payments (NR-9), UGC (NR-10)
  • GPS-required features / blue dot, online tiles as core map (NR-7, NR-8, ADR-008) — hook preserved only
  • Push as critical channel (NR-13, ARCHITECTURE-DECISIONS.md:406 pull-only)
  • Background Sync / Periodic Sync (DISCOVERY.md:433 PW-4, ARCHITECTURE-DESIGN.md:78 C-19)
  • Admin/CMS UI (NR-14; pipeline is file-based per ADR-014), private analytics (NR-15)
  • Multi-language i18n (NR-11, DD-6): keep strings in data but English-only

Violations fail the Definition of Done §40.

3. Technology stack (normative)

Concern Selection Trace
Language TypeScript strict ADR-003 (ARCHITECTURE-DECISIONS.md:86)
UI framework None — vanilla TS + tiny store + history router + escaping renderers ADR-003; fallback trigger → Preact only if store→render proves unworkable (ARCHITECTURE-DECISIONS.md:86)
Build Single-bundle bundler that emits hashed immutable assets + precache manifest (esbuild/vite-class) — implementation pick ARCHITECTURE-DESIGN.md:126
Service worker Hand-written, tiny (~precache + navigation fallback only) — no Workbox ADR-002 (ARCHITECTURE-DESIGN.md:14)
Structured storage IndexedDB via thin internal promise wrapper; Cache Storage for shell only; localStorage guarded non-load-bearing ADR-004 (ARCHITECTURE-DECISIONS.md:129, SPIKE-01:140 P1–P8)
Crypto WebCrypto SHA-256 + bundled audited pure-JS Ed25519 verifier (WebCrypto Ed25519 not baseline on iOS 16.4) ADR-013 (ARCHITECTURE-DESIGN.md:14), TQ-9 — YELLOW pending audit ARCHITECTURE-VALIDATION.md:522
Map WebP raster + DOM buttons + CSS-transform pan/zoom + Facilities list ADR-008 + SPIKE-03
Hosting Static HTTPS CDN, single origin ADR-014 (ARCHITECTURE-DESIGN.md:104), DISCOVERY.md:261 C-20
Push/analytics None in V1 ADR-010 / ADR-013

Forbidden without ADR amendment: React/Angular/Vue, Workbox, Dexie/idb, SQLite-WASM, any map SDK, any mesh lib, any auth system, any dynamic backend (ARCHITECTURE-DESIGN.md:144). Target: only the audited Ed25519 verifier beyond stdlib (ARCHITECTURE-DESIGN.md:930 rule 3).

4. Project structure (recommended)

Structure follows the layering in ARCHITECTURE-DESIGN.md:148 §6 and the boundaries B-1…B-7 ARCHITECTURE-DESIGN.md:195. A coding agent may rename leaves but must keep the boundaries and allowed/forbidden sets.

src/
  platform/        # Cross-cutting low-level adapters
    idb/           # Thin promise wrapper over raw IDB (ADR-004, SPIKE-01 P1–P8) — no business logic
    cache/         # Cache Storage adapter for shell
    sw/            # SW bridge (page side, message handling)
  storage/         # Re-exports platform adapters with typed contracts — no UI, no fetch
  data/            # DatasetStore + UserStore — read APIs + writes (typed, transactional)
  sync/            # SyncService orchestration (check→stage→verify→activate) + staging state
    transport/     # Transport interface + HttpTransport (V1 only)
    verifier/      # SHA-256 + Ed25519, quarantine, budgets, schema checks
  domain/          # Domain services, pure logic over data layer — no persistence, no network
    emergency/     # Resolution: floor vs dataset section, provenance, hardening F-1
    schedule/      # Queries: Now/Next, filters, search, conflict detection
    map/           # POI interpretation, level math, Facilities list queries
    festival/      # Info blocks
    readiness/     # ReadinessService predicate C1–C8 + state taxonomy (SPIKE-05)
    clock/         # ClockService (skew + monotonic + sanity F-3)
    favorites/     # FavoritesService over UserStore (B-6)
  ui/              # Views + presentation stores + router + escaping renderer
    components/    # Shared primitives (safe DOM helpers, status chip, etc.)
    views/         # emergency/ / schedule/ / map/ / festival/ / status/
    router/        # history-API router (one index.html, offline-safe deep links)
    render/        # Structured-node → safe DOM (C-22, no innerHTML of raw strings)
  app/             # App bootstrap, boot sequence (light verify → readiness → render), composition root
  emergency-baseline/ # Compiled-in floor JSON (generated, immutable per build)
  assets/          # Hashed shell assets (generated)
public/
  index.html       # Cached shell entry (no-cache per ARCH §24)
  manifest.webmanifest
  fallback.html    # Ring-0 static fallback (emergency text, no JS needed)
  sw.js            # Hand-written SW (generated precache list injected at build)
content/           # Not shipped — authoritative source for pipeline (ADR-014)
  emergency/ schedule/ map/ info/ assets/
pipeline/          # Validate→build→hash→sign→upload→smoke (separate from app)
tests/
  unit/            # Verifier, ClockService, readiness predicate, A/B transitions, escaping
  integration/     # Full update lifecycle with mocked transport + fault injection (9 stages)
  e2e/             # Headless/offline harness where useful
  device-matrix/   # Scripted manual protocols per ARCHITECTURE-VALIDATION.md §4

Directory responsibilities

Directory Owns May import Must never import
platform/idb, platform/cache Transaction discipline, durability, quota Browser APIs Any domain/ui/sync logic
data/ Typed local truth (read/write contracts) platform/ sync/, ui/
sync/ Orchestration, staging, version logic, activation txn data/, sync/verifier, sync/transport domain/, ui/ internals
sync/verifier Authenticity/integrity verdicts + budgets + schema platform/ (hash), bundled verifier Rollback/policy (lives in sync/)
sync/transport Bytes in/out, availability fetch (or equivalent) data/, domain/, ui/
domain/* Feature logic over data/ read APIs + clock/ data/ read, clock/ platform/, sync/, network
ui/ Rendering, routing, user intent, readiness display domain/, readiness/ platform/, storage/, sync/transport, raw fetch, indexedDB
app/ Boot sequence, system-meta read, compatibility check (§18.5), composition Everything via its public APIs —
public/sw.js Shell-cache precache + navigation fallback only caches Datasets, sync, IDB

This encodes ARCHITECTURE-DESIGN.md:704 responsibilities and the mesh anti-leak rule (invariant 12).

5. Architectural layers (validity: ARCHITECTURE-DESIGN.md:148)

Layers are UI → Domain → Data → Sync → Transport → Platform + SW (separate context). The layering diagram is ARCHITECTURE-DESIGN.md:148. Implementation must not collapse layers even if it seems convenient.

6. Dependency rules (normative, per ARCHITECTURE-DESIGN.md:195 B-1…B-7 + ARCHITECTURE-DESIGN.md:930 hard rules)

ID Rule
B-1 Views never touch indexedDB, fetch, caches, localStorage, or SW internals.
B-2 Domain services read only through data/ read APIs (DatasetStore/UserStore).
B-3 Feature modules never know which transport delivered data; transports never know content meaning (invariant: mesh concerns never leak).
B-4 Only sync/verifier decides authenticity/integrity; Sync orchestrates, never validates alone.
B-5 SW never writes dataset data; staging lives in page context (C-21, P8, ARCHITECTURE-DESIGN.md:83).
B-6 User state (lumen-user) is never touched by dataset updates/rollback/GC (SPIKE-02:69 X3).
B-7 No festival content rendered via innerHTML of raw strings (C-22, ARCHITECTURE-DESIGN.md:195).

Hard rules carried into implementation ARCHITECTURE-DESIGN.md:928: no feature→persistence/transport imports; no raw-HTML ingestion; no runtime dep without ADR amendment; no background work (C-19); every readiness state reachable in tests; budgets CI-enforced; no analytics/third-party/permissions.

7. UI / application boundaries

  • Single-page app, one cached index.html, history-API router (ARCHITECTURE-DESIGN.md:207).
  • Persistent bottom nav EMERGENCY · SCHEDULE · MAP · FESTIVAL (Emergency first, visually dominant) — satisfies R-N3 one-tap emergency (ARCHITECTURE-DESIGN.md:207).
  • Persistent status chip (READY/PARTIAL/NOT_READY/RECOVERY/BASELINE_ONLY/FAILED) in header → Status screen per SPIKE-05:60.
  • Views → VM/presentational stores → domain/* → data/ read APIs. Never direct persistence.

8. Local storage rules

  • Storage is best-effort, silent eviction, per-origin all-or-nothing (SPIKE-01:97, ASSUMPTIONS-AND-OPEN-QUESTIONS.md:43 B-03). Detection—not prevention—is the architecture: boot light verify (§20) + RECOVERY + baseline.
  • Do not assume navigator.storage.persist() is granted (ARCHITECTURE-DESIGN.md:285); request it once after READY (P6) but never rely on grant.
  • Do not handle storage pressure with retry loops; pre-check free space via storage.estimate() (P4).
  • Do not keep long transactions; one short txn per file (P1) — no await of non-IDB work inside a txn.

9. IndexedDB schema requirements (normative, ADR-004 + SPIKE-01/02)

Database Stores Keys / contents Lifecycle
lumen-system meta (single object store) Active slot pointer (A/B), activeEdition, activePackageVersion, verification record (what/when/which, fingerprint, manifestSha256), appVersionAtActivation, readbackPending, clock skew cache pointer Single source of truth for active/readback metadata; rewritten atomically on activation (P2); no per-file staging progress
lumen-slot-a files, assets, staging files: section docs keyed by section id (emergency/schedule/map/info/assets.json); assets: Blobs keyed by asset id (map-base-*, icons); staging: authoritative slot-local journal Inactive slot is staging target; file/asset + journal commit in one transaction; active slot is truth; GC per §13
lumen-slot-b files, assets, staging Same as above Same
lumen-user favorites (by stable event id), prefs, diag (scrubbed ring buffer) Favorites id → { addedAt }; prefs (theme, clock, displayMode flags); diag (quarantined versions, recent errors) Never GC'd by dataset logic (B-6); own idempotent migrations (package schema separate, SPIKE-04:210)
lumen-shell-v<build> (Cache) Cache Storage Precached shell (hashed JS/CSS/icons, index.html, fallback.html) Versioned per build; old caches deleted on SW activate
(localStorage) Guarded flags only Coach dismissed, etc. — try/catch Non-load-bearing

Notes: assets stored as Blobs in the slot DB to keep rollback in one failure domain (ARCHITECTURE-DESIGN.md:275, SPIKE-01:140). Dataset JSON parsed once per section per session; no persistent derived indexes.

10. A/B dataset rules (normative, ADR-006 + SPIKE-02)

Invariant: at every observable moment lumen-system.meta.activeSlot points at either the old verified dataset or the new verified dataset — there is no third state (ARCHITECTURE-DESIGN.md:659).

  • Two dataset slot databases; updates stage exclusively into the inactive slot (ARCHITECTURE-DESIGN.md:659).
  • Per-file staging is one short txn: bytes + progress record together (P1, F-1 SPIKE-02:89); no txn spans non-IDB await.
  • Staging progress is authoritative in the target slot's staging journal; resume is keyed on committed slot-local progress; staging older than 7 days is discarded (ARCHITECTURE-DESIGN.md:669).
  • Beginning a new staging run wipes the inactive slot — rollback depth is exactly 1 (three-slot rejected for footprint, SPIKE-02:100 F-4). Invariant holds (valid dataset always active); only undo depth is bounded.
  • Downloads run in page context, never SW (C-21, P8).

Violations (e.g., staging into active slot or cross-DB txn) contradict SPIKE-02:73 ordering proof and fail §40 acceptance.

11. Dataset validation rules (normative, ADR-005/013 + SPIKE-02/04)

Cheap + authoritative checks first; untrusted data never forces work.

  1. Fetch latest.json → monotonic version comparison: reject ≤ active from any network source (downgrade protection, ARCHITECTURE-DESIGN.md:349). Purely informational generatedAt never gates.
  2. Fetch candidate manifest.json.
  3. Signature over exact manifest bytes (Ed25519, signature.json manifestSha256 + publicKeyFingerprint) — verified against embedded key set (fingerprinted, rotation-capable, ARCHITECTURE-DESIGN.md:358) — before parsing or downloading anything else (SPIKE-02:29 step 3). Fail → quarantine version, diag entry, keep active (S07 PASS).
  4. Parse manifest → compatibility (appCompatibility.minAppVersion/maxAppVersion ∧ schemaVersion ∈ shell.supportedRange — SPIKE-04:22, ARCHITECTURE-DESIGN.md:688) and budgets (per-file ≤6 MB, totals, image caps) — before downloading files. Fail → user-visible "please update app" if app too old (ARCHITECTURE-DESIGN.md:668), else quarantine.
  5. Stage files into inactive slot; per-file SHA-256 + size check on each (SPIKE-02:29 step 5).
  6. Schema validation of staged sections (strict on required, allow unknown forward-compat fields, SPIKE-04:189 gate 1).
  7. Full verification of all staged files → only then eligible to activate.

No step may be skipped, reordered, or weakened without ADR amendment. Every rejection is quarantined so a known-bad version is not re-fetched until latest.json advances (ARCHITECTURE-DESIGN.md:675).

12. Dataset activation rules (normative, ADR-006 + SPIKE-02 F-2/F-3)

  • Activation is exactly one transaction on lumen-system (P2) that atomically flips activeSlot + activePackageVersion + verification record (F-2 SPIKE-02:89) and sets readbackPending. No cross-database transaction is attempted — single-DB atomicity suffices because inactive slot is already complete before flip (SPIKE-02:73).
  • On txn failure: retry once → keep old → diag (ARCHITECTURE-DESIGN.md:672).
  • After commit: readback spot-check synchronously if possible, otherwise left pending and checked at next boot (SPIKE-02:97 F-3). Spot-check reads the slot the pointer now points at.

A partially staged, un-verified, quarantined, or incompatible dataset can never be activated (invariants §3 #4–#5).

13. Dataset rollback rules (normative, ARCHITECTURE-DESIGN.md:677 §18.3)

  • Automatic: post-activation readback fails, or next-boot light verify of active slot fails while the other slot is still complete → flip back to previous slot (SPIKE-02:50 S14, X1). If both slots lack a complete dataset → RECOVERY + baseline (SPIKE-02:69 X2) — favorites in lumen-user remain.
  • User-initiated: Status screen "Restore previous version" while previous slot exists (until next staging or GC).
  • Organizer-initiated (canonical): republish old content at a new higher packageVersion (runbook, ARCHITECTURE-DESIGN.md:682) — clients accept it as a normal monotonic update. Local rollback is depth-1 only (F-4); server-side re-publish is the general rollback.

GC: previous slot retained until next staging needs it or N≥3 successful boots AND storage pressure (ARCHITECTURE-DESIGN.md:685). User data never GC'd.

14. Emergency baseline rules (normative, ADR-007 + SPIKE-06)

Three tiers — no tier may be omitted or merged silently:

Tier Content Mutability Delivery Survives
T1 Floor Emergency number + dial, address/coordinates, security contact, first-aid/AED summaries, muster/exits summaries, core procedures (medical/weather/fire/lost) — ≤16 KB, life-safety minimum only Immutable per shell build, compiled from the same emergency source sheet that produces the dataset section (no drift, ARCHITECTURE-DESIGN.md:345) Shell bytes Everything — eviction, failed update, incompatible dataset, BASELINE_ONLY
T2 Dataset section Full detail (all emergency POIs with map links, full procedure text, per-role contacts, contentVersion/updatedAt, emergencySchemaVersion) Updateable via signed dataset publish Package emergency.json Last-good dataset
T3 Notices (reserved) {id,severity,title,body,publishedAtUtc,expiresAtUtc,signature} — display-only, additive Ephemeral, signed, expiring Package or transport seam — deferred in V1 Only if delivered

Resolution SPIKE-06:60: prefer highest verified compatible tier with defensive merge (floor-only fields rendered even when section omits them); provenance always labeled (BASELINE v<floor> vs FESTIVAL DATA v<package> · <generatedAt>). Floor renderer is forward-tolerant (ignores unknown fields, never throws) and reachable with zero IDB access (F-1 SPIKE-06:75) — this is what makes invariants §41 #1–#2 hold.

Hard limits: 16 KB cap CI-enforced; floor version stamp validated (ARCHITECTURE-DESIGN.md:367). No localStorage copy — dies with eviction (SPIKE-06:102).

15. Festival Data Package rules (normative, ADR-005 + SPIKE-04)

An immutable versioned directory on the CDN:

Package /editions/<edition>/packages/<packageVersion>/
  manifest.json           → inventory, hashes, sizes, compatibility, festival metadata
  signature.json          → Ed25519 over sha256(exact manifest bytes) + publicKeyFingerprint
  emergency.json          → emergency/1  (+ independent emergencySchemaVersion/contentVersion)
  schedule.json           → schedule/1  (stages, artists, events with stable id, dayKey)
  map.json                → map/1  (levels + POIs normalized x/y + categories)
  info.json               → info/1  (ordered blocks of structured nodes)
  assets.json             → inventory {id,file,sha256,bytes,kind,role}
  assets/...              → WebP/PNG binaries, each listed in assets.json
/editions/<edition>/latest.json  → mutable pointer {edition,packageVersion,manifestUrl,generatedAt}

Normative schema fragments: SPIKE-04:79 (manifest, signature, emergency/schedule/map/assets). Rules:

  • Format lumen.package/1 (SPIKE-04:79).
  • packageVersion strictly monotonic per edition; never accept ≤ active from network (ARCHITECTURE-DESIGN.md:349). Organizer rollback = new higher version.
  • schemaVersion envelope with ordering rule ARCHITECTURE-DESIGN.md:690 C-23: schemas ship in shell range {1,2} first, datasets follow, freeze 7 days before festival.
  • sections[*].required bool (default true) gates readiness (SPIKE-04:64, SPIKE-05:47 F-2).
  • No expiration for datasets/sections (SPIKE-04:50 F-2) — only future announcements/notices may carry expiresAtUtc and expiry hides the notice, never critical data (time-independence, SPIKE-05:47).
  • No binary sections (F-5 SPIKE-04:203); per-file ≤6 MB; total ≤40 MB target / 50 MB ceiling ARCHITECTURE-DESIGN.md:363 enforced by pipeline.
  • Pipeline gates (publisher side, all blocking): 1) schema + unknown-fields forward-compat, 2) stable IDs (removals require status: cancelled), 3) time sanity (dayKey matches festival-zone date, no zero-length, within window ±1d), 4) budgets/dimensions, 5) hash→sign→upload→flip→smoke-fetch (SPIKE-04:189).

Identity vs location: edition + packageVersion and sha256(manifest) are identity; URL is location (SPIKE-04:15).

16. Service worker responsibilities (normative, ARCHITECTURE-DESIGN.md:241)

Scope deliberately tiny (ARCHITECTURE-DESIGN.md:243):

  • install: precache versioned shell list (generated at build, hashed index.html + JS/CSS + icons).
  • activate: delete previous shell caches.
  • fetch: navigation → cache-first against shell cache, fallback to network to re-fill; if both fail, serve built-in static fallback page (emergency text, Ring-0). Hashed /assets/<hash>.* → cache-first. Package endpoints (/latest.json, /editions/**) are NOT intercepted — pass through to network so app-layer sync controls staging (ARCHITECTURE-DESIGN.md:243 C-21).
  • message: minimal SKIP_WAITING handling only.

Shell update policy ARCHITECTURE-DESIGN.md:255: new SW installs under new cache name; serves old session; activation on next full app start (no mid-session clients.claim), except explicit user "Restart to update" on Status screen. Then §18.5 compatibility check (ARCHITECTURE-DESIGN.md:688) runs before data screens.

Every SW operation is idempotent/re-runnable; page never depends on SW in-memory state (ARCHITECTURE-DESIGN.md:261).

17. Service worker non-responsibilities (normative)

The SW must not do any of: dataset download/staging, background sync, retry logic, announcements, anything stateful beyond caches (ARCHITECTURE-DESIGN.md:253), any IDB access, any verification. Dataset data must never live in SW state — P8 SPIKE-01:140 + C-21 ARCHITECTURE-DESIGN.md:83. A coding agent that moves staging into the SW violates invariants §41 #3–#4.

18. Cache Storage responsibilities

  • Owns shell only: lumen-shell-v<build> versioned per build, enumerated in readiness predicate C1 (SPIKE-05:28).
  • Immutable hashed assets cached with long TTL; index.html with no-cache (revalidate) per deploy layout ARCHITECTURE-DESIGN.md:807.
  • Old caches purged on SW activate — no manual GC.

19. IndexedDB responsibilities

  • Owns datasets + user state as in §9; plus lumen-system.meta as the atomic activation/verification record.
  • Enforces P1–P5: single-file atomic, single-txn flip + verification, 6 MB cap, pre-stage free-space check, wrapped Quota handling.
  • Boot light verify C1…C5 is the detection mechanism for eviction/corruption; heavy hashing lives only in full verification (after staging, user-initiated "Check my data", after IDB error) per SPIKE-05:82 cadence.

20. Offline Ready state machine (normative, SPIKE-05 + §12)

20.1 Predicate (time-independent — SPIKE-05:47 F-1)

OFFLINE_READY ⇔
  C1 shell_valid             every precache entry present in current shell cache
∧ C2 dataset_present         active slot exists for active edition
∧ C3 authenticity            manifest signature verified against embedded key set; recorded verification matches active packageVersion
∧ C4 integrity               manifest-listed files present, sizes match; hashes verified at staging (recorded), spot-check at boot
∧ C5 schema_compatible       package schemaVersion ∈ shell.supportedRange ∧ appCompatibility satisfied
∧ C6 required_sections       sections with required=true present+parseable {emergency,schedule,map,info,assets.json}
∧ C7 required_assets         assets referenced by required sections present (size at boot, hash at staging)
∧ C8 emergency_floor         embedded baseline present (asserted; defense against broken build dropping floor)

Rules: predicate never consults clocks (SPIKE-08 parity); optional sections (required=false) never gate READY (SPIKE-05:47 F-2); evidence-based via stored verification record (SPIKE-02:89 F-2) — missing/mismatched record → no READY until full re-verify; all-or-nothing for READY.

20.2 States (six-state taxonomy, SPIKE-05:60)

State Definition Chip Action offered
READY C1–C8 all true Green "OFFLINE READY ✓" None (status detail on tap)
PARTIAL(list) Shell valid; required sections/assets missing (none corrupt) — e.g., prep interrupted Amber "Continue setup" (resumable staging)
NOT_READY Shell valid; no staged/active dataset (fresh install) Neutral "Get festival data" Preparation flow
RECOVERY(reason=missing/corrupt/incompatible) Previously verified now fails (eviction, corruption, app/dataset skew) Red "Restore festival data" (re-prep; needs online) — floor works meanwhile
BASELINE_ONLY Storage entirely unavailable (private mode, blocked "website data", quota 0) Distinct notice Install/normal-mode/free-space guidance
FAILED(op) Activation txn repeatedly failed or IDB error — distinct from RECOVERY (data gone vs error) Error screen Retry; re-prep; diagnostics share

Edge cases adjudicated SPIKE-05:90: eviction→RECOVERY, file-gone→RECOVERY, record-missing→PARTIAL→full re-verify, incompatible→RECOVERY(incompatible), wrong clock→READY unaffected (warning separate), optional missing→READY, private→BASELINE_ONLY, activation fail twice→FAILED.

20.3 Cadence and budgets

When Check Cost target
Every boot Light check C1+C2+C4-light+C5 (no hashing) ≤~150 ms SPIKE-05:82
After staging Full C3–C7 (all hashes + schema) Seconds; once per update
Activation Recorded with flip; readback spot-check (pending flag) ms
User "Check my data" Full re-verification with progress Seconds
After any IDB error Full re-verify active slot; quarantine on failure Seconds

20.4 Preparation mapping (SPIKE-07)

L0 (shell+floor) ↔ NOT_READY/BASELINE_ONLY; L1 (L0 + emergency + schedule) ↔ PARTIAL ("core ready"); L2 (L1 + map+info+assets) ↔ READY (SPIKE-07:31, SPIKE-05:108 cross-check). Download order emergency→schedule→info→map-base→assets maximises achievable level when interrupted (SPIKE-07:39).

20.5 UX contract

Status chip visible on every screen; Status screen shows per-section checklist, dataset version, generatedAt/fetchedAt, usage, plus "Check my data" / "Get festival data" / "Restore previous version" (ARCHITECTURE-DESIGN.md:424). App never shows READY unless predicate holds.

21. Schedule / time rules (normative, ADR-009 + SPIKE-08)

  • Storage: startUtc/endUtc UTC epoch ms on every event (DISCOVERY.md:297 A-10, ADR-009). Includes dayKey precomputed at publish as festival-zone calendar date (SPIKE-08:43 T2).

  • Zone: manifest carries festival.timezone (IANA) + festival.startUtc/endUtc (SPIKE-04:79).

  • Rendering: Intl.DateTimeFormat with timeZone = festival zone by default; toggle to device zone. Only explicit-zone Intl is allowed (clarification F-4 SPIKE-08:148); never Date.toLocaleString() without options, manual offsets, or wall-clock strings. dayKey groups days; no client day math.

  • Queries (§14.2 ARCHITECTURE-DESIGN.md:508):

    • Now: startUtc ≤ now < endUtc; Up Next: startUtc > now (next-N sorted). Overlaps shown as multiple now (SPIKE-08:43 T7).
    • Favorites join via stable event id (contractual — §24); status: scheduled|moved|cancelled rendering.
    • Filters/search: naive scan over ≤~1k events; no index lib unless proven slow (DISCOVERY.md:391 TQ-10 decision documented, ARCHITECTURE-DESIGN.md:508).
    • Change markers when status=moved|cancelled or manifest change notes.
  • ClockService (ARCHITECTURE-DESIGN.md:523, SPIKE-08:26):

    now() = deviceClock + skew  if persisted offset exists else deviceClock
    skew = serverNow − deviceNow  captured from any sync HTTP Date header, persisted {skew,capturedAtDevice,capturedAtMono,source}
    drift = |observedDevice − (base + monotonicElapsed)| > 2 min → warn + re-derive base
    sanity = now within festival window ±45d → else warn, but ONLY if now ≥ festivalStart−45d or skew was captured (F-3 suppression SPIKE-08:97) → READY unaffected
    

    No NTP endpoint, no timers, no background work (C-19); computed on demand (SPIKE-08:43 T5/T6). HTTP Date is NTP-synced CDN source, INFERRED accurate (SPIKE-08:84 B-10).

  • DST/unusual zones: Rendering inherits IANA database; epoch arithmetic correct across spring-forward/fall-back (SPIKE-08:43 T3/T4 validated 24/24 on V8/ICU; JSC parity UNVERIFIED queued for device T15 ARCHITECTURE-VALIDATION.md:301).

22. Map architecture (normative, ADR-008 + SPIKE-03 + §15)

Decision: Raster WebP base (≤2 levels) + data-driven DOM POI overlay + CSS-transform pan/zoom + first-class Facilities list (ARCHITECTURE-DESIGN.md:543). No tiles, no SDK, no GPS in V1.

  • Levels: overview longest edge ≤1600 px (~0.3–0.7 MB encoded, ~7.3 MB decoded), optional detail ≤3072 px (~1.5–3.5 MB encoded, ~27 MB decoded), total decoded ≤~35 MB, at most one detail resident (overview swapped out/downscaled when detail shown) — F-1 SPIKE-03:58 replaces prior 4096 cap (ARCHITECTURE-DESIGN.md:571). Per-file ≤6 MB cap applies.
  • Levels in map.json: SPIKE-04:157 — levels: {overview assetId width height, detail assetId width height nightAssetId?} with nightAssetId optional hook for F-4 dark mode. Alternatives SVG/Canvas/tile-pyramid rejected per matrix SPIKE-03:11.
  • Coordinate space: POIs normalized x:0..1, y:0..1 relative to base image (ARCHITECTURE-DESIGN.md:561, D-02). Authorship via tap-tool or measured coords; optional lat/lng hook preserved but unused in V1 (invariant 9).
  • Pan/zoom: pointer-events + pinch → single container transform: translate() scale(); markers counter-scaled constant on-screen; bounds clamped (ARCHITECTURE-DESIGN.md:561).
  • Interactivity/accessibility: POIs are real buttons with labels and 48 px targets; Facilities list is first-class, not fallback, and is the screen-reader path (SPIKE-03:79). Search shares schedule component (highlight + list). Category chip filtering via class toggles, no re-layout of base.
  • Dark mode (F-4 SPIKE-03:88): default filter: brightness(.72) saturate(.85) on base in dark, markers full-brightness (GPU-composited); optional map-base/overview-night asset — no white flash, no re-download.
  • Object URLs (F-3 SPIKE-03:68): lazy-create per visible level; revoke on switch — leak freedom is part of acceptance.
  • Flip conditions: vector art supplied → reconsider S1/S5; art >3072 px legible → 2×2 tile-split (DD-9, SPIKE-03:98); fps jank low-end → overview-only fallback.
  • Failure behavior: map section missing → list view if POIs present, else "Map not downloaded" card (ARCHITECTURE-DESIGN.md:568).

23. Festival information architecture

  • info.json ordered blocks {id,title,kind,body: structured nodes} where nodes ∈ {paragraphs,lists,links,emphasis,contact,address,hours} (ARCHITECTURE-DESIGN.md:572). No raw HTML (C-22), no innerHTML of raw strings — renderer maps node types → safe DOM (ARCHITECTURE-DESIGN.md:573, ADR-013 TH-4).
  • Categories data-driven, reorderable: About, Rules, FAQ, What to bring/Not, Parking, Camping, Transport, Accessibility, Food/Drink, Merch, Activities, Contacts, Hours, Venue (ARCHITECTURE-DESIGN.md:573).
  • Search indexes titles + text via shared search component.
  • Signed as required section; readiness gates on it (SPIKE-04:30); BASELINE_ONLY excludes it — floor is what must survive, per ARCHITECTURE-DESIGN.md:573 static-important note.

24. User preferences / favorites architecture (normative, DISCOVERY.md:545 §15.8, SPIKE-04:154)

  • Store: lumen-user.favorites keyed by stable event id (SPIKE-04:154 — contractual across versions). Never part of package; never overwritten by updates; survives rollback and GC (B-6, SPIKE-02:69 X3 PASS).
  • Event model SPIKE-04:140: {id stable, title, stageId, artistIds[], startUtc, endUtc, dayKey, tags[], status: scheduled|moved|cancelled, originalStartUtc?} — publisher must not churn ids; pipeline mints via deterministic key if source lacks ids (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:74 D-01, AQ-20). Removals require cancelled rather than deletion (SPIKE-04:189 gate 2).
  • On dataset update: event moved → keep fav with new time; cancelled → mark; removed → orphan policy (show as "no longer scheduled" but keep fav entry, do not crash).
  • Favorites: write-through persistence, no memory-only truth (DISCOVERY.md:456 OF-11). Includes overlap conflict surfacing (ARCHITECTURE-DESIGN.md:508). Duplicated export later (DD-4).
  • prefs (in lumen-user) includes theme/clock displayMode toggles; clock skew cache reference per §21. Small flags in localStorage only for non-load-bearing coach dismissed etc., guarded try/catch (ADR-004).

25. Networking abstraction (normative, ADR-011 + §17.2)

Application (features)  ──→ reads DatasetStore; sees readiness — nothing else
Data/Domain              ──→ DatasetStore, Verifier
Sync layer               ──→ SyncService: orchestrate(check→stage→verify→activate)
                            Transport interface isAvailable() / fetchPointer(edition) / fetchBytes(path)  byte-oriented, tiny
Transport impls          ──→ HttpTransport (V1, the only one) / future transports implement same iface
Verifier                 ──→ hashes + Ed25519 (sole decider)
Service worker           ──→ shell cache + navigation fallback — exclusive

Rules: features never import sync/transport (B-3); transports move bytes, Verifier decides truth identically for every transport (signed-payload rule, ADR-012); seam is one interface + one impl — no registry/plugin/negotiation (ARCHITECTURE-DESIGN.md:628). SyncService is transport-parameterized for fault-injection testing (ARCHITECTURE-DESIGN.md:628).

26. V1 networking limitations (normative, C-19 + ADR-010)

  • Triggers: on app open, on online event (opportunistic hint), and manual "Update now" (ARCHITECTURE-DESIGN.md:584 §17.1). Nothing runs while app hidden; no timers, no periodic sync.
  • Model: pull-only. Only operations: fetch latest.json → monotonic comparison → fetch candidate manifest → verify → stage. No uploads, no user-state sync, no telemetry, no push registration, no Background Sync registration (ARCHITECTURE-DESIGN.md:637 §17.4).
  • Announcements schema reserved but not implemented unless demanded during build (DD-1, ARCHITECTURE-DESIGN.md:631 §17.3). Emergency tier-T3 notices are reserved with that shape.
  • Rejected: periodic background sync (unsupported on iOS PW-4), push-based updates (iOS fragility), WebSocket/long-poll (no V1 need) (ARCHITECTURE-DECISIONS.md:406).
  • Latency = time-until-next-open-online is the accepted trade-off, documented and communicated via generatedAt/fetchedAt and changed-event markers.

27. Future mesh boundary (normative, ADR-012 + DISCOVERY §17.1/§20)

  • V1 does three things for future mesh and then stops: 1) Transport seam §25, 2) signed-payload rule — bytes from any transport are inert until Verifier passes them, so untrusted peers can never inject content (ME-4 DISCOVERY.md:596, ARCHITECTURE-DESIGN.md:718 §20.3), 3) package/announcement formats immutable, content-addressed, version-monotonic (ARCHITECTURE-DESIGN.md:717 §20.1–2). Cost in V1 is ~one small module (ARCHITECTURE-DECISIONS.md:482).
  • Feasibility posture: meaningful mesh will require something outside the browser (native companion, hardware relay, organizer LAN) because mobile browsers expose no BLE peripheral, ad-hoc Wi-Fi, background sockets, and iOS lacks Web Bluetooth (DISCOVERY.md:581, ARCHITECTURE-DESIGN.md:717). A future transport attaches as e.g. MeshTransport bridging to a companion over a browser-accessible channel, or a LAN mirror acting as a package source. If even that proves impossible, V1 loses nothing — HTTP is complete product functionality (ARCHITECTURE-VALIDATION.md:475 §6 scenario 20).
  • Deferred to future work (not V1): peer discovery, routing, store-and-forward, dedupe/replay beyond monotonic, peer auth, message-size adaptation, emergency broadcast semantics, conflict handling beyond version monotonicity (ARCHITECTURE-DESIGN.md:718 §20.4).
  • Anti-leak: no feature/UI code may reference transport identity, connectivity modality, or peer concepts; Status UI shows "updated ", never "via what" (ARCHITECTURE-DESIGN.md:718 §20.5).

28. Security requirements (normative, ADR-013 + TH-1…TH-11 ARCHITECTURE-DESIGN.md:728)

Threat Defense (must implement)
TH-1 tampered/malicious package (MITM, compromised hosting, DISCOVERY.md:616 SE-1) Ed25519-signed manifest (SHA-256 of exact manifest bytes) + per-file SHA-256+size (ARCHITECTURE-DESIGN.md:358), plus HTTPS+HSTS (ARCHITECTURE-DESIGN.md:743), single origin C-20; activation impossible without valid signature (ordering §11)
TH-2 publish path compromise Signing key offline; singular publisher + deputy (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:60 O-01); repo audit trail; smoke re-verify; rollback via new version
TH-3 malicious/stale emergency Emergency section signed; baseline immutable per build + same-source; provenance labels on every screen (SPIKE-06:60)
TH-4 XSS/content injection C-22 structured-data rendering only; CSP default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' blob: data:; connect-src 'self' (ARCHITECTURE-DESIGN.md:743); no inline script, no eval, no innerHTML of raw
TH-5 replay/downgrade Monotonic packageVersion acceptance; never accept ≤ active from network (quarantine)
TH-6 DoS oversized Budgets in manifest validated before download + per-file 6 MB caps + quota pre-check (ARCHITECTURE-DESIGN.md:358, SPIKE-01:140 P4)
TH-7/TH-8 privacy/storage No accounts, no telemetry, no location capture; no secrets stored; dataset public; favorites innocuous (data minimization ARCHITECTURE-DECISIONS.md:524)
TH-9 SW/cache poisoning Single origin, same-origin caches, no CORS relied upon
TH-10 mesh injection Signed-payload rule §27 — mesh auth deferred but rule enforced from V1
TH-11 supply chain Minimal deps (target zero beyond audited verifier), lockfiles, CI, pin

Platform hardening ARCHITECTURE-DESIGN.md:743: HTTPS+HSTS, no mixed content, no third-party scripts/fonts/analytics, tel: data-driven + explicit tap only (A-14), key set (fingerprinted) for rotation, lost-key runbook (ship new set via shell).

Device matrix must verify tel: in standalone (T19, SPIKE-01:171) and CSP allows blob object URLs for map assets (ARCHITECTURE-DESIGN.md:743 img-src blob: — must include).

29. Privacy requirements (normative, ADR-013 + NR-15)

  • No accounts, no auth, no session/token (NR-4).
  • No telemetry/ analytics collection by default (NR-15, ARCHITECTURE-DECISIONS.md:524 4): diagnostics are on-device, scrubbed, and user-initiated — manual "share diagnostics" only, no automatic upload.
  • No location capture in V1 (ARCHITECTURE-DESIGN.md:222 zero-permission).
  • No PII in content or favorites (favorites = public event id list, ASSUMPTIONS-AND-OPEN-QUESTIONS.md:87 S-03).
  • Data minimization as a rule (ARCHITECTURE-DECISIONS.md:524).

30. Error handling requirements

  • Every failure maps to a defined renderable state — no-blank-screen rule ARCHITECTURE-DESIGN.md:775 §22. Contract in §20.2 + §31.
  • IDB errors (QuotaExceededError, txn abort) are caught, wrapped, and keep active dataset — never crash (SPIKE-01:140 P3, ARCHITECTURE-DESIGN.md:672 retry once).
  • Network errors (fetch pointer/manifest timeout) are silent no-ops; retry next trigger (ARCHITECTURE-DESIGN.md:664).
  • Hash/signature/schema mismatches → quarantine + diag, never activate (ARCHITECTURE-DESIGN.md:667).
  • localStorage guarded try/catch (non-load-bearing) per ADR-004.
  • Renderer forward-tolerant for floor path; dataset content unknowns tolerated (SPIKE-06:75).

31. Recovery requirements (normative, ARCHITECTURE-DESIGN.md:755 §22 + SPIKE-05/02)

Behavior matrix is the contract (ARCHITECTURE-DESIGN.md:755 FA-1…FA-16). Coding agent must implement verbatim:

FA-1 offline startup / FA-2 browser restart / FA-3 phone restart → boot from caches+IDB, light verify, budgets §33. FA-4 SW killed → page unaffected (C-21). FA-5 evicted → RECOVERY + floor + one-tap re-prep (needs online to re-prep). FA-6 corrupt → quarantine/rollback §13 + baseline. FA-7 unavailable → BASELINE_ONLY. FA-8 interrupted → resume §10. FA-9 invalid package published → reject+quarantine, client-side no online needed to reject. FA-10 wrong clock → corrected or warned per §21 (F-3). FA-11/12 GPS/low battery → no degradation. FA-13 schedule changed → update on next online, last-good meanwhile. FA-14 emergency unreachable → T1/T2 remain + physical channels (A-16). FA-15 browser update → baseline targets conservative. FA-16 shell mid-festival → next-start activation + §18.5 compatibility check.

Every row ends renderable; Ring-0 static fallback.html covers shell-cache-missing case (ARCHITECTURE-DESIGN.md:775).

32. Accessibility requirements (normative, WCAG 2.1 AA per DISCOVERY.md:334 AMB-10, ARCHITECTURE-DESIGN.md:783)

  • Landmarks/heading order per view; visible focus.
  • POIs are real buttons/links (not canvas hit-testing); Facilities list is the primary screen-reader path (SPIKE-03:79).
  • Emergency: giant targets, max contrast both themes, zero clutter, one-handed reachable (ARCHITECTURE-DESIGN.md:783 + DISCOVERY.md:470 EM-5); tel: with selectable-text copy fallback.
  • Touch targets ≥48 CSS px (ARCHITECTURE-DESIGN.md:783).
  • prefers-reduced-motion honoured (no vestigial autoplay).
  • High-contrast light + true dark themes; map handles F-4 dark filter without breaking markers (SPIKE-03:88).
  • In-app back affordances (standalone safe), history-API back (ARCHITECTURE-DESIGN.md:783).
  • Screen-reader pass required on device T13 ARCHITECTURE-VALIDATION.md:283 using VoiceOver/TalkBack.

Do not weaken this for convenience — §40 fails if a11y is degraded to save code.

33. Performance budgets (hard ceilings, CI-enforced — ARCHITECTURE-DESIGN.md:848 §27 + ARCHITECTURE-DESIGN.md:363 §10.6)

Part Target / Ceiling Enforcement
Shell JS (gz) ≤150 KB Bundler gate (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:32 AQ-10)
Shell total (JS+CSS+icons, gz) ≤1 MB Same
Emergency floor ≤16 KB Build asserted (ARCHITECTURE-DESIGN.md:367)
Sections JSON total (emergency+schedule+map.json+info+assets.json) ≤3 MB Pipeline gate SPIKE-04:189 + client budget check
Map base images (all levels) ≤28 MB (overview ≤1600 px / detail ≤3072 px, total decoded ≤~35 MB) Pipeline + client §22
Other assets (icons, etc.) ≤6 MB Same
Total dataset ≤40 MB target / 50 MB hard ceiling (DISCOVERY.md:297 A-02) Pipeline reject above 40 MB; client refuses above 50 MB
Single file ≤6 MB Hash/digest memory bounded SPIKE-01:140 P5
Cold start → Emergency usable (D3 cached, mid-range 2021 Android) ≤2 s Device matrix T06
Cold start → interactive ≤3 s T06
Schedule list (1k events) ≤100 ms interaction; window rendering if needed Unit/perf
Map pan/zoom ≥30 fps T13
Boot light verify ≤150 ms T03

Tactics: vanilla JS parse trivial, WebP, single JSON parse per section per session, no idle timers/observers, zero background work (ARCHITECTURE-DESIGN.md:858).

Silently increasing any budget violates §40.

34. Browser support (normative, ARCHITECTURE-DESIGN.md:819 §25)

Dimension Baseline (must support) Best-effort Explicitly unsupported
iOS Safari 16.4+ installed PWA 16.0–16.3 tab (eviction-prone, coach pushes install) <16
Android Chrome ~110+ (including 2021 mid-range) Older with SW+IDB Without SW
Other (Samsung Internet, Firefox Android) Where SW+IDB exist Same code paths —
Desktop Works via same code Not a target IE, legacy
JS disabled / storage blocked Static fallback.html with emergency text (Ring-0) — Full app

Capability detection (not UA sniffing): 'serviceWorker' in navigator, indexedDB, caches, Intl.DateTimeFormat, navigator.storage.estimate. Missing → degrade per §20/§31 (never crash) ARCHITECTURE-DESIGN.md:829.

35. Testing requirements (normative, ARCHITECTURE-DESIGN.md:832 §26)

Layer What How
Pipeline / schema / contract Package schema, manifest, budgets, stable IDs, dayKey/time sanity (≤40 MB, ≤6 MB/file, dimension caps, no-zero-length, dayKey matches zone), forward-compat unknown fields Pipeline validation + golden-package fixture tests
Unit ClockService (skew/drift/sanity + F-3 suppression), Verifier (bad sig, bad hash, replay/lower version, quarantine), readiness predicate C1–C8 (SPIKE-05:28), A/B state transitions (14+3 scenarios), escaping renderer, forward-tolerant floor Standard unit suite
Integration (sync) Full update lifecycle including all 9 failure stages ARCHITECTURE-DESIGN.md:664 §18.2 — mocked transport + fault injection (kill mid-stage, corrupt bytes, quota errors); readbackPending + rollback; favorites survive (X3) Scripted harness modeled on exp2-ab-update-sim.mjs:1
PWA audits Manifest/SW/offline load Lighthouse PWA + scripted offline reload
Content Emergency provenance stamps, dataset age/staleness, wrong-clock warnings (absolute times always shown) Scenario tests
Accessibility Emergency/Schedule/ Facilities list screen-reader flows + contrast both themes + 48 px targets Automated + manual (VoiceOver/TalkBack) — must run on devices T13

Rule: any bug found on a real device that harness missed becomes a harness case (ARCHITECTURE-DESIGN.md:843). Every readiness state §20.2 must be reachable in tests (ARCHITECTURE-DESIGN.md:930 rule 5).

36. Physical-device validation requirements (blocking for production)

Required to declare storage/map/time/SW production-ready. Defined in ARCHITECTURE-VALIDATION.md:301 §4 (19 groups × 4 devices, 76 device-tests) and queued throughout spikes as UNVERIFIED.

Device families: D1 iPhone 16.4 low-bound (tab + installed), D2 iPhone latest installed, D3 low-end Android 2021 mid-range Chrome ~110+ (primary perf), D4 modern Android Chrome latest (ARCHITECTURE-VALIDATION.md:158).

Minimum required before a real festival: T01 first visit → T02 install → T03 offline launch → T04 airplane simulation → T05 browser restart → T06 phone restart → T07 persist()/exemption → T08 storage pressure → T09 happy update → T10 interrupted updates (kill at S01–S14 points SPIKE-02:50) → T11 recovery (eviction/corrupt/private/incompatible) → T12 map rendering (1600/3072/35 MB) → T13 map interaction (≥30 fps, leaks, a11y) → T14 GPS absence → T15 incorrect clock (5 steps per SPIKE-08:152, including F-3 near-festival gating) → T16 SW lifecycle (C-21) → T17 shell update (next-start + §18.5) → T18 dataset compatibility → T19 emergency floor in every failure state.

Severity per ARCHITECTURE-VALIDATION.md:171: T01,T03–T06,T08–T13,T16–T19 BLOCKING; T02 BLOCKING on iOS; T07/T14/T15 graded HIGH/MEDIUM as marked. Device matrix is BLOCKING for production but does not block starting implementation on fixtures (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:138, ARCHITECTURE-VALIDATION.md:493).

37. Deployment assumptions (normative, ADR-014 + DISCOVERY AMB-4)

  • Static HTTPS CDN, single origin (DISCOVERY.md:261 C-20, ARCHITECTURE-DESIGN.md:88) with layout / (no-cache) + /assets/<hash>.* (immutable 1y) + /latest.json (max-age 60) + /editions/<ed>/packages/<v>/** (immutable 1y) ARCHITECTURE-DESIGN.md:807.
  • Two environments: staging origin + production origin, same code, different origins (storage isolation automatic) — ARCHITECTURE-DESIGN.md:817.
  • No dynamic backend, DB, or auth endpoint in V1; publisher auth at pipeline/CDN credential layer (ARCHITECTURE-DESIGN.md:815).
  • Origin is hard to reverse post-users (origin-keyed storage strands data) — choose production origin deliberately before any public staging link (AQ-18 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:116); provisional staging origin is fine for development.
  • Provider choice (AQ-14 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:112) and budget (OQ-8) are provisional — pick before public staging share, keep stable.
  • Publisher runbook: publish → smoke re-verify → monitor latest.json; rollback runbook: republish old content at new higher packageVersion; emergency runbook gated by sign-off (ARCHITECTURE-DESIGN.md:697).

38. Content / data assumptions (normative, ADR-005/014 + ASSUMPTIONS §5)

  • Single annual edition (A-01 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:15), keyed edition for cheap multi-event later; budgets §33.
  • Organizers can supply schedule/map art/POIs/emergency/info digitally (DISCOVERY.md:297 A-04, ASSUMPTIONS-AND-OPEN-QUESTIONS.md:73 A-04) via intake templates (CSV/JSON + raster art) or existing CMS adapter must stay import-friendly (AMB-2 DISCOVERY.md:324).
  • Event/POI IDs stable (SPIKE-04:189 gate 2, ASSUMPTIONS-AND-OPEN-QUESTIONS.md:74 D-01) — pipeline mints deterministically if source lacks ids and persists mapping (AQ-20); favorites depend on it.
  • Map art: raster with known dimensions, POIs locatable normalized x/y (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:75 D-02); if only vector/GIS arrives, representation flip SPIKE-03:98 DD-9.
  • Emergency contacts/coordinates valid through event, provenance contentVersion/updatedAt + emergencySchemaVersion (SPIKE-04:70, SPIKE-06:110); sign-off gate O-02 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:62.
  • Single festivals IANA zone (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:78 D-05); multi-zone would amend ADR-009.
  • Volumes ≤1k events / ≤200 POIs / ≤30 info blocks within budgets (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:77 D-04).
  • Offline LAN/mesh update delivery not in V1 (A-09).
  • Provisional until real content arrives: map art quality, exact schedule sample; pipeline gates prove but cannot pass without samples (ARCHITECTURE-VALIDATION.md:501).

39. Feature acceptance criteria (normative per DISCOVERY requirements)

Area Requirement Must hold
Platform R-P1…P4 DISCOVERY.md:132 Mobile-first PWA, iOS Safari + Android Chrome; HTTPS; no native; desktop only incidental.
Navigation R-N1…N4 DISCOVERY.md:143 4 destinations; nav obvious; Emergency one tap from anywhere (persistent); one-handed 48 px, high-contrast day/night.
Emergency R-E1…E5 DISCOVERY.md:152 All tiers; tel: from standalone (SPIKE-01:171); dial requires explicit tap; address/coordinates/GPS multiple formats; procedures/muster/exits/AEDs; provenance stamps.
Schedule R-S1…S5 DISCOVERY.md:162 Full schedule offline ≤~1k; Now/Next via ClockService; favorites local stable ids; filters/search offline; R-S5 time model §21 + device-clock handling + dayKey groups.
Map R-M1…M5 DISCOVERY.md:172 Offline raster+POI+list §22; organizer raster+POIs supplied; geolocation never required (M4 narrowed per ARCHITECTURE-DESIGN.md:18); representation validated 1600/3072.
Festival R-F1…F2 DISCOVERY.md:182 Info blocks offline, data-driven, not hardcoded.
Offline R-O1…O9 DISCOVERY.md:189 Shell without network; SW caches shell; local data truth; startup never needs network; atomic updates; OFFLINE READY evidenced §20; survive restarts/pressure/eviction; storage budgets §33; versioning/migrations.
Package R-D1…D3 DISCOVERY.md:203 Versioned signed package §15; manifest inventory; source/pipeline/delivery/version/tracking/atomic/rollback defined §§10–13.
Extensions R-X1…X3 DISCOVERY.md:211 Online enhancements never gate V1; transport seam (§25) behind SyncService; no mesh in V1.
Security R-G1…G3 DISCOVERY.md:219 TH-1…TH-11 §28 defended; package-signed; no secrets client-side (C-24).

Failure matrix ARCHITECTURE-DESIGN.md:755 FA-1…FA-16 must be satisfied for each feature; §31 is the contract.

40. Definition of done (checklist before merging or declaring READY)

  • No rule in §6 B-1…B-7 violated (verified by module-import lint + review).
  • No direct innerHTML of festival content; escaping renderer + CSP self-only; no inline/eval (ARCHITECTURE-DESIGN.md:743).
  • Emergency floor compiled from same sheet as dataset section; ≤16 KB; forward-tolerant; zero-IDB path renders in every failure state; provenance visible (SPIKE-06:60, ARCHITECTURE-DESIGN.md:367).
  • IDB protocol P1–P8 SPIKE-01:140 implemented exactly; no txn spans non-IDB await; QuotaExceededError path keeps active; pre-check 2×.
  • A/B ordering §§10–13 holds; per-file atomic; verification record + readbackPending + rollback depth 1; page-context downloads only (C-21).
  • Validation ordering §11: signature before hash before schema before activate; budgets checked before download; quarantine + monotonic anti-replay.
  • Shell/dataset compatibility dual-checked at boot (§18.5 ARCHITECTURE-DESIGN.md:688) and respects C-23 ordering ARCHITECTURE-DESIGN.md:690 (freeze 7d before festival).
  • Readiness predicate C1–C8 §20.1 computed locally; every state §20.2 reachable in tests; READY is time-independent (SPIKE-05:47); chip always honest.
  • ClockService: UTC+IANA + dayKey, skew capture, monotonic guard, F-3 suppression SPIKE-08:97`, only explicit-zone Intl (F-4).
  • Map: 1600/3072/35 MB budgets, lazy object URLs (F-3), dark filter (F-4), DOM buttons 48 px + Facilities list, a11y §32.
  • Schedule: Now/Next, favorites with stable ids, filters/search, §21 queries, status markers.
  • Info: structured nodes → safe DOM §23.
  • Sync pull-only at open/online/manual §26; HTTP transport only; transport seam empty for future mesh §27.
  • Security §28: signed manifest + per-file SHA-256, CSP, HSTS, no secrets, key set rotation-capable; verifier library audit recorded (AQ-06) even if not blocking staging.
  • Privacy §29: no accounts, no telemetry, no location capture, manual diagnostics only.
  • Recovery §31: every FA row renderable; Ring-0 fallback, no blank screen.
  • Performance §33 budgets CI-gated (shell ≤150 KB gz / 1 MB, dataset ≤40 MB, single file ≤6 MB, decode ≤35 MB, cold 2 s/3 s, boot ≤150 ms, map ≥30 fps).
  • Unit + contract + integration (fault-injected) + content + a11y tests §35; any device bug that harness missed becomes a harness case ARCHITECTURE-DESIGN.md:843.
  • Device-matrix hand-off documented per §36 — remaining UNVERIFIED items tracked as YELLOW in ARCHITECTURE-VALIDATION.md:476 §7 (IDB jetsam, map/GPU, JSC parity, tel:/SW).

Implementation may not be declared done for a production festival until the BLOCKING items in §36 and ops gates §37 (origin, key custody runbook) have been exercised.


ARCHITECTURAL INVARIANTS (implementation-oriented, non-negotiable)

These copy the validated invariants into rules a coding agent cannot break. Each cites the validated freeze (ARCHITECTURE-VALIDATION.md:301 §3, DISCOVERY constraints DISCOVERY.md:230 §5, and specific hard rules).

# Invariant (implement as stated) Trace
I-1 Emergency baseline is Ring-0 shell bytes and never uses IndexedDB. Tier-1 floor is compiled into the shell at build time from the same source as the dataset emergency section; its renderer parses a frozen schema, is forward-tolerant, and must succeed with zero IDB access in every state (NOT_READY, PARTIAL, READY, RECOVERY, BASELINE_ONLY, FAILED, eviction, corrupt, incompatible, offline). ADR-007 ARCHITECTURE-DESIGN.md:465, SPIKE-06:12, SPIKE-06:75 F-1, C8 SPIKE-05:43
I-2 No emergency path may fetch the network. Rendering preferences (floor vs dataset section), provenance stamps, and tel: number display must be satisfiable from shell + verified active slot only. Online is an enhancement for receiving updates, never a prerequisite for displaying emergency info. DISCOVERY C17, ADR-001 ARCHITECTURE-DECISIONS.md:14, SPIKE-06:81, ARCHITECTURE-DESIGN.md:492
I-3 Schedule works fully offline. Full browse, day groups by dayKey, Now/Next via ClockService, My Schedule (favorites), stage/type filters, and search over ≤~1k events all over the active slot — zero network calls, zero hidden APIs. DISCOVERY R-S1…S4, R-O1…R-O9, ADR-009, ARCHITECTURE-DESIGN.md:508, SPIKE-08:43
I-4 Map works fully offline. Overview (+ optional detail) WebP bases + POI normalized coords + Facilities list + search/filter all from active slot Blobs and JSON — no online tiles, no map SDK, no network. Factors: capped decode §22, no GPS. DISCOVERY R-M1…M5, ADR-008 SPIKE-03:58, ARCHITECTURE-DESIGN.md:543
I-5 Festival information works fully offline. All info blocks rendered as structured safe DOM from the active slot — no remote fetches while festival is running. DISCOVERY R-F1, ARCHITECTURE-DESIGN.md:572
I-6 Favorites / My Schedule works fully offline and survives every data transition. lumen-user is separate from lumen-slot-* and lumen-system; updates/rollback/GC never touch it; writes are write-through. DISCOVERY DISCOVERY.md:545 §15.8, SPIKE-02:69 X3, ARCHITECTURE-DESIGN.md:275 B-6
I-7 GPS is optional and V1 has no blue dot. Never request location permission in V1; find-nearest-facility is solved by the Facilities list and categorised POIs, not GPS. lat/lng on POIs is an optional hook only. DISCOVERY DISCOVERY.md:172 R-M4 narrowed ARCHITECTURE-DESIGN.md:18, invariant 9, SPIKE-03:112
I-8 Network loss cannot break the core app. After first successful shell+dataset bootstrap, every critical path (boot, nav, emergency, schedule, map, festival info, readiness display, favorites) renders from Cache Storage + IDB with no blocking fetch — online is only SyncService enhancement (ARCHITECTURE-DESIGN.md:383). DISCOVERY C19/C20 + ADR-001, C-19 ARCHITECTURE-DESIGN.md:78, FA-1 ARCHITECTURE-DESIGN.md:755
I-9 No partial dataset can ever become the active dataset. Staging exclusively into the inactive slot; per-file hash verified; full verification before pointer move; active is observable only as "old verified" or "new verified" (SPIKE-02:50 14 crash points, ordering proof SPIKE-02:73). DISCOVERY C8, ADR-006, ARCHITECTURE-DESIGN.md:659
I-10 No invalid dataset (bad sig, bad hash, bad schema, incompatible, over-budget) can ever become active. Cheap+authoritative validation order §11; Verifier is sole decider (B-4); quarantine prevents refetch-loop; incompatible bubbles "please update app" (ARCHITECTURE-DESIGN.md:668). DISCOVERY C22, R-G2, ADR-013 TH-1/TH-3/TH-5, SPIKE-02:60 S06–S09
I-11 Existing valid data must survive every failed or interrupted update. Kill at any of the 9 failure stages → old active untouched or restored via rollback/readbackPending (ARCHITECTURE-DESIGN.md:664 §18.2, SPIKE-02:50 S01–S14, SPIKE-02:97 F-3, ARCHITECTURE-DESIGN.md:672 retry once). DISCOVERY C9, ADR-006 F-4, ARCHITECTURE-DESIGN.md:659, W-4 ARCHITECTURE-DESIGN.md:898
I-12 Dataset authenticity/integrity must be proved before activation. Ed25519 over sha256(exact manifest bytes) against embedded key set + per-file SHA-256+size + schema; recorded in lumen-system.meta verification record (F-2) and spot-checked at boot. ADR-005/013, ARCHITECTURE-DESIGN.md:358, SPIKE-02:29, SPIKE-04:203 F-5, SPIKE-01:140 P2
I-13 Shell and dataset compatibility must be checked at two points. Before staging (manifest appCompatibility + schemaVersion) and at boot (schemaVersion ∈ shell.supportedRange + appCompatibility) per ARCHITECTURE-DESIGN.md:688 §18.5; ordering rule C-23 ARCHITECTURE-DESIGN.md:690 — shell range ships first, datasets follow, freeze 7d before festival.
I-14 V1 has no mesh networking — exactly one transport exists. No WebRTC/BLE/ad-hoc/HP code, no discovery/routing/dedupe in V1. The only transport is HttpTransport implementing the byte interface §25. DISCOVERY DISCOVERY.md:268 NR-1, NR-12, ADR-011/012, ARCHITECTURE-DESIGN.md:704
I-15 V1 has no user accounts. No login, JWT, session, or server-side identity. All state stays device-local. DISCOVERY DISCOVERY.md:270 NR-4, ADR-010 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:60 A-07, ARCHITECTURE-DESIGN.md:930 rule 7
I-16 UI code must never directly manipulate persistence or transport. Views/components/router may import only domain/* read APIs and readiness/ — never platform/, storage/, sync/, fetch, indexedDB, caches. Enforce via import lint and review (B-1…B-3 ARCHITECTURE-DESIGN.md:195, hard rules ARCHITECTURE-DESIGN.md:928).
I-17 Future transport/mesh concerns remain behind the transport boundary. No feature domain service or UI may import sync/transport nor branch on transport identity; Verifier decides truth for every transport equally (signed-payload rule SPIKE-06:53 ME-4, ARCHITECTURE-DESIGN.md:718). Status shows "updated ", never "via what". ADR-011/012, DISCOVERY.md:596 ME-4, ARCHITECTURE-DESIGN.md:704/ARCHITECTURE-DESIGN.md:718 §20.5

Violations of any I-1…I-17 fail §40 Definition of Done.


IMPLEMENTATION SEQUENCE (do not attempt to build everything simultaneously)

Prerequisites use ASSUMPTIONS-AND-OPEN-QUESTIONS.md:138 and ARCHITECTURE-VALIDATION.md:493 — every stage is buildable against a provisional staging origin and fixtures. Do not block on production origin (AQ-18), real map art (OQ-6/AQ-19), or emergency sign-off (OQ-2) — model them with placeholders that respect budgets/schemas.

Stage 1 — Project foundation

  • Prerequisites: Contract read; architecture docs understood; decision to use provisional staging origin.
  • Work: Dedicated git repo (VCS-1 DISCOVERY.md:34), TypeScript strict base, lint (import boundaries for B-1…B-7 + §6), formatter, commit hooks, CI skeleton.
  • Tests: CI runs lint + typecheck on empty src.
  • Acceptance: Import-lint for forbidden edges (ui→platform, etc.) passes; no app code yet.
  • Must NOT yet: PWA, DB, any feature code; no runtime deps except possibly the audited verifier placeholder.

Stage 2 — Application shell

  • Prerequisites: Stage 1.
  • Work: index.html (single entry), manifest (display: standalone, icons), build emits hashed /assets/<hash>.* + precache manifest deterministically (AQ-12 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:36 Validated), production no-cache vs immutable headers ARCHITECTURE-DESIGN.md:807. Install coach visuals scaffolded (iOS manual steps + Android beforeinstallprompt detection, ARCHITECTURE-DESIGN.md:217).
  • Tests: Lighthouse PWA (shell installable), scripted offline reload (ARCHITECTURE-DESIGN.md:838).
  • Acceptance: tests PWA audits pass; budgets §33 bundle gate visible.
  • Must NOT yet: IDB, dataset, sync.

Stage 3 — PWA / service worker

  • Prerequisites: Stage 2 shell hashes stable.
  • Work: Hand-written public/sw.js §16 (install/activate/fetch/message). Cache name lumen-shell-v<build>; navigation cache-first with fallback.html; hashed assets cache-first; package endpoints passthrough (C-21). SKIP_WAITING + next-start activation + in-app "Restart to update" affordance ARCHITECTURE-DESIGN.md:255.
  • Tests: SW lifecycle harness: install → activate → old SW serves current session → next-start flip; idempotent ops; package passthrough not cached. Offline launch without IDB still renders shell+floor.
  • Acceptance: T01/T03 blue/green path passes on Chromium headless; T16 readiness for device kill test deferred to §36.
  • Must NOT yet: Any IDB write from SW — keep P8 SPIKE-01:140.

Stage 4 — Data model

  • Prerequisites: §8 budgets known.
  • Work: Festival Data Package types (SPIKE-04:79 manifests + section schemas), emergency floor schema (SPIKE-06:12), map/POI categories, info structured nodes. Define schemaVersion envelope and sections[*].required SPIKE-04:64, no-expiration rule SPIKE-04:50, stable-id contract SPIKE-04:189.
  • Tests: Contract fixtures — golden packages pass gates SPIKE-04:189; stable-id invariant tests; dayKey contract SPIKE-08:43.
  • Acceptance: Pipeline can validate/reject a fixture package deterministically; no UI yet.

Stage 5 — Local persistence

  • Prerequisites: §4 types.
  • Work: platform/idb thin wrapper (SPIKE-01:140 P1–P8), data/ stores, lumen-system/meta single-txn discipline (P2), lumen-user migrations, no txn spans await, QuotaExceededError keeps active (P3), 6 MB single-record cap (P5), storage.estimate() free-space (P4), persist request (P6). Assets as Blobs in slot DB ARCHITECTURE-DESIGN.md:275.
  • Tests: Wrapper atomicity tests SPIKE-01:60; QuotaError injection keeps active; boot light verify detects missing/corrupt; heavy BLOB read-back timing instrumentation.
  • Acceptance: Unit tests mirror exp2-ab-update-sim.mjs:1 infrastructure at harness level (without claiming device proof).
  • Must NOT yet: Verifier/crypto — reads only.

Stage 6 — Festival Data Package (content pipeline stub)

  • Prerequisites: §4 types + §5 stores exist (even without real art).
  • Work: content/ templates + pipeline script validate→build→hash→sign→upload→smoke (ARCHITECTURE-DESIGN.md:697 runbook). Share same emergency source sheet for floor + dataset section (ARCHITECTURE-DESIGN.md:345) using test emergency data. Mutable latest.json.
  • Tests: Pipeline gates SPIKE-04:189 on synthetic data; signed fixture → smoke fetch re-verifies.
  • Acceptance: A staging lumen-2026 edition with Budget ≤40 MB produces a smoke-verified signed package reachable via immutable URLs; test key only.
  • Must NOT yet: Real organizer content — provisional placeholder.

Stage 7 — Dataset validation

  • Prerequisites: Stages 5–6.
  • Work: sync/verifier (SHA-256 via WebCrypto + bundled pure-JS Ed25519 verifier placeholder). Implements ordering §11 before any download, quarantine store, budgets checked before download (ARCHITECTURE-DESIGN.md:358, SPIKE-02:29).
  • Tests: Bad signature → keep old, no file fetched (S07), bad hash (S06), bad schema (S08), incompatible (S09), replay lower version, bundled verifier smoke against signature.json SPIKE-04:79. Enforces quarantine no-loop.
  • Acceptance: Every rejection keeps active; Verifier is sole decider (B-4).

Stage 8 — A/B activation

  • Prerequisites: Stages 5–7.
  • Work: sync/ staging into inactive slot (per-file atomic, resumable 7-day discard), verification record + readbackPending (F-2/F-3 SPIKE-02:89), single-txn flip §12, retry-once, automatic rollback on spot-check/next-boot, user-initiated restore (ARCHITECTURE-DESIGN.md:677).
  • Tests: Fault-injection integration exactly covering SPIKE-02:50 S01–S14 + X1–X3 (kill between files, before/during/after flip, corrupt at boot, both-gone RECOVERY, favorites survive). Mirrors exp2-ab-update-sim.mjs:1.
  • Acceptance: 16/16 analogous PASS at state-machine level; coverage does not claim iOS jetsam proof — that is device T10 §36.

Stage 9 — Offline Ready

  • Prerequisites: Stages 3 (C1), 5/8 (C2–C8).
  • Work: domain/readiness predicate C1–C8 §20.1, six-state taxonomy §20.2, cadence §20.3 (light at boot ≤150 ms, full after staging / user check / error), persistence of readbackPending, chip mapping, Status screen checklist/version/usage.
  • Tests: Predicate truth table (SPIKE-05:90 edge cases: eviction→RECOVERY, incompatible→RECOVERY, private→BASELINE_ONLY, wrong clock→READY+warning, optional missing→READY, activation fail→FAILED). Every state reachable ARCHITECTURE-DESIGN.md:930 rule 5.
  • Acceptance: Time-independent READY (SPIKE-05:47 F-1) — wrong clock produces warning §21, never demotion.

Stage 10 — Emergency

  • Prerequisites: Ready §9 + baseline generation from content pipeline §6.
  • Work: Compiled emergency-baseline/ ≤16 KB (ARCHITECTURE-DESIGN.md:367), three-tier resolution SPIKE-06:60, forward-tolerant zero-IDB renderer SPIKE-06:75 F-1, provenance stamps, tel: (primary) + copyable text, explicit tap to dial (DISCOVERY.md:470 EM-5/EM-10), persistent nav one-tap reach (ARCHITECTURE-DESIGN.md:207).
  • Tests: Every §20.2 state → Emergency renders floor or highest compatible tier; floor with zero-IDB (BASELINE_ONLY); incompatible demotes to floor with guidance; signature/compatibility failure never removes floor (SPIKE-06:81 7 cases).
  • Acceptance: Emergency coverage is Ring-0 — invariants I-1/I-2 pass in all failure states §31.

Stage 11 — Schedule

  • Prerequisites: §9 readiness, §21 ClockService.
  • Work: domain/schedule + views/schedule + domain/favorites. Stages/artists/events with stable ids + dayKey; Now/Next (§21), per-stage/global up-next, conflict detection, stage/tag filters, substring search (naive, ≤1k) (ARCHITECTURE-DESIGN.md:508). status: scheduled|moved|cancelled rendering, changed-event notes. ClockService integration: device+skew → monotonic guard → F-3 suppressed sanity → absolute times always visible SPIKE-08:97.
  • Tests: Intl zone rendering T1a/b, dayKey T2a–c, DST T3a–e, unusual zones T4a–d (SPIKE-08:43); skew arithmetic T5a–c; sanity T6a–c with F-3; overlap T7a–d — unit suite mirroring exp1-time-model.mjs:1. Display stays inside festival zone vs device toggle.
  • Acceptance: §21 queries work offline; wrong-never-online clock is accepted residual with warnings, not READY demotion.

Stage 12 — Map

  • Prerequisites: §9 + §22 types; provisional art meeting caps suffices for build even without real art (placeholder art does not block dev ASSUMPTIONS-AND-OPEN-QUESTIONS.md:105 OQ-6).
  • Work: domain/map + views/map — two-level WebP decoding (≤1600/≤3072/≤35 MB, one resident), DOM POI buttons, CSS transform pan/zoom with counter-scaled markers, Facilities list first-class (SPIKE-03:79), search highlight, category filter, object-URL lifecycle F-3, dark filter F-4 (SPIKE-03:88).
  • Tests: Headless mechanics OK (EXP-3 caveat experiments/README.md:36); unit decode-budget guards; filter/search; navigation without GPS.
  • Acceptance: Correct at mechanism level on headless; production proof deferred to device T12/T13 §36 (SPIKE-03:116 6-item protocol: ≥30 fps, no leaks, a11y VoiceOver/TalkBack, texture-cap fallback).

Stage 13 — Festival information

  • Prerequisites: Structured blocks types §23.
  • Work: domain/festival + views/festival — renderer for structured nodes (no raw HTML, C-22). Category-driven nav, shared search index, readability (day/night contrast).
  • Tests: Escaping/structured-node mapping + search index; no innerHTML of content lint passes (B-7).
  • Acceptance: Signed required section; degraded BASELINE_ONLY correctly excludes it while emergency stays.

Stage 14 — User state

  • Prerequisites: Earlier favorite model must align with stable ids (§24).
  • Work: domain/favorites over lumen-user.favorites (write-through), prefs (theme/clock), scrubbed diag ring buffer, flags for coach. Idempotent lumen-user migrations separate from package schema (SPIKE-04:210).
  • Tests: Favorite survives A/B updates/rollback (SPIKE-02:69 X3); moved/cancelled/orphan handling; no cross-slot GC into lumen-user.
  • Acceptance: X3 analog passes at integration level.

Stage 15 — Synchronization

  • Prerequisites: Ready §9 + verifier §7 + activation §8 + §25 seam.
  • Work: SyncService pull-only on open/online/manual §26 — latest.json monotonic check → manifest verify → budget/compat → stage → full verify → flip; quarantine; budget + compatibility + hash rejection paths. Transport seam with HttpTransport only (DISCOVERY.md:596 ME-4).
  • Tests: §18.2 nine-stage fault tests (ARCHITECTURE-DESIGN.md:664): pointer error, manifest error, bad sig (quarantine), incompatible, interrupted download, quota, full-verify mismatch, activation txn fail, post-activation readback rollback.
  • Acceptance: Every row in §31 FA-8/FA-9/FA-13 satisfied; no background work (C-19, ARCHITECTURE-DESIGN.md:78).

Stage 16 — Accessibility

  • Prerequisites: Views exist (10–14).
  • Work: Landmarks, heading order, focus management per §32; map Facilities list; emergency targets/contrast; 48 px targets; reduced-motion; standalone safe-area handling (ARCHITECTURE-DESIGN.md:217).
  • Tests: Automated a11y (headings, landmarks, contrast) + manual screen-reader passes on Emergency/Schedule/Facilities per §32 / §36 T13.
  • Acceptance: No feature merges without a11y checklist in §40.

Stage 17 — Performance

  • Prerequisites: Meaningful budgets from cached staging.
  • Work: CI gates enforcing §33 (bundle JS 150 KB gz / shell 1 MB, dataset 40 MB / file 6 MB, decode 35 MB, cold 2 s/3 s, boot 150 ms, map 30 fps). Optimisations: windowed schedule list if needed, single JSON parse per section, no idle timers ARCHITECTURE-DESIGN.md:858.
  • Tests: Light perf harness for boot verify, schedule 1k, WebP decode memory meter.
  • Acceptance: Every gate green; any exceedance requires ADR amendment — do not silently raise budgets (RULES).

Stage 18 — Device testing

  • Prerequisites: All above buildable with fixtures; at least one staging edition smoke-verified (stage 6).
  • Work: Execute the full physical-device matrix §36 (19 groups × D1–D4, 76 tests). Devices: D1 iOS 16.4, D2 iOS latest, D3 low-end Android 2021 ~Chrome 110+, D4 modern Android ARCHITECTURE-VALIDATION.md:158. Scripts cover T01–T19 as worded there.
  • Tests: Device tests themselves — every BLOCKING must pass before production festival; bugs that harness missed feed back into harness (ARCHITECTURE-DESIGN.md:843).
  • Acceptance: UNVERIFIED items from spikes (IDB jetsam, quota/Blob, map/GPU, JSC Intl, tel:, SW re-registration, cold-start) become CONFIRMED by observation or fall back to documented degraded paths (overview-only, tile-split, re-prep).

Stage 19 — Deployment

  • Prerequisites: Pipeline producing signed packages; verifier audit (AQ-06) and key custody drill (AQ-21) in progress.
  • Work: Chosen provider (AQ-14) and production origin (AQ-18, hard to reverse) + HSTS/CSP/headers ARCHITECTURE-DESIGN.md:807, two envs staging/production ARCHITECTURE-DESIGN.md:817, runbooks ARCHITECTURE-DESIGN.md:697 (publish smoke re-verify, rollback via new version, emergency gate). Provisional staging remains usable until public link (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:116).
  • Tests: Staging → production dry-run festival (AQ-23 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:121) with test edition + field devices.
  • Acceptance: Staging smoke-verified package activates on real devices; rollback via latest.json advance is exercised; emergency floor provenance correct.

RULES FOR FUTURE AI CODING AGENTS

  1. Read before you code. Read — in full — ARCHITECTURE-VALIDATION.md, ARCHITECTURE-DECISIONS.md, ARCHITECTURE-DESIGN.md, ASSUMPTIONS-AND-OPEN-QUESTIONS.md, all eight SPIKE docs, and DISCOVERY.md before touching code.
  2. Do not invent infrastructure. Static CDN + import maps only; no backend/DB/auth/mesh service because none exists in the architecture (ARCHITECTURE-DESIGN.md:815). If you need one, file an ADR and wait for approval.
  3. Do not add dependencies without justification. Target only the audited Ed25519 verifier beyond stdlib (ARCHITECTURE-DESIGN.md:930 rule 3). Any new runtime dep requires an ADR addendum with size impact (JSP 150 KB gz, §33).
  4. Do not silently replace decisions. If vanilla TS + tiny store feels inconvenient, use the Preact reconsider trigger in ADR-003 (ARCHITECTURE-DECISIONS.md:86) via a documented proposal — do not just switch.
  5. Do not introduce accounts/authentication. V1 has no login (§2, INVARIANT I-15); favorites are device-local. Any future account needs explicit approval and must not touch emergency/schedule paths.
  6. Do not introduce mesh in V1. Seam costs one interface §25; no WebRTC/BLE/ad-hoc code, no discovery/routing (DISCOVERY.md:270 NR-1, INVARIANT I-14). A future transport is the only extension.
  7. Do not create a hidden network dependency. No critical view, domain service, readiness check, emergency render, or boot path may fetch or await a server. Sync runs only as SyncService enhancement with online hint (INVARIANT I-8, C-19 ARCHITECTURE-DESIGN.md:78).
  8. Do not put festival content directly into UI components. Content lives in the signed Festival Data Package §15 and is read via data/ APIs; no hard-coded schedules, POIs, or emergency strings in components. §6 B-2, DISCOVERY.md:230 C13, failures FA-6/FA-9.
  9. Do not duplicate emergency data by hand. Floor and dataset section share the same source sheet (ARCHITECTURE-DESIGN.md:345, SPIKE-06:12). Manual copy-paste creates drift — generate, do not duplicate §14 (and enforce the 16 KB cap).
  10. Do not bypass validation or A/B activation. Check signature before hash before schema before activate, quarantine, and flip in a single lumen-system txn §§11–12 — every rejection path in §31. B-4 ARCHITECTURE-DESIGN.md:195. Shortcuts fail INVARIANTS I-9…I-12 and §40.
  11. Do not drop the defensive IDB protocol. P1–P8 SPIKE-01:140: short txns, QuotaExceededError keeps active, free-space 2× pre-check, 6 MB cap, no dataset in SW, boot light detection.
  12. Do not weaken accessibility for convenience. A11y is BLOCKING in §36 T13 and §40 checklist; POIs remain real buttons with 48 px and Facilities list is first-class, not fallback SPIKE-03:79.
  13. Do not silently raise budgets. Map 1600/3072/35 MB (SPIKE-03:58), dataset 40 MB, file 6 MB, bundle 150 KB gz / 1 MB, boot 150 ms, cold 2 s/3 s, map 30 fps — all in §33. Negotiate an ADR if genuinely needed; do not inflate in code.
  14. Do not treat UNVERIFIED as confirmed. IDB jetsam atomicity (SPIKE-01:75), JSC Intl parity (SPIKE-08:152), SW lifecycle (SPIKE-01:82), storage pressure/Blob (SPIKE-01:159), map decode (SPIKE-03:116), tel: standalone (SPIKE-01:171) are queued for §36 device matrix. A desktop pass does not clear them.
  15. When a conflict is discovered, stop and document. File a gap note citing the conflicting lines (e.g., ARCHITECTURE-DECISIONS.md:129 vs SPIKE-01:140) and propose a patch — do not silently pick a side. The valid resolver is the validation report ARCHITECTURE-VALIDATION.md:1 which already reconciles all eight spikes.
  16. Respect the ordering and layering contracts. Per-file atomic + quarantine (F-1, §10), activation single txn (P2/F-2 §12), time-sane rendering via explicit-zone Intl only (F-4 §21), content-as-data via structured nodes (C-22 §23), transport byte-seam (§25), and INVARIANTS I-16/I-17 behind the transport boundary.

CROSS-TRACE: every major rule → validated decision

Rule in contract Source decision(s) Spike reconciliation
V1 scope / NL-1…NR-15 §1/§2 ADR-001…ADR-014, ARCHITECTURE-DESIGN.md:25 ARCHITECTURE-VALIDATION.md:40 §2 (no spike contradicts scope)
Stack vanilla TS / no framework §3 ADR-003 ARCHITECTURE-DECISIONS.md:86 SPIKE-03 EXP-3 SPIKE-03:28 + ARCHITECTURE-VALIDATION.md:56 (no dedicated spike, YELLOW device perf)
Thin IDB wrapper + Cache for shell §3/§8/§9 ADR-004 ARCHITECTURE-DECISIONS.md:129 SPIKE-01 P1–P8 SPIKE-01:140, ACCEPT WITH CHANGES
Package JSON+assets+signed manifest §15 ADR-005 ARCHITECTURE-DECISIONS.md:177 SPIKE-04 F-1…F-5 SPIKE-04:203, ACCEPT WITH CHANGES
A/B inactive staging + single-txn activation §10/§12 ADR-006 ARCHITECTURE-DECISIONS.md:225 SPIKE-02 F-1…F-4 SPIKE-02:89 16/16 PASS, proof SPIKE-02:73
Emergency 3 tiers (floor compiled) §14 ADR-007 ARCHITECTURE-DECISIONS.md:272 SPIKE-06 SPIKE-06:12 + F-1 SPIKE-06:75
Map raster+DOM+list §22 ADR-008 ARCHITECTURE-DECISIONS.md:315 SPIKE-03 F-1 SPIKE-03:58, F-3 SPIKE-03:68, F-4 SPIKE-03:88
Time UTC+IANA+ClockService §21 ADR-009 ARCHITECTURE-DECISIONS.md:362 SPIKE-08 24/24 SPIKE-08:43, F-3 SPIKE-08:97, F-4 SPIKE-08:148
Sync pull-only §26 ADR-010 ARCHITECTURE-DECISIONS.md:406 C-19/B-06/PW-4 DISCOVERY.md:433 + cheapest-first ordering SPIKE-02:29
Transport byte seam §25 ADR-011 ARCHITECTURE-DECISIONS.md:441 DISCOVERY.md:596 ME-4 + signed-payload rule
Mesh future-only §27 ADR-012 ARCHITECTURE-DECISIONS.md:482 ARCHITECTURE-VALIDATION.md:475 scenario 20 + feasibility DISCOVERY.md:581
Security: sig+hash+key set+CSP §28 ADR-013 ARCHITECTURE-DECISIONS.md:524 TQ-9/SQ-1 pure-JS verifier correct per ARCHITECTURE-DESIGN.md:14, SPIKE-02 rejection paths SPIKE-02:60
Static CDN single origin §37 ADR-014 ARCHITECTURE-DECISIONS.md:575 SPIKE-04 gates SPIKE-04:189 + origin-keyed strand risk ASSUMPTIONS-AND-OPEN-QUESTIONS.md:116
Offline Ready C1–C8 + six states §20 ARCHITECTURE-DESIGN.md:388 §12 + ADR-006 SPIKE-05 SPIKE-05:28/SPIKE-05:60 + time independence SPIKE-05:47
Invariants I-1…I-17 ARCHITECTURE-VALIDATION.md:301 §3 + DISCOVERY.md:230 §5 Every freeze-test scenario ARCHITECTURE-VALIDATION.md:524 §6 proven after spikes
Device matrix §36 ARCHITECTURE-DESIGN.md:832 §26 → ARCHITECTURE-VALIDATION.md:156 §4 All §5 UNVERIFIED queues in spikes
Hard rules B-1…B-7 §6 ARCHITECTURE-DESIGN.md:195 + ARCHITECTURE-DESIGN.md:930 Reinforced by SPIKE-01:140 P8 + SPIKE-06:75

INTENTIONALLY PROVISIONAL (do not pretend otherwise)

Per ARCHITECTURE-VALIDATION.md:476 §7 YELLOW tables and ASSUMPTIONS-AND-OPEN-QUESTIONS.md:9 validation plan:

  • Physical-device YELLOW (not blocking start, blocking production): IDB atomicity near-quota/jetsam (AQ-04), map decode/fps/leaks/texture-cap (AQ-11, SPIKE-03:116), JSC/DayKey/ClockService on iOS (AQ-08, SPIKE-08:152), SW re-registration, tel: in standalone, persist() heuristics. These are §36 T07/T08/T10/T12/T13/T15/T16, not architecture gaps.
  • Content YELLOW (not blocking start): A-02 budgets ASSUMPTIONS-AND-OPEN-QUESTIONS.md:16 with pipeline gates SPIKE-04:189 + real schedule/map samples (D-01/D-02/AQ-11) ASSUMPTIONS-AND-OPEN-QUESTIONS.md:73, OQ-6 illustrated art.
  • Ops YELLOW (not blocking start on provisional origin): production origin (AQ-18 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:116), provider (AQ-14 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:112), emergency sign-off gate O-02/OQ-2 DISCOVERY.md:345, legal review, key custody drill AQ-21/S-01 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:85 (verifier audit AQ-06 ASSUMPTIONS-AND-OPEN-QUESTIONS.md:29).

The only class that could ever turn YELLOW to RED is a device-matrix failure for which fallbacks are already defined (overview-only mode, 2×2 tile-split DD-9 ARCHITECTURE-DESIGN.md:918, Preact trigger ADR-003, re-prep path).


FIRST IMPLEMENTATION PHASE (upon approval)

Stage 1 — Project foundation (IMPLEMENTATION SEQUENCE Stage 1): dedicated repo at /home/avi/Projects/Lumen (per VCS-1 DISCOVERY.md:34), TypeScript strict base, lint rule enforcing B-1…B-7 §6, commit hooks, CI skeleton — no deps yet. This is gated only by the decision to proceed; no provisional item blocks it.


End of Implementation Contract. No application source was created to write it. Cross-check instruction: verify every row above appears in ARCHITECTURE-VALIDATION.md and reconcile any drift by preferring the validation report.