# Secure Health Dashboard Release Acceptance

Use this checklist for versioned health-dashboard previews, especially desktop-first dashboards with mobile capture paths.

## Contract and provenance boundary

- Make the bundle validator independently enforce the same safety boundary as the provider; never rely only on provider filtering.
- Keep shared canonical allowlists in a dependency-light registry imported by both provider and contract. For laboratories, bind canonical **parameter + unit + original-source type** together, and share the same exact un-censored numeric-value parser at both boundaries. Normalize provider output (for example, trim accepted numeric strings) before validation; add a direct-contract regression proving free text such as `NOT A NUMBER` is rejected even when provenance and parameter/unit are otherwise valid.
- Reject future measurements/labs, unordered or duplicate series days, inconsistent `quality`/`value` combinations, and baselines that do not exactly match prior-only registry semantics.
- A `scheduled_next_date` is a plan only when sourced from a recognized past administered event; alternatively accept a recognized explicit-plan event. Unknown event types fail closed.
- Cancellations are medication-specific, not date-global. “Next planned” must be strictly later than the dashboard day.

## Chart and event semantics

- Dense calendar axes must preserve missing days as explicit nulls with `spanGaps: false`.
- If a valid series spans more calendar days than the density cap, preserve **every observed endpoint** and insert compact null sentinels between distant observations. Never cap forward from the oldest day and silently drop recent values.
- Keep events outside measurement datasets. Their timeline tick must remain at its true 0–100% position, including exact endpoints.
- Keep label boxes/details in bounds without moving the tick. Provide full event text in normal document flow; do not rely on `title` or a non-focusable ellipsis for touch/keyboard access.
- Regression-test lazy chart creation, single initialization on revisit, period updates, resize/update behavior, a measurement-gap event day, and long-span first/latest endpoints.

## Nutrition chart and mapping-workflow acceptance

When migrating a health-record profile to one visible chart engine, test the **fully generated profile**, not source-file references: assert the engine asset is requested exactly once, the legacy engine is requested zero times, and every visible main, mini, and nutrition chart uses the selected engine. A dormant renderer for another profile is not evidence of a request in the target profile.

Build nutrition browser fixtures around the visible period contract before asserting canvas creation:

- include at least one fully mapped day and one incomplete day inside the default selected range;
- include breakfast, lunch, dinner, snack, and an unassigned meal case;
- include known-unit nutrients plus an unknown-unit micronutrient that must be omitted;
- include multiple mapped exposure days and a synthetic symptom/event marker at the required lag;
- assert the empty state, rather than a canvas, when no complete chartable day exists.

A chart test that expects a canvas from an intentionally incomplete-only fixture is contradictory. Fix the fixture or assert the fail-closed empty state; never weaken missingness semantics merely to instantiate a chart. Accessible chart tables may live inside collapsed `details`; open the disclosure before a visibility assertion, while still asserting the table exists in the DOM when collapsed.

For nutrition mapping, freeze one exact HTTP → queue → worker contract before implementation. Keep source classification and personal tolerance separate:

- mapping decisions are explicit (`assign`, `composite`, `ignore`), with composite requiring ingredient review and remaining unclassified;
- queue payloads carry bounded alias, confidence, method, source label/version, note, and definition version;
- personal tolerance uses its own bounded status/note fields and separate persistence, never a SIGHi/source-classification field;
- only the worker mutates mapping, provenance, and summary tables, transactionally and idempotently;
- recomputation touches only affected days, preserves missing macros as unknown, and retains only allowlisted nutrients with known units;
- migration acceptance includes backup, integrity before/after, repeat application, restore, and proof that raw imported nutrition tables are data-equivalent.

## Responsive and accessibility gates

- Test 390×844, 768×1024, 1024×768, 1440×900, and 1920×1080.
- Exercise 200% text at the actual mobile viewport. Checking only desktop at enlarged root font can produce a false green.
- For five-tab mobile navigation, use compact visible labels with full `aria-label` names when full labels cannot reflow. Assert each label has no internal clipping/collision and the document itself has no horizontal overflow.
- For views opened outside the primary tablist (for example, a header-level “More” panel), never leave every primary tab at `tabindex=-1`. Preserve exactly one primary tab as the keyboard re-entry point while the external view is active, then regression-test keyboard activation of the external view, traversal back into the tablist, and activation of a primary panel. It is acceptable for no primary tab to be `aria-selected=true` while the external panel is active, but the tablist must remain reachable.
- Fixed mobile navigation needs sufficient bottom reserve and explicit non-occlusion checks after scrolling actionable content into its final reachable position.
- Timeline controls and all interactive targets remain at least 44×44 CSS pixels.

## Synthetic-only browser safety

- Browser acceptance must refuse a configured URL unless the embedded bundle contains a deterministic synthetic sentinel from the fixture.
- Perform that sentinel check in `beforeAll` via request context, before opening pages or creating failure traces that could persist private DOM data.
- Keep traces/screenshots/reports ignored, but treat ignore rules as secondary protection rather than the synthetic gate.
- Document a clean-checkout recipe: fixture generation, dashboard generation, hardened local server environment, pinned dependency install, browser command, and cleanup.

## Chart-engine migration prototypes

Use these gates when evaluating or replacing a local dashboard chart engine:

- Keep the prototype default-off and prove the normal render contains neither prototype markup nor its engine script. Serving a pinned local asset is not the same as activating it.
- Pin package version and lockfile, vendor the exact runtime asset, record SHA-256 and raw/gzip size, and preserve both upstream license and NOTICE. Verify there are no CDNs, runtime fetches, CSP relaxations, browser persistence, or remote source maps.
- Path-under-`/tmp` is not proof of synthetic data. Use the fixed literal root `/tmp` rather than `tempfile.gettempdir()` so `TMPDIR`/`TEMP`/`TMP` cannot widen the boundary. Require a fixture-only DB marker and an owner-controlled `0700` parent; reject every symlink component and non-regular file. Bind the DB to an `O_NOFOLLOW` file descriptor, verify device/inode, then use the same SQLite `mode=ro&immutable=1` connection through `/proc/self/fd` for marker validation and bundle generation.
- Prototype output must be new-only, never update-style: owner-controlled `0700` parent, no symlink or existing target, inode-verified `O_NOFOLLOW` directory FD, `O_EXCL` temporary file at `0600`, and link-style publication that cannot replace an existing name. Add hostile `TMPDIR`, direct/ancestor symlink, existing-target, public-parent, and real-directory parent-swap tests; verify the victim remains unchanged.
- Keep the product data/bundle contract unchanged during the engine spike. The prototype may generate its own deterministic series but must not add pseudo-health metrics to the real registry.
- For Canvas, pair engine ARIA/decal support with a complete accessible data table. Chart point selection and keyboard table selection must emit the same strict ISO-date callback.
- Test real gestures, not only option presence: legend toggle/solo, zoom, a mouse drag that demonstrably changes `dataZoom`, mouse Brush with a non-empty selected date range, clear, chart click, keyboard selection, and a real hover that produces the tooltip. Also assert the option contract uses axis-triggered tooltip, crosshair and linked x-axes. ECharts line-series Brush may return empty `selected[].dataIndex`; treat `batch[].areas[].coordRange` as the authoritative selected date range and derive the day count from that range.
- At 200% mobile text, rerun the 44×44 target check and assert chart/container resize agreement, minimum chart geometry, ARIA text and no page overflow—not overflow alone. In print mode, open the accessible table and verify date buttons remain visible despite global `button { display:none }` rules.
- Provide one repository command that runs legacy and prototype browser specs together; separate convenience commands are fine, but the documented acceptance command must reproduce the full count from a clean checkout.
- Avoid integration collisions with existing semantics: do not add generic button names such as `Alle` near existing controls, and do not add a second generic `role="status"` where legacy tests or assistive navigation expect one. Prefer scoped names and `aria-live` without an unnecessary role.
- A visually hidden skip link should remain at least 44×44 while positioned fully off-screen, then move on-screen on focus. A 1×1 hiding pattern can falsely fail global touch-target acceptance.
- Run the complete legacy browser suite with the prototype enabled; isolated prototype tests are insufficient because role/name/query collisions appear only in integration.
- Record repeated synthetic benchmarks, not a single self-timed initialization. Start the in-app timer before synthetic data/series construction, and add at least five full browser navigations that include local asset loading/parsing plus a user-interaction latency gate. Report median and maximum, and label them as spike gates rather than production SLOs. Define API, interaction, memory, and rendering budgets separately before release.

## Medical wording and print

- Doctor/print output must state: verified canonical source limits, missing/incomplete remains unknown, and no diagnosis/causality/treatment recommendation.
- Privacy mode remains transient; print unmasking must be deliberate and tested only with synthetic data.

## Release-candidate integration pitfalls

- Freeze the candidate before the final full Python suite and isolated browser matrix. During development, rerun only causally affected focused tests; after an RC failure, repair each distinct root cause with focused regressions, refreeze, and run the complete gates once more. Do not repeatedly burn full suites for selector, fixture, or wording edits.
- A target-profile chart migration must preserve every other render profile. Generate the chart host conditionally: an ECharts-enabled profile needs a `<div>` and local ECharts assets, while a profile that still loads Chart.js needs its `<canvas>` and legacy sparkline renderer. Assert requests and instances in the **target generated profile** rather than deleting dormant compatibility code used by other profiles.
- Keep event-only/internal overlay metrics out of general metric-card counts and pickers unless acceptance explicitly adds them. They may remain in the validated bundle for a dedicated combined chart.
- Production recompute functions often stamp `CURRENT_TIMESTAMP`. Deterministic fixtures must normalize every derived timestamp after recompute, including day summaries, meal summaries, provenance, and audit rows; otherwise logically identical SQLite fixtures can differ byte-for-byte.
- When extending a strict series contract with metric-specific metadata, either update every synthetic bundle builder or accept two explicit exact shapes (base and enriched) and validate enriched fields only when present. Never loosen to arbitrary extra keys.
- Keep committed schema DDL, runtime `ensure_*_schema` DDL, and the complete schema manifest synchronized. Test legacy-row migration explicitly, preserving prior personal-tolerance statuses and notes when new semantics add values rather than invalidate old observations.
- Reinspect generated screenshots after tests manipulate disclosure state. A fixed-height chart panel plus an opened accessible `<details>` table can overlap following sections even when assertions pass; use auto-height panels, dimension the chart host itself, and close the disclosure before clean evidence capture.
- Keep full-profile-only write controls disabled or read-only in older profiles. A shared nutrition view must not emit active mapping forms unless the generated profile carries an explicit mapping-enabled flag and valid session/CSRF workflow.

## Contextual routing, day detail, and document-status acceptance

- A global range control is visible only in views that consume it. Treat Cockpit, Explorer, and nutrition trend as separate range-owning contexts; Today, a single-day view, record, and calendar use their own local controls. Test each view's visible toolbar, active-range label, reload, and browser back/forward behavior.
- Centralize history ownership. Before adding `pushState`/`replaceState`, inventory every script listening to the same navigation and range events. A metric hand-off can otherwise create an intermediate Explorer entry, so one Back press does not return to the originating Cockpit. Regression-test exact URL and selected panel after one Back and one Forward, not URL presence alone.
- A day controller that augments `/api/v1/day/{date}` with nutrition detail needs one monotone request version plus a shared abort signal/timeout for both requests. A stale nutrition response must never overwrite the newly selected day; an incomplete histamine classification must hide only the aggregate score, never the documented food list.
- Keep document states orthogonal: safe original availability, extraction state, human review, and verified-search eligibility. The descriptor-backed original probe must not short-circuit on review status. Opening an unreviewed safe original or clearly labelled machine-extracted preview must never mutate review state or add content to verified FTS/report paths.
- Browser fixtures and API assertions must use opaque queue/document identities and synthetic content. Never persist raw payloads, source IDs, paths, food names, document names, or health text in release evidence.

## Generated-artifact discipline

- A complete browser matrix may intentionally rewrite tracked screenshot evidence. Immediately after the run, inspect `git status`; restore pre-existing evidence files unless the sprint explicitly requires fresh screenshots, and stage only the named synthetic artifacts.
- Treat a green browser count and a clean release diff as separate gates. Run `git diff --check`, inspect the changed-path allowlist, and verify the protected V4 hash after restoring generated artifacts.
- If a push/deploy approval gate times out, do not retry or route around it. Preserve the clean local commit, report the exact commit and completed gates, and resume only after a fresh user approval. Never begin a dependent sprint whose ordering requires the release to have completed.

## Release sequence

1. Read-only production bundle validation without emitting values.
2. Focused implementation tests until stable; then freeze the RC.
3. Full Python tests, isolated browser matrix, lint/compile/JS/diff checks against that RC.
4. Fresh synthetic screenshots including mobile 200% text, followed by visual overlap/privacy review.
5. Independent UX/accessibility, medical-safety/privacy, and release/QA reviews of the **current working tree**, not only HEAD.
6. Fix findings test-first; if runtime code changed, refreeze and rerun complete gates before release.
7. Verify v4 files/routes remain unchanged and no secrets/medical artifacts entered the diff.
8. Keep preview deployment, commit, and push blocked until explicit user approval.
