festagBlog

Engineering Deep Dive

Der tägliche Statusloop, technisch erklärt

Vom Vercel-Cron um 16:00 bis zur Notification im Client-Postfach: jede Schicht, jede Tabelle, jede Sicherheitspolicy in einem Bild.

19. Mai 2026 · 8 min Lesezeit · für dev, internal

Warum dieser Loop existiert

Projektmanagement-Tools sammeln entweder zu wenig oder zu viel. Slack ist zu laut, Tickets sind zu trocken, und der Kunde bekommt entweder gar keine Updates oder eine ungefilterte Wand aus Commits. Beides macht Vertrauen kaputt.

Festag löst das mit einem strukturierten täglichen Mini-Ritual. Tagro fragt jeden Developer einmal am Tag — pünktlich um 16:00 Berlin — kurz nach dem Stand. Aus diesen Sätzen baut Tagro einen ruhigen, übersetzten Bericht, den der Client bei Bedarf sofort abrufen kann. Mehr nicht.

Der Kern: Devs schreiben einen Satz, Tagro macht daraus eine professionelle Lage. Kein Daily, kein Statusmeeting, keine Excel-Liste.

Der Ablauf, end-to-end

Zwei Eingänge, ein Ausgang. Der Cron-Job feuert deterministisch um 16:00, der Client darf jederzeit on-demand fragen. Beide Wege schreiben in dieselbe Tabelle — der Client sieht keinen Unterschied zwischen „heute geplant gepingt" und „eben frisch erzeugt".

Grafik 01Beide Eingänge, eine Wahrheit
16:00 Berlin                          jederzeit
   │                                     │
   ▼                                     ▼
Vercel Cron ──► /api/cron/tagro-daily   Client klickt
                -dev-prompt              "Status jetzt abrufen"
                │                              │
                ▼                              ▼
   dev_daily_prompts (eine pro       /api/client/status-now POST
   dev + projekt + heute)            (60s Cooldown)
                │                              │
   Notification: "Wie weit bist               │
   du heute mit X gekommen?"                  │
                │                              │
                ▼                              ▼
   Dev sieht Card auf /dev          Tagro liest die letzten 24h
                │                    developer_updates, baut einen
   Textarea: ein Satz                ruhigen Status-Bericht, schreibt
                │                    via service-role in status_reports
                ▼                              │
   /api/dev/daily-update                       ▼
                │                    Report ist sofort im Client-
   ▶ developer_updates (intern)      Dashboard sichtbar
   ▶ status_reports                       │
     (audience=client, visible=true)      │
   ▶ Notification an den Client     ──── ◄  matched ─────┐
     mit Link auf /reports                              │
                                                        │
   Client sieht roten Inbox-Counter,                    │
   liest in der Rail die übersetzte Lage ───────────────┘

Wer sieht was

Sichtbarkeit ist Architektur, nicht UI. Tagro filtert nicht erst beim Render — die Trennung beginnt schon in der Datenbank, mit RLS-Policies und einer ausdrücklichen visible_to_client-Flag auf jedem Bericht.

Grafik 02Sichtbarkeitsmatrix
              │  Dev Panel │  Tagro Layer │  Client Panel
──────────────┼────────────┼──────────────┼─────────────────
Rohes Update  │  ●         │  ● (filtert) │  ─
Evidence      │  ●         │  ● (prüft)   │  ─
Blocker       │  ●         │  ● (ordnet)  │  ● (als Risiko)
Commits / PRs │  ●         │  ● (mapped)  │  ─
Confidence    │  ─         │  ●           │  ─
Übersetzung   │  ─         │  ●           │  ● (liest)
Audio Briefing│  ─         │  ●           │  ● (hört)
Entscheidung  │  ─         │  ● (markiert)│  ● (entscheidet)

Der Client sieht nie rohe Commit-Messages, nie Confidence-Werte und nie interne Dev-Notizen. Was er liest, ist eine bewusste, kurze, professionelle Lage — mit klarem Hinweis, wenn eine Entscheidung gebraucht wird.

Datenmodell

Drei zentrale Tabellen halten den Loop zusammen. Sie sind so klein wie möglich gehalten — jede Spalte ist da, weil sie konkret benutzt wird.

Grafik 03Tabellen + Beziehungen
dev_daily_prompts            developer_updates             status_reports
──────────────────           ──────────────────            ──────────────
id              uuid         id              uuid          id              uuid
developer_id    fk           developer_id    fk            project_id      fk
project_id      fk           project_id      fk            audience        text
prompt_date     date  ─┐     update_text     text          visible_to_     bool
state           text   │     status          text          client
submitted_at    ts     │     blocker         bool          summary         text
related_update  fk ────┤     blocker_desc    text          current_work_   jsonb
payload         jsonb  │     created_at      ts            blockers_json   jsonb
                       │            │                      generated_on    date
                       │            ▼                      created_by      fk
                       └─► matched on submit     ◄────────────────────────┘
                            (related_update_id)            ▲
                                                           │
client_status_queries                                      │
─────────────────────                                      │
user_id        fk                                          │
project_id     fk                                          │
status_report_id fk ──────────────────────────────────────►│
generated_fresh bool                                       │
created_at      ts (used for 60s cooldown)
  • dev_daily_prompts hält den 16:00-Ping. unique(developer_id, project_id, prompt_date) sorgt dafür, dass der Cron idempotent ist — der doppelte 14:00/15:00-UTC-Fire (für Sommer- und Winterzeit) erzeugt nie zwei Karten.
  • developer_updates ist die intern lesbare Rohversion. Sie wird nie an den Client gespiegelt.
  • status_reports mit audience=client und visible_to_client=true ist die einzige Quelle, die der Client je zu sehen bekommt.
  • client_status_queries ist der Audit-Log für jeden „Status jetzt abrufen"-Klick. Er trägt auch den 60-Sekunden-Cooldown.

Sicherheit & Cron

Der Cron-Endpoint ist öffentlich erreichbar — er wird von Vercels Cron-Infrastructure aufgerufen, und der Code muss damit umgehen können, dass jemand anderes ihn anpingt.

Grafik 04Wer darf was schreiben
┌──────────────────────────┐     ┌──────────────────────────┐
│       VERCEL CRON        │     │       USER REQUEST       │
│                          │     │                          │
│   Authorization:         │     │   Supabase Auth Session  │
│   Bearer $CRON_SECRET │     │   (cookie-bound, RLS)   │
│                          │     │                          │
└──────────────┬───────────┘     └──────────────┬───────────┘
               │                                │
               ▼                                ▼
       service-role client               user-session client
       (bypasses RLS)                    (RLS enforced)
               │                                │
               │  writes:                       │  reads:
               ▼                                ▼
       • dev_daily_prompts             • status_reports
       • notifications                   (filtered by project membership)
       • status_reports                • developer_updates
         (audience=client)               (only own + admin)
               │                        • notifications
               ▼                          (user_id = me)
       inserts are always
       intentional + audited
       in audit_logs

Zwei Authentifizierungspfade nebeneinander: Der Cron-Pfad nutzt einen serverseitigen Bearer-Token (oder Vercels eigenen x-vercel-cron-Header) und schreibt mit der Supabase Service-Role — sauber abgegrenzt vom User-Pfad, der weiterhin durch RLS gegated ist. Jeder Write landet zusätzlich in audit_logs, damit auch der Cron rückverfolgbar bleibt.

Trust Layer

Das eigentliche Festag-Prinzip steht hinter dem Loop: Ein Statusbericht darf nicht aus dünner Luft entstehen. Tagro liest, verdichtet, übersetzt — aber jeder Satz muss eine Wurzel in echter Arbeit haben.

Grafik 05Trust Layer Stack
        Trust Layer
        ───────────

        ┌──────────────┐
        │  STATUS      │  ← Was der Client liest
        │  REPORT      │
        └──────┬───────┘
               │ generiert aus
               ▼
        ┌──────────────┐
        │  TAGRO       │  ← Engine: Plausibilität, Confidence, Übersetzung
        │  VERIFICATION│
        └──────┬───────┘
               │ liest
               ▼
        ┌──────────────┐
        │  EVIDENCE    │  ← developer_updates, commits, tasks, decisions
        │  LAYER       │
        └──────┬───────┘
               │ basiert auf
               ▼
        ┌──────────────┐
        │  AUSFÜHRUNG  │  ← echte Arbeit des Developers
        │              │
        └──────────────┘

   Kein Bericht ohne Evidence.
   Kein Evidence-Punkt ohne Audit-Spur.

Kein Bericht ohne Evidence. Keine Evidence ohne Audit-Spur. Der Client kann sich darauf verlassen, dass ein „Alles im Plan" in seinem Dashboard nicht eine freundliche Behauptung ist, sondern eine Folge aus geprüften Signalen.

Was als nächstes kommt

Drei Ausbaustufen sind im Code schon vorbereitet:

  1. Tagro-LLM-Übersetzung statt der aktuellen Heuristik — bessere Tonalität, immer noch dieselbe Evidence-Wurzel.
  2. Voice-Input in der Dev-Card — der Dev darf sprechen statt tippen, Tagro transkribiert + übersetzt.
  3. Per-Project-Berichte mit Dropdown im Client-Dashboard — bei Kunden mit mehreren Projekten parallel.

Der Loop ist der erste vollständige Tagro-Kreis, der ohne Mensch in der Mitte funktioniert. Die nächsten Schichten — Risiken automatisch erkennen, Entscheidungen vorschlagen, Tasks aus Berichten ableiten — bauen alle darauf auf.