# Private Health Dashboard Capture Queue

Use this workflow when a health dashboard needs a small write path while its HTTP process must remain unable to mutate the health database directly.

## Architecture

1. **Static/private UI:** render a complete form with all medically required fields. Missing values must remain missing; explicit numeric zero must remain observed.
2. **HTTP boundary:** validate method, allowed Host, exact same Origin, one-time CSRF token, body-size limit, exact field set, one value per field, value ranges, note length, allowed return target, and local-date boundary. Exact Origin means scheme + canonical host + effective port; comparing only `Origin.netloc` with `Host` is insufficient and accepts wrong-scheme requests.
3. **Private queue:** atomically write a minimal versioned JSON action into a user-owned `0700` directory as a `0600` regular file. Use `O_CREAT|O_EXCL`, fsync the file, rename, then fsync the directory. Bound pending actions and return `503` when full.
4. **Local worker:** open actions with `O_NOFOLLOW`, validate owner/mode/type/size/name, parse JSON with duplicate-key rejection, then revalidate the complete canonical payload before any mutation.
5. **Explicit data target:** require the database explicitly at every write-capable leaf. Worker startup must fail before inbox access when its DB environment variable is absent; mutation CLIs must require `--db`; `connect`/`upsert`/refresh functions must accept a mandatory `Path`, never `None` with a production fallback. Validate canonical date, known dimensions, exact integer `0..3` values, and note length immediately before opening SQLite. Any legacy command that cannot accept the same explicit DB must be skipped or fail closed for non-default/synthetic databases.
6. **Acknowledgement:** distinguish **queued** from **applied**. A redirect receipt may confirm only that the queue write succeeded, never that the DB mutation or dashboard refresh succeeded.

## Date integrity

Do not copy the capture date from a potentially stale generated dashboard bundle. Inject the current date at GET time using the product's declared local timezone, show it visibly in the dialog, and validate the POST against the same timezone/day helper.

Trace the timezone contract through the entire post-processing chain, not only the form and worker validators. The server, queue worker, direct leaf CLI, analytics refresh, and dashboard renderer must derive “today” from the same declared timezone or pass it explicitly between subprocesses. A renderer defaulting to bare `date.today()` silently reintroduces host-timezone behavior even when every upstream boundary uses `ZoneInfo` correctly. Inspect worker subprocess arguments and require an explicit `--today` when the renderer cannot guarantee the product timezone itself.

Add a synthetic midnight-boundary regression with deliberately different host and product-local dates. A complete seven-zero capture on the product-local day must survive regeneration with value `0`, remain `quality=complete`, and clear the check-in task. Also run the counterfactual renderer with the previous host day and assert that the test detects the missing point/open task; this proves the regression is capable of catching timezone drift rather than merely exercising the happy path.

Use one fixed UTC instant and two real zones for an auditable counterexample (for example `2026-01-02T00:30:00Z` gives `2026-01-01` in `Pacific/Honolulu` and `2026-01-02` in `Europe/Zurich`). Report both derived dates, persisted row count, explicit-zero count, bundle point/quality, check-in-task state, and `PRAGMA integrity_check`. Avoid tests that construct the form date with the product helper but assert against bare `date.today()`; they can pass on ordinary hosts yet become flaky at the exact boundary they are meant to protect.

Treat timezone configurability as contract surface. If one component accepts a timezone environment override, trace the same setting through worker and generator units—including `EnvironmentFile` precedence—or pin every component to the fixed declared zone. A configurable Zurich default in one process and hard-coded Zurich in another are equivalent only while no override is active; release evidence must distinguish verification of the shipped fixed configuration from verification of a genuinely shared configurable timezone.

### Configuration-divergence probe

Do not accept matching default values as proof of an end-to-end timezone contract. During the final read-only gate:

1. Inventory every timezone source in the server, worker, mutation leaf, renderer/generator, service units, timers, and environment files. Record whether each source is fixed, defaulted, or overridable.
2. Check unit-level propagation explicitly. A server unit that loads an `EnvironmentFile` while its worker unit does not is a split contract even if both currently resolve to `Europe/Zurich`.
3. Run a synthetic import/runtime probe with an override that is on the opposite calendar day from Zurich at the frozen UTC instant (choose a suitable real IANA zone dynamically). The regression must first assert the counterfactual itself—`frozen_instant.astimezone(hostile_zone).date() != frozen_instant.astimezone(ZoneInfo("Europe/Zurich")).date()`—then assert that every immutable component still returns the Zurich day despite the hostile environment value. Merely naming an exotic zone without proving the dates differ can leave a boundary test non-adversarial. Print only component timezone names and derived dates; use disposable synthetic paths and disable bytecode writes.
4. When an already-running synthetic server is part of release evidence, verify its identity header first, then inspect only allowlisted process metadata to prove the owning CWD, disposable inbox, fixed instance ID, and actually hostile timezone environment. Pair source-level assertions with this runtime evidence so a stale or benignly configured server cannot satisfy the gate.
5. Treat any differing component-derived dates as **Important**: the HTTP boundary can queue a date that a downstream worker rejects as future, or downstream regeneration can render the wrong day. Green happy-path tests in the default timezone do not downgrade this finding.
6. Approve only after either the timezone is immutable and identical at every boundary, or the same validated setting is propagated to every process and covered by a proven opposite-day regression.

## One-time queue receipts

- Generate an opaque, short-lived, non-medical receipt after the atomic queue write.
- Accept it only once on a body-producing GET.
- A HEAD request must not consume it.
- Replay and forged values render no confirmation.
- Remove the query parameter from browser history with `history.replaceState` after render.
- Never place scores, notes, dates, patient data, or action payloads in the URL.
- **Chromium pitfall:** `Referrer-Policy: no-referrer` can make a native same-origin form submit `Origin: null`; an exact Origin check will then correctly return 403. Do not weaken the Origin check to accept `null`. Prefer `Referrer-Policy: strict-origin`, then prove the POST Origin equals the served origin in a real browser.

## Accessibility and truthful UX

- State that all required dimensions are needed for a complete day.
- Label queue status as “locally queued/pending processing,” not “saved” or “successful.”
- Use a native dialog or equivalent semantics; focus the first score field, include `select` controls in focus trapping, support Tab/Shift+Tab/Escape/cancel, and restore focus to the opener.
- Verify 44×44 CSS-pixel targets, 390×844 layout, 200% text, no horizontal overflow, and internal dialog scrolling.

## Deterministic synthetic verification

Run the entire form → HTTP validation → queue file → worker → synthetic SQLite DB → regenerated dashboard path without reading production artifacts. The browser gate must perform a **real native POST**; do not fake a queued DOM state or stop at cancel. Protect that write-capable browser gate with two independent controls:

1. require an explicit synthetic instance ID in server and browser process;
2. return the opaque ID in a response header and compare it before POSTing;
3. when test mode is enabled, require the action inbox to resolve below `/tmp` (or another dedicated disposable root) and fail server startup otherwise;
4. require the browser harness to name the expected temporary inbox, write a synthetic sentinel, verify the resulting file, and remove it;
5. retain the existing synthetic-bundle content guard as a second check.

Assert:

- valid native browser POST sends the served Origin, creates a `0600` queue file, redirects to the allowlisted route, displays the one-time queued acknowledgement, and removes the receipt query;
- a fresh-token adversarial POST with the same host/port but a different Origin scheme is rejected before queue mutation; this negative probe is required even when the native browser happy path reports the correct Origin;
- unknown, duplicate, incomplete, future, oversized, cross-origin, replayed-CSRF, and queue-full requests fail closed;
- missing database selection at worker/CLI/function boundaries exits before queue creation or SQLite connection;
- queue directory/file modes are `0700`/`0600` and no temporary files remain;
- symlink, wrong owner/mode, oversized file, duplicate JSON keys, and wrong payload version are rejected;
- seven explicit zero scores create seven observations, not missing values;
- synthetic DB integrity remains `ok` and no command implicitly falls back to production;
- queue receipt survives HEAD, works once on GET, and fails on replay/forgery;
- full Python/browser gates run from a freshly rebuilt fixture, because an earlier mutation E2E probe can invalidate deterministic browser expectations.

## Private image quarantine and append-only capture records

For symptom, medication, supplement, event, measurement, and photo capture, keep the browser-facing upload boundary separate from the patient action queue:

1. Upload at most the contractually allowed number of bounded files into an owner-only quarantine. Return opaque random tokens only; never return or persist browser filenames, paths, EXIF, or reusable authentication material.
2. Open quarantine bytes and metadata with no-follow semantics. Check regular-file type, owner/mode, descriptor size, bounded read length, and metadata/token consistency. Validate both claimed MIME and file magic, then actually decode the image. Reject active formats and unsupported HEIC/HEIF locally rather than using a cloud converter.
3. Re-encode accepted raster images into a fixed safe format and create private thumbnails. This strips EXIF/GPS/device metadata. Store only internal random media IDs, dimensions, safe MIME, byte size, and SHA-256.
4. Verify quarantine metadata against the newly decoded result before promotion. Treat any token/hash/MIME/size/dimension mismatch as tampering.
5. Deduplicate by SHA-256 in the worker, not by browser filename. If a promoted duplicate is replaced by an existing canonical media object, delete only the newly created duplicate files and mark the reused record so rollback cleanup can never unlink canonical media.
6. Corrections and withdrawals are new immutable versions referencing the original capture. Never hard-delete or silently update the original patient record. A correction may inherit prior attachments deliberately, but must not duplicate media bytes.
7. Keep medication and supplement plan identities separate. A plan value is not an actual administration; require an explicit plan-value or deviation confirmation before recording an administered status. Missing intensity remains unknown; explicit intensity `0` remains documented zero.
8. Queue retries must reuse the same idempotency key and already-issued quarantine tokens. Resume partial multi-file uploads from the known token count so a lost action response does not create unbounded orphan uploads.

Synthetic gates should cover oversized bytes with a lowered test limit, valid-magic-but-undecodable images, unsupported HEIC/HEIF magic, symlink rejection, metadata mismatch, EXIF removal, duplicate-hash reuse, idempotent replay, correction/withdrawal version history, and zero-versus-unknown semantics.

## Read-only frontend/media expansion audit

Before proposing camera-roll, direct-capture, multi-image, or video work, map the existing feature as separate layers: rendered controls, client state, upload/status/media HTTP routes, quarantine validation, worker promotion/deduplication, append-only correction semantics, day/calendar/Today/record providers, and browser fixture/runner wiring. A visible picker or an existing browser spec is not proof that the end-to-end route is runnable.

Audit and design rules:

- Treat Camera Roll, direct photo capture, and direct video capture as distinct controls. A single `multiple` input with `capture="environment"` does not prove that users can deliberately choose Camera Roll versus camera.
- Keep the visible `accept` contract aligned with server validation. Do not advertise HEIC/HEIF if the local boundary always rejects it.
- Derive capture date/time from an authenticated server context and an explicit selected-day route, not browser `Date` or browser storage. Test with a browser timezone on the opposite calendar day from the product timezone.
- Track each selected file independently (`file`, object URL, kind, token, progress, error). Positional token arrays break when a previously uploaded middle item is removed and can cause redundant uploads or quarantine orphans.
- For real per-file byte progress, use an upload primitive that exposes progress events; status text around `fetch` is not upload progress.
- Preserve the existing image cap when adding one video unless acceptance explicitly changes it (for example, three images plus one video rather than silently redefining three total attachments).
- A video path needs bounded local container/codec/duration/dimension validation, metadata stripping or normalization, a private poster, and descriptor-based range-capable streaming for mobile playback. Keep conversion local and fail closed.
- Live Photo pairing must be an explicit payload relation chosen by the user. Never infer pairing from filenames, timestamps, adjacency, or upload order.
- Append-only row versions are necessary but not sufficient for media history. Corrections need explicit opaque retained-attachment refs so users can keep/remove selected prior media while adding new media; every historical version must remain inspectable, and rollback cleanup must never unlink canonical deduplicated bytes.
- Make day chronology loading date-keyed, abort/version safe, and router-driven. Timer delays and broad click matching can render stale media after rapid day navigation.
- Reuse one bounded chronology presentation across Today, Day, and Record where practical. Verify the calendar category/count mapping and doctor/report timeline separately; worker mirroring into legacy tables is not proof that every surface includes capture rows.
- Inspect package scripts and isolated browser runners as well as spec files. A syntactically valid spec with no fixture builder, server profile, or runner group is inventory—not executable acceptance evidence.
- The synthetic browser gate should perform a real upload → queue → worker → read API/display flow. Preview-only `setInputFiles` coverage cannot prove quarantine, processing, history, or private media delivery.
- In a read-only audit, record the starting commit/status and re-check at the end. If an unrelated untracked or modified file appears concurrently, do not inspect, delete, or attribute it; report it separately from audit findings.

A compact 5–7 scenario browser plan should normally cover: source-choice/accessibility; multi-media preview/remove/caps; explicit Live Photo pairing; real progress/queue/idempotency; authoritative date plus stale-request rejection; Today/Day/calendar/Record display and video range playback; and correction/withdrawal history preservation. Include no external requests, no path/token/original-name leaks, no browser-storage dependency, console errors, 390px overflow, and 44px targets in the relevant scenarios.

## Copy-first private deployment and real-data read-only smoke

- Back up the productive SQLite database and private media tree as one consistency unit before migration. Record an empty-media state explicitly when applicable. Prove restore on a disposable copy, migration idempotency, unchanged legacy-table counts, `integrity_check`, and `foreign_key_check` before touching production.
- Install runtime files and user units atomically with owner-only staging files. The network server may read private media and write only quarantine/action state; only the network-isolated worker may write the database and promoted-media tree.
- Derive the smoke URL from the effective private service configuration. `EnvironmentFile` ordering can override the default bind host while a later unit assignment still fixes the port; verify the actual listener rather than assuming loopback.
- A real-data smoke remains read-only and aggregate-only: no screenshots, payload logging, patient values, file uploads, or capture submission. Open the hub and all capture types, exercise GET-only plan/timeline endpoints, count external requests, and count only patient-write routes rather than treating an authentication/session bootstrap POST as a patient mutation.
- Use the exact routed endpoint shape from source when probing dynamic paths (for example, date as a path segment when that is the contract). Sanitize diagnostic output to status codes and route names only.
- After restart, verify service state/restart count, private listener, runtime/unit hashes against the pushed commit, DB integrity/foreign keys, empty action inbox, new tracebacks since the restart timestamp, clean Git state, and the frozen legacy-dashboard checksum.

## Review and release gates

- Before an independent review, require evidence for the absolute `pwd`, expected Git origin/branch, and presence of the sprint document. If any differs, return **environment error** rather than issuing code findings from a foreign worktree.
- Use a cost-optimized read-only worker for a bounded diff audit when requested, but keep health semantics, secrets, integration, release, and deployment decisions with the primary agent.
- After any late fix, rerun changed-file lint/syntax/diff gates and the full Python/browser suites.
- Global lint debt outside changed files is disclosed separately; do not “fix the world” inside a focused health sprint.
- Run independent Medical-Safety/Data-Contract, mobile UX/Accessibility, and Security/Release reviews on the exact final tree.
- Do not install units, chmod productive state, commit, push, or deploy before explicit release approval.
