This runbook covers the staging-only rollout of `ReportDocument v1`, personal report generation, the report reader, D1 SVG rendering, and browser print-to-PDF. It does **not** authorize a production migration or deployment.
The server-owned `/opt/jyotisha-staging/.env.staging` must remain mode `0600`. Do not print or copy the file. In addition to the existing self-hosted identity settings, configure:
```dotenv
PERSONAL_REPORT_ENABLED=true
PERSONAL_REPORT_DAILY_LIMIT=5
```
`PERSONAL_REPORT_ALLOWED_ORIGINS` is normally omitted because browser requests are same-origin. Add it only for an explicitly reviewed origin. `JYOTISH_SKILL_SNAPSHOT_SHA256` and `JYOTISH_SKILL_SOURCE_COMMIT` are optional deployment pins; without a valid override, the web bundle uses the statically packaged, validated source manifest and fails closed if its SHA is invalid.
If the complete frontend suite has environment-only failures, preserve their exact count and cause; do not report the suite as green. The personal-report-focused test set, TypeScript check, Python contract test, migration contract, production build, and `git diff --check` must pass before release.
## Synthetic browser acceptance
Use only synthetic accounts and synthetic birth facts. Never paste real birth data into tickets, logs, screenshots, or this document.
Prepare these profiles:
1.`confirmed` with an exact synthetic active time.
2.`accepted` with a user-adopted synthetic candidate time.
3. A report whose evidence contains blocked/conflicting techniques.
4. A second synthetic owner account for isolation checks.
For each supported owner profile:
1. Sign in and complete a personal consultation with workflow evidence.
2. Confirm the “生成个人报告” CTA is visible only for `accepted` or `confirmed` active time. It must be hidden for `reported` and unaccepted `candidate` states.
3. Generate one report. Confirm the button prevents duplicate in-flight clicks and the app navigates to `/reports/<uuid>`.
4. Confirm content order is summary, thematic narrative, then evidence appendix.
5. Confirm D1 renders from real document facts. D9/D10 must be absent when their complete facts are unavailable.
6. Confirm blocked claims use uncertainty language and contain no deterministic prediction.
7. Reload the report URL and confirm the same validated document is returned.
8. Attempt the report URL as the second synthetic owner; expect `404`/not found and no document data.
9. Delete as the owner and confirm subsequent retrieval is not found.
10. Generate with the same request ID and payload to confirm replay behavior; a different payload under the same ID must return `409 report_request_conflict`.
PDF/browser matrix:
| Client | Required check | Status before user handoff |
| --- | --- | --- |
| Desktop Chrome | Print → Save as PDF; SVG sharp; Chinese text intact; tables not clipped | pending manual |
| macOS Safari | Print → Save as PDF; pagination and fonts | pending manual |
| iPhone Safari | Share/Print flow and mobile hint | pending manual |
For a long synthetic document, target 20–40 printed pages and verify that print CSS expands the evidence appendix as designed. The report action must only wait for `document.fonts.ready` and call `window.print()`.
## Security and server assertions
Verify logged-out and cross-owner behavior without recording tokens:
Expected logged-out status is `401`. Authenticated owner/non-owner checks must be performed in the browser or with ephemeral local credentials that are never pasted into logs.
On the host, confirm no product PDF browser runtime exists:
```bash
cd /opt/jyotisha-staging
docker compose --env-file .env.staging -f deploy/docker-compose.server.yml exec -T web \
docker compose --env-file .env.staging -f deploy/docker-compose.server.yml exec -T web \
sh -lc '! ps aux | grep -E "[c]hromium|[p]laywright|[p]uppeteer"'
```
Inspect logs only for stable codes and operational state. Do not log or copy report text, prompts, birth facts, JWTs, cookies, model keys, exception stacks, or the staging env file.
## Resource evidence
Record before, during one generation, and during two synthetic users attempting concurrent generation:
docker compose --env-file .env.staging -f deploy/docker-compose.server.yml logs --tail=200 api web caddy
```
Capture only aggregate CPU, memory, container restart count, request duration, stable failure state, and serialized report byte size. Confirm:
- one generation per user at a time;
- a second concurrent request for the same user returns the stable in-progress response;
- serialized `ReportDocument` is at most 1.5 MiB;
- no OOM or container restart;
- no Chromium/Playwright server process.
One successful sample does not establish high-concurrency capacity.
## Rollback
### Application
Run `Deploy staging` from the trusted `main` controller using the previous known-good full SHA and `allow_rollback=true`. The selected SHA must still have a successful quality-gate artifact. Do not use a mutable tag or manually overwrite the server tree.
### Database
The personal report migration is additive. Application rollback intentionally leaves `public.personal_reports`, its grants, constraints, indexes, and RLS policies in place. Do not run a destructive down migration. Delete only explicitly identified synthetic test rows if cleanup is required.
### Feature pause
If generation must stop while the current application remains deployed, set `PERSONAL_REPORT_ENABLED=false` in the server-owned staging env under the deployment lock and recreate only the web service after validation. This is an operational containment action, not a substitute for an application rollback. Restore `true` only after the incident is resolved.
### Skill
Revert the commercial repository import/semantic-merge commits as reviewed. Do not modify the read-only upstream source directory. Preserve the import manifest and record that the target revision was rolled back; never rewrite provenance history.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.