ORISO Dokumentation

ADR-018: Erstantwort — one persisted event rendered as a Carimat bubble sequence, configured per Träger

Status: Accepted — Frank, 2026-07-30 (grill-with-docs session) · Datum: 2026-07-30

  • Status: Accepted — Frank, 2026-07-30 (grill-with-docs session)
  • Date: 2026-07-30
  • Deciders: Frank (product) + AI (engineering)
  • Related: CONTEXT-erstantwort-und-carimat.md (full glossary), ADR-003 + ADR-014-shared-legal-text-objects-multi-topic-agencies-topic-before-consent.md (Fachbereich owns DPP/Impressum; topic before consent), ADR-004/ADR-005 (Matrix + Megolm), ADR-008 (who reads along), ADR-010 (platform-controlled per-tenant allowlist — the same shape, deferred here), CONTEXT-conversation-types.md, ORISO-Frontend/CONTEXT.md (Activity Timeline), ORISO-Frontend issue #772
  • Trigger: U25 supplied a first-response text that deviates drastically from the platform default — informal, gender-free, "we counsel by MAIL and are NOT a chat service", a peer-counselling and supervision disclosure, an explicit "do not send us personal data" and therefore no invitation to leave an e-mail address, plus emergency numbers. Björn's open questions to Janna (2026-07-24) asked whether such a message must become per-agency configurable.

Context

Retiring the Rocket.Chat transport (ADR-004) took the first-response posting with it, and nothing replaced it. Verified on origin/pre-dev: postWelcomeMessageIfConfigured, postFurtherStepsIfConfigured and FURTHER_STEPS have zero occurrences in UserService src/main, and createEnquiryMessage delegates unconditionally to createMatrixEnquiryMessage. What survived is the scaffolding around the removed mechanism:

  • The configuration without its mechanism: welcomeMessage.sendWelcomeMessage + welcomeMessageText (per-Träger free text, default off, Admin form field never built) and sendFurtherStepsMessage (default on) still exist and now toggle nothing.

  • The renderer without its transport: FurtherSteps.tsx renders only when alias.messageType === 'FURTHER_STEPS', and no Matrix code path produces an alias object. The frontend still targets /service/messages/...; UserService on origin/pre-dev has no PATCH /messages/{id} handler, and no messageservice deployment exists in the Pre-Dev manifests while MESSAGE_SERVICE_API_URL still points at one. Both call sites swallow their errors silently, which is why this stayed invisible.

    Measured on Pre-Dev 2026-07-30 (api.oriso-dev.site): GET /service/messages/ answers 401 with a Bearer challenge, like the routed control /service/users/data, while an unknown path answers 404 — so the path is not unrouted and not an SPA fallthrough, contrary to what earlier drafts of this ADR and of the linked issues claimed. Those drafts reasoned from the ORISO-Helm chart, which Pre-Dev does not run (values-pre-dev.yaml header: Pre-Dev still runs the archived ORISO-Kubernetes release, ORISO-Helm#110) — a trap worth remembering for any future infrastructure claim about Pre-Dev. Whether an authenticated PATCH reaches a handler is untested and must not be assumed either way.

  • TopicEntity.welcomeMessage / sendNextStepMessage exist per Fachbereich with no consumer anywhere.

Meanwhile a Carimat persona is shipped (robot avatar, name, subtitle line above the bubble) in the pseudonym/privacy onboarding, together with the animation primitives (TypingReveal, TypewriterText) and a multi-bubble precedent (the three breathing cards). The only surviving in-chat consent surface carries role="dialog" aria-modal="true" although it is an inline card, and it blocks the ratsuchende person's composer. Its promise ("Erst danach dürfen unsere Berater_innen einen Chat mit ihnen starten") does hold: the gate is active exactly while the session is STATUS_EMPTY/STATUS_ENQUIRY, so nothing has been sent and nobody has picked the case up. No server-side check of dataPrivacyConfirmation exists, but that is a hardening gap against a bypassed client, not a false statement.

Naively "making the message editable per agency" would therefore paste a Träger's prose over a mechanism that does not run, drop two working action buttons, drop the formal/informal axis, and turn statements the system can verify (who reads along, which modality, which deadline) into unverifiable typed claims — in the one document that is supposed to serve as the KDG §11 transparency record.

Decision

  1. The Erstantwort is composed from Bausteine, not authored as one text. A fixed platform catalogue of ~15 units, each with a trigger, a source (Plattform-Text / Träger-Text / derived), an optional action, a modality assignment, and a toggle. Rejected both extremes: a hard-coded message (would need a release per Träger) and a free rich-text field (loses the action buttons and turns verifiable facts into claims).
  2. Exactly one free Baustein. A single per-Träger "Freier Hinweis" at a fixed position is the deliberate escape hatch — U25's peer-counselling disclosure lands there. Guardrail: derived Bausteine are never editable. What the system knows, the system renders; a typed claim can contradict the configuration, a rendered one cannot.
  3. Delivery = one persisted [SYSTEM_NOTIFICATION] event with a versioned structured payload, rendered client-side as a staged bubble sequence with typing dots. Carimat is a rendering identity, not a Matrix account. A bot account in the room would be an additional Megolm key holder in a room holding §11 KDG special-category data, and a bot has no Schweigepflicht — so either the operator gains a technical read path (and the zero-data-emission claim falls), or the bot sends plaintext into an encrypted room. Neither is acceptable. A purely client-side sequence was also rejected: it would leave no evidence that the person was told the deadline, the confidentiality terms, who reads along, and the emergency numbers — precisely what a supervisory authority asks about for a service with real minors.
  4. Frozen words, live state. The event stores the resolved wording (so what was said is provable and later configuration changes do not rewrite history); completion is read live from state that already exists (displayName, dataPrivacyConfirmation, email, 2FA) through endpoints that already work. Consequence, accepted deliberately: a Baustein whose completion is not already represented elsewhere does not belong in this catalogue — the Case-Handover team-access opt-out switch from ORISO-UserService/CONTEXT.md is therefore explicitly out of scope here, even though it looks like a sibling.
  5. Ordering belongs to the platform, content to the Träger. Each Baustein's trigger (chat entry · after the first message · after the enquiry was dispatched · after assignment) is platform-assigned, because consent must precede data transmission and no Träger may reorder that by accident.
  6. Editorial content hangs at Träger level; resolution is a chain from day one. Fachbereich ?? Beratungsstelle ?? Träger ?? Plattform is implemented immediately, but only the Träger level gets an Admin UI — the middle levels become a form later, not a migration. U25 is its own Träger, so this covers the trigger case. Each Baustein also carries a "platform-permitted" field pre-set to yes, so the two-level permission model (ADR-010's shape) is likewise a later UI. Interim safety measure instead of that model: the two safety-bearing Bausteine — "send us no personal data" and the emergency numbers — get no toggle at all for now.
  7. The platform's own voice is gender-neutral by reformulation, not by notation. Rejected a Träger-selectable notation axis: the actual request is the absence of gendered terms, notation × 7 locales × formal/informal is a combinatorial and machine-translation dead end, and German notation has no counterpart in the six other locales. Scope now: every text where the platform speaks for itself (the person reads them consecutively from one sender — a mixed notation is a contradiction inside one conversation, not a style blemish in two documents). The remaining UI is a separate sweep. This also resolves the shipped inconsistency between Berater*in and Berater_innen.
  8. A Träger writes one German variant, chosen by its existing languageFormal; only Plattform-Texte carry both. Translation is optional and machine-assisted on publish, reusing the legal-texts mechanism; a missing locale falls back to the original, with English as second choice. Rejected requiring all seven locales (nobody would ever publish) and German-only (U25's core statements — peer counselling, send no personal data, emergency numbers — would silently vanish for every non-German-speaking person on a suicide-prevention service).
  9. Forward lock yes, backward lock no. The composer stays blocked until consent is confirmed — which today is what actually guarantees the promise, since the gate is active only in the pre-assignment enquiry phase. A server-side check of dataPrivacyConfirmation is added on top as defence in depth, scoped to entry paths where the person did not choose the agency beforehand (in Agency Counselling consent is given at registration per ADR-014 shared legal text objects), together with a regression test pinning the flow invariant so a later refactor cannot remove the protection silently. No Baustein ever blocks assignment or a counsellor's reply; e-mail, 2FA and credential saving are voluntary throughout.
  10. The send confirmation is hollowed out: it acknowledges dispatch and no longer names the deadline, which now has exactly one owner. It closes when the first bubble is up rather than after a fixed 10 s.
  11. One Activity Timeline entry for the whole Erstantwort, targeting the chat — which requires a deliberate exception to the current filter that discards system notifications. Rejected one entry per open action: a person who has just sent a first message about suicidal thoughts should not receive three to-do items.

Delivery cut

Issue #772 is repurposed, not closed (it carries a stakeholder and a live conversation thread) and becomes the first slice under a new EPIC: the Baustein sequence with platform defaults only — gender-neutral wording, hollowed-out send confirmation, one Activity Timeline entry, action Bausteine reading existing state. No Admin UI and no Träger texts in the first slice, which is what makes it independent of Janna's pending answers: everything still open concerns Träger content.

Sibling issues under the EPIC:

  • Admin UI + Träger texts (the per-Träger editor, reusing the legal-texts editor and its machine translation)
  • Konto-vervollständigen Baustein — display name plus optional e-mail, triggered after the enquiry was dispatched
  • The chat bubble with all existing variants and alignments in Storybook, plus the variants designed here (FurtherSteps, SystemMessage, ReassignMessage, E2EEActivatedMessage, the Case-Handover cards and the Carimat cards have no stories today)
  • The Admin card for display-name editing and optional e-mail — as the first card under Einstellungen/Berechtigungen, modelled on CaseHandoverCard; it also has to fix the existing "Login-Funktion" card, whose description promises behaviour that does not exist
  • Credential saving: repair the browser save prompt at registration (name attributes, do not cut the prompt off with the hard redirect) plus "set your password now" after the enquiry, verified in Safari, Firefox and Chromium
  • Server-side enforcement of dataPrivacyConfirmation on the assign and reply paths
  • Reconcile handover.mdx with the code and with the 2026-07-06 opt-out decision

Consequences

Positive. The first slice ships without waiting on Janna's answers, because everything still open concerns Träger texts, which arrive in a later slice. No new key holder in any counselling room. No new state, no new table, no resurrection of MessageService — every action reuses a working routed endpoint. The catalogue makes U25's text expressible without a release, and switching off the e-mail invitation is a toggle rather than a text deletion, so the contradiction Janna objected to disappears structurally.

Cost. A structured payload needs versioning, or a later frontend renders old events wrongly. A foreign Matrix client sees a JSON blob instead of bubbles. Neutral reformulation is genuine copywriting, not search-and-replace, and reads flatter than the gendered original. Roughly fifteen Bausteine × several locales is real editorial surface. The Activity-Timeline filter exception and the server-side consent check are both new backend work that #772 did not previously contain.

Surfaced but not decided here. The Wiederherstellungslücke — no e-mail means no recovery, and the generated 16-character password is never shown again — is a structural consequence of the anonymity model rather than a fault, but it is written down nowhere and has no row in edge-cases.mdx. Note that the reset endpoint's uninformative response is deliberate account-enumeration protection and must be preserved. One candidate shape for a recovery path is a recovery code at registration, following the shipped Matrix recovery-key flow; "no recovery, credential saving is the mitigation" is an equally legitimate outcome. Either way the decision belongs in its own record, not here. Cross-browser credential saving must be verified in Safari, Firefox and Chromium rather than assumed: the Credential Management API is Chromium-only, and the registration flow's preventDefault() + XHR + hard window.location.href is not a form submission that Safari or Firefox recognise. Separately, handover.mdx §4.7.3/§4.7.7 documents in-chat consent buttons the code does not have and predates the 2026-07-06 opt-out decision; and two accepted ADRs both carry the number 014.

Auf dieser Seite