audit(rollup): produce global readiness scorecard from subsystem audits #393
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Part of Epic #387 — Codebase Legibility Audit. This is AUDIT-6: the synthesis step that turns five per-subsystem reports into one Legibility Readiness Scorecard that the user (and eventually the evaluators) can read end-to-end.
This issue is blocked by AUDIT-1 (#388), AUDIT-2 (#389), AUDIT-3 (#390), AUDIT-4 (#391), AUDIT-5 (#392). All five must be closed before this one starts.
The output is a Markdown document under
docs/LEGIBILITY-READINESS.md.Inputs
docs/audits/audit-{frontend,backend,ocr,db,rest}-report.mdRequired content of
docs/LEGIBILITY-READINESS.md1. Executive summary
One paragraph: what state the codebase is in for human evaluation, written so Anja and Tobias could read it cold.
2. Per-subsystem health table
3. Aggregate rubric scorecard
For each rubric category C1–C10, total counts across all subsystems:
4. Top-10 prioritized actions
Across all subsystem reports, the ten highest-leverage actions in priority order. Each ≤2 sentences, with which subsystem they affect and which rubric check they unblock.
5. Refactor readiness verdict
Explicit answer: is the codebase ready for the big-bang refactor (Epic 4)?
6. Evaluation readiness verdict
Explicit answer: is the codebase ready to send to Anja and Tobias?
7. Open questions for the user
Any boundary decision that AUDIT-1..5 surfaced but couldn't resolve.
Acceptance criteria
docs/LEGIBILITY-READINESS.mdexists on a feature branch with all 7 sectionsDefinition of Done
docs/LEGIBILITY-READINESS.mdcommitted onmain. Closing comment on this issue states the §5 and §6 verdicts succinctly so the user sees the result without opening the file. Issue closed viaCloses #Nin the merge commit.Dispatch
This one is best done by you (Marcel) or a single agent with full context, NOT a parallel Explore agent — the synthesis step needs to read all five reports in full and form a coherent narrative.
AUDIT-6 — Global Legibility Readiness Scorecard
Synthesis of the five subsystem audits (#388 frontend, #389 backend, #390 ocr-service, #391 db, #392 rest-of-repo) against
RUBRIC-LEGIBILITY-001. Read-only rollup; no code or docs modified.1. Executive summary
Familienarchiv is a working full-stack family-archive application — Spring Boot 4 + SvelteKit 2 + Python OCR microservice + PostgreSQL + MinIO — and the code itself is largely sound: layering inside each subsystem is mostly disciplined, tests are well-named and order-independent, and non-obvious decisions tend to carry good why-comments. The legibility gap is in the wrapper around the code, not the code. There is no human-targeted
README.mdat the repo root (the LLM-targetedCLAUDE.mdis the de facto front door), nodocs/ARCHITECTURE.md, nodocs/GLOSSARY.mdto disambiguatePerson ≠ AppUserand the German/English term split (Briefwechsel/Chronik/Geschichte/Stammbaum/Richtlinien), the backend is layer-packaged rather than domain-packaged with 11 cross-domain repository injections plus 2 controller-injects-repository violations, the frontend'slib/components/is a flat 79-file bucket spanning ≥9 domains, and theuserstable maps to entityAppUserwhilepersonsis a separate concept — a stranger will conflate them within 30 seconds. A first-time evaluator (Anja or Tobias) cannot today form a correct mental model from the documentation alone or locate every file for a domain in ≤2 minutes.2. Per-subsystem health table
lib/components/bucket spanning ≥9 domains; no real frontend README (thesv createstub)CLAUDE.mdendpoint table (/training/submitdoesn't exist; missingBLLA_MODEL_PATHenv var) and no human-facing READMEuserstable →AppUserentity name mismatch sharing lexical space withpersons; zeroCOMMENT ON TABLE; nodocs/SCHEMA.mdREADME.md;CLAUDE.mdis the de facto front door;docs/specs/has 47 unindexed HTML files3. Aggregate rubric scorecard
Counts aggregated across all five subsystem reports, applicable checks only (N/A and deferred excluded). Aggregate verdict uses the same threshold as per-subsystem: 🟢 if 0 Critical AND ≥80% pass; 🟡 if 0 Critical AND ≥50%; 🔴 otherwise.
Overall aggregate: 🔴. Only C2 (Local Bootstrap) clears the 🟡 threshold; every other category has Critical fails or pass-rate below 50%. The pattern is unambiguous: code-quality categories (C6 layering, C8 tests, C10 sanity) fail on a small number of identifiable hotspots, while doc-quality categories (C1, C3, C5, C7, C9) fail across the board because the human-facing documentation layer is largely absent.
4. Top-10 prioritized actions
Selected for maximum unblock per unit of work — each closes multiple rubric categories or removes a refactor-gating risk.
README.md— ≤3-sentence purpose, 5 components table, prerequisites with versions, single bootstrap command, default-creds note, links to COLLABORATING / CODESTYLE / docs / Gitea issues. Source: AUDIT-5 (#392). Unblocks: C1.1, C1.2, C1.4, C2.2, C2.3.docs/ARCHITECTURE.md+docs/GLOSSARY.md— narrative wrappingdocs/architecture/c4-diagrams.md; glossary covers Person ≠ AppUser, Briefwechsel/Conversation, Chronik/Activity, Geschichte ≠ Document, Kurrent vs Sütterlin, the fourscriptTypevalues. Source: AUDIT-2 (#389), AUDIT-3 (#390), AUDIT-4 (#391), AUDIT-5 (#392). Unblocks: C1.3, C3.1, C3.2, C3.3.MassImportService/Thumbnail*/TranscriptionQueueService/training exports/SenderModelService/OcrTrainingService/TranscriptionService/AnnotationService/PasswordResetService→ introduce read/write helpers on owning services;StatsController→ newStatsService;AuthE2EController→PasswordResetService.findE2EToken(email). Source: AUDIT-2 (#389) §9. Unblocks: C6.1, C6.2 — and removes the gating risk for REFACTOR-1.lib/components/intolib/<domain>/{component,store,util,hook,type}per the canonical set pluslib/shared/{ui,discussion,dashboard,i18n}— REFACTOR-2 territory; mapping spelled out file-by-file in AUDIT-1 §5. Source: AUDIT-1 (#388). Unblocks: C3.5, C3.6, C4.1, C4.2, C6.3, C6.4.document/,person/,tag/,user/,geschichte/,notification/,ocr/,shared/{file-storage,audit,dashboard,import,transcription-queue,security,error-handling,config}) — REFACTOR-1 territory, but only after action #3 lands so the package move is purely cosmetic. Source: AUDIT-2 (#389). Unblocks: C3.4, C4.1, C4.2.CLAUDE.mdto aREADME.mdcompanion per AUDIT-5 §2a (root, docs, scripts, .devcontainer, infra; plus per-subsystembackend/README.md,frontend/README.md,ocr-service/README.md); convert eachCLAUDE.mdinto a thin pointer. Replacebackend/HELP.md(Initializr boilerplate) with realbackend/README.md; replacefrontend/README.md(sv createstub) with real frontend README; promoteocr-service/CLAUDE.mdtoocr-service/README.mdand fix the stale/training/submitendpoint table + missingBLLA_MODEL_PATHenv var. Source: AUDIT-1 (#388), AUDIT-2 (#389), AUDIT-3 (#390), AUDIT-5 (#392). Unblocks: C2.5, C5.3, C7.1.RuntimeException/IllegalArgumentException/IllegalStateExceptioninMassImportService:159,FileService:91,167,RestClientOcrClient:218,250,288,PolygonConverter,DocumentService:967toDomainException; convert controllerResponseStatusExceptionvalidation to@Valid+DomainException. Source: AUDIT-2 (#389) C5.2. Unblocks: C5.2 — and gives the frontend a uniformcodeto translate.DocumentService(971 LOC),PersonService,TranscriptionService,MassImportService,NotificationService,OcrAsyncRunner+ frontend mutation pass onPerson*,Transcription*,Annotation*,Document*,bulkSelection,mention,blockConflictMerge,saveBlockWithConflictRetryspecs — without this, REFACTOR-1/REFACTOR-2's "tests still green" signal is unreliable for the largest blast-radius services. Source: AUDIT-1 (#388) §9.1, AUDIT-2 (#389) §9. Unblocks: C8.1 (currently Unverified-Critical).docs/SCHEMA.md(domain-grouped table overview) + add a one-time migration to renameusers→app_users(andusers_groups,comment_mentions.user_id,audit_log.actor_idcascading FKs) OR addCOMMENT ON TABLE users IS 'AppUser accounts — NOT historical persons. See persons.'; renametag→tagsand add@Table(name="tags")toTag.java; patch V37/V43 sequence holes with skipped-stub migrations; resolve audit-log REVOKE no-op (split DB roles or drop the REVOKE with a why-comment); fix or delete the stalescripts/schema.sqlsnapshot. Source: AUDIT-4 (#391). Unblocks: C3.3, C7.5, C9.3, C10.4..gitignoreto coverproofshot-artifacts/,.agent/,.claude/worktrees/, rootnode_modules/,.pytest_cache/,**/test-results.locked/,**/.svelte-kit.old/;git rm -r --cachedthe existing committed copies; delete the 6frontend/src/lib/paraglide_bak*/dirs (~4 MB); delete dead frontend components (OcrProgress.svelte,DocumentStatusChip.svelte,ExpandableText.svelte,DashboardRecentDocuments.svelte,lib/index.ts,lib/utils.ts); deletemodel/DocumentSort.java(duplicate); deleteroutes/demo/; remove strayconsole.loginroutes/api/tags/+server.ts:27; indexdocs/specs/with a README/index.html marking each spec Active/Superseded/Implemented; migratedocs/TODO-backend.mdanddocs/TODO-frontend.mditems to Gitea then delete; pick betweendocs/style-guide.htmlanddocs/STYLEGUIDE.md. Source: AUDIT-1 (#388) §7, AUDIT-2 (#389) §7, AUDIT-5 (#392) §7. Unblocks: C1.5, C10.1, C10.4.5. Refactor readiness verdict (gate for Epic 4)
🔴 — NO, address Critical findings first.
Running REFACTOR-1 and REFACTOR-2 against the current codebase would carry the existing layering violations into the new domain packages, where they would be visible as cross-package imports rather than fixed — masking the real defect under a cosmetic move. Specific gating concerns:
MassImportService → DocumentRepository;Thumbnail*(3 services) →DocumentRepository;TranscriptionQueueService → DocumentRepository;Segmentation/TrainingDataExportService→Document/Block/Annotationrepos (3 each);SenderModelService → TranscriptionBlockRepository;OcrTrainingService → TranscriptionBlockRepository;TranscriptionService → AnnotationRepository;AnnotationService → TranscriptionBlockRepository;PasswordResetService → AppUserRepository. Source: AUDIT-2 §3 C6.2 + §9. Each must be replaced with a service-level method on the owning domain before the package move.StatsControllerinjectsPersonRepository+DocumentRepository;AuthE2EControllerinjectsPasswordResetTokenRepository. Source: AUDIT-2 §3 C6.1.shared/file-storageordocument.thumbnail?Document.trainingLabelfield stays indocumentor moves toocr?AppUsernotification-preference fields stay on User or move tonotification? Page-local-vs-lib/paradigm for the 39+ frontend components currently insideroutes/?PersonMentionEditor→ person or shared/discussion? Source: AUDIT-1 §9.4, AUDIT-2 §9.Path to 🟢: complete actions #3, #7, #8 from §4 (fix layering violations, unify error handling, run mutation tests), then ratify the open boundary decisions in §7 below. The
users → app_usersrename should run as a separate, dedicated migration before or after REFACTOR-1 — never bundled — because it touches FKs in 6+ tables.6. Evaluation readiness verdict (gate for sending to Anja/Tobias)
🔴 — NO, run Epics 2–5 first.
A first-time reader cannot form a correct mental model from the documentation alone today, and several of the rubric's measurable success criteria fail outright:
docs/ARCHITECTURE.md, no domain enumeration in any human-targeted doc; the canonical domain set lives only in #387 first comment.lib/components/is a flat 79-file bucket; backend is layer-packaged; "find every Person file" requires scanning ≥6 different parents (AUDIT-1 C4.1).README.mdin ≤15 min): fails outright — there is no rootREADME.md.CLAUDE.mdopens with "This file provides guidance to Claude Code".grepis clean across all subsystems).Specific blockers that must be cleared before sending:
README.md(action #1)docs/ARCHITECTURE.md+docs/GLOSSARY.md(action #2) — Tobias will not get pastBriefwechsel,Chronik,Stammbaum,Richtlinien,Aktivitäten,Geschichtewithout it; Anja will not get pastPerson≠AppUserwithout it.backend/README.md(replacingHELP.md) and realfrontend/README.md(replacing thesvstub) and realocr-service/README.md(action #6).userstable →AppUserentity name confusion resolved (action #9) — this is the single thing most likely to make Tobias close the IDE saying "this is weird".docs/specs/indexed (action #10) — 47 ungrouped HTML files are the single biggest discoverability failure ofdocs/.docs/TODO-backend.mdanddocs/TODO-frontend.mdmigrated to Gitea then deleted — they actively contradict the project's own workflow rule.The Epic 5 cleanup tasks (action #10) alone are not sufficient. Actions #1, #2, #6, and #9 are documentation-only and could conceivably ship without REFACTOR-1/REFACTOR-2, but doing so would have the docs describe the old structure that the refactor is about to replace — not useful. Recommendation: ship Epics 2–5 in the order Critical-fixes → REFACTOR-1 → REFACTOR-2 → docs migration → cleanup, and re-run AUDIT-6 before sending.
7. Open questions for the user
shared/file-storageordocument.thumbnail? AUDIT-2 §5 default isshared/file-storagesince thumbnails are a file-storage concern over any document type, but the entity-coupling argument fordocument.thumbnailis also valid. Pick one before REFACTOR-1.Document.trainingLabelfield — stays indocumentor moves toocr? AUDIT-2 §5 / AUDIT-4 §5: the field lives onDocumentbut its only consumer is the OCR training pipeline. Same question for thedocument_training_labelstable.AppUsernotification-preference fields (isNotifyOnReply,isNotifyOnMention) — stay onAppUseror move tonotification? AUDIT-2 §5./users/[id](Person profile) vs/admin/users/[id](AppUser admin) — rename, merge, or document? AUDIT-1 §7, §10.4. The semantic overlap directly threatens the Person ≠ AppUser glossary distinction.PersonMentionEditor—person/orshared/discussion/? AUDIT-1 §5. Is "person mention" a person concept or a discussion concept?EnrichmentBlock,DistributionBar,ConversationThumbnail— boundary calls. AUDIT-1 §5 lists each as ambiguous between two homes.routes/(39+ files) — co-location stays as a legitimate paradigm, or everything moves tolib/<domain>/? AUDIT-1 §5, §9.2. REFACTOR-2 must answer this before moving anything; otherwise the result will be inconsistent.userstable rename — execute the fullusers → app_usersmigration (touches FKs in 6+ tables) or settle for aCOMMENT ON TABLEclarification? AUDIT-4 §3 C3.3. The full rename is correct but expensive; the comment is cheap but doesn't fix the lexical confusion in code that readsusersdirectly.REVOKEactually bites, or drop the REVOKE statements with a comment? AUDIT-4 §3 C7.5 / §7. Currently the migration claims protection it doesn't deliver.docs/style-guide.html(1403 lines) vsdocs/STYLEGUIDE.md(389 lines) — which is the source of truth? AUDIT-5 §5, §10.docs/security-guide.md(persona-voiced "Nora 'NullX' Steiner") — deliverable doc or training material? AUDIT-5 §5.Permission.ANNOTATE_ALLandPermission.BLOG_WRITEexist inPermission.javabut are not inbackend/CLAUDE.md's documented list of 6. AUDIT-2 §8. Document or remove.AdminController— slice per-domain (import endpoints toshared/import/, backfill endpoints with their owners) or keep as a thin façade? AUDIT-2 §5.TranscriptionQueueService—shared/transcription-queueordocument.transcription/queue/? AUDIT-2 §5. The queue is a read view over Document; canonical set saysshared/, but the read coupling is to one domain only.