Adds the installation manual, architecture overview, and illustrated user
manual as a five-story epic, plus the acceptance gate that keeps them true.
The documentation is markdown under docs/, so the same files are readable in
the repository and rendered by the deployed application at /app/#/docs. An
operator who was handed a URL and an access secret has no repository checkout
in front of them, and the network the application is deployed to is not
assumed to reach a CDN.
Two decisions are recorded in the epic rather than left to implementation:
- The renderer is vendored (marked), not written and not fetched.
- Diagrams are mermaid, rendered client-side, with script-src untouched.
Whether mermaid needs 'unsafe-eval' was measured rather than assumed: its
bundle contains no eval( and no new Function, and rendered under this
application's exact CSP it produced an SVG with no script-src violation.
What it does violate is style-src, which gains 'unsafe-inline'. With
script-src, img-src, connect-src, and font-src all unchanged, that leaves
defacement rather than execution or exfiltration. The alternative --
pre-rendering diagrams to committed SVG with the already-installed
Playwright -- is recorded as the migration if that trade is ever refused.
Screenshots are generated from the running application, never pasted, and
every documented setting, command, exit code, error code, and state is
cross-checked against the code that implements it, so stale documentation
fails a test instead of misleading an operator.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015csGY8XV1M92fHfKnMmjtu
Ports the safety review and the Photo Analyzer Library/Analyze/Stats
experiences onto the shared API + service layer, and adds the Workflow
home, enforcing the pipeline gates and the one-mutating-job policy.
Backend
- migration 0005 + models: safety_reviews (append-only, latest row is the
current decision) and analysis_results (donor photos schema re-keyed to
asset_id).
- SafetyService: persist scores/decisions, review queue with filters, and
the EXIF safety checkpoint (mutually-exclusive sfw/nsfw keyword written,
read back, current_sha256 refreshed) that upload eligibility depends on.
- AnalysisService: the privacy gate — the vision provider is called ONLY for
canonical, confirmed-SFW assets; nsfw/undecided are recorded skipped without
a request. Provider is an injected adapter (real OpenAI-compatible Gemini
call extracted from photo_analyzer.analyze_image; a fake in tests).
- LibraryService: Library search + Stats read model ported from webapp/query.py
(LIKE search in place of FTS5; facets, top tags, years, albums, people).
- WorkflowService + GET /api/v1/workflow: per-stage readiness derived from the
source tables — counts, blockers, last-run, action, and an active_job that
drives read-only-during-jobs. Safety scoring and analysis run as durable jobs
under the library_write lock via new domain handlers, so a second mutating
job is refused.
- routes: workflow, safety (queue/counts/decisions/jobs), analysis
(counts/results/jobs), library (assets/facets/stats).
Frontend
- five views (frontend/js/views.js) on the US02-05 shell: Workflow stepper
(status text+icon, not colour alone; actions disabled with a reason while a
job runs), Safety review (filter tabs, decide, persists across reload),
Library (search + cards), Analyze (counts + live job log via the SSE
adapter), Stats. Shared DOM helpers extracted to dom.js; Workflow is the home
route.
Tests
- integration: provider-call privacy (nsfw never reaches the provider),
sfw→nsfw flip drops analysis eligibility, decision persistence, one-mutating-
job rejection, workflow counts, and the exiftool safety-keyword write/verify.
- e2e: Workflow cards, actions disabled+explained during a job, safety
decide-persists-across-reload, Library search, Stats, Analyze counts.
- traceability map updated for US02-05 and US02-06.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>