- dedicated git repo at /home/avi/Projects/Lumen (main)
- TypeScript strict (target ES2022, bundler, exactOptionalPropertyTypes, noUncheckedIndexedAccess)
- ESLint 9 + typescript-eslint strictTypeChecked + eslint-plugin-boundaries for B-1..B-7, no-restricted-globals/syntax for B-1/B-7
- Prettier 3.5
- Structure per IMPLEMENTATION-CONTRACT.md §4 (src/platform/idb|cache|sw, storage, data, sync/{transport,verifier}, domain/{emergency,schedule,map,festival,readiness,clock,favorites}, ui/{components,views,router,render}, app, emergency-baseline, assets, public, content, pipeline, tests, scripts)
- CI: .github/workflows/ci.yml (typecheck + lint + format + test)
- Boundary tests: tests/unit/boundaries.test.ts (4 tests) + scripts/check-boundaries.ts
- No feature code, no PWA/IDB/sync/mesh/accounts per contract Stage 1
63 KiB
Lumen — Architecture Validation Report
- Phase: Architecture Validation — consolidation of SPIKE-01 … SPIKE-08
- Date: 2026-08-30
- Source documents:
DISCOVERY.md,ARCHITECTURE-DESIGN.md,ARCHITECTURE-DECISIONS.md,ASSUMPTIONS-AND-OPEN-QUESTIONS.md,SPIKE-01…SPIKE-08,experiments/README.md+exp1/exp2/exp3 - Scope guard: No application code, no
package.json, no dependencies, no PWA/DB/network implementation was created in this phase. All claims below reconcile spike evidence against design decisions without introducing implementation. - Previous state: 7 spikes existed (
SPIKE-01…SPIKE-07) when this report was requested;SPIKE-08was created immediately before this report (exp1 already existed perexperiments/README.md:11). This report is the first document that reconciles all eight together and freezes the architecture.
Relationship to prior phase
DISCOVERY.md:9 and ARCHITECTURE-DESIGN.md:9 noted narrowings/resolutions. This report does not re-litigate discovery; it checks whether the design decisions survive adversarial validation and records the exact changes the spikes require before implementation may start.
1. Validation summary
1.1 Spike inventory
| Spike | Decision(s) under test | Method | Evidence class | Result |
|---|---|---|---|---|
SPIKE-01 IndexedDB + iOS (SPIKE-01-INDEXEDDB-IOS.md:1) |
ADR-004, foundations of ADR-006 | Spec + WebKit/Chromium policy analysis; workload ≤40 MB/90 MB worst case (SPIKE-01:22) |
CONFIRMED (spec), INFERRED (quota/BLOB), UNVERIFIED (device) | ACCEPT WITH CHANGES P1–P8 |
SPIKE-02 Atomic A/B (SPIKE-02-ATOMIC-DATASET-UPDATES.md:1) |
ADR-006 | State-machine simulation exp2-ab-update-sim.mjs:1 with crash/fault injection at 9 stages |
CONFIRMED (simulation) 16/16 PASS | ACCEPT WITH CHANGES F-1…F-4 |
SPIKE-03 Map (SPIKE-03-MAP-REPRESENTATION.md:1) |
ADR-008 | Candidate matrix + decode-memory analysis + exp3-map-bench.html (headless) |
CONFIRMED (JS ~0ms), INFERRED (GPU/memory), UNVERIFIED (device fps) | ACCEPT WITH CHANGES F-1,F-3,F-4 |
SPIKE-04 Package (SPIKE-04-FESTIVAL-DATA-PACKAGE.md:1) |
ADR-005, parts of ADR-013/ADR-007 | Schema design + pipeline gate analysis | CONFIRMED (design) | ACCEPT WITH CHANGES F-1…F-4 |
SPIKE-05 Offline Ready (SPIKE-05-OFFLINE-READY.md:1) |
ARCH §12, invariants 8/10, ADR-006/ADR-005 | Predicate formalization | CONFIRMED (logic) | ACCEPT (formalized) C1–C8, time independence, FAILED state |
SPIKE-06 Emergency baseline (SPIKE-06-EMERGENCY-BASELINE.md:1) |
ADR-007 | Tier analysis + failure cases | CONFIRMED (design) | ACCEPT + F-1 hardening |
SPIKE-07 Bootstrap (SPIKE-07-BOOTSTRAP.md:1) |
ADR-002, ARCH §11/§12 | Journey + failure-point analysis | CONFIRMED (design) | ACCEPT WITH CHANGES L0–L2 |
SPIKE-08 Time model (SPIKE-08-TIME-MODEL.md:1) |
ADR-009, SPIKE-05 time independence | exp1-time-model.mjs:1 24/24 PASS (V8/ICU ECMA-402) |
CONFIRMED (V8/ICU), INFERRED (JSC), UNVERIFIED (device) | ACCEPT WITH CHANGE F-3 |
Experiments re-executed for this report on this host: exp1-time-model.mjs:129 24/24 PASS; exp2-ab-update-sim.mjs:241 16/16 PASS. exp3 desktop caveat stands (experiments/README.md:36).
1.2 What was validated vs what was not
- Validated at design-logic level on this host: UTC/IANA rendering, dayKey, DST, Now/Next, A/B invariant under 14 crash/fault points, map-candidate JS cost, readiness predicate, emergency tier resolution, bootstrap levels, package schema/no-expiration/emergency sub-versioning.
- Inferred from vendor policy/spec but not observed here: iOS storage quotas/eviction/persist heuristics, BLOB/IDB near-quota behaviour, JSC parity for Intl, CDN Date accuracy, SW lifecycle on iOS.
- Unverified and queued for physical devices: all of the above as experienced by Lumen on actual iPhones/Androids plus performance (fps, decode, cold start). This is expected and documented in every spike's §5.
2. Decision reconciliation
For each ADR: original decision → spike finding → final validated decision → changes incorporated → unresolved.
ADR-001 Offline-first
- Original (
ARCHITECTURE-DECISIONS.md:14): Accepted. Offline-first; local source of truth; no blocking network on critical paths. - Spikes touching it: All. SPIKE-05 makes the readiness predicate time-independent; SPIKE-07 defines L0–L2 so the invariant holds even partially provisioned; SPIKE-02 proves updates preserve it under crashes.
- Final: Unchanged — Accepted. No spike contradicts it. Reinforced by SPIKE-05 F-1 (clock does not demote READY) and SPIKE-07 minimum-safe guarantees. No unresolved issues.
ADR-002 PWA / installation
- Original (
ARCHITECTURE-DECISIONS.md:48): Installed-first PWA, single origin, manual iOS coach, functional in tab with degraded persistence warning. - Spike findings: SPIKE-01 §3.12/§5 confirms 7-day ITP tab eviction vs installed exemption is INFERRED/UNVERIFIED and load-bearing; SPIKE-07 introduces L0–L2 preparation levels (
SPIKE-07-BOOTSTRAP.md:31), fixed download orderemergency→schedule→info→map→assets, minimum-safe L1 (emergency+schedule), and explicit rule that prep before install is allowed (SPIKE-07:58). SPIKE-05 maps L0/L1/L2 to NOT_READY/PARTIAL/READY. - Final: Accepted with addendum. Install-first stands; the only change is the formal L0–L2 model, ordered download, and tab-before-install allowance. No scope creep. Unresolved:
persist()grant rates and tab-eviction timing remain UNVERIFIED (device matrix).
ADR-003 Frontend technology
- Original (
ARCHITECTURE-DECISIONS.md:82): Vanilla TypeScript, no framework; Preact as explicit reconsider trigger. - Spike findings: No dedicated spike (TQ-1 not run as a benchmark beyond EXP-3 JS cost). SPIKE-03 EXP-3 confirms no candidate has disqualifying JS cost (
SPIKE-03-MAP-REPRESENTATION.md:28), which is consistent with but not proof of vanilla perf. DISCOVERY §11 TQ-1/TQ-10 remain device-dependent. - Final: Accepted — unchanged. The reconsider trigger is retained deliberately. No spike requires a framework. Unresolved: low-end Android render/JS parse cost remains UNVERIFIED until device matrix and real schedule-list measurement (≤100 ms target,
ARCHITECTURE-DESIGN.md:853). Risk RK-7 unchanged; mitigation is the layering inARCHITECTURE-DESIGN.md:195B-1…B-7 which keeps domain/data untouched by a future view-layer swap.
ADR-004 Local storage
- Original (
ARCHITECTURE-DECISIONS.md:126): Proposed. IndexedDB (thin wrapper) + Cache Storage for shell + localStorage flags; assets as Blobs in slot DB; OPFS/SQLite-WASM rejected. - Spike findings: SPIKE-01 ACCEPT WITH CHANGES. Validates IDB is correct (spec CONFIRMED for transactions/atomicity/multiple DBs) but mandates defensive protocol P1–P8 (
SPIKE-01-INDEXEDDB-IOS.md:140): one short txn per file (bytes+progress together), activation single txn, wrapped IDB + QuotaExceededError handling, free-space pre-check 2× package, 6 MB single-record cap, persist requested but not relied upon, boot light verification as detection, no dataset state in SW. SPIKE-02 confirms this protocol plus F-1 (bytes+progress atomic) is what makes the A/B invariant hold. - Final: Accepted with conditions — status moves from Proposed to Accepted (YELLOW, device-dependent). The layout is validated at design-logic level; production readiness is conditional on the device tests in
SPIKE-01:159(fresh install + reboot with ~40 MB, quota-near-full writes, kill-mid-download resume, Blob read-back). No alternative store is better for this workload. Changes P1–P8 are normative and now reflected inARCHITECTURE-DESIGN.md:9.
ADR-005 Festival Data Package
- Original (
ARCHITECTURE-DECISIONS.md:174): Accepted. JSON sections + assets + manifest + Ed25519 signature; content repo → pipeline → static CDN; immutable versioned URLs + mutable latest.json. - Spike findings: SPIKE-04 ACCEPT WITH CHANGES + SPIKE-05/06/08 cross-checks. Adds: F-1
sections[*].requiredflag gating readiness (SPIKE-04:64), F-2 no-expiration rule for datasets (SPIKE-04:50), F-3 emergency independentemergencySchemaVersion/contentVersion(SPIKE-04:70,SPIKE-06:110), F-4 user-data schema separate from package schema (SPIKE-04:210). SPIKE-08 confirms UTC+IANA+dayKey works offline; SPIKE-05 confirms time-independent readiness needs F-2. - Final: Accepted with addendum. Schema is now normative as in
SPIKE-04:79fragments; no binary format needed at this scale (F-5). Unresolved: pipeline gates need real content to prove budgets; stable IDs (AQ-20) remain Proposed until source samples are inspected.
ADR-006 Atomic updates and rollback
- Original (
ARCHITECTURE-DECISIONS.md:220): Accepted. A/B dual-slot, staged hash-verified, single-txn activation, rollback, page-context downloads (C-21), monotonic versions. - Spike findings: SPIKE-02 ACCEPT WITH CHANGES, 16/16 PASS (
SPIKE-02:50). Mandates: F-1 bytes+progress in same txn, F-2 verification record in activation txn, F-3readbackPendingflag carried across boots, F-4 rollback depth = 1 (new staging wipes inactive slot — accepted trade-off). SPIKE-01 P1–P8 are preconditions. Ordering proof inSPIKE-02:73— single-DB atomicity is sufficient because staging completes and validates before the pointer moves. - Final: Accepted with addendum (F-1…F-4). Invariants 5–7 are proven at state-machine level given IDB transaction atomicity (which itself is INFERRED/UNVERIFIED on iOS per
SPIKE-01:75). The only remaining blocker is physical proof of IDB atomicity under jetsam kill on iOS.
ADR-007 Emergency baseline
- Original (
ARCHITECTURE-DECISIONS.md:266): Accepted. Three tiers: embedded floor (≤16 KB, same-source-generated), dataset section, reserved T3 notices; provenance labels. - Spike findings: SPIKE-06 ACCEPT. Fixes tier-1 contents (
SPIKE-06:12exact JSON shape, life-safety minimum only), resolution rules (SPIKE-06:60prefers highest compatible, defensive merge), forward-tolerant floor renderer and zero-IDB requirement for floor path (SPIKE-06:75F-1), eviction/compatibility/corruption cases. SPIKE-05 C8 asserts floor presence in readiness. - Final: Accepted — addendum records fixed tier contents, merge rules, and F-1. No direction change. Unresolved: organizer-provided emergency content correctness still requires the sign-off gate (
DISCOVERY.md:346OQ-2,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:62O-02) — a product/ops gate, not an architecture defect.
ADR-008 Map
- Original (
ARCHITECTURE-DECISIONS.md:308): Proposed. Raster WebP ≤2 levels + DOM POI overlay + CSS transform + Facilities list; no GPS; no tiles. - Spike findings: SPIKE-03 ACCEPT WITH CHANGES. F-1 tightens caps: overview ≤1600 px longest edge, detail ≤3072 px, total decoded ≤~35 MB, at most one detail resident (
SPIKE-03:58); replaces prior 4096 cap. F-3 lazy object-URL lifecycle (SPIKE-03:68), F-4 dark-mode treatment (brightness(.72) saturate(.85), optional night asset hookSPIKE-03:88). EXP-3 desktop result is non-discriminating — decision rests on memory/a11y/workflow reasoning. Flip conditions documented (SPIKE-03:98). - Final: Accepted with addendum — YELLOW (device-dependent). Representation stands; production declaration is conditional on device protocol in
SPIKE-03:116(fps ≥30, decode latency, no URL leaks, screen-reader pass, 2048 texture-cap fallback). Budgets updated (ARCHITECTURE-DESIGN.md:855).
ADR-009 Time model
- Original (
ARCHITECTURE-DECISIONS.md:354): Accepted. UTC epoch + IANA zone + precomputed dayKey + ClockService (server offset + monotonic anchor + ±45d sanity window). - Spike findings: SPIKE-08 ACCEPT WITH CHANGE, 24/24 PASS (
SPIKE-08:43). Confirms T1 (zone rendering), T2 (dayKey), T3 (DST), T4 (unusual zones), T5 (skew arithmetic), T7 (Now/Next). F-3 change is required: sanity warning suppressed untilnow ≥ festivalStart − WINDOWor a skew has been captured (SPIKE-08-TIME-MODEL.md:97), otherwise early preparation (July for September festival) would spuriously warn every user. F-4 clarification: onlyIntl.DateTimeFormatwith explicittimeZoneis allowed. - Final: Accepted with change F-3 + clarification F-4. Time remains the only user-visible heuristic; readiness is explicitly time-independent per
SPIKE-05:47. Unresolved: JSC parity on iOS and real CDNDateheader observation remain UNVERIFIED (device matrix).
ADR-010 Synchronization
- Original (
ARCHITECTURE-DECISIONS.md:396): Accepted. Pull-only on open / online hint / manual; monotonic versions; no background sync, no push, no uploads. - Spike findings: No dedicated spike; covered indirectly by SPIKE-02 verification ordering (
SPIKE-02:29cheapest/authoritative first) and SPIKE-05 time independence. Discovery C-19/B-06 (no background work) and PW-4 (no iOS Background Sync) confirm the pull model is the only viable one. - Final: Accepted — unchanged. Latency = time-until-next-open-online remains the documented trade-off. No spike contradicts it. Unresolved:
onlineas a hint is best-effort; update urgency still depends on organizers opening the app online.
ADR-011 Networking abstraction
- Original (
ARCHITECTURE-DECISIONS.md:431): Accepted. Minimal byte-oriented Transport interfaceisAvailable()/fetchPointer()/fetchBytes()under SyncService; V1 has only HttpTransport; Verifier decides truth. - Spike findings: Consistent with SPIKE-02 F-5/F-6 and SPIKE-06/ADR-012's signed-payload rule. No spike required a richer interface; mesh semantics are correctly pushed into future transport impls per
DISCOVERY.md:596. - Final: Accepted — unchanged. Cost stays one interface + one impl. No evidence that a message/CRDT abstraction is needed in V1.
ADR-012 Future mesh strategy
- Original (
ARCHITECTURE-DECISIONS.md:472): Accepted (strategy only). Three obligations: Transport seam, signed-payload rule, package/announcement monotonicity; all mesh concerns deferred; out-of-browser companion assumed likely perDISCOVERY.md:581. - Spike findings: SPIKE-04 F-2/F-3, SPIKE-02 ordering, SPIKE-06 T3 reserved shape are all compatible with this seam. No spike built mesh groundwork and none was needed.
ARCHITECTURE-DESIGN.md:895risk 16 documents the impossibility-in-browser posture correctly. - Final: Accepted — unchanged. V1 loses nothing if future mesh proves impossible via browser; HTTP transport is complete product functionality.
ADR-013 Security model
- Original (
ARCHITECTURE-DECISIONS.md:514): Accepted on model, Proposed on crypto library choice pending audit. Ed25519 over manifest SHA-256 + per-file SHA-256, WebCrypto SHA-256 + bundled pure-JS Ed25519 verifier (WebCrypto Ed25519 not on iOS 16.4), key set in shell, CSPdefault-src 'self'etc., data minimization. - Spike findings: SPIKE-04 §2 fixes signature/manifest/hash/compatibility ordering (
SPIKE-02:34), SPIKE-02 proves rejection path (bad sig → keep old, quarantine). Discovery TQ-9/SQ-1 predicted the WebCrypto gap; the pure-JS verifier is the correct resolution perARCHITECTURE-DESIGN.md:14. No spike claims to have audited the verifier library — AQ-06 remains Proposed. Strict CSP impacts hashed-asset build output — design-consistent. - Final: Accepted as a model; crypto library choice remains Proposed (YELLOW) pending audit. The scheme defeats TH-1…TH-6 with minimal machinery; key custody/rotation runbook remains an ops requirement (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:85S-01, AQ-21).
ADR-014 Deployment
- Original (
ARCHITECTURE-DECISIONS.md:564): Accepted on topology (static CDN, single origin, immutable versioned URLs, two environments), Proposed on provider choice (AQ-14). - Spike findings: SPIKE-04 pipeline gates (
SPIKE-04:1895 gates) and SPIKE-01 storage origin-keying make provider choice the only unresolved piece. No spike proposes a dynamic backend; A-17 small-team ops budget still favours static. - Final: Accepted on topology; provider remains Proposed (YELLOW). Origin choice is hard to reverse (storage is origin-keyed → strands installed data on change), so provisional staging origin is fine for development but production origin must be chosen before any public staging link (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:116AQ-18, explicitly not blocking implementation).
3. Critical invariants
Each invariant is checked against the reconciled architecture (after incorporating spike changes). All pass at design-logic level; device-dependent invariants are marked YELLOW pending physical proof.
| # | Invariant | Verdict | Evidence |
|---|---|---|---|
| 1 | Emergency baseline always available from app shell (DISCOVERY.md:230 C2) |
PASS (GREEN) | Tier-1 floor compiled into shell bytes; renderer has zero-IDB path and is forward-tolerant (SPIKE-06:75 F-1); survives eviction, failed update, incompatible dataset, BASELINE_ONLY (SPIKE-06:81); C8 asserts floor in readiness (SPIKE-05:43). |
| 2 | Emergency never requires internet | PASS (GREEN) | Floor path and T2 rendering have no network calls (SPIKE-06:91); sync is enhancement-only (ADR-001). |
| 3 | Failed update cannot destroy active valid dataset (DISCOVERY.md:230 C9) |
PASS (YELLOW) | A/B inactive-slot staging + single-txn activation + automatic rollback on readback fail (SPIKE-02:50 14 scenarios + X1–X3 PASS). YELLOW only because IDB atomicity under iOS jetsam is INFERRED/UNVERIFIED (SPIKE-01:75). Logic is proven. |
| 4 | Partially downloaded dataset can never become active (DISCOVERY.md:230 C8) |
PASS (GREEN at state-machine level, YELLOW end-to-end) | Per-file atomic staging + full verification before pointer flip + quarantine (SPIKE-02:29 ordering). Same device caveat as #3. |
| 5 | Integrity & authenticity verified before activation | PASS (GREEN) | Ordering: signature → compatibility/budgets → per-file SHA-256+size → schema → activate (SPIKE-02:29); verifier is sole decider (B-4). SPIKE-02:52 bad-sig/bad-hash cases PASS (keep old). |
| 6 | Shell/dataset compatibility explicitly checked | PASS (GREEN) | Dual check: schemaVersion ∈ shell.supportedRange ∧ appCompatibility (SPIKE-04:22); boot check prefers compatible slot else limited mode (ARCHITECTURE-DESIGN.md:688); ordering rule C-23 prevents most skew (ARCHITECTURE-DESIGN.md:690). |
| 7 | OFFLINE READY = actual local availability, not connectivity (DISCOVERY.md:230 C10) |
PASS (GREEN) | Predicate C1–C8 is evidence-based and time-independent (SPIKE-05:28 + SPIKE-05:47); states READY/PARTIAL/NOT_READY/RECOVERY/BASELINE_ONLY/FAILED are proven local evidence (SPIKE-05:60). |
| 8 | GPS optional (invariant 9) | PASS (GREEN) | V1 has no GPS dependency at all; POI lat/lng hook preserved only (SPIKE-03:112, ARCHITECTURE-DESIGN.md:540/565). |
| 9 | Schedule works offline (DISCOVERY.md:132 R-S1…S4, R-O1…R-O5) |
PASS (GREEN) | Schedule is signed section in active slot; all queries (Now/Next, My Schedule, filters/search) are local (ARCHITECTURE-DESIGN.md:508); time model works offline (SPIKE-08:43). |
| 10 | Map works offline (DISCOVERY.md:172 R-M1, R-O1) |
PASS (YELLOW) | Raster WebP base + POI overlay all local; Facilities list is first-class GPS-free access (SPIKE-03:79). YELLOW pending device performance/decoding (SPIKE-03:116). |
| 11 | Festival info works offline (DISCOVERY.md:182 R-F1) |
PASS (GREEN) | Info is signed required section (SPIKE-04:30); renderer maps structured nodes → safe DOM (C-22). |
| 12 | Favorites/My Schedule work offline | PASS (GREEN) | lumen-user store separate; never touched by updates/rollback/GC (B-6, SPIKE-02:70 X3 favourites survive) |
| 13 | No V1 feature depends on mesh (DISCOVERY.md:268 NR-1, invariant 11) |
PASS (GREEN) | V1 builds no mesh; seam costs one interface (ADR-012). |
| 14 | Future mesh has defined extension boundary | PASS (GREEN) | Transport seam (ADR-011) + signed-payload rule + immutable versioned payloads (ADR-012); location per ARCHITECTURE-DESIGN.md:704. |
| 15 | No user accounts in V1 (DISCOVERY.md:270 NR-4) |
PASS (GREEN) | No auth, no server state; favourites device-local (ADR-010). |
No invariant requires new architecture. Two are YELLOW solely due to device-dependent platform behaviour already isolated and queued for physical testing.
4. Physical-device validation matrix
4.1 Device definitions
| ID | Device | OS/browser | Why it matters |
|---|---|---|---|
| D1 | iPhone low-bound | iOS 16.4 Safari (tab + installed PWA) | Baseline policy A-06; worst WebKit for storage/Persist/Intl |
| D2 | iPhone latest | iOS latest Safari installed PWA | Current WebKit reality; EU variability sanity check |
| D3 | Low-end Android | 2021 mid-range, Chrome ~110+ (and latest if different) | Performance budget target (ARCHITECTURE-DESIGN.md:851); 2048 texture-cap cohort |
| D4 | Modern Android | Current flagship Android, Chrome latest, installed PWA | High-end correctness + Chrome persistence auto-grant |
All tests are manual scripted scenarios per ARCHITECTURE-DESIGN.md:838. Expired context after 24h; bugs found on device that harness missed become harness cases (ARCHITECTURE-DESIGN.md:843).
4.2 Legend
Severity: BLOCKING = cannot ship/recurring data loss or safety impact; HIGH = major degradation; MEDIUM = degraded UX/workaround exists; LOW = polish. BLOCKING tests must pass before public festival use.
4.3 Matrix (19 groups — each has 4 device columns; shared steps where identical)
T01 — First visit
- Objective: Shell loads over HTTPS, SW registers, status is NOT_READY, emergency floor renders.
- Preconditions: Site data cleared for origin; normal (non-private) browsing; HTTPS.
- Steps: 1) Open
https://<origin>/2) Wait for load 3) Check status chip 4) Open Emergency. - Expected: Shell ≤1 MB loads quickly; SW installed; chip NOT_READY / "Get festival data"; emergency shows BASELINE with version and address/coordinates/procedures.
- Pass: All visible within target (<2 s emergency usable on D3 per
ARCHITECTURE-DESIGN.md:851) with no console errors. - Severity: BLOCKING. Applies: D1–D4.
T02 — Installation
- Objective: Installed-first flow produces persistence-exempt, standalone launch.
- Preconditions: First visit completed.
- Steps (D1/D2 iOS): Share → Add to Home Screen → launch from home screen → check
display-mode: standalone/navigator.standalone. (D3/D4 Android): triggerbeforeinstallpromptwhere available → install → launch standalone. Attempt "install first (recommended)" and "get data now" paths perSPIKE-07:58. - Expected: Standalone launch with no browser chrome;
viewport-fit=coversafe-area respected; install-state detectable; coach dismissible but re-surfaced until installed or READY. - Pass: Standalone detection true when launched from home screen; tab still functional when dismissed.
- Severity: BLOCKING (iOS). HIGH (Android — eviction risk lower). Precondition note: failure to install must not block use (
SPIKE-07:48FP-1) — verify degraded-persistence warning appears in tab mode.
T03 — Offline launch
- Objective: Cold start with no network serves shell from Cache Storage + data from IDB; no error walls.
- Preconditions: READY state achieved (see T09). Then enable airplane mode.
- Steps: Kill app/browser → airplane ON → launch PWA from home screen → navigate all 4 destinations + Status.
- Expected: Every screen renders from local stores; status READY (or PARTIAL if L1 only); no spinner that never resolves; light boot check ≤~150 ms (
SPIKE-05:83). - Pass: Emergency, Schedule, Map, Festival all usable offline; no network calls in devtools.
- Severity: BLOCKING. D1–D4 airplane mode.
T04 — Airplane mode (festival simulation)
- Objective: Full festival day in airplane mode — schedule Now/Next, filters/search, favorites, facilities list all local.
- Preconditions: READY. Airplane ON for entire test.
- Steps: Browse schedule by day (dayKey groups), test Now/Next at several simulated times, toggle stage/type filters, use shared search, add favourites, open map + facilities list, follow emergency dial/text paths (do not actually dial).
- Expected: All of the above work with zero connectivity. Favorites persist after kill/restart (write-through).
- Pass: No feature silently disabled without indication (FA-1 requirement,
DISCOVERY.md:653). - Severity: BLOCKING. D1–D4.
T05 — Browser restart
- Objective: Browser/process kill does not lose committed data.
- Preconditions: READY.
- Steps: Force-quit browser (swipe away) or
chrome://restartequivalent → relaunch PWA. - Expected: State intact; data verified via light check; favourites preserved.
- Pass: READY without re-prep.
- Severity: BLOCKING. D1–D4.
T06 — Phone restart
- Objective: Device reboot preserves committed dataset and shell; SW re-registers; cold-start budget met.
- Preconditions: READY.
- Steps: Reboot device → launch PWA → measure cold start.
- Expected: Data intact; SW rehydrates; emergency usable ≤2 s and interactive ≤3 s on D3 (
ARCHITECTURE-DESIGN.md:851); boot verification is the detection, not prevention (SPIKE-01:154P7). - Pass: Meets budgets; no re-prep needed. UNVERIFIED on iOS until measured (
SPIKE-01:94). - Severity: BLOCKING. D1–D4.
T07 — Storage persistence (persist() / exempt)
- Objective: Installed PWA gets favourable persistence signalling; 7-day tab vs installed behaviour is honest.
- Preconditions: Fresh profile.
- Steps: Fresh install → prep READY → query
navigator.storage.persist()(grant) andpersisted(). Separately: prepare same origin in Safari tab (D1) and leave unused 7+ days vs installed PWA — observe eviction honesty. - Expected: D4 auto-granted; D1/D2 heuristic grant when installed (best-effort, never relied upon per
ARCHITECTURE-DESIGN.md:285); tab data evictable per PW-1, installed exempt perASSUMPTIONS-AND-OPEN-QUESTIONS.md:44B-01 — but must be confirmed on our test devices (SPIKE-01:162). - Pass: Grant outcome recorded; missing grant does not block use — design never relies on it (P6). Failure of the exemption claim would be BLOCKING on iOS.
- Severity: HIGH (grant itself) / BLOCKING if exemption fails. D1–D2 primary.
T08 — Storage pressure
- Objective: QuotaExhaustedError path keeps active dataset; guidance shown; baseline still works.
- Preconditions: READY. Device storage deliberately near-full (or devtools fill +
storage.estimate()low free space). - Steps: Attempt new staging (publish new packageVersion) with free < 2× package; then attempt with free just barely enough; observe behaviour when writing blobs near quota.
- Expected: Pre-check refuses staging if free < 2× size (P4) with plain guidance; mid-write QuotaExceeded aborts staging, active untouched (P3) —
SPIKE-02:60hash/schema/validation failures map to keep-old. - Pass: No crash, no partial activation; Status shows guidance; emergency floor intact. UNVERIFIED on both platforms (
SPIKE-01:56). - Severity: BLOCKING. D1–D4.
T09 — Dataset update (happy path)
- Objective: Modern monotonic update stages, verifies, atomically activates, shows READY with new version.
- Preconditions: READY vN. Staging origin has vN+1 published and signed; online.
- Steps: Open app → online hint fires → fetch
latest.json→ fetch manifest → signature/compatibility/budget checks → stage sections in priority orderemergency→schedule→info→map(SPIKE-07:39) → per-file hash → full verification → atomic flip → READY confirmation. - Expected: New version active after single txn; old slot retained for rollback; status shows version/generatedAt/fetchedAt; quarantine for rejected versions.
- Pass: EXP-2 S10 PASS analog on device; favourites survive (X3); bloated version rejected by budget gate.
- Severity: BLOCKING. D1–D4.
T10 — Interrupted update
- Objective: Every interruption point preserves the old valid dataset; resume works.
- Preconditions: READY vN, staging a new vN+1.
- Steps (repeat, kill at different points): Kill app between files (staged 0/3/5), kill mid-file, kill before activation, kill during activation txn, kill immediately after flip but before readback — as modelled in
SPIKE-02:52S01–S14. Also: connectivity lost mid-prep, phone reboot before activation. - Expected: Before flip → old pointer stands; during flip → txn uncommitted, old stands; after flip → new stands and next-boot light verify confirms or rolls back (
SPIKE-02:64); progress persisted per file for resume (SPIKE-01:145P1); staging older than 7 days discarded (ARCHITECTURE-DESIGN.md:669). - Pass: No scenario exposes a partial dataset; boot always yields READY (old or new) or RECOVERY+baseline if both slots lost (X2). UNVERIFIED on iOS jetSAM kill (
SPIKE-01:75). - Severity: BLOCKING. D1–D4.
T11 — Recovery (eviction / corrupt / missing)
- Objective: Every "data is gone/wrong" state enters RECOVERY (or BASELINE_ONLY) with honest messaging and a one-tap fix.
- Preconditions: READY.
- Steps: 1) Simulate eviction: Settings → clear site data (or devtools → delete databases/caches) → relaunch. 2) Simulate corrupt: manually corrupt active slot (test harness analog
SPIKE-02:225X1/X2) → relaunch. 3) Private-mode launch or storage blocked. 4) Incompatible shell/dataset: launch old data with new-shell supportedRange (ARCHITECTURE-DESIGN.md:688). - Expected: Missing/corrupt → RECOVERY with reason
missing/corrupt/incompatible, emergency floor works; other slot's fallback served when intact (X1); both gone → RECOVERY+baseline+favourites logic preserved (SPIKE-02:69). BASELINE_ONLY when storage is entirely unavailable (SPIKE-05:68). FAILED(activation) distinct from RECOVERY for repeated txn failures (SPIKE-05:62). - Pass: No blank screen; correct chip colour; "Restore festival data" or "Check my data" affordances present (
SPIKE-05:62). - Severity: BLOCKING (eviction/corrupt); HIGH (private-mode guidance). D1–D4.
T12 — Map rendering
- Objective: Map base decodes within memory/performance budgets and renders legibly.
- Preconditions: READY with real map art.
- Steps: Launch Map with overview already staged; measure initial load; verify budgets: overview ≤1600 px / ~7.3 MB decoded, detail ≤3072 px / ~27 MB, total ≤~35 MB, at most one detail resident (
SPIKE-03:58F-1,ARCHITECTURE-DESIGN.md:855). - Expected: Overview renders immediately (~0.5 MB WebP); detail lazy-decodes on first zoom-in; no 48–64 MB single bitmap; object URLs revoked on level switch (F-3).
- Pass: Decode time acceptable; no OOM/jetsam; dimension caps enforced by pipeline.
- Severity: BLOCKING (OOM/fails to render) / HIGH (quality). D3 primary, D1–D2 sanity.
T13 — Map interaction
- Objective: Pan/zoom via pointer-events + pinch meets fps and hit-target and a11y contract.
- Preconditions: READY; 200 POIs visible.
- Steps: 1) Pan/zoom sustained 10 s, observe frame drops 2) Overview↔detail↔overview cycling, watch
performance.memory(Chrome) / Instruments (iOS) for leaks 3) Dark-mode filterbrightness(.72) saturate(.85)(SPIKE-03:88) visual check 4) Older 2048-texture-cap path on D3 low-end vs 3072 image 5) Tap POI → detail sheet, test category chip filtering, search highlight. - Expected: ≥30 fps sustained (
ARCHITECTURE-DESIGN.md:854); no leaked object URLs; screen readers (TalkBack/VoiceOver) traverse POI buttons and Facilities list (SPIKE-03:120protocol); fallback overview-only mode engages if detail path janks. - Pass: All six items in
SPIKE-03:116protocol PASS. - Severity: BLOCKING (fps/leak), HIGH (a11y), MEDIUM (filter). D3/B1 primary.
T14 — GPS (V1 absence)
- Objective: App requests no location permission and shows no blue dot; map/facilities work GPS-free.
- Preconditions: Fresh profile; READY.
- Steps: Open app → verify no permission prompt → map → facilities list; optionally deny location if prompted by OS.
- Expected: Zero permission requests (
ARCHITECTURE-DESIGN.md:222); last-known behaviour from earlier discovery R-M4 narrowed to no-GPS in V1 (ARCHITECTURE-DESIGN.md:18); optionallat/lnghook does not affect UX. - Pass: No prompt; finding facilities never requires location; available without consent.
- Severity: MEDIUM (UX trust). D1–D4.
T15 — Incorrect device clock
- Objective: ClockService corrects when online, warns honestly when not, never bricks READY.
- Preconditions: Two runs — (a) never-online device, (b) device later brought online so skew captures.
- Steps (per SPIKE-08 §5): 1) Render July event → expect 01:00 PM CDT on
America/Chicago(T1a) 2) Set device clock +30 min → open online → capture skew fromDateheader → go airplane → check Now/Next uses corrected time 3) Mid-session bump clock +10 min → expect drift warning 4) Set clock to month before festival → no warning; then +60 days past end → warning shown (F-3 suppressed-until-near) 5) Airplane + clock wrong by 2h at festival-time → warning, schedule still readable. - Expected: Corrected Now when skew exists; warning only near festival or when evidence of skew exists (
SPIKE-08:104F-3); READY unaffected by clock (SPIKE-05:47time independence); never-online wrong clock is accepted residual (SPIKE-08:124). - Pass: Behaviours match lines above; JSC parity with V8/ICU at DST edges (T3a–T3d).
- Severity: HIGH (festival-time correctness), MEDIUM (early-prep noise), LOW (never-online residual accepted).
T16 — Service worker lifecycle
- Objective: Tiny SW (precache + navigation fallback only) survives iOS killing; page-context downloads are unaffected.
- Preconditions: READY; staging not required for basic check.
- Steps: Simulate SW idle kill (devtools terminate SW, or background app for 30 s on iOS) → stage a package in document context → kill SW again → resume staging → verify page progress persists.
- Expected: C-21 respected: dataset staging runs in page context, not SW (
ARCHITECTURE-DESIGN.md:83); SW handles only install/activate/fetch (ARCHITECTURE-DESIGN.md:243); operations are idempotent/re-runnable; no dataset state in SW (SPIKE-01:157P8); activation is next-start, notclients.claimmid-session (ARCHITECTURE-DESIGN.md:256). - Pass: Stage completes regardless of SW death; no corrupted slot.
- Severity: BLOCKING (if SW death could corrupt — must not). D1/D2 iOS primary.
T17 — Application shell update
- Objective: New shell installs under new cache name; activates on next full start; compatibility with both slots checked; mid-session not disrupted.
- Preconditions: READY vX. New shell vY available (different
lumen-shell-v<build>). - Steps: Fetch new shell → install new SW → verify old SW still serves current session → full restart → observe
SKIP_WAITING/ next-start activation → boot compatibility check (ARCHITECTURE-DESIGN.md:688§18.5) → prefer compatible slot, else limited mode with update guidance. - Expected: Old session uninterrupted; compatibility envelope publishing rule C-23 honoured (schemas
{1,2}range, datasets only after shell window, freeze 7 days before festival —ARCHITECTURE-DESIGN.md:690§18.6). - Pass: No blank screen; no incompatible dataset exposed.
- Severity: BLOCKING. D1–D4.
T18 — Dataset compatibility
- Objective: Incompatible packageVersion or schemaVersion never activates.
- Preconditions: READY. Staging server publishes v incompatible with this shell.
- Steps: Attempt sync → observe manifest
appCompatibility/schemaVersioncheck before download (SPIKE-02:29step 4) → attempt older monotonic version. - Expected: Incompatible → reject + quarantine + diag; "please update app" when
minAppVersion > current(ARCHITECTURE-DESIGN.md:668); lower version from network never downgrades (ARCHITECTURE-DESIGN.md:349monotonic rule, SPIKE-02 F-5). - Pass: Active untouched; READY stays; no refetch loop.
- Severity: BLOCKING. D1–D4.
T19 — Emergency baseline availability
- Objective: Every failure path still renders emergency floor with provenance and dial/text paths.
- Preconditions: Enumerate: never-prepared (NOT_READY), evicted (RECOVERY), corrupt (FAILED), incompatible, storage-blocked (BASELINE_ONLY), offline, update mid-flight.
- Steps: In each state, open Emergency → verify contents (emergency number + security/first-aid summaries + muster/exits + procedures), labels (
BASELINE v<...>vsFESTIVAL DATA v<package>perSPIKE-06:62), forward-tolerant rendering (unknown fields ignored),tel:primary + copyable text, explicit tap to dial, zero IDB access required for floor path. - Expected: Floor always renders; highest compatible tier shown; 16 KB cap enforced by pipeline (
ARCHITECTURE-DESIGN.md:367); single-source generation prevents drift (ARCHITECTURE-DESIGN.md:345). - Pass: No state shows blank emergency;
tel:works on standalone (D1/D2) where possible, else number copyable (SPIKE-06:81); one-tap away from every screen via persistent nav. - Severity: BLOCKING (life-safety). D1–D4, with
tel:verified separately perSPIKE-01:171.
Total tests: 19 groups × 4 device families = 76 device-tests before considering iOS tab vs installed splits for T07/T02 (handled as sub-cases). Every BLOCKING test must pass before a production festival deployment.
5. Known weaknesses
Weaknesses are not hidden; each has owner/category and whether it blocks implementation. Per ARCHITECTURE-DESIGN.md:897 declared weaknesses W-1…W-6 and ARCHITECTURE-DESIGN.md:863 RK-1…RK-10.
W-1 — Installation / bootstrap dependency
- Risk: Offline-first requires one successful online bootstrap (shell + dataset) before arrival (
DISCOVERY.md:252C-17). Attendees who never complete prep arrive with L0 floor only (SPIKE-07:66minimum safe). - Impact: HIGH — without prep the core promise (schedule/map/info) is not met, even though emergency still works.
- Likelihood: Medium (depends on distribution effectiveness —
DISCOVERY.md:346OQ-3,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:60A-05). - Mitigation: L0–L2 with ordered/prioritised resumable downloads, honest PARTIAL messaging, tab-before-install allowed, QR/short-URL campaign, install coach with visuals (
ARCHITECTURE-DESIGN.md:428), one-tap re-prep, organizer physical fallback (PA/signage/handout —DISCOVERY.md:324AMB-1,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:64O-05). - Residual: Medium — a minority of unprepared attendees remains inevitable; architecture softens but cannot eliminate it.
- Owner/category: Product/Ops (distribution) + Architecture (prep UX).
- Blocks implementation? No — invalidates product promise only for the unprepared cohort, not the build. Flagged as RK-2.
W-2 — Browser storage is best-effort; eviction can never be prevented
- Risk: Storage may be wiped under pressure or by ITP/policy (
DISCOVERY.md:429PW-1, PW-3, PW-7). Eviction is silent and all-or-nothing (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:45B-03). - Impact: HIGH — festival data lost.
- Likelihood: Low for installed PWAs with persist granted and regular use (
SPIKE-01:105), Medium for tab usage or near-full devices (SPIKE-01:56). - Mitigation: Install-first exempt,
persist()request (P6, heuristic only), lean footprint (≤40 MB, 2× ≤80 MB bounded), free-space pre-check 2× (P4), boot light verification as detection (SPIKE-01:154P7), RECOVERY state with one-tap re-prep, embedded emergency floor survives everything (SPIKE-06:81). - Residual: Low-medium — loss remains possible, recovery is fast and honest.
- Owner/category: Platform (browser) + Architecture (detection/recovery).
- Blocks implementation? No — detection/recovery is the architecture. Device tests prove the path.
W-3 — Never-online incorrect device clock
- Risk: Wrong clock makes Now/Next wrong without detection when device has never been online to capture skew (
SPIKE-08:124,DISCOVERY.md:452OF-7). - Impact: Medium (misleading "now" during festival).
- Likelihood: Medium (some devices drift; airplane mode prevents correction).
- Mitigation: Persisted server offset when any sync occurs (
SPIKE-08:34), monotonic anchor for mid-session jumps (SPIKE-08:35), sanity window warning near festival (F-3 suppressed-until-near,SPIKE-08:104), absolute times always shown alongside Now markers (ARCHITECTURE-DESIGN.md:537). - Residual: Medium for the never-online subset — accepted per
DISCOVERY.md:525OF-7 andARCHITECTURE-DESIGN.md:898W-2. No network fix exists offline. - Owner/category: Architecture (ClockService) + UX (warning/absolute times).
- Blocks implementation? No.
W-4 — Physical-device verification requirements
- Risk: This Linux-host validation is spec/policy-level; iOS JSC parity, IDB atomicity under jetsam, SW re-registration, quota-near-full writes, BLOB read-back, fps/decode,
tel:in standalone are all UNVERIFIED (SPIKE-01:59,SPIKE-03:116,SPIKE-08:152,experiments/README.md:28). - Impact: HIGH if any assumption fails on target hardware.
- Likelihood: Unknown until measured.
- Mitigation: Narrow device matrix (§4) runs before production; budgets and fallbacks (overview-only, detail tile-split DD-9, Preact reconsider trigger) are predefined.
- Residual: Unknown pre-device; zero post-device if protocol passes.
- Owner/category: Architecture Validation / QA.
- Blocks implementation? Blocks production declaration, not implementation start — staging work proceeds on provisional origin with fixtures (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:128).
W-5 — Emergency content correctness and sign-off
- Risk: Wrong security number, moved AED/muster point, or unapproved procedure text is safety-critical (
DISCOVERY.md:468EM-1,DISCOVERY.md:345OQ-2). - Impact: Critical.
- Likelihood: Low-medium without a gate.
- Mitigation: Single emergency source sheet → both baseline and dataset section (no drift,
ARCHITECTURE-DESIGN.md:345), ≤16 KB cap keeps scope life-safety-minimum, signed dataset section + provenance stamps on every screen (SPIKE-06:60), sign-off gate with named approver and legal review (ASSUMPTIONS-AND-OPEN-QUESTIONS.md:62O-02,DISCOVERY.md:352OQ-7), version/updatedAt in manifest. - Residual: Low with gate enforced; high without it.
- Owner/category: Content/Ops (organizers) + Pipeline.
- Blocks implementation? Blocks emergency content publish, not scaffolding (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:128).
W-6 — Map asset quality / organizer-provided artwork
- Risk: Organizers may not have raster art at required quality/dimensions; POI placement quality varies (
DISCOVERY.md:328AMB-3,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:105OQ-6). - Impact: Medium (map degrades to facilities list).
- Likelihood: Medium (illustrated raster most likely, quality unknown).
- Mitigation: Architecture accepts raster as-is; pipeline POI tap-tool/normalized coords (
ARCHITECTURE-DESIGN.md:560), WebP budgets with margin (F-2), predefined fallbacks overview-only and 2×2 tile-split (SPIKE-03:98), SVG flip if vector supplied, facilities list is first-class (SPIKE-03:79). - Residual: Medium — list view compensates but artwork quality caps experience.
- Owner/category: Content/Ops + MapService.
- Blocks implementation? No — placeholder art unblocks development (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:128).
W-7 — Vanilla-TS hand-rolled UI accretion
- Risk: No framework safety net; view-layer complexity could accrue bugs (
DISCOVERY.md:863RK-7,ARCHITECTURE-DESIGN.md:898W-5). - Impact: Medium.
- Likelihood: Medium as feature count grows.
- Mitigation: Small surface (4 destinations + status), strict layering B-1…B-7 (
ARCHITECTURE-DESIGN.md:195), small store/router, unit tests, reconsider trigger → Preact (ARCHITECTURE-DECISIONS.md:117) at first sign of sustained interdependence. - Residual: Low with discipline.
- Owner/category: Architecture/Implementation.
- Blocks implementation? No.
W-8 — Bundled pure-JS Ed25519 verifier supply chain
- Risk: Verifier library must be audited and stay tiny; WebCrypto Ed25519 not baseline on iOS 16.4 (
ARCHITECTURE-DESIGN.md:14,DISCOVERY.md:401TQ-9). - Impact: High if verifier is wrong/pulled; medium on bundle.
- Likelihood: Low with pinned audited dep.
- Mitigation: Pin, audit, bundle-budget gate (≤150 KB gz JS), WebCrypto SHA-256 + pure-JS Ed25519 chosen correctly per spikes; CSP restricts to self.
- Residual: Low pending audit (AQ-06).
- Owner/category: Security + Build.
- Blocks implementation? Blocks production signing declaration, not scaffolding (staging uses test key).
W-9 — Signing key custody / rotation
- Risk: Loss of the offline festival signing key or compromised publish credentials push bad data (
ARCHITECTURE-DESIGN.md:863RK-6,DISCOVERY.md:616SE-1/SE-2,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:85S-01, AQ-21). - Impact: High.
- Likelihood: Low with process.
- Mitigation: Offline key, sealed backup, singular publisher + deputy (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:60O-01), fingerprinted key set in shell for rotation, repo audit trail, rollback via new higher version, rotation drill pre-festival. - Residual: Low with runbook + drill.
- Owner/category: Ops/Security.
- Blocks implementation? Blocks production publishing, not dev.
W-10 — Single-origin availability
- Risk: All shell+data on one HTTPS origin (
DISCOVERY.md:261C-20); CDN outage concentrates risk (ARCHITECTURE-DESIGN.md:863RK-10,ARCHITECTURE-DESIGN.md:898W-6). - Impact: Low (updates are enhancement-only; last-good datasets already on devices).
- Likelihood: Low (static CDN).
- Mitigation: Static files trivially mirrorable; Transport seam also future-mitigates via LAN mirror (
ARCHITECTURE-DESIGN.md:920DD-8); origin chosen deliberately before public link (AQ-18). - Residual: Low.
- Owner/category: Ops/Deployment.
- Blocks implementation? No.
6. Architecture freeze test
20 adversarial scenarios — each: what happens, what remains functional, acceptability, any required architecture change, V1 impact. All answers are after incorporating spike changes.
-
User never installs Lumen. Tab-only on iOS. Shell works while open; storage subject to 7-day ITP eviction (
DISCOVERY.md:429PW-1). Coach re-surfaces; degraded-persistence warning shown (SPIKE-07:48). Emergency L0 floor always works after any shell load; L1/L2 are available until evicted then RECOVERY. Acceptable: no technical fix inside web platform — install is the fix (ARCHITECTURE-DESIGN.md:880). No new architecture. V1: tab path stays. -
User opens Lumen once and never connects again. Never stages dataset. After first shell load: L0 floor only; NOT_READY state honest, not blank (
SPIKE-07:66). Map/info show "not downloaded" cards. Acceptable: expectation management is product/ops (distribution before arrival + physical fallback,SPIKE-07:66W-1). No new architecture. V1 unchanged. -
iOS evicts storage. Per-origin all-or-nothing, silent. Boot light check (sizes + verification-record match,
SPIKE-05:83) fails → RECOVERY with reasonmissing; emergency floor intact (SPIKE-06:81); one-tap re-prep when online; favourites in user DB survive unless user DB was also evicted (then RECOVERY+baseline,SPIKE-02:69). Acceptable: eviction can never be prevented (ARCHITECTURE-DESIGN.md:898W-4), only detected/recovered. No new architecture beyond implemented detection. -
Browser kills the service worker during an update. SW holds no dataset state (C-21, P8). Staging runs in page context (
SPIKE-01:83). Page persists per-file progress; resume path defined (SPIKE-02:50S03). SW kill cannot corrupt staging (ARCHITECTURE-DESIGN.md:882). Acceptable. No change. -
Page is killed during a dataset transaction. IDB transactions are atomic per database (spec CONFIRMED,
SPIKE-01:60); an uncommitted write leaves last committed intact (SPIKE-01:111). Single short txn per file (P1, F-1); activation single txn onlumen-system(P2, F-2). Kill before flip → old stands; during flip → uncommitted, old stands; after flip before readback → new stands but readbackPending guard (SPIKE-02:97) covers next-boot recheck. Acceptable pending iOS jetsam atomicity UNVERIFIED (SPIKE-02:111). V1: document and verify on device. No new architecture if device tests pass; if fails, architecture fallback is re-prep path plus retry-once behaviour (ARCHITECTURE-DESIGN.md:672). -
Device reboots during a dataset update. Same as #5 at OS level. Committed IDB survives browser/device reboot; SW/Caches rehydrate (
SPIKE-01:90). Cold-start path is budgets-only (ARCHITECTURE-DESIGN.md:848§27). Acceptable. No change beyond device-measured cold-start timing. -
Festival dataset is corrupt (bits wrong). Per-file SHA-256 fails → discard staging, quarantine version, keep active (
SPIKE-02:60S06,ARCHITECTURE-DESIGN.md:667). Corruption discovered post-activation by readback → automatic rollback when fallback slot intact, else RECOVERY+baseline (SPIKE-02:50S14/X1/X2). Never exposes partial dataset (invariant 4). Acceptable. No change. -
Dataset has valid hash but invalid signature (MITM / compromised hosting, DISCOVERY SE-1). Signature checked over exact manifest bytes before parsing or downloading anything else (
SPIKE-02:29step 3). Ed25519 verifier rejects → quarantine, never fetch files (SPIKE-02:60S07), diag entry. Organizer rollback via new higher signed version. Acceptable. No change. Future transports inherit the same verifier (SPIKE-02:26composition). -
Dataset incompatible with shell (schema or appCompatibility). Checked before staging and at boot (
ARCHITECTURE-DESIGN.md:688). Rejected + "please update app" whenminAppVersion > current; boot prefers compatible slot else limited mode (floor+status only). Publishing rule C-23 (shell range first, dataset after, freeze 7 days before festival) prevents most cases. Acceptable. No change. -
User has incorrect device clock. If any online sync occurred,
ClockService.now()uses persisted skew (SPIKE-08:34); mid-session jumps flagged by monotonic anchor (SPIKE-08:35). Sanity window warning is suppressed until near festival or evidence of skew (F-3) so early prep is quiet (SPIKE-08:97). READY is time-independent (SPIKE-05:47), so wrong clock never bricks availability. Never-online wrong clock is accepted residual — absolute times remain readable (SPIKE-08:124). Acceptable. No new architecture; NTP endpoint rejected (C-19). -
GPS denied. No permission requested in V1 (
ARCHITECTURE-DESIGN.md:222). All map functionality is GPS-free; Facilities list is the primary non-visual path (SPIKE-03:79). Acceptable. No change. -
GPS unavailable. Identical to #11. Hook
lat/lngon POIs preserved for future blue dot without touching features (ARCHITECTURE-DESIGN.md:540). No change. -
Festival changes schedule after preparation. New packageVersion published, monotonic. Device fetches on next open-while-online opportunistically (
SPIKE-02:29); moved/cancelled markers render distinctly (ARCHITECTURE-DESIGN.md:509); datasetgeneratedAt/fetchedAtshows staleness. Users who never come online keep old schedule — accepted and mitigated by organizer PA/boards (DISCOVERY.md:460OF-8). No new architecture. Urgency beyond pull would be DD-1 annoucements (reserved, not required for core promise). -
Emergency information changes after preparation. Tier-2 dataset emergency section updates via new signed package; floor frozen at shell build remains as fallback (
SPIKE-06:93). Provenance labels show age; organizer physical channels remain urgent path (A-16,ARCHITECTURE-DESIGN.md:891W-3). Acceptable. No change. -
User arrives with no usable festival dataset (NOT_READY). Shell + floor guarantee L0; PARTIAL levels each fully functional (
SPIKE-07:66); Status enumerates exactly what's missing; one-tap prep if any connectivity appears; physical fallback at venue. Acceptable — distribution is product/ops problem (W-1). No new architecture. -
User has low storage.
storage.estimate()pre-check refuses staging if free < 2× package with guidance; QuotaExceeded aborts and keeps active (P3/P4); L0/L1 partial still possible when full package won't fit (SPIKE-07:52FP-4). Baseline always works. Acceptable. No change. -
User has low-end Android phone. Vanilla TS + GPU-transform map + ≤1600/3072 caps + windowed lists + WebP keep within budgets (
ARCHITECTURE-DESIGN.md:848§27,SPIKE-03:58F-1). Fallback overview-only if decode/transform janks (SPIKE-03:98). Perf is YELLOW until device protocol inSPIKE-03:116passes. Acceptable pending device proof. No speculative complexity added. -
User uses iOS Safari without installing (tab). Same as #1. Works while open; eviction risk; no background sync;
tel:still standard but verify on standalone (SPIKE-01:171). Acceptable with honest messaging. No change. -
User's browser clears site storage (Settings → Clear). Identical to eviction path (FA-5/
DISCOVERY.md:460OF-13). Next boot → RECOVERY; baseline intact; re-prep path exists. Only shell reloads from network need connectivity. Acceptable. No change. -
Future mesh proves impossible inside a browser. Expected per feasibility analysis (
DISCOVERY.md:581,ARCHITECTURE-DESIGN.md:717§20) — no BLE peripheral on iOS, no ad-hoc Wi-Fi, no background sockets. The seam is transport-agnostic: any future delivery (native companion, hardware relay, LAN mirror implementing Transport) would attach as a bridge; V1 loses nothing because HTTP transport is complete product functionality, not a placeholder (ARCHITECTURE-DESIGN.md:89516). Acceptable. No V1 architecture stranded.
No scenario requires new architecture. Two scenarios expose device-dependent behaviour already tracked as UNVERIFIED (IDB atomicity under kill, map/GPU performance) — both have defined fallbacks.
7. Final architecture status
Incorporates all spike addenda. Status values per directive: GREEN = validated and suitable for implementation; YELLOW = architecturally acceptable but requires physical-device or operational validation; RED = must change before implementation (none).
| Decision | Status | Evidence | Remaining validation | Implementation impact |
|---|---|---|---|---|
| ADR-001 Offline-first | GREEN | SPIKE-05 time independence + SPIKE-07 L0–L2 + SPIKE-02 crash suite | None | Builds proceed as designed |
| ADR-002 PWA / install | GREEN (functional) / YELLOW (persistence) | Correct: install-first + single origin; L0–L2 formalised (SPIKE-07:31) |
persist() grants + 7-day tab vs installed timing on D1/D2 (SPIKE-01:159) |
Coach + honest tab warning required; no spec change |
| ADR-003 Frontend vanilla TS | GREEN (with trigger) | Chosen correctly; small surface; EXP-3 no disqualifying JS cost; boundaries B-1…B-7 keep data layer safe | Low-end render cost on D3; first implementation spike is the real proof (watch for sustained store→render complexity → Preact per ARCHITECTURE-DECISIONS.md:117) |
Hand-rolled store/router + hashed assets + precache manifest |
| ADR-004 Storage (IDB + Cache) | YELLOW (device-conditional) | Design-logic valid: transactions atomic (spec), A/B ordering proven 16/16 (SPIKE-02:50), P1–P8 normative |
Device tests SPIKE-01:159 §5: iOS jetsam atomicity, quota-near-full writes, Blob read-back on D1/D3 |
Must implement P1–P8 exactly; production-ready only after device pass; fallback = re-prep path already designed |
| ADR-005 Package (JSON + manifest) | GREEN (schema) / YELLOW (content) | JSON+hash+Ed25519 sufficient (F-5); required flag, no-expiration, emergency sub-versioning, user-data schema split added (SPIKE-04:203) |
Real content must pass 5 pipeline gates + budget enforcement (SPIKE-04:189); stable IDs pending source sample (AQ-20) |
Pipeline tooling + fixture packages; dayKey validated at publish |
| ADR-006 Atomic A/B updates | YELLOW (device-conditional) | State machine proven 14+3 scenarios PASS with F-1…F-4 (SPIKE-02:50 + X1–X3) |
IDB transaction atomicity under real kills is the sole platform assumption (SPIKE-01:75) |
Activation = one txn + verification record + readbackPending; rollback depth 1 is accepted trade-off |
| ADR-007 Emergency baseline (3 tiers) | GREEN | Fixed tier-1 contents + resolution/merge + zero-IDB path + same-source generation (SPIKE-06:12, SPIKE-06:60, SPIKE-06:75) |
Organizer content sign-off gate and real tel: on standalone (SPIKE-06:81) |
Generate floor + section from same sheet at build; 16 KB cap enforced |
| ADR-008 Map | YELLOW (device-conditional) | Raster+DOM correct choice; memory caps tightened 1600/3072 ≤~35 MB (SPIKE-03:58); object-URL (F-3) + dark filter (F-4) added |
Device protocol SPIKE-03:116 §7: fps ≥30, decode, no leaks, a11y, texture-cap fallback |
Lazy object URLs; one detail resident; facilities list first-class; no GPS/blue dot |
| ADR-009 Time model | GREEN (logic) / YELLOW (JSC parity) | 24/24 PASS on V8/ICU (DST, unusual zones, skew, Now/Next); F-3 sanity suppression until near (SPIKE-08:97); clarification F-4 (only Intl) |
JSC parity at DST edges + CDN Date observation + low-end timing on D1/D3 (SPIKE-08:152) |
ClockService.now() pure computation; no timers; read Date header opportunistically |
| ADR-010 Sync pull-only | GREEN | Pull on open/online/manual + monotonic + no background is the only viable model (C-19, B-06) | Online hint best-effort nature (not a bug) | SyncService + staging ordering; announcements schema reserved (DD-1) |
| ADR-011 Transport seam | GREEN | Minimal isAvailable/fetchPointer/fetchBytes succeeds for HTTP and any future byte-pipe; verifier-only truth |
None — deliberately tiny | One interface + HttpTransport; tests inject fault transport |
| ADR-012 Mesh strategy | GREEN (strategy) | Future-only; costs zero in V1; out-of-browser assumption documented; no mesh fails V1 | None — feasibility study deferred until concrete opportunity (DD-7) | Nothing built; docs only |
| ADR-013 Security | GREEN (model) / YELLOW (library audit) | Threat model TH-1…TH-11 covered with minimal parts; WebCrypto-SHA256 + pure-JS Ed25519 correct per TQ-9; CSP self only; privacy by design |
Bundled verifier audit + bundle gate (AQ-06); key custody/rotation drill (AQ-21, S-01) | Pin audited verifier; HSTS; no inline/eval; no secrets client-side (C-24) |
| ADR-014 Deployment (static CDN) | GREEN (topology) / YELLOW (provider) | Static immutable URLs + latest.json pointer + staging/production origins correct; cache headers per ARCHITECTURE-DESIGN.md:807 |
Provider + production origin choice (AQ-14, AQ-18) — not blocking dev; swap is cheap pre-users, costly post-users | Content repo + sign/publish pipeline + smoke re-verify |
No RED. All YELLOW are device-dependent or content/ops-dependent — no architecture must change before implementation. The only path that could turn a YELLOW to RED is a device test failure for which fallbacks are already defined (overview-only, tile-split, re-prep, Preact trigger).
8. Implementation gate
Recommendation: B. READY FOR IMPLEMENTATION AFTER SPECIFIED BLOCKERS
Base: actual remaining risks after incorporating every spike's accepted change. Physical-device testing is the only architectural-level remaining blocker for a production festival deployment; it does not block starting implementation against fixtures.
Blockers, distinguished
ARCHITECTURAL BLOCKERS — none.
No ADR requires redesign before code starts. The architecture is frozen at the design-logic level with all spike addenda incorporated. Starting implementation now will not incur speculative rework.
PHYSICAL-DEVICE VALIDATION — required before a production festival, not before implementation start
- P-D-1 — IDB atomicity under real kills on iOS (D1/D2). Verifies
SPIKE-01:75/SPIKE-02:111. BLOCKING for production. Runnable immediately on any iPhone once a staging shell exists. - P-D-2 — IDB quota/Blob behaviour near-full + persistence signals on D1–D4.
SPIKE-01:159items 4–6, 8–10. BLOCKING (quota path) / HIGH (persistence heuristics). - P-D-3 — Map fps, decode, memory, leaks, texture-cap fallback on D3 (+ sanity D1/D2).
SPIKE-03:116. BLOCKING for map production claim; facilities list already covers degraded path. - P-D-4 — Intl/JSC parity at DST edges, dayKey grouping, ClockService skew + monotonic drift, F-3 sanity suppression on D1/D2 + D3.
SPIKE-08:152. HIGH; early-prep vs near-festival distinction must read correctly. - P-D-5 — SW lifecycle (kill mid-staging) on iOS.
SPIKE-01:82C-21 +ARCHITECTURE-DESIGN.md:243. BLOCKING for invariant proof. - P-D-6 —
tel:from standalone on D1/D2 + D3.SPIKE-06:81,SPIKE-01:171. HIGH for safety. - P-D-7 — Cold-start / boot light-check timing on D3 (and iOS sanity).
ARCHITECTURE-DESIGN.md:851;SPIKE-05:83. MEDIUM/HIGH.
All are listed in §4 with severity. None invents new architecture if they pass; each has a documented fallback if they fail.
PRODUCT / OPERATIONS BLOCKERS — required before content/production, not before code
- O-1 — Emergency content sign-off + legal review (
DISCOVERY.md:345OQ-2/OQ-7,ASSUMPTIONS-AND-OPEN-QUESTIONS.md:62O-02, W-5). BLOCKING for emergency content publish. - O-2 — Distribution plan + on-site physical fallback (
DISCOVERY.md:346OQ-3, W-1). HIGH — determines unprepared cohort size. - O-3 — Production origin choice (AQ-18, AQ-14, A-11) + hosting provider/budget (OQ-8). Must be chosen deliberately before any public staging link because storage is origin-keyed; not blocking for development on provisional origin (
ASSUMPTIONS-AND-OPEN-QUESTIONS.md:116AQ-18). - O-4 — Publisher/deputy + signing key custody + rotation drill (O-01, O-02, S-01, AQ-16/AQ-17/AQ-21). BLOCKING for production signing; staging uses test key.
CONTENT / DEPLOYMENT REQUIREMENTS — parallelizable with implementation
- C-1 — Real map art at ≥1600 px (and optionally ≥3072) + POI sheet to prove budgets (
SPIKE-03:58,SPIKE-04:189). HIGH. - C-2 — Schedule sample to confirm dayKey, stable IDs / mint mapping, and changed-event handling (D-01, AQ-20,
SPIKE-04:189). HIGH. - C-3 — Staging dry-run festival with test edition + field devices (AQ-23). Strongly recommended pre-festival.
Can implementation begin?
Yes — immediately, on a provisional staging origin with fixtures. ASSUMPTIONS-AND-OPEN-QUESTIONS.md:138 explicitly states nothing above blocks shell, data-layer, sync/verifier, and feature modules against staging fixtures — by design. The only work that must complete before the festival is the physical-device protocol above plus the ops gates O-1…O-3; the architecture itself needs no further change.
End of Architecture Validation.