diff --git a/README.md b/README.md index 98f26e45..675b5849 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ A disciplined daily readiness instrument. Five sectors. Three states. Thirty day
-
+
Figure 1. Desktop readiness grid showing the full 30-day picture, state legend, streak state, and selected-cell detail on the primary history surface.
+Figure 1. Status dashboard showing the 30-day readiness picture, live streak, today completion, data posture, and backup health on a single operator surface.
--- @@ -32,25 +32,26 @@ A disciplined daily readiness instrument. Five sectors. Three states. Thirty day > > Five fixed sectors. Three states. One trailing 30-day picture. > -> There are no accounts, no backend data plane, no telemetry path, and no sync layer. State lives in IndexedDB. Durable recovery depends on operator-controlled JSON exports backed by integrity checks. +> There are no accounts, no backend data plane, no telemetry path, and no sync layer. State lives in IndexedDB. Durable recovery depends on operator-controlled JSON exports backed by canonical SHA-256 integrity checks. > -> The operating boundary is documented in [35 ADRs](./docs/decisions/README.md), a published [security and trust boundary](./SECURITY.md), and a release pipeline that publishes only the exact CI-verified production artifact under Sigstore-backed provenance and SBOM attestations. +> The operating boundary is documented in [35 ADRs](./docs/decisions/README.md), a published [security and trust boundary](./SECURITY.md), and a release pipeline that publishes only the exact CI-verified production artifact under Sigstore-backed provenance and a runtime-only SBOM attestation. ## At a glance -| Facet | Current posture | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| Runtime | React 19, TypeScript, Tailwind CSS 4, Vite, `vite-plugin-pwa` | -| Persistence | IndexedDB through Dexie 4; compound uniqueness on `[date+sectorId]` | -| Deployment | Static PWA on GitHub Pages, custom domain `opsnormal.app` | -| Data posture | Local only. No accounts, no backend, no analytics, no sync | -| Release pipeline | Mainline integrity -> re-smoke CI-verified artifact in Chromium, WebKit, Firefox -> publish | -| Release artifact | `dist-ci-verified` reused end-to-end; no separate deploy build | -| Release attestations | Sigstore-backed build-provenance and SBOM attestations, verified on Pages release (ADR-0027, ADR-0032) | -| Static analysis | CodeQL `security-extended` + `security-and-quality` as a merge gate (ADR-0028) | -| Coverage gates | 100% on `date`, `exportSerialization`, `entryWrittenCoordination`; calibrated per-file floors on `importService` and `appDb` | -| Accessibility | Dedicated WCAG 2.1 A / AA Playwright scans with service workers blocked | -| Decisions of record | 35 ADRs under [`docs/decisions/`](./docs/decisions/README.md) | +| Facet | Current posture | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Runtime | React 19, TypeScript, Tailwind CSS 4, Vite, `vite-plugin-pwa` | +| Engine contract | Node `>=22.13.0` and npm `>=10.9.2` enforced by `devEngines` before install, CI, and run commands | +| Persistence | IndexedDB through Dexie 4; compound uniqueness on `[date+sectorId]` | +| Deployment | Static PWA on GitHub Pages, custom domain `opsnormal.app` | +| Data posture | Local only. No accounts, no backend, no analytics, no sync | +| Release pipeline | Mainline integrity -> re-smoke CI-verified artifact in Chromium, WebKit, Firefox -> publish | +| Release artifact | `dist-ci-verified` reused end-to-end; no separate deploy build | +| Release attestations | Sigstore-backed build-provenance and runtime-only SBOM attestations, verified on Pages release (ADR-0027, ADR-0032, ADR-0035) | +| Static analysis | CodeQL `security-extended` + `security-and-quality` as a merge gate (ADR-0028) | +| Coverage gates | 100% on `src/lib/date.ts`, `src/lib/exportSerialization.ts`, `src/lib/trustedTypes.ts`, and `src/services/entryWrittenCoordination.ts`; calibrated per-file floors on `src/services/importService.ts`, `src/db/appDb.ts`, and `src/db/migrations/index.ts`; aggregate 70/65 floor across targeted modules | +| Accessibility | Dedicated WCAG 2.1 A / AA Playwright scans with service workers blocked | +| Decisions of record | 35 ADRs under [`docs/decisions/`](./docs/decisions/README.md) | ## Built for, and not built for @@ -87,29 +88,18 @@ The model is intentionally coarse. The objective is a usable signal under stress ---
-
+
Figure 2. Daily check-in surface enforcing the fixed five-sector model with direct state selection and immediate local commit behavior.
+Figure 2. 30-day readiness grid showing the full trailing history window, active streak, state legend, and selected-cell detail brief.
--- -## Operational philosophy and target operator - -This instrument is engineered for operators who want deterministic local state tracking and who accept the backup burden that isolation requires. - -Designed for operators who: - -- want a fixed, legible readiness picture instead of feature sprawl -- prefer local control over hosted convenience -- will maintain their own export cadence and restore drills - -Explicitly not designed for users seeking: - -- cross-device cloud sync or account recovery -- passive telemetry, social features, or background data collection -- custom categories, free-form journaling, or browser storage treated like a guaranteed archive +
+
+
Figure 3. Daily check-in surface showing the fixed five-sector model, direct state selection, and an honest Degraded state on the Rest sector.
-These are not missing features. They are deliberate constraints that protect the operating model. +--- @@ -120,8 +110,8 @@ OpsNormal runs on a shared-responsibility model. The repository makes hard commi ### The repository commits to - Core readiness records stay local to the device after the first successful load unless the operator exports them -- JSON and CSV export preserve an operator-controlled recovery path, and JSON exports carry a SHA-256 integrity envelope -- Import validates structure before commit, applies replace gating, and fails closed on malformed or unsafe data +- JSON and CSV export preserve an operator-controlled recovery path; JSON exports carry a canonical SHA-256 checksum using the `sha256-canonical-v1` algorithm, with legacy exports remaining importable but flagged +- Import validates structure and checksum before staging; `applyImport` re-verifies the SHA-256 checksum immediately before opening the write transaction; checksum-free payloads require explicit operator acknowledgment; import fails closed on malformed, checksum-failed, or unsafe data - Root-level and section-level error boundaries preserve recovery surfaces and keep export reachable during localized render faults - Release publication reuses the exact `dist-ci-verified` artifact that passed mainline integrity instead of rebuilding a new deploy bundle - The released bundle carries a Sigstore-backed build-provenance attestation that Pipeline: Pages Release verifies before upload @@ -135,6 +125,13 @@ OpsNormal runs on a shared-responsibility model. The repository makes hard commi Run a test export early. Export routinely. Keep important JSON exports in more than one location you control. When the shell raises a backup action prompt, treat it as a direct order to refresh the JSON export. +
+
+
Figure 4. Backup and Recovery surface showing the local-only data boundary, export-first safe path with honest backup-state warning, and locked replace posture.
+ +--- + ## Storage volatility and WebKit risks OpsNormal uses IndexedDB for persistence. Local-only architecture means no external server can lose your readiness record. It also means no external server can save it. Browser-managed storage must be treated as an ephemeral cache, not a durable archive. @@ -156,9 +153,9 @@ To reduce that risk: Narrow screens switch from the 30-day grid to week groups with a daily brief so the history surface stays readable on phone-sized viewports.
-
+
Figure 3. Mobile history surface proving week-paginated review and daily brief continuity under phone-sized viewport constraints.
+Figure 5. Mobile history surface proving week-paginated review and daily brief continuity under phone-sized viewport constraints.
--- @@ -166,9 +163,9 @@ Narrow screens switch from the 30-day grid to week groups with a daily brief so The Pages release pipeline is not a second build. It is a verification and publishing pipeline over the exact artifact that passed mainline integrity. -On every push to `main`, Pipeline: Mainline Integrity runs lint, typecheck, Vitest coverage, Playwright Chromium, merge-blocking WebKit and Firefox smoke lanes, and a production build, then uploads the `dist-ci-verified` artifact, emits a Sigstore-backed build-provenance attestation, generates an SPDX JSON SBOM, and emits a Sigstore-backed SBOM attestation for the same release artifact digest. Pipeline: Pages Release then resolves that artifact by upstream run ID, verifies both attestations against repository, signer workflow, source reference, and triggering commit SHA, re-smokes Chromium, WebKit, and Firefox against the extracted bundle, and only then publishes. The bundle on `opsnormal.app` is the same bytes that passed verification. +On every push to `main`, Pipeline: Mainline Integrity runs lint, typecheck, Vitest coverage, Playwright Chromium, merge-blocking WebKit and Firefox smoke lanes, and a production build, then uploads the `dist-ci-verified` artifact, emits a Sigstore-backed build-provenance attestation, generates an SPDX JSON SBOM from a clean production-only install, validates the SBOM package boundary, and emits a Sigstore-backed SBOM attestation for the same release artifact digest. Pipeline: Pages Release then resolves that artifact by upstream run ID, verifies both attestations against repository, signer workflow, source reference, and triggering commit SHA, re-smokes Chromium, WebKit, and Firefox against the extracted bundle, and only then publishes. The bundle on `opsnormal.app` is the same bytes that passed verification. -Third parties can verify a downloaded CI artifact archive with `gh attestation verify`. See [SECURITY.md](./SECURITY.md), ADR-0027, and ADR-0032. +Third parties can verify a downloaded CI artifact archive with `gh attestation verify`. See [SECURITY.md](./SECURITY.md), ADR-0027, ADR-0032, and ADR-0035. ## Threat model and reliability posture @@ -200,8 +197,8 @@ Read [WebKit CI coverage boundary](./docs/webkit-limitations.md) and [Firefox CI - Root-level and section-level error boundaries contain render faults instead of allowing full-application unmount and blank-screen failure - Recovery surfaces keep JSON and CSV export reachable, including the crash-state export path documented in [ADR-0011](./docs/decisions/0011-react-error-boundaries-for-render-fault-containment.md) and [ADR-0016](./docs/decisions/0016-expand-sectional-error-boundaries-to-today-and-export.md) - Backup action prompts escalate when Safari-tab risk, quota pressure, or storage instability make a fresh JSON export urgent -- Import validation, replace gating, and fail-closed commit verification prevent silent corruption -- Undo support remains available for data correction and pre-replace recovery drills +- Import validation, replace gating, pre-transaction checksum re-verification, and fail-closed commit verification prevent silent corruption +- Undo support remains available for data correction and pre-replace recovery drills, with the undo snapshot derived inside the write transaction and invalidated by subsequent writes ## Accessibility posture @@ -213,7 +210,7 @@ Accessibility is architectural, not decorative. - Desktop history keyboard navigation and mobile day selection with a daily brief - Focus treatment designed to stay visible on the clipped cockpit geometry - State encoding that does not rely on color alone -- Dedicated WCAG 2.1 A and AA Playwright accessibility scans cover the desktop shell, the direct-select radiogroup pattern, and the mobile history region with service workers blocked for deterministic DOM evaluation +- Dedicated WCAG 2.1 A and AA Playwright accessibility scans cover the desktop shell, the direct-select radiogroup pattern, the mobile history region, and recovery surfaces with service workers blocked for deterministic DOM evaluation - ARIA snapshot coverage locks the direct-select radiogroup structure so intended accessibility-tree changes stay explicit in review ## Verification discipline @@ -222,9 +219,9 @@ Quality is enforced through release gates, test coverage, and explicit design co - GitHub Actions runs lint, typecheck, Vitest coverage, Playwright Chromium verification, merge-blocking Playwright WebKit and Firefox smoke lanes, and build validation - GitHub Pages release downloads the `dist-ci-verified` artifact from the successful mainline integrity run, re-smokes that exact bundle in Chromium, WebKit, and Firefox, and only then publishes -- The released bundle carries Sigstore-backed build-provenance and SBOM attestations that Pipeline: Pages Release verifies before upload. See ADR-0027 and ADR-0032. +- The released bundle carries Sigstore-backed build-provenance and runtime-only SBOM attestations that Pipeline: Pages Release verifies before upload. See ADR-0027, ADR-0032, and ADR-0035. - GitHub CodeQL code scanning gates mainline with the `security-extended` and `security-and-quality` query packs. See ADR-0028. -- JSON export carries versioning and integrity checks, and import commit verification fails closed before the app claims success +- JSON export carries versioning and a canonical SHA-256 checksum (`sha256-canonical-v1`), and `applyImport` re-verifies that checksum inside the write path before the IndexedDB transaction opens; import commit verification fails closed before the app claims success - Save-picker pre-replace backups are read back before the app claims a verified disk write, and fallback Blob downloads keep a conservative delayed-revoke cleanup window - Any database schema change must update the migration registry, migration tests, the relevant ADR, and browser-level upgrade proof before merge - Playwright accessibility verification includes service-workers-blocked scans, and ARIA snapshots lock the direct-select radiogroup structure @@ -307,6 +304,6 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md). ## License and citation -MIT License. Copyright © Bradley Saucier. See [LICENSE](./LICENSE). +MIT License. Copyright (c) Bradley Saucier. See [LICENSE](./LICENSE). If you reference OpsNormal in research, operations, or published work, cite the software using [CITATION.cff](./CITATION.cff). diff --git a/docs/images/desktop-backup-recovery.png b/docs/images/desktop-backup-recovery.png new file mode 100644 index 00000000..adfc482d Binary files /dev/null and b/docs/images/desktop-backup-recovery.png differ diff --git a/docs/images/desktop-daily-checkin.png b/docs/images/desktop-daily-checkin.png index a7e57ecf..e17b59e1 100644 Binary files a/docs/images/desktop-daily-checkin.png and b/docs/images/desktop-daily-checkin.png differ diff --git a/docs/images/desktop-readiness-grid.png b/docs/images/desktop-readiness-grid.png index f5f9dade..bb7779d6 100644 Binary files a/docs/images/desktop-readiness-grid.png and b/docs/images/desktop-readiness-grid.png differ diff --git a/docs/images/desktop-status-dashboard.png b/docs/images/desktop-status-dashboard.png new file mode 100644 index 00000000..55ff7c79 Binary files /dev/null and b/docs/images/desktop-status-dashboard.png differ diff --git a/docs/images/mobile-history-week-brief.png b/docs/images/mobile-history-week-brief.png index 02155e38..5765014e 100644 Binary files a/docs/images/mobile-history-week-brief.png and b/docs/images/mobile-history-week-brief.png differ diff --git a/index.html b/index.html index 4acb35ff..5e801f5c 100644 --- a/index.html +++ b/index.html @@ -70,7 +70,7 @@ /> @@ -84,7 +84,7 @@ /> diff --git a/package-lock.json b/package-lock.json index 43b1da11..85f61f9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -982,8 +982,8 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "version": "7.29.4", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "dev": true, "license": "MIT", "dependencies": { @@ -4372,8 +4372,8 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 33951942..0834e116 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,9 @@ }, "vite-plugin-pwa": { "vite": "$vite" - } + }, + "@babel/plugin-transform-modules-systemjs": "^7.29.4", + "fast-uri": "^3.1.2" }, "lint-staged": { "*.{ts,tsx,js,jsx,mjs,cjs}": [ diff --git a/public/social-preview.png b/public/social-preview.png index f5f9dade..5d200978 100644 Binary files a/public/social-preview.png and b/public/social-preview.png differ