Skip to content

fix(samples): settle samples an aborted seed run left behind - #92

Merged
Fluory merged 4 commits into
mainfrom
claude/chore-sample-leftovers-84
Sep 28, 2026
Merged

Fluory merged 4 commits into
mainfrom
claude/chore-sample-leftovers-84

Conversation

@Fluory

@Fluory Fluory commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Warum

Fixes #84 · Folge aus dem Re-Review von #83

Arbeitsstand

  • Ziel: Ein abgebrochener Seed-Lauf hinterlässt keine dauerhaft sichtbaren Reste in der Anfragenliste.
  • Nicht-Ziele: Löschen von Zeilen, Aufräumen normaler (nicht vorbereiteter) Anfragen.
  • Erledigt: Status-Ereignis sample.retired (test-first); Regel isSampleLeftover im requests-Modul (Beispiel in NEW, PROCESSING oder ERROR aus der Verarbeitung – nie ERROR aus dem Export), angewendet über retireSampleLeftover unter Zeilensperre; der Seed setzt Reste vor dem Anlegen auf abgelehnt, mit fester Begründung und Audit-Ereignis; Ergebnis { seeded, retired }; Runbook §6, CHANGELOG; Review-Befunde eingearbeitet.
  • Offen: Merge.
  • Annahmen: Zwei Seed-Läufe derselben Firma laufen nicht gleichzeitig (Operator-Schritt).
  • Nächster kleinster Schritt: Merge.

Was ist passiert (Klartext)

Bricht das Anlegen der Beispiel-Anfragen mittendrin ab, blieb bisher ein halbfertiges Beispiel („in Verarbeitung“ oder „Fehler“) für immer in der Liste stehen – in der Oberfläche ließ es sich weder ablehnen noch neu verarbeiten (Letzteres ist absichtlich gesperrt, weil es die Live-KI aufrufen würde). Jetzt räumt der nächste Lauf solche Reste zuerst auf: Sie werden mit dem Grund „Beispiel durch einen neuen Lauf ersetzt – das Anlegen war abgebrochen.“ abgelehnt und im Verlauf festgehalten. Gelöscht wird nichts. Ein bereits freigegebenes Beispiel, dessen Übergabe ans ERP gescheitert ist, bleibt dagegen stehen – es kann weiter erneut exportiert werden.

Plan-Pflicht (SYSTEM.md §4)

  • Kein Auslöser – keine Modulgrenze, öffentliche API, Migration, Auth/Rechte, kein Zahlungs-/Daten-/Infrapfad, höchstens zwei Module, keine Architekturvarianten, umkehrbar
  • Auslöser zutreffend – Impact Manifest ausgefüllt (Plan vor Code)

Impact Manifest

  • Betroffene Module: requests (Statusmaschine: Ereignis sample.retired; Regel isSampleLeftover; listSampleLeftoverIds(), retireSampleLeftover()), samples (Seed räumt Reste auf); Operator-Skript src/seed-samples.ts (Ausgabe); Doku.
  • Schnittstellen / Datenänderungen: neuer Übergang der Statusmaschine NEW/PROCESSING/ERROR → REJECTED für das Ereignis sample.retired – nur über retireSampleLeftover, das isSampleLeftover unter der Zeilensperre prüft (Beispiel; ERROR nur aus der Verarbeitung). Keine Migration, keine API-Änderung. Audit-Aktion request.sample_retired.
  • Akzeptanzkriterien: die aus chore(samples): settle samples left over by an aborted seed run #84.
  • Testplan: Unit – Statusmaschine erlaubt sample.retired aus NEW/PROCESSING/ERROR und verbietet es aus REVIEW/APPROVED/EXPORTED/REJECTED; isSampleLeftover (9 Fälle, u. a. ERROR aus dem Export und Uploads nie). Integration – abgebrochener Lauf (Speicher fällt aus) → Rest in ERROR; nächster Lauf → Rest REJECTED mit Begründung, genau ein Audit-Ereignis; ein freigegebenes Beispiel in ERROR aus dem Export übersteht einen Lauf; Idempotenz mit { seeded: [], retired: 0 }.
  • Verifizierte Fakten: die Verarbeitung eines Beispiels wird nie eingereiht (feat(demo): prepared sample requests from recorded AI answers #83) – NEW/PROCESSING/ERROR aus der Verarbeitung bedeutet also nur „Lauf abgebrochen“; der Export des freigegebenen Beispiels wird dagegen eingereiht und kann in ERROR aus dem Export enden (Review); die Oberfläche kann nur aus REVIEW ablehnen; reprocessRequest lehnt Beispiele nur in der Verarbeitungsstufe ab.
  • Offene Annahmen: keine parallelen Seed-Läufe derselben Firma – erzwungen wird das in chore(samples): serialize concurrent seed runs per company #93.
  • Nicht-Ziele: Löschen, normale Anfragen.
  • Risiken und Rollback: ein Seed-Lauf, der parallel zu einem anderen läuft, könnte dessen Beispiel in PROCESSING ablehnen – Operator-Schritt, im Runbook als Einzellauf beschrieben; Rollback per Revert.

Geändert

  • src/features/requests/status.ts (+ Test): Ereignis sample.retired
  • src/features/requests/sample-leftover.ts (+ Test, neu): isSampleLeftover (Eingabetyp aus dem Schema, kein Import aus dem Repository – vermeidet einen Zirkel)
  • src/features/requests/repository.ts, index.ts: listSampleLeftoverIds(tx), retireSampleLeftover(tx, row, reason)
  • src/features/samples/seed.ts, index.ts: Reste vor dem Anlegen ablehnen (Begründung, Audit), Rückgabe SeedResult { seeded, retired }
  • src/seed-samples.ts: Ausgabe der abgelehnten Reste
  • tests/integration/samples.test.ts: Abbruch-Fall prüft das Aufräumen; neuer Fall „freigegebenes Beispiel mit Export-Fehler bleibt“; Rückgabetyp angepasst
  • docs/technical/deployment-vercel.md §6, CHANGELOG.md

Nachweis (SYSTEM.md §11)

  • verify:changed: grün – vitest status.test.ts 36/36, zuerst rot (7 neue Fälle: unbekanntes Ereignis); sample-leftover.test.ts zuerst rot (Modul fehlte); vitest src/features/samples src/features/requests 58/58; typecheck, lint grün
  • verify: grün – CI am PR: Unit 253/253, Integration 19 Dateien grün (darunter samples.test.ts 7/7), depcruise, build, audit. Ein erster CI-Lauf fiel an no-circular (sample-leftover.ts ↔ repository.ts) – lokal hatte ich depcruise nicht laufen lassen; behoben durch einen Schema-Typ, lokal depcruise jetzt ohne Verletzungen
  • verify:full / E2E-Spec: nicht betroffen (die E2E-Spec der Beispiele prüft Kennzeichnung und Export; ein frischer Lauf hat keine Reste)
  • Manueller Prüfnachweis: nicht nötig – im Integrationstest belegt
  • Frischer Review (P1 vor Ready-for-review; Architektur/API/DB immer): siehe PR-Kommentar – changes requested, Befunde 1 und 2 behoben (eaf94e1), 3 → chore(samples): serialize concurrent seed runs per company #93

Doku-Entscheidung (genau eine)

  • Keine langlebige Doku betroffen – Begründung: –
  • Doku betroffen und im selben PR aktualisiert:

Entferntes oder Umbenanntes: docs/ + README gegrept, Treffer bereinigt: Rückgabetyp von seedSamples geändert – nur im Code referenziert

Dateigrößen und neue Bausteine (SYSTEM.md §7)

Dateien über 500 Zeilen im Diff (Ausnahmen: generierter Code, Lockfiles, Fixtures, Migrationen, Schemas, Ressourcen, Doku, Konfiguration):

  • keine
  • bewusst belassen – Begründung: –
  • im selben PR nach fachlicher Verantwortung geteilt
  • Folge-Issue –

Über 800 Zeilen mit neuer Fachlogik oder über 1000 Zeilen (P1/P2): nicht betroffen

Neue Shared-Komponente, Utility-Datei, Adapter oder fachlicher Service:

  • nein
  • ja – gesucht nach: –

Subagent-Einsätze

Keine.

Risiken / offene Punkte


🤖 Generated with Claude Code

Fluory and others added 2 commits September 28, 2026 13:57
A seed run aborted midway left a sample in NEW/PROCESSING/ERROR that
stayed in the list forever: the UI cannot reject it (only from REVIEW)
and reprocessing a sample is refused on purpose. Samples are never
queued, so these states only mean the run broke off. The seed now first
rejects such leftovers through a dedicated status event
(sample.retired), with a fixed reason and an audit event - never
deleting a row.

Fixes #84

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
requestflow Ready Ready Preview Sep 28, 2026 12:19pm UTC
requestflow-ai Ready Ready Preview Sep 28, 2026 12:19pm UTC

The fresh review of #92 found that the leftover step rejected every
sample in ERROR - including an approved one whose export failed, which
may already be in the ERP and can be exported again. Only NEW,
PROCESSING and ERROR from processing mean an aborted run. The rule now
lives in the requests module (isSampleLeftover, retireSampleLeftover),
re-checked under the row lock, so no other caller can apply it to an
ordinary request. Concurrent seed runs are a follow-up (#93).

Refs #84

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Fluory

Fluory commented Sep 28, 2026

Copy link
Copy Markdown
Owner Author

Frischer Review (/review-pr) – unabhängiger Reviewer-Agent ohne Umsetzungskontext, 2026-09-28

  1. [Important] – src/features/samples/seed.ts:25/90 (LEFTOVER, retireLeftovers) – Beispiele in ERROR werden abgelehnt, ohne dass errorStage geprüft wird. Das Export-Beispiel hat einen eingereihten Job (approveAndExport reiht ihn über deps.boss ein). Ist das ERP beim Seed nicht erreichbar und gibt der Drain auf, steht das Beispiel in ERROR(export): freigegeben und über „Erneut verarbeiten“ weiter exportierbar; nach einem Timeout ggf. schon im ERP. – Der nächste Seed-Lauf setzt diese freigegebene Anfrage auf REJECTED; Exportzeile und ggf. ERP-Datensatz bleiben. Widerspricht „never a decided one“ und dem Manifest-Fakt „Beispiele haben nie einen eingereihten Job“. – Nur NEW, PROCESSING und ERROR mit errorStage === "processing" abräumen (Muster duplicateDecidable), Integrationstest „ein Beispiel in ERROR(export) übersteht einen Seed-Lauf“, Kommentar (status.ts:35-37), Manifest und Runbook §6 korrigieren.
  2. [Note] – status.ts:38 / transitionRequest – sample.retired steht jedem Aufrufer der öffentlichen requests-API offen; die source-Prüfung gibt es nur im samples-Modul (ein Aufrufer, unter Zeilensperre, Muster wie reject.duplicate). – Ein künftiger Aufrufer könnte eine normale Anfrage in PROCESSING ablehnen. – Die Regel ins requests-Modul legen, z. B. retireSampleLeftover(tx, row, reason) mit Prüfung von source und Stufe.
  3. [Note] – seed.ts:69 – Parallele Läufe: der Aufräumschritt trifft nie ein Beispiel desselben Laufs (geprüft); ein gleichzeitiger zweiter Lauf kann aber das Beispiel des ersten in PROCESSING ablehnen, weil processRequestJob die Sperre nicht über den KI-Aufruf hält. Im PR als Annahme dokumentiert. – Annahme erzwingen (Advisory-Lock je Firma über den ganzen Seed), gern als Folge-Issue.

Ohne Befund geprüft: AK von #84 (Statusmaschine mit Audit, kein Löschen, Test, Runbook §6); Tenant-Scoping von listSampleIds (tenantOf + RLS); Idempotenz { seeded: [], retired: 0 }; alle Aufrufer an den neuen Rückgabetyp angepasst (E2E-Global-Setup ruft nur das Skript); Audit-Daten { from } ohne Personenbezug; Tests können fehlschlagen (Unit lokal 36/36, CI check grün); Klartext vorhanden; Plan-Pflicht ehrlich bis auf den falschen Fakt aus Befund 1.

Urteil: changes requested – ein Seed-Lauf könnte eine freigegebene, bewusst weiter exportierbare Beispiel-Anfrage ablehnen; der Fix ist klein.


Bearbeitung (eaf94e1): 1 → Regel isSampleLeftover (Beispiel; NEW, PROCESSING oder ERROR aus der Verarbeitung – nie ERROR aus dem Export), test-first (9 Fälle); Integrationstest „approved sample whose export failed is kept“; Kommentar, Runbook §6, CHANGELOG, Manifest korrigiert. 2 → Regel liegt im requests-Modul: listSampleLeftoverIds + retireSampleLeftover(tx, row, reason) prüft unter der Zeilensperre; das samples-Modul ruft nur noch diese Funktion. 3 → Folge-Issue #93.

sample-leftover.ts took RequestRow from repository.ts, which imports the
rule - dependency-cruiser's no-circular failed in CI. The rule now types
its input from the schema.

Refs #84

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Fluory

Fluory commented Sep 28, 2026

Copy link
Copy Markdown
Owner Author

Merge auf Freigabe des Orchestrators (2026-09-28: „merge getestetes“) – CI grün, Review-Befunde bearbeitet.

@Fluory
Fluory merged commit 406c938 into main Sep 28, 2026
7 of 9 checks passed

This branch was successfully deployed

2 active deployments
Preview – requestflow — 37710eaf Deployed Sep 28, 2026 by vercel[bot]
Preview – requestflow-ai — 37710eaf Deployed Sep 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(samples): settle samples left over by an aborted seed run

1 participant