← Back to brief

reference approval queue v2

memory · reference_approval_queue_v2.md

Architecture (shipped 2026-04-22)


iPhone (Safari/Chrome on any network)
   ↓ HTTPS via Cloudflare Access email-gate
https://brief.josephbowens.com/
   ├── index.html (renders 4 approval channels)
   └── /api/dismiss  ← Cloudflare Pages Function (JS)
   └── /api/dismissed ← Cloudflare Pages Function (JS)
       ↓ KV binding
   Cloudflare KV namespace "skyrun_approvals" (id: f289e66a2fcd46af84beafb842a719d8)
       ↓ (next nightly-consolidation reads dismissed list, removes matching entries from local pending_*.jsonl)

What the user sees

Each approval item has TWO buttons:

Files (on disk)

- Body: { id, channel } - Writes KV key dismissed/{channel}/{id} with 30-day TTL - Captures dismissed_by from Cloudflare Access authenticated email - Lists all dismissed/* keys from KV - PWA client-side JS calls on page load to hide already-dismissed items - On load: GET /api/dismissed → hide matching cards - On Dismiss click: POST /api/dismiss → hide card + empty channel section if last item

KV namespace details

Deployment

Every deploy_pwa.sh run now deploys:

Mac-side reconciliation — WIRED (Apr 22)

nightly-consolidation Section G reconciles KV ↔ JSONL every 11pm: 1. Lists all dismissed/* keys in KV via the CF API (bypasses Access using the server token in .env) 2. For each key, prunes matching entries from the relevant pending_*.jsonl file 3. Deletes the KV key after successful local prune (no-op if KV key was already dismissed more than once) 4. Appends audit record to dismissed_archive.jsonl (1-year retention)

KV ↔ JSONL stays in sync every 24h. Between reconciliations, the client-side JS hides dismissed cards via GET /api/dismissed, so the user sees a consistent state regardless of reconciliation timing.

Auth

All /api/* endpoints inherit Cloudflare Access — only Joseph.Bowens@SkyRun.com can POST to dismiss. Public (non-authenticated) requests get 302 to the CF Access login page.

Access app config (2026-04-24)

Auth-expiry handling (PWA client — shipped 2026-04-24)

Fetch wrapper in build_pwa.py (apiPost) uses redirect: 'manual' + content-type check. When a POST hits an expired Access session:

This was required because prior to the fix, POST got 302→cross-origin→CORS TypeError and the Dismiss button just showed "Retry" forever. KV showed zero dismissals ever written until 2026-04-24.

Rollback

See ~/Library/Application Support/SkyRun/snapshots/2026-04-22_pre-live-ea/ROLLBACK.md for pre-v2 state + restore commands.

Future upgrades (NOT yet built)