Skip to content

Annotate-Only Cross-Cutting Features

Problem

When a feature spans multiple entities (e.g., a project mentions another project, a ticket references another ticket, a transcript segment relates to another segment), there is a temptation to "do the right thing" automatically: copy the cross-reference into the target, update target status, append text, re-order outputs. This breaks the user's mental model:

  • Single-source-of-truth becomes ambiguous (did this update come from the speaker, or from a heuristic?).
  • Order dependencies emerge (target must be processed AFTER source).
  • The user loses authority over what crosses entity boundaries.

Symptoms when this is violated: - Target entity's was_passiert (or equivalent narrative field) drifts from what the speaker actually said about the target. - Closures or status changes happen "automatically" from ambiguous phrases (e.g., "übernommen ins Handbuch" silently flips a project to closed). - Pipeline produces different outputs depending on processing order.

Pattern: Annotate-Only

Cross-cutting features must annotate the source with a structured signal, never mutate the target.

Source entity (where the cross-reference was observed)
  └─ adds: cross_references: [list of CrossRef]
       ├─ target_name
       ├─ target_id (when resolvable)
       ├─ kind (informational | status_relevant | absorption | ...)
       ├─ quote (verbatim source text)
       └─ reason (one-line classifier rationale)

Target entity
  └─ UNCHANGED. Pipeline produces identical output as if the cross-cutting
     feature were disabled.

The downstream review/UI layer becomes the place where the user decides what (if anything) to copy where.

Why this works

  • Order-independent: Source and target can be processed in any order. The pipeline stays single-pass.
  • Reversible: Disabling the feature produces the same target output. Easy A/B comparison.
  • User keeps authority: Cross-entity actions (close project, append note, change status) require an explicit user decision in review.
  • Auditable: Every cross-entity effect has a paper trail (which CrossRef was acted on by whom).

Three layers, three guarantees

When implementing, separate concerns across at least three layers:

Layer 1 — Detection (cheap, deterministic): Rule-based pre-pass that emits candidates. Optimized for recall over precision. Fast enough to run inline.

Layer 2 — Classification (semantic): LLM (or richer model) classifies candidates into kind and writes reason. Detection candidate list bounds the LLM's job.

Layer 3 — Action (user authority): Review UI presents the annotations. User decides whether to copy, ignore, or close.

If the LLM is unavailable, Layers 1+3 still produce a useful artifact (Layer 2's kind can default to informational or unclassified, and the review UI shows raw candidates). The feature degrades gracefully — it does not break the pipeline.

Status-preservation invariant

When a signal could suggest changing an authoritative field on the source (e.g., absorption suggests the source project is closable), the signal MUST NOT overwrite the authoritative field. Instead, it sets a suggestion field:

# WRONG — auto-overwrite
if cross_ref.kind == "absorption":
    extraction.status_farbe = "erledigt"   # silently flips status

# RIGHT — annotate, preserve authority
if cross_ref.kind == "absorption":
    extraction.closure_suggested = (
        f"Content moved to {cross_ref.target_project_name} — close?"
    )
    # status_farbe equals what rule-based detection produced. Unchanged.

The user (in review) is the one who decides whether the suggestion becomes a fact.

Where this came from

Pattern emerged during SPEC-069 (Cross-Referenz-Handling) for the V000-Projektupdates pipeline. The naive design would have: - Detected "Notfallkoffer ins Handbuch übernommen" in the Notfallkoffer block - Auto-flipped Notfallkoffer's status_farbe to erledigt - Auto-appended a note to Handbuch's was_passiert

That design was rejected because: - The phrase is ambiguous. "Übernommen" could mean "absorbed" or "we covered the topic in the other meeting." - Auto-flipping status_farbe removes user authority over closures. - Appending to Handbuch creates an order dependency and an untraceable narrative drift.

Architecture-strategist review (during plan iteration 2) confirmed the annotate-only choice and added a Stage 6 visibility hint: when cross-references exist, the review UI shows a count badge so the user knows to look for them. This is the third guarantee — the user is informed there are decisions to make, but no decision is made for them.

Applicability

This pattern applies whenever a feature needs to surface a relationship between two entities and one of these is true:

  • The relationship is detected by a heuristic that has any non-trivial false-positive rate.
  • The downstream effect is destructive or hard to reverse (status change, closure, narrative mutation).
  • The user is the authority on whether the relationship matters.

It does not apply when:

  • The relationship is structurally certain (e.g., a foreign-key reference).
  • Both entities are owned by the same actor in the same workflow step.
  • The downstream effect is purely additive and reversible (e.g., adding a non-authoritative tag).
  • SPEC-069 (Cross-Referenz-Handling) — origin
  • SPEC-065 (Status-Extraktion) — host pipeline
  • SPEC-066 (Interaktive Review) — Layer 3 consumer
  • SPEC-067 (Asana-API-Integration) — writes source-only, never auto-writes targets