85 KiB
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, nonode_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:145R-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:162R-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:182R-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:270NR-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:240constraints 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:406pull-only) - Background Sync / Periodic Sync (
DISCOVERY.md:433PW-4,ARCHITECTURE-DESIGN.md:78C-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:43B-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
awaitof 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 recordtogether (P1, F-1SPIKE-02:89); no txn spans non-IDB await. - Staging progress is authoritative in the target slot's
stagingjournal; 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:100F-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.
- Fetch
latest.json→ monotonic version comparison: reject≤ activefrom any network source (downgrade protection,ARCHITECTURE-DESIGN.md:349). Purely informationalgeneratedAtnever gates. - Fetch candidate
manifest.json. - Signature over exact manifest bytes (Ed25519,
signature.jsonmanifestSha256+publicKeyFingerprint) — verified against embedded key set (fingerprinted, rotation-capable,ARCHITECTURE-DESIGN.md:358) — before parsing or downloading anything else (SPIKE-02:29step 3). Fail → quarantine version, diag entry, keep active (S07 PASS). - 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. - Stage files into inactive slot; per-file SHA-256 + size check on each (
SPIKE-02:29step 5). - Schema validation of staged sections (strict on required, allow unknown forward-compat fields,
SPIKE-04:189gate 1). - 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 flipsactiveSlot+activePackageVersion+ verification record (F-2SPIKE-02:89) and setsreadbackPending. 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:97F-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:50S14, X1). If both slots lack a complete dataset → RECOVERY + baseline (SPIKE-02:69X2) — favorites inlumen-userremain. - 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). packageVersionstrictly monotonic per edition; never accept≤ activefrom network (ARCHITECTURE-DESIGN.md:349). Organizer rollback = new higher version.schemaVersionenvelope with ordering ruleARCHITECTURE-DESIGN.md:690C-23: schemas ship in shell range{1,2}first, datasets follow, freeze 7 days before festival.sections[*].requiredbool (default true) gates readiness (SPIKE-04:64,SPIKE-05:47F-2).- No expiration for datasets/sections (
SPIKE-04:50F-2) — only future announcements/notices may carryexpiresAtUtcand 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 ceilingARCHITECTURE-DESIGN.md:363enforced by pipeline. - Pipeline gates (publisher side, all blocking): 1) schema + unknown-fields forward-compat, 2) stable IDs (removals require
status: cancelled), 3) time sanity (dayKeymatches 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, hashedindex.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:243C-21).message: minimalSKIP_WAITINGhandling 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.htmlwithno-cache(revalidate) per deploy layoutARCHITECTURE-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.metaas 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:82cadence.
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/endUtcUTC epoch ms on every event (DISCOVERY.md:297A-10, ADR-009). IncludesdayKeyprecomputed at publish as festival-zone calendar date (SPIKE-08:43T2). -
Zone: manifest carries
festival.timezone(IANA) +festival.startUtc/endUtc(SPIKE-04:79). -
Rendering:
Intl.DateTimeFormatwithtimeZone = festival zoneby default; toggle to device zone. Only explicit-zone Intl is allowed (clarification F-4SPIKE-08:148); neverDate.toLocaleString()without options, manual offsets, or wall-clock strings.dayKeygroups 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 multiplenow(SPIKE-08:43T7).- Favorites join via stable event
id(contractual — §24);status: scheduled|moved|cancelledrendering. - Filters/search: naive scan over ≤~1k events; no index lib unless proven slow (
DISCOVERY.md:391TQ-10 decision documented,ARCHITECTURE-DESIGN.md:508). - Change markers when
status=moved|cancelledor 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 unaffectedNo NTP endpoint, no timers, no background work (C-19); computed on demand (
SPIKE-08:43T5/T6). HTTPDateis NTP-synced CDN source, INFERRED accurate (SPIKE-08:84B-10). -
DST/unusual zones: Rendering inherits IANA database; epoch arithmetic correct across spring-forward/fall-back (
SPIKE-08:43T3/T4 validated 24/24 on V8/ICU; JSC parity UNVERIFIED queued for device T15ARCHITECTURE-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:
overviewlongest edge ≤1600 px (~0.3–0.7 MB encoded, ~7.3 MB decoded), optionaldetail≤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-1SPIKE-03:58replaces 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?}withnightAssetIdoptional hook for F-4 dark mode. Alternatives SVG/Canvas/tile-pyramid rejected per matrixSPIKE-03:11. - Coordinate space: POIs normalized
x:0..1, y:0..1relative to base image (ARCHITECTURE-DESIGN.md:561, D-02). Authorship via tap-tool or measured coords; optionallat/lnghook 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): defaultfilter: brightness(.72) saturate(.85)on base in dark, markers full-brightness (GPU-composited); optionalmap-base/overview-nightasset — 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.jsonordered 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), noinnerHTMLof 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, perARCHITECTURE-DESIGN.md:573static-important note.
24. User preferences / favorites architecture (normative, DISCOVERY.md:545 §15.8, SPIKE-04:154)
- Store:
lumen-user.favoriteskeyed 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:69X3 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:74D-01, AQ-20). Removals requirecancelledrather than deletion (SPIKE-04:189gate 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:456OF-11). Includes overlap conflict surfacing (ARCHITECTURE-DESIGN.md:508). Duplicated export later (DD-4). prefs(inlumen-user) includes theme/clockdisplayModetoggles; clock skew cache reference per §21. Small flags inlocalStorageonly for non-load-bearing coach dismissed etc., guardedtry/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
onlineevent (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/fetchedAtand 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-4DISCOVERY.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.MeshTransportbridging 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:5244): diagnostics are on-device, scrubbed, and user-initiated — manual "share diagnostics" only, no automatic upload. - No location capture in V1 (
ARCHITECTURE-DESIGN.md:222zero-permission). - No PII in content or favorites (favorites =
public event idlist,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:87S-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:140P3,ARCHITECTURE-DESIGN.md:672retry 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). localStorageguardedtry/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:470EM-5);tel:with selectable-text copy fallback. - Touch targets ≥48 CSS px (
ARCHITECTURE-DESIGN.md:783). prefers-reduced-motionhonoured (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:283using 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:261C-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:
stagingorigin +productionorigin, 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 higherpackageVersion; 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), keyededitionfor cheap multi-event later; budgets§33. - Organizers can supply schedule/map art/POIs/emergency/info digitally (
DISCOVERY.md:297A-04,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:73A-04) via intake templates (CSV/JSON + raster art) or existing CMS adapter must stay import-friendly (AMB-2DISCOVERY.md:324). - Event/POI IDs stable (
SPIKE-04:189gate 2,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:74D-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:75D-02); if only vector/GIS arrives, representation flipSPIKE-03:98DD-9. - Emergency contacts/coordinates valid through event, provenance
contentVersion/updatedAt+emergencySchemaVersion(SPIKE-04:70,SPIKE-06:110); sign-off gate O-02ASSUMPTIONS-AND-OPEN-QUESTIONS.md:62. - Single festivals IANA zone (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:78D-05); multi-zone would amend ADR-009. - Volumes ≤1k events / ≤200 POIs / ≤30 info blocks within budgets (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:77D-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
§6B-1…B-7 violated (verified by module-import lint + review). - No direct
innerHTMLof festival content; escaping renderer + CSPself-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:140implemented exactly; no txn spans non-IDB await;QuotaExceededErrorpath keeps active; pre-check 2×. - A/B ordering
§§10–13holds; 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 orderingARCHITECTURE-DESIGN.md:690(freeze 7d before festival). - Readiness predicate C1–C8
§20.1computed locally; every state§20.2reachable in tests; READY is time-independent (SPIKE-05:47); chip always honest. - ClockService: UTC+I
ANA +dayKey, skew capture, monotonic guard, F-3 suppressionSPIKE-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,
§21queries, 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
§33budgets 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 caseARCHITECTURE-DESIGN.md:843. - Device-matrix hand-off documented per
§36— remaining UNVERIFIED items tracked as YELLOW inARCHITECTURE-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-12ASSUMPTIONS-AND-OPEN-QUESTIONS.md:36Validated), productionno-cachevs immutable headersARCHITECTURE-DESIGN.md:807. Install coach visuals scaffolded (iOS manual steps + Androidbeforeinstallpromptdetection,ARCHITECTURE-DESIGN.md:217). - Tests: Lighthouse PWA (shell installable), scripted offline reload (
ARCHITECTURE-DESIGN.md:838). - Acceptance:
testsPWA audits pass; budgets§33bundle 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 namelumen-shell-v<build>; navigation cache-first withfallback.html; hashed assets cache-first; package endpoints passthrough (C-21).SKIP_WAITING+ next-start activation + in-app "Restart to update" affordanceARCHITECTURE-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:79manifests + section schemas), emergency floor schema (SPIKE-06:12), map/POI categories, info structured nodes. DefineschemaVersionenvelope andsections[*].requiredSPIKE-04:64, no-expiration ruleSPIKE-04:50, stable-id contractSPIKE-04:189. - Tests: Contract fixtures — golden packages pass gates
SPIKE-04:189; stable-id invariant tests; dayKey contractSPIKE-08:43. - Acceptance: Pipeline can validate/reject a fixture package deterministically; no UI yet.
Stage 5 — Local persistence
- Prerequisites: §4 types.
- Work:
platform/idbthin wrapper (SPIKE-01:140P1–P8),data/stores,lumen-system/metasingle-txn discipline (P2),lumen-usermigrations, no txn spans await,QuotaExceededErrorkeeps active (P3), 6 MB single-record cap (P5),storage.estimate()free-space (P4), persist request (P6). Assets as Blobs in slot DBARCHITECTURE-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:1infrastructure 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 scriptvalidate→build→hash→sign→upload→smoke(ARCHITECTURE-DESIGN.md:697runbook). Share same emergency source sheet for floor + dataset section (ARCHITECTURE-DESIGN.md:345) using test emergency data. Mutablelatest.json. - Tests: Pipeline gates
SPIKE-04:189on synthetic data; signed fixture → smoke fetch re-verifies. - Acceptance: A staging
lumen-2026edition 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§11before 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.jsonSPIKE-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-3SPIKE-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:50S01–S14 + X1–X3 (kill between files, before/during/after flip, corrupt at boot, both-gone RECOVERY, favorites survive). Mirrorsexp2-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/readinesspredicate C1–C8§20.1, six-state taxonomy§20.2, cadence§20.3(light at boot ≤150 ms, full after staging / user check / error), persistence ofreadbackPending, chip mapping, Status screen checklist/version/usage. - Tests: Predicate truth table (
SPIKE-05:90edge cases: eviction→RECOVERY, incompatible→RECOVERY, private→BASELINE_ONLY, wrong clock→READY+warning, optional missing→READY, activation fail→FAILED). Every state reachableARCHITECTURE-DESIGN.md:930rule 5. - Acceptance: Time-independent READY (
SPIKE-05:47F-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 resolutionSPIKE-06:60, forward-tolerant zero-IDB rendererSPIKE-06:75F-1, provenance stamps,tel:(primary) + copyable text, explicit tap to dial (DISCOVERY.md:470EM-5/EM-10), persistent nav one-tap reach (ARCHITECTURE-DESIGN.md:207). - Tests: Every
§20.2state → 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:817 cases). - Acceptance: Emergency coverage is Ring-0 — invariants I-1/I-2 pass in all failure states
§31.
Stage 11 — Schedule
- Prerequisites: §9 readiness,
§21ClockService. - 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|cancelledrendering, changed-event notes. ClockService integration: device+skew → monotonic guard → F-3 suppressed sanity → absolute times always visibleSPIKE-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 mirroringexp1-time-model.mjs:1. Display stays inside festival zone vs device toggle. - Acceptance:
§21queries work offline; wrong-never-online clock is accepted residual with warnings, not READY demotion.
Stage 12 — Map
- Prerequisites: §9 +
§22types; provisional art meeting caps suffices for build even without real art (placeholder art does not block devASSUMPTIONS-AND-OPEN-QUESTIONS.md:105OQ-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:1166-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
innerHTMLof 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/favoritesoverlumen-user.favorites(write-through),prefs(theme/clock), scrubbeddiagring buffer, flags for coach. Idempotentlumen-usermigrations separate from package schema (SPIKE-04:210). - Tests: Favorite survives A/B updates/rollback (
SPIKE-02:69X3); moved/cancelled/orphan handling; no cross-slot GC intolumen-user. - Acceptance: X3 analog passes at integration level.
Stage 15 — Synchronization
- Prerequisites: Ready
§9+ verifier§7+ activation§8+§25seam. - Work:
SyncServicepull-only on open/online/manual§26—latest.jsonmonotonic check → manifest verify → budget/compat → stage → full verify → flip; quarantine; budget + compatibility + hash rejection paths.Transportseam withHttpTransportonly (DISCOVERY.md:596ME-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
§31FA-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/§36T13. - 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 timersARCHITECTURE-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 AndroidARCHITECTURE-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 envsstaging/productionARCHITECTURE-DESIGN.md:817, runbooksARCHITECTURE-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.jsonadvance is exercised; emergency floor provenance correct.
RULES FOR FUTURE AI CODING AGENTS
- 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, andDISCOVERY.mdbefore touching code. - 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. - Do not add dependencies without justification. Target only the audited Ed25519 verifier beyond stdlib (
ARCHITECTURE-DESIGN.md:930rule 3). Any new runtime dep requires an ADR addendum with size impact (JSP 150 KB gz,§33). - 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. - 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. - Do not introduce mesh in V1. Seam costs one interface
§25; no WebRTC/BLE/ad-hoc code, no discovery/routing (DISCOVERY.md:270NR-1,INVARIANT I-14). A future transport is the only extension. - Do not create a hidden network dependency. No critical view, domain service, readiness check, emergency render, or boot path may
fetchor await a server. Sync runs only asSyncServiceenhancement withonlinehint (INVARIANT I-8, C-19ARCHITECTURE-DESIGN.md:78). - Do not put festival content directly into UI components. Content lives in the signed Festival Data Package
§15and is read viadata/APIs; no hard-coded schedules, POIs, or emergency strings in components.§6B-2,DISCOVERY.md:230C13, failuresFA-6/FA-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). - Do not bypass validation or A/B activation. Check signature before hash before schema before activate, quarantine, and flip in a single
lumen-systemtxn§§11–12— every rejection path in§31. B-4ARCHITECTURE-DESIGN.md:195. Shortcuts failINVARIANTS I-9…I-12and§40. - Do not drop the defensive IDB protocol. P1–P8
SPIKE-01:140: short txns,QuotaExceededErrorkeeps active, free-space 2× pre-check, 6 MB cap, no dataset in SW, boot light detection. - Do not weaken accessibility for convenience. A11y is BLOCKING in
§36T13 and§40checklist; POIs remain realbuttons with 48 px and Facilities list is first-class, not fallbackSPIKE-03:79. - 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. - 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§36device matrix. A desktop pass does not clear them. - When a conflict is discovered, stop and document. File a gap note citing the conflicting lines (e.g.,
ARCHITECTURE-DECISIONS.md:129vsSPIKE-01:140) and propose a patch — do not silently pick a side. The valid resolver is the validation reportARCHITECTURE-VALIDATION.md:1which already reconciles all eight spikes. - 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-zoneIntlonly (F-4§21), content-as-data via structured nodes (C-22§23), transport byte-seam (§25), andINVARIANTS I-16/I-17behind 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§36T07/T08/T10/T12/T13/T15/T16, not architecture gaps. - Content YELLOW (not blocking start): A-02 budgets
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:16with pipeline gatesSPIKE-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-14ASSUMPTIONS-AND-OPEN-QUESTIONS.md:112), emergency sign-off gate O-02/OQ-2DISCOVERY.md:345, legal review, key custody drill AQ-21/S-01ASSUMPTIONS-AND-OPEN-QUESTIONS.md:85(verifier audit AQ-06ASSUMPTIONS-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.