anthodev/lapin-futepublic Fork
⑂ develop
Commits
⬇ Clone ▾
git clone https://git.rickub.com/anthodev/lapin-fute.git
git clone ssh://git@rickub.com/anthodev/lapin-fute.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

[feature] Harden failures and compatibility #8

Closed@github-anthodev opened this issue Aug 29, 2026
  • @github-anthodev opened this issue · Aug 29, 2026

    🔢 Impact Version (SemVer)

    PATCH


    🌿 Suggested Branch

    feature/harden-failures/reliability/8


    📝 Description

    Harden the phone-only D2 path against missing/invalid/revoked personal keys, disconnection, source failure, 429, malformed/late responses, suspension, relaunch, and rapid input. PebbleKit JS (PKJS) owns direct PRIM HTTP, normalization, the phone-local 60-second cache/single-flight, and display preparation; there is no hosted backend. This ticket fixes useful-stale policy, proves the already-frozen event-only/60-second-cache rules, enforces secret-safe diagnostics, and supplies emulator, memory, message, idle, quota, and physical-battery evidence.


    ✅ Expected Behavior

    Every failure deterministically becomes UNCONFIGURED, STALE, or UNAVAILABLE; late data never crosses favorites. Only APP_OPEN, settled favorite selection, and manual Select can trigger PRIM requests, and the phone-local cache/single-flight prevents excess calls; cache-only list entry, watch recovery, and changed-commit reacquisition serve results from the phone cache — overview first, then the visible detail only, never an automatic traffic request. App exit is silent, rapid input remains bounded, secrets stay redacted, and measured watch memory/battery remain within the frozen D2 budgets (57,344-byte RAM: 3,584-byte stack, 41,472-byte slot arena, 12,288-byte chunk arena; 768-byte inbox, 192-byte outbox, queue depth 4).


    📋 Acceptance Criteria

    • Select/record maximum useful client-result age independently of the 60-second phone-local fresh TTL; preserve a complete result as visibly aged STALE only through that single PKJS-owned boundary and test immediately before/at/after.
    • Map missing key, miss-time invalid/revoked key, disconnect, network/timeout/source failure, 429, invalid/partial payload through frozen errors to UNCONFIGURED, STALE, or UNAVAILABLE in one tested table; never claim cache-hit key validation.
    • Enforce the fixed policy: only APP_OPEN, 500-ms-settled favorite selection, and manual Select trigger PRIM requests; every trigger consults the 60-second per-service phone-local cache; concurrent misses single-flight; cache-only list entry, watch recovery, and changed-commit reacquisition serve results from the phone cache (overview first, then the visible detail only — no automatic traffic request) without new automatic PRIM requests; no periodic/background trigger and no immediate retry.
    • Enforce D2 request binding and synchronization semantics: six favorites uniformly (no legacy 7/8 configurations exist); a changed DIFF (complete valid inventory plus nonzero NEED) may release the watch departure overview/traffic datasets and show synchronization loading while old committed metadata is retained until the DIFF commit; no-op/reorder stays warm; FULL remains destructive with already-opened FULL requiring manual recovery; late data never crosses favorites.
    • Treat 429 as terminal for the current event, preserve useful stale data, expose only frozen sanitized retryAfterSeconds, retain credential-free quota counters, and prove zero automatic retry burst.
    • Prove rapid Select presses and rapid Up/Down navigation produce at most the policy-permitted PRIM calls and no request for intermediate favorites.
    • Verify key remove/replace and 401/403 revocation on a forced cache miss: removal erases the local key and keeps favorites; invalidation redacts the key and directs the user to options.
    • Capture PKJS (phone-side) diagnostics with Authorization/apikey redacted and no key, favorites, search terms, config fragments, or AppMessage payloads.
    • Watch-closed idle verification — conditional, no redundant watching: reuse the exact existing lifecycle evidence when adequate and unaffected by this ticket's changes; only if watch-closed evidence is missing or affected, run exactly one targeted 120-second observation per emulator (each once) with the watch app closed, verifying zero PKJS XHR, AppMessage, Wakeup/Worker/timeline, or log activity on the phone side. The existing 600-second APP_OPEN idle proof does not count as watch-closed evidence.
    • Run a 50-refresh plus rapid-navigation soak on both emulators (emery and gabbro): stay inside the frozen D2 budgets above, with zero dropped Alloy AppMessages and no post-steady-state memory growth.
    • Run the frozen controlled 24-hour A/B scenario on the Pebble Time 2 (emery) physical watch and meet its battery budget; record firmware (≥4.32)/watch/app versions, conditions, activity counts, and before/after charge.
    • Pass the complete recovery flow on both emulators (emery and gabbro) and the Pebble Time 2 physical watch; attach boundary/fault/quota/throttle/idle/soak/battery/matrix/log-audit evidence before close.

    🔗 Affected Surface

    • No hosted backend and no new route: harden the PKJS direct-PRIM transport and phone-local cache around the frozen credential, error, and event contracts.
    • Consume sanitized PRIM quota/retry metadata without persisting credentials or adding a client/background retry scheduler.

    🧪 Tests to Add

    Type Coverage
    Unit/property tests Failure→state table, stale boundary, request/favorite/key binding, only-three-triggers invariant, 60-second boundary, no retry
    Integration fault/quota tests Missing/invalid/revoked key, disconnect/timeout/source/429/malformed/partial, rapid input, favorite/key change, late data, reconnect, changed-DIFF loading versus warm no-op/reorder, FULL destructive recovery
    Lifecycle/performance tests Suspend/relaunch, conditional 2-minute watch-closed zero activity (or equivalent existing evidence), 50-refresh heap/AppMessage/latency soak on both emulators
    Runtime battery matrix Complete recovery on both emulators and the Pebble Time 2; controlled 24-hour physical A/B against the frozen budget
    Security audit Captured PKJS/page logs and metrics contain no key, Authorization/apikey, fragment, favorite, search, or AppMessage data

    ⚙️ Constraints

    • Reuse API_KEY_REQUIRED, API_KEY_INVALID, INVALID_SERVICE, SOURCE_UNAVAILABLE, RATE_LIMITED, and INVALID_RESPONSE; amend the frozen D2 contract before adding codes/fields.
    • Validate against the frozen D2 contracts (domain schemaVersion 1, display wire v2, 19 application aliases, reserved SDK key 15025 kept separate) before exposing INVALID_RESPONSE.
    • STALE is a phone/watch view of the last complete useful result after fresh retrieval fails, never a fabricated or expired-response substitute; visibly mark age through one PKJS-owned boundary.
    • One policy source drives tests/runtime: three event triggers (APP_OPEN, 500-ms-settled selection, manual Select), 60-second fresh phone-local cache, useful-stale age, zero immediate retries.
    • PKJS operational counters are credential- and user-data-free; raw request headers/bodies are never logged.
    • Bilingual copy and verbatim user-facing labels remain intact through every error state.

    📊 Evidence Baseline (existing; do not re-run)

    Already proven on the pushed D2 commit d894f407473f (branch feature/redesign-watch-experience/watch/16; merged to develop via PR #17, merge commit a46bf24ac09c8c752207a60434830706b31320b9):

    • 214/214 host tests; emery and gabbro release builds pass (19 aliases, radio-modeled gate).
    • Native emulator fixture runs on both platforms: six-favorite sync, navigation, changed-DIFF loading with cache-only recovery, warm no-op/reorder, FULL manual recovery, and graceful retained reopen (NEED 0, no reseed).
    • 600-second APP_OPEN idle on both emulators with 0 new AppMessages and 0 fixture/network calls.
    • Bounded compacted-live chunk reserve 11.9466% (Emery) / 10.9701% (Gabbro) — bounded samples only, not exact transient/lifetime peaks and not the 50-refresh soak this ticket requires.

    Honest limitations: the detailed evidence files live in the local, git-ignored var/production-verification/ tree and are therefore not attached here; the public proof is the merged PR and the repository source itself. Not yet proven: physical watch/phone pairing and the 24-hour battery gate.


    🔀 Dependencies

    • Builds on delivered [feature] Deliver Pebble watch consultation (https://github.com/Anthodev/lapin-fute/issues/7, closed) and the pushed D2 phone-only commit d894f407473f (merged to develop via PR #17, merge commit a46bf24).
    • Reuse the error/cache behavior from [feature] Normalize live PRIM departures (https://github.com/Anthodev/lapin-fute/issues/4, closed) and restart/synchronization semantics from [feature] Persist and synchronize favorites (https://github.com/Anthodev/lapin-fute/issues/6, closed), now living in the PKJS phone-side implementation.

    ⚠️ Impact on Existing Behavior

    Hardens the event/state/request surface without changing its three triggers or introducing polling: explicit useful-stale, credential-recovery, quota, secret-redaction, memory, message, and battery guarantees on the phone-only path.


    🔍 Technical Context

    Required references

Sign in to comment.