Captures the read-model invariant that was only living in a code comment:
views are assembled in-transaction, entities never cross the controller
boundary in the geschichte domain. CLAUDE.md §DTOs now names the exception
so the convention text stops contradicting the code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The append dedup guard was check-then-insert: two concurrent appends of the
same document both pass exists() and both insert. The partial unique index
on (geschichte_id, document_id) WHERE document_id IS NOT NULL closes the
race; append saveAndFlush-es and maps the DataIntegrityViolationException to
the same 409 JOURNEY_DOCUMENT_ALREADY_ADDED as the friendly pre-check. The
CHECK on note length pins the 2000-char contract in the schema, mirroring
chk_text_length on transcription_blocks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GESCHICHTE_TYPE_IMMUTABLE and JOURNEY_NOTE_TOO_LONG were declared in
errors.ts, translated, and documented — but never existed in the backend.
update() now rejects a type change with 409 (omitted/same type still pass);
note length is enforced at 2000 with its own code, matching the frontend
maxlength and the i18n message (resolves the #793 discrepancy in favour of
the spec). JOURNEY_ITEM_NOT_IN_JOURNEY is deleted everywhere instead — the
deliberate 404 posture for cross-journey item ids must not leak existence
via a distinct code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the '#753 deferred' placeholder with the actual implementation:
JOURNEY edit route now opens JourneyEditor (item list, drag/move-up/down,
add bar, sidebar) while STORY route continues to use GeschichteEditor.
Add item-mutation endpoints to the relation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Interlude is a first-class editorial concept introduced by the JourneyEditor
(#753): a note-only JourneyItem with no backing document, stored in the note
column and distinguished visually by --color-interlude-* CSS tokens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both error-handling sections now list JOURNEY_ITEM_NOT_IN_JOURNEY,
JOURNEY_NOTE_TOO_LONG, JOURNEY_DOCUMENT_ALREADY_ADDED, and
GESCHICHTE_TYPE_IMMUTABLE alongside the existing security codes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add JourneyEditor, JourneyItemRow, JourneyAddBar, GeschichteSidebar to the
geschichte README props table. Strike @dnd-kit/svelte-dnd-action library refs
and raw orange-*/blue-600 color classes in the editor spec HTML.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
geschichten components now describe the type-based reader split
(StoryReader / JourneyReader / JourneyItemCard / JourneyInterlude),
the TypeSelector creation flow, and the full set of API endpoints
(including DELETE /api/geschichten/{id} and GET /api/persons/{id}
for person pre-population).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add GeschichteQueryService component to the L3 supporting-domains diagram.
Remove the now-deleted Rel(geschSvc, journeyItemSvc, "Delegates getItems()")
arrow and add the correct Rel(journeyItemSvc, geschQuerySvc, ...) arrow that
reflects the actual dependency direction after the refactor in the prior commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes GLOSSARY position-step value (1000→10), adds DEFERRABLE constraint note,
and documents GeschichteView, JourneyItemView, and DocumentSummary read-model types.
ADR-035 records the decision to use Optional<String> for three-way PATCH semantics
instead of jackson-databind-nullable (which targets Jackson 2.x and is incompatible
with Spring Boot 4.0 / Jackson 3.x).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- db-orm.puml: replace geschichten_documents with journey_items, add type column to geschichten, bump schema version to V72
- l3-backend-3g-supporting.puml: update GeschichteController and GeschichteService descriptions to mention STORY/JOURNEY subtypes and JourneyItem
- geschichten/[id]/+page.svelte: replace raw UUID fallback with m.geschichten_document_link_placeholder() i18n key
- messages/{de,en,es}.json: add geschichten_document_link_placeholder translation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ARCHITECTURE.md: expand geschichte domain description — two subtypes
(STORY/JOURNEY), JourneyItem ownership, ON DELETE SET NULL FK note
- GLOSSARY.md: add JourneyItem and Lesereise terms; update Geschichte
entry to mention type discriminator
- db-relationships.puml: replace geschichten_documents with journey_items
(ON DELETE CASCADE to geschichten, ON DELETE SET NULL to documents)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lesereisen-reader-spec.html — Issue #752
LR-0 type selector on /geschichten/new
LR-1 REISE badge on the list
LR-2 Journey reader (ordered cards, interlude asides, no position numbers)
lesereisen-editor-spec.html — Issue #753
LE-1 empty JourneyEditor layout
LE-2 editor with mixed items (documents + interludes, drag handles)
LE-3 inline note-editing state
LE-4 mobile layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GLOSSARY entry for NameMatches (direct vs partial name-match strength and how
the search layer maps it); person/README adds resolveByName to the public
surface. No ADR — the matching rule is localized and justified inline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture the why behind deploying Ollama to prod/staging compose: the
corrected init recipe (supersedes ADR-028 §10's never-functional curl
loop), the OLLAMA_KEEP_ALIVE=-1 pin (so a future maintainer doesn't
optimize it away and reintroduce the post-idle cold-load 503), the
30->60s timeout NFR, and the memswap==mem hard-OOM trade-off.
Addresses #759 review (Markus #3, Nora #2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The diagram declared Container(ollama, ...) twice — an alias collision that
renders a duplicate box. It also declared the backend->ollama relationship
twice. Keep the richer 'Ollama LLM Service' declaration and the more
specific 'NL query parsing (POST /api/generate)' relationship; drop the
duplicates.
Addresses #759 review (Markus #2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docker-compose.prod.yml declares the volume as `ollama-models` (hyphen),
so the compose-project-prefixed name is `archiv-production_ollama-models`,
not the underscored `archiv-production_ollama_models` the model-upgrade
guide documented. The documented `docker volume rm` would not have matched
the real volume.
Addresses #759 review (Tobias #2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
application.yaml sets app.ollama.timeout-seconds: 60 (raised from 30 to
absorb the cold model load on the first query after an Ollama restart),
but DEPLOYMENT.md still documented 30. A doc that contradicts the shipped
value is a traceability defect.
Addresses #759 review (Markus, Felix, Elicit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Markus (architect): document SearchFilterBar + the search/ components
(SmartModeToggle, InterpretationChipRow, SmartSearchStatus,
DisambiguationPicker) and the POST /api/search/nl relation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both the first-time model pull runbook (from this branch) and the model
upgrade procedure (from main) belong in DEPLOYMENT.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers the SmartModeToggle pill (inside the search input, Google AI Mode
style), InterpretationChipRow anatomy, DisambiguationPicker, and all
status/error/empty states as full-result-area panels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses Elicit's and Sara's review concerns on PR #749:
- Expand §6 ollama_models section into a full model upgrade runbook (step-by-step
docker volume rm + recreate, including production volume name prefix)
- Add re-deploy idempotency note to §3.4 (init container exits quickly when model
already present on the volume)
- Add NL search smoke test to §3.4 (curl command distinguishing 200 from 503
NL_SEARCH_UNAVAILABLE)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updated OLLAMA_API_KEY env vars table from 0.6.5 to 0.6.5 or 0.30.6 to
match both tested versions. Added an explicit warning in §3.4 that
docker compose up -d --wait blocks for 60–90 min on first deploy when the
model pull has not yet completed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
§12 stated OLLAMA_API_KEY guards against lateral movement — contradicts
§7's empirical finding that it is not enforced. Replaced with an accurate
note referencing §7. Stale pre-merge placeholder in Consequences ("Three
TBD items must be resolved") removed; all three are resolved. §7 section
title updated from "0.6.5" to "0.6.5 and 0.30.6" to match the body text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OLLAMA_API_KEY: non-enforcement confirmed on both 0.6.5 and 0.30.6
- read_only: true: confirmed working on both 0.6.5 and 0.30.6
- Peak RSS during pull: ~108 MiB (well under 2g limit)
- All TBD placeholders resolved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all references to the CX32 VPS (8 GB RAM, Hetzner Cloud) with the
actual production server: a Hetzner Serverbörse dedicated server with an
Intel Core i7-6700 (4C/8T, 3.4 GHz) and 64 GB RAM.
Affected files:
- .claude/personas/devops.md — monthly cost line + upgrade example
- docs/infrastructure/production-compose.md — sizing section + cost table
- docs/DEPLOYMENT.md — OCR memory table + OCR_MEM_LIMIT env var description
- docs/adr/004-pdfbox-thumbnails.md — thumbnailExecutor memory ceiling note
- docs/adr/021-tmpdir-persistent-volume-staging.md — OOMKill rationale in alternatives
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both #730 (tag case-collision) and #684 (person-delete DB integrity) landed
an ADR-032 on main. Renumber the tag/case-collision one to 033 — it is
referenced only from this PR's person-domain comments and its own file, so the
move is self-contained and touches no Flyway migration. The person-delete
ADR-032 and the V71 migration comment that cites it are deliberately left
untouched (editing an applied migration would drift its Flyway checksum).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review noted the "never throws" claim was overstated: the exact-case Optional
lookups still surface a NonUniqueResultException on two byte-identical
same-case rows. That is a true data anomaly out of #731's scope (ambiguous =
case-insensitive) and resolves to the opaque INTERNAL_ERROR, never a wrong
row. Record that boundary at both resolution points and in ADR-032 so the gap
is not silently assumed covered.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
findByName resolved via Optional<Person>
findByFirstNameIgnoreCaseAndLastNameIgnoreCase, which threw
NonUniqueResultException once two people shared a first+last name case-
insensitively (hans müller / Hans Müller) — a 500 on the routine upload path
(DocumentService.storeDocument sender resolution).
findByName now resolves exact-case → single case-insensitive match → else
empty. The sender path deliberately diverges from the alias path: an
ambiguous name leaves the sender UNSET rather than guessing the lowest id,
because correct provenance beats a confidently-wrong pre-fill a reviewer
won't re-check. The two new name queries use explicit HQL equality so a null
first name binds as `= NULL` (no match) instead of the derived-query fold to
`first_name IS NULL`, which would widen a last-name-only row in as a sender.
Pins the opaque error path (IncorrectResultSizeDataAccessException stays
INTERNAL_ERROR with no Hibernate/SQL/row-count leak) and extends ADR-032 with
the Person section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AC-3 cascade test: assert an innocent bystander's mention row survives the
delete, proving the cascade is scoped to the deleted person (Nora).
- Fix integration-test comment: receivers is @ManyToMany(LAZY), not an EAGER
@ElementCollection (Sara).
- ADR-032: note the @ prefix is kept in the degraded path, stripped in live
mentions (Leonie).
- Add trailing newline to PersonRepository.java (Felix).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture the reversal of V56's no-FK decision, the DB-layer-integrity
principle, and the cascade-boundary invariant (the cascade never reaches
documents rows). Numbered 032 — 028-031 are already taken on main; the
issue's '028 is next' was written before main moved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Annotate SET NULL on documents.sender_id and CASCADE on
document_receivers.person_id, and add the new
transcription_block_mentioned_persons -> persons person_id FK (CASCADE)
to both db-relationships.puml and db-orm.puml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records the lasting decision behind the #730 fix: exact-case-first
resolution, deterministic lowest-id case-insensitive fallback, and the
explicit refusal of a unique(lower(name)) constraint (collisions are
valid canonical nodes). Previously the rationale lived only in code
comments and the issue body. Raised as a blocker in the PR #733 review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ADR-031 records the shared document-package title factory, the exact-match save-time
regeneration, and the grammar-heuristic one-time backfill (with the ReDoS / no-version-spam
/ file-replace-is-manual decisions). Adds an "auto-generated title" glossary entry, extends
the document-management c4 diagram with DocumentTitleFactory / DocumentTitleBackfillMatcher
and the backfill flows, and documents POST /api/admin/backfill-titles in Admin-Auth.http as
a one-shot ADMIN call hitting port 8080 directly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-up (Markus/Architect): ADR-026 pre-committed a successor ADR if the
in-house layout stopped converging; its UX stop-trigger (Albert smeared across the
canvas) fired. ADR-030 records the bottom-up tidy-tree, the module split, and the two
maintainer-confirmed decisions (hybrid intra-family, per-bloodline width metric),
superseding ADR-026's block-packer in part (no-dagre + seeded-rank retained). GLOSSARY
replaces the deleted sibling-block / parented / anchor-index vocabulary with the new
family-forest model (unit, tidy tree, structural owner, bloodline, cross-link).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI proved cross-file sharing of a virtual-module mock body cannot work in
@vitest/browser-playwright 4.1.6: the static-import spread fails the hoist
("no top level variables"), and the await-vi.hoisted-import form fails to
parse ("Unexpected identifier 'vi'"). vi.hoisted has the same hoist
constraint as vi.mock, so there is no way to thread an external module's
body into the factory here.
Reverts Phase 1: restores the 4 $app/forms/$app/navigation specs to their
inline factories, inlines NotificationBell.spec's forms stub, deletes the
src/__mocks__/$app/* modules and the $mocks alias (vite, vitest-coverage,
kit). The no-factory-ban meta-test stays (no-factory vi.mock is still
banned). ADR-012 amended to record the infeasibility. Everything else
($app/state migration, confirm context-inject, notification refactor, the
pin, the meta-test) is unaffected. Part of #560.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>