ADR-003: Department = unique (Agency × Topic) carrying its own imprint + DPP
Status: Accepted — Frank, 2026-06-25 (team ratification pending) · Datum: 2026-06-25
⚠️ Partially superseded by ADR-014 (2026-07-16). Decision #3 (admin single-select topic picker) is REVERSED: one Beratungsstelle hosts several Fachbereiche, the admin topic picker is multi-select again, and legal texts are shareable
legal_textobjects (dpp_id/imprint_id). The UNIQUE(agency_id, topic_id) constraint and per-department imprint/DPP from this ADR remain in force. Read ADR-014 before acting on this document.Also extended by
ADR-021(2026-08-16). The level ladder described here stops at the Träger; there is a platform level above it (main tenant, gated bylegalContentChangesBySingleTenantAdminsAllowed). Legal texts also have no version history yet —ADR-021decides to build one generically.ADR-022covers when these documents are shown and how a help-seeker's agreement is recorded.
- Status: Accepted — Frank, 2026-06-25 (team ratification pending)
- Date: 2026-06-25
- Deciders: Frank + backend/admin leads
- Related: ADR-001 (modalities as modules — topic/agency/modality orthogonal);
ADR-002 (silent membership);
ORISO-UserService/CONTEXT.md; AgencyServiceAgency,agency_topic; TenantServicetenant.content_impressum/content_privacy;CentralDataProtectionTemplateService
Context
A "department" (e.g. Debt counselling, Suicide prevention, Pregnancy inside one agency centre) is, in the domain diagram, an (agency × topic) pairing that must carry its own individualised imprint and data privacy policy, because economically several departments work "under one roof" but are legally distinct.
Today in code:
agency_topicis a plain join with noUNIQUE(agency_id, topic_id)→ the same topic can be linked to one agency multiple times ("duplicate topics"), which breaks any per-pair imprint/DPP mapping and makes routing ambiguous.- Imprint is per-tenant only (
tenant.content_impressum); DPP is per-tenant content rendered with per-agency contact placeholders (CentralDataProtectionTemplateService). Neither can be individualised per department.
The silent-membership routing (ADR-002) needs a clean "topic → the one department → its counsellors + its imprint/DPP".
Decision
- Department is the first-class unit = a unique (agency_id, topic_id). Enforce
UNIQUE(agency_id, topic_id). Duplicate (agency, topic) becomes a prevented data error, not a supported case. - Attach
imprint_id+dpp_idto the department, individualised per pairing (overriding/extending the tenant-level imprint and agency-rendered DPP). - Admin topic picker becomes single-select (was multi-select). Existing duplicate
agency_topicrows are removed before the constraint is added. (ORISO is pre-prod with no real users → duplicates can simply be deleted/recreated, not carefully back-filled.) - Imprint/DPP carry a
draft | publishedstatus. Counselling may start while the basis is still a draft/unpublished document; the draft state is recorded in the audit/log (provisional basis) rather than presented to the client as a finalised legal document.
Consequences
Positive: unambiguous routing (topic → one department → counsellors + imprint/DPP); the "each department its own individualised imprint/DPP" requirement is satisfied; duplicate-topic class of bug is structurally prevented.
Negative / cost: a migration is required (dedup existing duplicates, backfill
imprint_id/dpp_id, then add the constraint); imprint moves from tenant-only to a
department-level object (new tables/columns across AgencyService and/or TenantService); admin
UX change (single-select + dedup tooling); draft/published lifecycle adds state to manage.
Alternatives considered
- Support duplicate topics, each with its own imprint (reject): ambiguous routing — "topic → which of the duplicate departments?" has no clean answer; the bug class persists.
- Keep imprint per-tenant only (reject): cannot individualise per department, which is the whole legal requirement.
Implementation status (2026-07-01)
The DPP half of this decision is already built and live on dev (agency_topic.content_dpp +
publication_status, DepartmentDataProtectionService — commits 54fe868/a84e81a). Confirmed
in a grill-with-docs session: the remaining work — the UNIQUE(agency_id, topic_id) constraint +
dedupe migration, and the still-missing per-department Impressum column (the imprint_id half) —
will be built together in one slice, since both touch agency_topic. See also ADR-009 (Topic
itself stays global; Department is only where the legal texts live).
Implementation status (2026-07-03)
Verified 2026-07-03: UNIQUE(agency_id, topic_id), the dedup migration, and the
imprint_id/dpp_id reference columns exist ONLY on the stale local branch
feat/adr-003-department-agency-topic-unique in ORISO-AgencyService (base 55 commits behind
dev, pre-AVV; its changeset dir 0021_agency_topic_department collides with dev's
0021_agency_topic_legal and its SQL lacks IF NOT EXISTS). Dev stores DPP inline
(agency_topic.content_dpp), the local branch models references — mutually inconsistent, needs
rebase + renumber + a reconcile decision (recommend: keep inline content_dpp, drop dpp_id;
keep imprint as a content_imprint inline column for symmetry). Per-department imprint not
started anywhere. AgencyTopicRepository.findByAgency_IdAndTopicId already assumes uniqueness
(Optional return). Tracked as ORISO-UserService#203 under EPIC #205.
Implementation status (2026-07-07)
The 2026-07-03 note above is superseded — the reconcile it recommended was carried out and
the whole backend now lives on dev (the stale reference-column branch was abandoned):
- AgencyService (done, on
dev): changeset0023_agency_topic_departmentruns the dedup, addsUNIQUE(agency_id, topic_id)(guarded by anonFail=MARK_RANprecondition), and adds the inlinecontent_imprint+publication_status_imprintcolumns — mirroring the existing inlinecontent_dpp/publication_statusfrom0021.dpp_id/imprint_idreference columns were dropped as recommended.AgencyTopiccarries all four fields with a@PrePersistNOT-NULL guard. Admin API exposes per-department read + publish for both DPP and imprint (commita2e638f, IDOR-scoped to the caller's agencies). Public, published-only read side isDepartmentLegalService(draft/never-authored →null) wired intoAgencyController. - Admin FE — PR opened: the agency topic picker becomes single-select (was multi-select) —
the last open acceptance item of #203. The 2026-06-26 attempt (
02435c3) was never merged anddevdiverged, so it was re-applied fresh onto currentdev, then hardened: the id-normalisation logic (previously duplicated acrossaddAgencyData.ts,updateAgencyData.ts, and the Edit page) was unified into onenormalizeTopicIds()helper, and test coverage was added where there was none before —updateAgencyData.tshad zero tests prior to this PR. See ORISO-Admin#245 (fix/adr-003-agency-single-topic-picker), not yet merged.
Remaining after this slice: the client-facing render of the department imprint/DPP is part of the broader case-handover client view (#204), not ADR-003 proper.
ADR-002: Silent room membership with an access-control confidentiality curtain (not E2EE)
Status: Accepted — Frank, 2026-06-25 (team ratification pending) · Datum: 2026-06-25
ADR-004: Keep the custom chat UI; adopt matrix-js-sdk Megolm under it (don't embed Element Web)
Status: Accepted — 2026-06-26 (grill-with-docs session). June-30 scope and the crypto sequencing are fixed; the SDK-Megolm migration itself is scheduled, not yet built. · Datum: 2026-06-26