From 3431956de190bd97125bb9886e77bc8e1fdd8e89 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 6 Aug 2026 17:01:17 +0800 Subject: [PATCH] docs(staging): record personal report acceptance evidence --- ...al-report-staging-acceptance-2026-08-06.md | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/operations/personal-report-staging-acceptance-2026-08-06.md diff --git a/docs/operations/personal-report-staging-acceptance-2026-08-06.md b/docs/operations/personal-report-staging-acceptance-2026-08-06.md new file mode 100644 index 00000000..69f764cd --- /dev/null +++ b/docs/operations/personal-report-staging-acceptance-2026-08-06.md @@ -0,0 +1,137 @@ +# Personal Report Staging Acceptance — 2026-08-06 + +This record contains only release identities, aggregate operational evidence, schema metadata, stable status codes, and synthetic transaction counts. It contains no user identity, birth data, report body, OTP, password, cookie, JWT, model key, database credential, SSH private key, or raw environment file. + +## Release identity + +- Pre-change application baseline: `49da8f916960030d5760d8dedf4e77820732a527` +- Read-only upstream Skill source commit: `unknown` (source directory has no usable Git metadata) +- Read-only upstream tree SHA-256: `9034e1967032d09c7fbae83fc2205f7e75e8ad482c5f9eba1bf309fe30aef5bb` +- Personal-report implementation merge: `e018dc90a73d49596563b5ae2b5fc203402cc86a` +- Security/control-plane merge: `f7a615a5bf11ed95b3a6c7e6d28dfe8150a825ef` +- Staging migration and application deployment SHA: `f7a615a5bf11ed95b3a6c7e6d28dfe8150a825ef` +- Application rollback target: `49da8f916960030d5760d8dedf4e77820732a527`, subject to retained successful gate artifacts. The additive database migration remains in place after application rollback. + +At deployment verification, Gitea `main`, Gitea `staging`, `/opt/jyotisha-staging/.state/deployed-revision`, and public `/api/health` all reported the same full SHA. + +## Automated quality evidence + +### Local + +- Mandatory `scripts/pre_work_check.py`: pass, including fragment scan, external-engine adapter diagnostic, remote visibility, and focused governance tests. +- Personal-report frontend suite: 142/142. +- Core Python report/import/orchestrator matrix: 87/87. +- TypeScript: pass. +- Next production build: pass; NFT whole-project warning count 0. +- ESLint: 0 errors; 4 unrelated pre-existing warnings. +- Staging workflow/security contracts after credential hardening: 31/31. +- JSON Schema/Zod/Python contract, dual migration, owner service, API, entitlement, grounded generation, reader, D1 SVG, and browser print contracts: pass. + +The complete frontend suite could not be made fully executable on the local macOS host because Docker CLI and the bare `python` command were absent. This was not reported as green locally. + +### Gitea complete runner + +- PR quality gate `1459`: success; Python quick gate 291 passed / 1 skipped and frontend 1409/1409, including real Docker/PostgreSQL migration fixture. +- Final personal-report PR quality gate `1461`: success. +- Security-control-plane PR quality gates `1465` and `1467`: success, including Docker/PostgreSQL fixtures. +- Final `staging` push quality gate `1469`: success for full SHA `f7a615a5bf11ed95b3a6c7e6d28dfe8150a825ef`; immutable API/web manifest published. +- Automatic deploy check `1470`: stopped safely with exit 3 because the report migration was pending. Its logs showed the base64 SSH secret as masked and no private-key header/material. +- Manual migration `1471`: success; `20260806000000_personal_reports.sql` applied and present once in the migration ledger. +- Manual deploy `1472`: success with the same exact SHA and `allow_rollback=false`. + +## Security incident and containment + +Before the final release, an earlier failed staging-only deploy exposed the then-current multiline staging SSH key in Gitea logs. The application had not switched and production was not involved. + +Containment completed before any rerun: + +- generated and verified a new staging ED25519 key; +- revoked the exposed authorized key and proved it no longer authenticated; +- deleted the old local key; +- replaced the Gitea and GitHub staging secrets; +- deleted 28 potentially affected Gitea deploy/migration runs; +- retained the quality-gate run and immutable image artifact, which never received the SSH secret; +- changed the staging secret contract to one-line base64, decoded only into a mode-0600 temporary key and validated by `ssh-keygen`; +- fixed `.env.staging*` ownership validation to use the deployment-tree owner and fixed rollout replacement to preserve owner/gid. + +The staging host key had also changed before inspection. Strict SSH was paused until the observed ED25519 key exactly matched the independently administered Gitea `STAGING_KNOWN_HOSTS` value. Strict checking was never disabled. See `ERR-092`, `ERR-093`, `ERR-094`, and `BUG-128`. + +## Migration and authorization evidence + +Post-deploy schema inspection reported: + +- `public.personal_reports` exists; +- RLS enabled; +- policies: owner SELECT and owner DELETE; +- `authenticated`: SELECT, DELETE only; +- `service_role`: SELECT, INSERT, UPDATE, DELETE; +- personal-report migration ledger count: 1. + +A two-owner synthetic RLS test ran entirely inside one PostgreSQL transaction and then rolled back: + +- owner SELECT count: 1; +- cross-owner SELECT count: 0; +- cross-owner DELETE count: 0; +- owner DELETE count: 1; +- persisted synthetic users after rollback: 0; +- persisted synthetic reports after rollback: 0. + +No report document or birth fact was needed or persisted for this check. + +## Health and unauthenticated smoke + +- Public `/api/health`: `ok`; deployment SHA exact match. +- `/login`: 200. +- Logged-out `/api/account`: 401. +- Logged-out report GET and POST: 401. +- Logged-out report reader route: reachable; its data API remains authenticated. +- Internal Python `/api/health`: 200, `status=ok`, `swisseph_available=true`. +- API, web, PostgreSQL, worker, and Caddy containers running; API/web/PostgreSQL health checks healthy. +- API/web/Caddy recent fatal/report-guard error-signature count: 0. +- All five staging containers restart count: 0. +- Server web container: no Chromium, Chrome, Playwright, or Puppeteer executable/process. +- Staging report selectors: one canonical enabled selector and one canonical daily-limit selector; env files remain `deploy:deploy` mode 0600. +- Legacy-compatible model API key, HTTPS base URL, model ID, Python API URL, report feature flag, and report limit are present in the web runtime. Values were not printed. + +## Idle resource baseline + +One post-deploy no-load sample (not a concurrency claim): + +| Service | CPU | Memory | +| --- | ---: | ---: | +| API | 0.02% | 48.54 MiB | +| Caddy | 0.35% | 36.15 MiB | +| PostgreSQL | 0.16% | 44.67 MiB | +| Rectification worker | 0.30% | 194.8 MiB | +| Web | 1.69% | 130.3 MiB | + +This is an idle snapshot only. It does not satisfy the planned single-user/two-user report-generation performance measurement. + +## Backups + +- Pre-migration encrypted staging backup: success. +- Post-deploy encrypted staging backup: success. +- Retention after post-deploy backup: 3 archives. +- Backup directory mode: 0700. +- Latest archive mode: 0600; non-empty (717,008 bytes). +- No secret was passed in argv or printed. + +## Blocked / user handoff + +The following are intentionally **not** marked complete because no authorized synthetic browser account credentials were configured in Gitea/GitHub, and existing real users were not borrowed: + +1. End-to-end authenticated model report generation from an accepted/confirmed synthetic profile. +2. Browser verification of summary → themes → evidence order using a real ready document. +3. Browser cross-owner report URL check (database RLS isolation was verified transactionally). +4. Desktop Chrome Print → Save as PDF. +5. macOS Safari Print → Save as PDF. +6. iPhone Safari Share/Print and WeChat guidance. +7. 20–40-page pagination, Chinese font, SVG sharpness, and table clipping checks. +8. Serialized ready-document byte size and generated PDF byte size from a real report. +9. Single-user generation CPU/memory/duration and two-user concurrent-generation resource evidence. + +These require the user/browser handoff in `docs/operations/personal-report-staging.md`. Failure, unavailable model, blocked evidence, or generation timeouts must be recorded as observed; they must not be converted into a success claim. + +## Production boundary + +No production deployment, production migration, production secret rotation, production database operation, domain change, or Supabase production change was performed.