fix(bin): make status-presentation manifest errors loud instead of silent - #3554
Closed
Valentino-Sole wants to merge 8 commits into
Closed
Valentino-Sole wants to merge 8 commits into
Valentino-Sole wants to merge 8 commits into
Conversation
status_presentation_cursor_offset, status_outcome_backstop_cursor_offset, and status_retire_presentation_task all returned 1 with zero output on a malformed $state/.status-presentation-cursor row (an unexpected extra field, a missing field, or a non-numeric offset/backstop). Since bin/fm-teardown.sh calls status_retire_presentation_task with no error message of its own, a malformed manifest made teardown exit 1 with nothing on stderr - it printed "Worktree returned to pool" but never "teardown ... complete", leaving finished tasks stuck "in flight" with no visible cause. Root cause: commit d977128 (PR kunchenguid#3495, merged the same day) widened this manifest from 3 to 4 TAB-separated fields (task, ident, offset, backstop) in the same commit that updated the reader. A process still running the pre-d977128 3-field reader against a row a post-d977128 writer had already written in the new 4-field shape hit exactly this silent branch. No writer in the current tree produces a row its paired reader does not already accept; the hazard was the unversioned same-manifest schema change during rollout, not a live mismatched writer. Add a diagnostic that every malformed-row return path now prints to stderr, naming the manifest path, the 1-based row number, the reason, and the expected 4-field format - visible for free in fm-teardown.sh since it calls these functions unredirected. The existing fail-closed behavior (return 1, delete nothing) is unchanged; only its visibility changes. The 4-field format itself stays the accepted shape - widening it further without loosening validation was out of scope here. Document the row format once, above status_presentation_cursor_offset, as this file's sole owner of the schema. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUy6yWRzWtGarb9ZAEjCD
The prior no-mistakes review-fix round accidentally swept .squish/squish.db (a 724KB local squish-MCP scratch database, unrelated to this branch's change) into its commit via a broad add. Untrack it and ignore .squish/ so it cannot happen again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUy6yWRzWtGarb9ZAEjCD
…ort rewrite write failures
…agnostic doc pointers
Confidence Score: 5/5The PR appears safe to merge, with no concrete changed-code defect identified. The new parser matches current and historical writer formats, rejects malformed state before destructive work, reports each refusal clearly, and retains the original manifest and task files on failure. Reviews (1): Last reviewed commit: "no-mistakes(document): correct manifest ..." | Re-trigger Greptile |
Author
|
Superseded: redirecting to Valentino-Sole#2 per captain instruction (firstmate changes now default to the fork). Closing this upstream PR without merging. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Vor jeder Änderung an geteiltem, versioniertem Firstmate-Material die Skill firstmate-coding-guidelines geladen und befolgt.
Befund: bin/fm-teardown.sh scheiterte vier Mal still (Rückgabewert 1, kein stderr) beim Aufräumen bereits fertiger Aufträge, weil state/.status-presentation-cursor Zeilen mit vier tabgetrennten Feldern enthielt, die die damals laufende 3-Feld-Leserversion in bin/fm-classify-lib.sh als 'extra' Feld erkannte und mit stillem 'return 1' ablehnte.
Auftrag:
Grenzen: nur diese Fehlerklasse (stille Manifestfehler), keine sonstige Umstrukturierung der Statusverarbeitung, keine Änderung an Aufsichts- oder Watcher-Logik, nichts unter projects/ anfassen, nichts mergen.
Verlauf dieses Laufs: Der erste no-mistakes-Review-Durchgang fand 3 berechtigte ask-user-Befunde (Kopfkommentar behauptete mehr Strenge als der Parser tatsächlich hatte; IFS=TAB kollabiert bei einem leeren Zwischenfeld und lässt dadurch eine kaputte Zeile mit falschem Wert durchrutschen statt den neuen Fehler auszulösen - echter Regressionsfall gegen die eigene Zusage; Datei-/IO-Fehler am Manifest selbst - Symlink/unlesbar/cat/mv fehlgeschlagen - blieben weiterhin komplett still) plus 1 auto-fix (doppelte Fehlermeldung beim Retire-Pfad ohne Statusdatei) und 1 no-op (vier fast identische Validierungsblöcke, bewusst so belassen, da Kontrollfluss/Rückgabewerte unverändert bleiben sollten und der Auftrag keine Umstrukturierung erlaubt). Alle drei ask-user-Befunde wurden dem Captain vorgelegt; Entscheidung: FIX bei allen dreien. Der Fix-Durchgang hat daraufhin: (a) den Kopfkommentar korrigiert, um die 3-Feld-Alt-Form korrekt zu beschreiben; (b) einen manuellen, IFS-unabhängigen Zeilen-Parser (_fm_status_presentation_row_parse, feldweise über $'\t'-Aufteilung in ein Array statt über read mit IFS=TAB) eingeführt, der eine hinter einem leeren Feld versteckte Überzahlspalte korrekt erkennt, in allen vier betroffenen Leseschleifen verwendet; (c) einen neuen Helfer _fm_status_presentation_manifest_error für Datei-Level-Fehler (Symlink, unlesbar, cat/mv/Schreibfehler) ergänzt und an allen bisher stillen return-1-Stellen in status_presentation_cursor_offset, status_outcome_backstop_cursor_offset und status_retire_presentation_task (Vorab-Prüfung UND gesperrte Rewrite-Schleife) verdrahtet; (d) die doppelte Diagnose im Vorab-Prüfpfad von status_retire_presentation_task behoben (die Vorab-Schleife kehrt bei einem erkannten Formatfehler jetzt direkt mit rc=1 zurück statt rc auf 0 zurückzusetzen und die gesperrte Rewrite-Schleife dieselbe Zeile erneut melden zu lassen); (e) 7 neue Regressionstests ergänzt (versteckte Überzahlspalte hinter leerem Feld bei Lesen UND Retire-Rewrite, tolerierte 3-Feld-Alt-Form bei Lesen UND verlustfreiem Hochschreiben im Retire, symlinktes Manifest bei Cursor-Leser UND Retire, sowie 'ein Fehler erscheint nur einmal').
Dieser Lauf ist ein Neustart nach einem vorherigen Lauf, der terminal mit outcome=failed endete: waehrend des Fix-Runden-Antwortversuchs auf den auto-fix-Befund 'stray-squish-db-committed' (der Fix-Commit des vorherigen Laufs hatte versehentlich .squish/squish.db - eine 724KB lokale SQLite-Datenbank eines unabhaengigen MCP-Speicherservers, mit dieser Aufgabe voellig unzusammenhaengend - mit committet) hat der Pipeline-eigene interne Review-Schritt einen Head erzeugt, der laut dessen eigener Sicherheitspruefung kein Nachfahre des zuletzt aufgezeichneten Pipeline-Head war; die Pipeline hat sich daraufhin bewusst mit outcome=failed abgebrochen, um die bereits erarbeitete Aenderung vor Verlust zu schuetzen, statt sie stillschweigend zu verwerfen. Custody wurde per 'no-mistakes axi sync --recover' zurueckgeholt (branch_sync state=custody_returned, relation=equal). Der wiederhergestellte Commit 2f786b7 enthielt .squish/squish.db weiterhin unveraendert (der Fix fuer genau diesen Befund war der Teil, der beim Head-Mismatch abgebrochen war). Da zu diesem Zeitpunkt kein aktiver Lauf mehr bestand (custody bereits zurueckgegeben, outcome bereits terminal), wurde die triviale, eindeutige Bereinigung selbst als gewoehnlicher lokaler Commit vorgenommen statt eine weitere volle Review-Runde dafuer abzuwarten: .squish/squish.db aus dem Tracking entfernt (git rm --cached) und .squish/ zur .gitignore hinzugefuegt, damit es nicht erneut versehentlich getrackt wird - sonst nichts an diesem Commit veraendert.
Lokal verifiziert vor diesem Neustart: bash -n und bin/fm-lint.sh (ShellCheck, betroffene Dateien) sauber; die vollstaendige aktualisierte Testdatei tests/fm-classify-status-presentation-manifest.test.sh laeuft mit 14/14 gruen (7 urspruengliche plus 7 aus dem Fix-Durchgang); sechs benachbarte Testsuiten (fm-classify-corr-token, fm-classify-decision-key, fm-wake-drain-open-decisions-cursor, fm-wake-drain-open-decisions, fm-wake-drain-outcome-backstop, fm-wake-drain-unread-status) laufen mit insgesamt 74/74 gruen; fm-teardown.test.sh laeuft noch (grosse Suite, vorheriger Lauf vor dem Fix-Durchgang war 58/58 gruen).
What Changed
bin/fm-classify-lib.shnow parses$state/.status-presentation-cursorrows through a shared helper (_fm_status_presentation_row_parse) that splits the raw line on TAB by hand instead ofIFS=$'\t' read, so a surplus column hidden behind an empty field is detected rather than silently shifting values; offset and backstop must be canonical decimal byte counts (digits only, no leading zero, at most 18 digits so shell integer comparison still holds), and a 3-field legacy row without the backstop column is the one tolerated shape, read as backstop 0 and rewritten as a full 4-field row.return 1instatus_presentation_cursor_offset,status_outcome_backstop_cursor_offsetandstatus_retire_presentation_tasknow prints a diagnostic to stderr — per-row errors name the manifest, line number, reason and expected format, and file-level errors (symlinked/unreadable manifest, failedcat, failed rewrite create/append/mv) get their own message. Fail-closed behavior is unchanged (nothing is deleted), the retire pre-check returns immediately so one bad row is reported once, and the manifest format contract is documented at the head of the readers.tests/fm-classify-status-presentation-manifest.test.shwith 23 regression tests covering extra/hidden/missing fields, non-numeric, colon-bearing, leading-zero and out-of-range offsets, the tolerated 3-field row and its loss-free upgrade, symlinked and dangling-symlink manifests, and rewrite write failures;.squish/was added to.gitignoreafter an unrelated local SQLite database was accidentally tracked.Risk Assessment
✅ Low: Die Änderung ist eng auf die eine Fehlerklasse begrenzt, scheitert in jedem geprüften Fehlerfall geschlossen und laut, löscht dabei nichts, erfüllt alle fünf Auftragspunkte quellcodeseitig, und die neue Strenge ist von keinem Schreiber im Baum erreichbar - der einzige Befund ist eine irreführende Kommentarbegründung ohne Verhaltenswirkung.
Testing
Ran the colocated manifest regression suite (23/23 green), re-ran it unchanged against a base-commit copy of the library where it fails, and then demonstrated the change at the product level: the real
bin/fm-teardown.shwas executed end-to-end in the repository's own teardown sandbox against four manifest shapes on both the base commit and this branch. The base tree reproduces the incident (exit 1 with nothing on stderr) and silently drops a column when a surplus field hides behind an empty TAB column; this branch prints a diagnostic naming file, line number, reason and expected format, keeps the manifest untouched, and still accepts the one legacy 3-field row and rewrites it losslessly to 4 fields. The direct consumer suitefm-teardown.test.shand three neighbouring reader suites are green. No test failures, no flakiness, and no setup problems; the working tree is clean and all scratch dirs were removed. This change is CLI-only, so there is no rendered UI surface to screenshot — the reviewer-visible artifact is the before/after teardown transcript.Evidence: Operator transcript: real fm-teardown.sh before (d22318e) vs after (7b6f021) on four broken-manifest scenarios
Source: Operator transcript: real fm-teardown.sh before (d22318e) vs after (7b6f021) on four broken-manifest scenarios
Evidence: Key before/after excerpt (same scenario, same real CLI)
Evidence: Colocated regression suite on this branch (23/23 ok)
Source: Colocated regression suite on this branch (23/23 ok)
Evidence: Same suite against the base-commit library (fails - proves true regression)
Source: Same suite against the base-commit library (fails - proves true regression)
Evidence: Reproduction drivers for the end-to-end transcript
Source: Reproduction drivers for the end-to-end transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-classify-lib.sh:1022- Die Numerik-Pruefung in _fm_status_presentation_row_parse nutztcase "$OFFSET:$BACKSTOP" in *[!0-9:]*). Weil ':' Teil der erlaubten Zeichenklasse ist, passiert jeder Wert aus Ziffern und Doppelpunkten die Pruefung. Verifiziert an der echten Funktion (Worktree-Stand 94fab35): Manifestt1<TAB><echter-ident><TAB>1:2<TAB>0-> status_presentation_cursor_offset gibt1:2mit rc=0 aus; die einzige stderr-Ausgabe ist ein rohesbin/fm-classify-lib.sh: line 1084: [: 1:2: integer expression expectedaus dem nachfolgenden[ "$offset" -gt "$size" ], nicht die neue Diagnose. Ebenso passiert::(Ausgabe::, rc=0) und ein Backstop3:4(status_outcome_backstop_cursor_offset degradiert still auf 0). Damit liefert genau der Leser, den diese Aenderung haerten soll, einen falschen Wert ohne Fehler - und der Kopfkommentar (Zeile 951) sowie Auftragspunkt 3 ('nicht-numerischer Versatz/Backstop bleibt ein harter Fehler mit lauter Diagnose') behaupten das Gegenteil. Der zugehoerige Schreiber im selben File, status_commit_presentation_snapshot (Zeile 1405/1409), prueft bereits korrekt feldweise mitcase "$x" in ''|*[!0-9]*). Korrektur: OFFSET und BACKSTOP einzeln mit derselben''|*[!0-9]*-Form pruefen statt beide zu einem durch ':' getrennten String zu verketten; Kontrollfluss und Meldungstext bleiben unveraendert.bin/fm-classify-lib.sh:1328- In der gesperrten Rewrite-Schleife von status_retire_presentation_task istprintf '%s\t%s\t%s\t%s\n' ... >> "$tmp" || { rc=1; break; }die letzte verbliebene stille return-1-Stelle am Manifest. Schlaegt der Append fehl (ENOSPC, Quota, I/O-Fehler auf dem State-Verzeichnis), bricht die Schleife mit rc=1 ab, mv wird uebersprungen, nichts wird geloescht - und bin/fm-teardown.sh:2874|| exit 1beendet sich mit Rueckgabewert 1 und exakt 0 Byte stderr, also genau dem gemeldeten Ausgangssymptom. Die uebrigen Datei-/IO-Stellen desselben Zweigs (Symlink/nicht lesbar,cat,: > "$tmp",mv -f) sind in dieser Aenderung bereits mit _fm_status_presentation_manifest_error verdrahtet; nur dieser Zweig fehlt. Korrektur: denselben Helfer mit einem Grund wie 'could not be written to the rewrite file $tmp' aufrufen, bevor rc=1 gesetzt wird.🔧 Fix: validate manifest offset/backstop per field, report rewrite write failures
2 issues (1 warning, 1 info) still open:
bin/fm-classify-lib.sh:1101- status_outcome_backstop_cursor_offset betritt seine Datei-Level-Pruefung erst nach[ -e "$manifest" ] || { printf '0'; return 0; }(Zeile 1101).-efolgt dem Symlink, also ist die Bedingung bei einem toten Symlink am Manifestpfad falsch - die Funktion gibt 0 aus und kehrt mit rc=0 zurueck, bevor die neue Diagnose in Zeile 1103 ueberhaupt erreichbar ist. Direkt verifiziert am aktuellen Stand (0c3f767): state/.status-presentation-cursor -> /tmp/dsl/gone ergibtbackstop rc=0 out=[0] stderr=[], waehrend status_presentation_cursor_offset fuer exakt denselben Zustand rc=1 plus 'unusable status-presentation-cursor manifest: not a readable regular file' liefert (die nutzt in Zeile 1044[ -e ] || [ -L ], status_retire_presentation_task in Zeile 1313 ebenso). Das widerspricht dem Auftragspunkt 2 ('Manifestfehler duerfen nicht mehr still scheitern ... bzw. Grund (bei Datei-/IO-Fehlern)') und der daraus abgeleiteten Vorgabe der ersten Runde ('das Manifest ist ein Symlink oder keine regulaere Datei oder unlesbar ... Betroffene Stellen in status_presentation_cursor_offset, status_outcome_backstop_cursor_offset und status_retire_presentation_task'): ein Symlink-Manifest ist in genau dieser der drei genannten Funktionen weiterhin still. Es bleibt nicht bei der fehlenden Meldung, es entsteht ein falscher Wert ohne Fehler: bin/fm-wake-drain.sh:285 nimmtreceipt=0entgegen, wodurch[ "$receipt" -lt "$endpoint" ]fuer jede Aufgabe zutrifft und der Outcome-Backstop-Zweig bereits quittierte Ereignisse erneut vorlegt; status_commit_presentation_snapshot (Zeile 1404) schreibt dieselbe 0 anschliessend als Backstop-Spalte fest. Der aufgeloeste Symlink ist abgedeckt (Test test_symlinked_manifest_*), nur die tote Variante nicht. Korrektur an der frueheste gemeinsamen Stelle: Zeile 1101 auf dieselbe Form wie Zeile 1044/1313 bringen -[ -e "$manifest" ] || [ -L "$manifest" ] || { printf '0'; return 0; }; der Fast-Path 'noch kein Manifest vorhanden' bleibt dabei unveraendert, weil dann weder -e noch -L greift. Regressionstest analog zu test_symlinked_manifest_is_loud_for_the_cursor_reader_too, aber mit einem Symlink auf einen nicht existierenden Pfad und gegen status_outcome_backstop_cursor_offset.bin/fm-classify-lib.sh:1046- Der Datei-Level-Block ([ ! -f ] || [ ! -r ] || [ -L ]-> _fm_status_presentation_manifest_error 'not a readable regular file', danachcat-> 'could not be read') steht jetzt dreimal nahezu wortgleich: Zeile 1045-1052, 1102-1109 und 1314-1320. Die Zeilenvalidierung wurde in dieser Aenderung korrekt in _fm_status_presentation_row_parse zusammengefasst, die Dateivalidierung nicht - und genau an dieser Dreifachkopie sind die Vorbedingungen bereits auseinandergelaufen (siehe dangling-symlink-manifest-silently-yields-backstop-zero: zwei Stellen pruefen[ -e ] || [ -L ], die dritte nur[ -e ]). Bewusst kein Blocker und ausdruecklich keine Forderung fuer diesen Lauf: die Auftragsgrenze 'keine sonstige Umstrukturierung der Statusverarbeitung' schliesst eine Extraktion hier aus. Als Folgearbeit waere ein gemeinsamer Helfer (Manifest oeffnen und lesen, oder nichts) die natuerliche Stelle, an der die Vorbedingung nur noch einmal existiert.🔧 Fix: fail loudly on dangling symlink manifest in backstop reader
2 issues (1 warning, 1 info) still open:
bin/fm-classify-lib.sh:1022- Die neue Feldvalidierung in _fm_status_presentation_row_parse prueft nurcase "$X" in *[!0-9]*), also die Zeichenklasse, nicht den Wertebereich. Ein Offset/Backstop aus mehr als 19 Ziffern besteht ausschliesslich aus Ziffern, passiert die Pruefung und reproduziert exakt das Symptom, das die vorige Runde fuer den Doppelpunkt-Fall behoben hat (der neue Test test_colon_in_offset_fails_loudly_like_any_non_numeric_offset schlaegt sogar ausdruecklich fehl, wenn 'integer expression expected' statt der Diagnose erscheint). Direkt am aktuellen Stand (4e796ea) verifiziert, Manifestt1<TAB><echter-ident><TAB>99999999999999999999<TAB>0:99999999999999999999. Auf stderr steht nur rohesbin/fm-classify-lib.sh: line 1091: [: 99999999999999999999: integer expression expected. Ursache:[ "$offset" -gt "$size" ](Zeile 1091) bricht mit Status 2 ab, die Bedingung wird damit falsch, der Klemmzweigoffset=0laeuft nie - der Leser gibt einen unbrauchbaren Wert mit Erfolgsstatus zurueck.[ "$offset" -lt "$size" ] || return 0) scheitert am selben[und nimmt deshalb den|| return 0-Zweig. Fuer eine Statusdatei mitneeds-decision: pick oneundnote: helloliefert die Funktion rc=0 mit LEERER Ausgabe; mit gueltigem Offset 0 liefert sie beide Zeilen. Eine offene Entscheidung verschwindet also lautlos aus dem Drain - genau die Klasse 'Operator sieht nichts', gegen die dieser Auftrag angetreten ist....<TAB>5<TAB>99999999999999999999:[ "$backstop" -le "$size" ](Zeile 1126) scheitert ebenso, der||-Zweig setzt backstop=0 und die Funktion gibt 0 mit rc=0 aus - dieselbe stille Degradierung auf 0, die fuer den Backstop-Doppelpunkt-Fall bereits als Befund akzeptiert wurde.Erreichbarkeit: kein Schreiber im aktuellen Baum erzeugt eine solche Zeile (status_commit_presentation_snapshot faellt bei
[ "$endpoint" -le "$size" ]selbst auf denselben[-Fehler und bricht mit return 1 ab). Es braucht ein fremd geschriebenes oder beschaedigtes Manifest - also exakt die Praemisse dieses Auftrags, denn der gemeldete Vorfall kam ebenfalls von einem fremden Schreiberstand. Der Kopfkommentar (Zeile 951-954) sagt zu, jeder Leser scheitere geschlossen, 'rather than guessing at a partial or newer format'; hier raet er.Korrektur an der frueheste gemeinsamen Stelle - _fm_status_presentation_row_parse, damit alle vier Leseschleifen sie gemeinsam bekommen: zusaetzlich zur Zeichenklasse die Feldlaenge begrenzen (z.B. mehr als 18 Ziffern = dieselbe laute Diagnose 'non-numeric offset or backstop', oder ein eigener Grund 'offset or backstop out of range'). Dabei sinnvollerweise auch fuehrende Nullen ablehnen:
007wird heute akzeptiert und woertlich zurueckgegeben, und$((size - offset))(Zeilen 786 und 1551) wertet einen solchen Wert als Oktalzahl aus, waehrend[ -lt ]daneben dezimal rechnet - dieselbe Kanonisierung schliesst beides. Kontrollfluss und Meldungsformat bleiben unveraendert. Regressionstest: Offset mit 20 Ziffern muss dieselbe Diagnose ausloesen wie 'NaN', und status_new_lines_since_cursor darf danach keine unread-Zeile mehr mit rc=0 verschlucken..gitignore:7- Sachstand-Korrektur zum Befund der Vorrunde, damit die Entscheidung auf richtiger Grundlage steht - kein Handlungsbedarf abgeleitet. Der Baum bei HEAD ist sauber (git ls-tree -r HEAD | grep squishleer,.squish/steht jetzt in .gitignore neben.no-mistakes/und.lavish/). Der 724-KB-Blob 260871f liegt aber weiterhin in der Branch-Historie unter Commit 2f786b7 und wird beim Push dieses Branches mitgeliefert (94fab35 hat ihn nur untracked, die Historie nicht umgeschrieben - genau der Weg, den der Captain im Auftragstext als bewusst gewaehlt beschreibt).Wichtig fuer die Bewertung: der Inhaltsvorwurf der Vorrunde traegt nicht. Ich habe den Blob aus der Objektdatenbank gelesen und das Schema ausgewertet - saemtliche Inhaltstabellen sind LEER (memories 0, messages 0, conversations 0, learnings 0, entities 0, core_memory 0, session_summaries 0;
select count(*) from memories where has_secrets=1= 0). Es handelt sich um eine frisch initialisierte, reine Schema-Datei ohne Sitzungsinhalte und ohne Geheimnisse. Es bleibt also nur der Gewichtsanteil von 724 KB, der dauerhaft in jedem Klon des geteilten Repositories mitgeschleppt wird. Ob das eine Historienbereinigung (Rebase/Squash der beiden Commits 2f786b7 und 94fab35) wert ist, ist eine Captain-Entscheidung; als Sicherheitsproblem ist es nach dieser Pruefung erledigt.🔧 Fix: reject out-of-range and non-canonical manifest offsets
2 issues (1 warning, 1 info) still open:
bin/fm-classify-lib.sh:1046- Die beiden neuen Kanonik-Pruefungen melden jeden Verstoss mit dem alten Grund 'non-numeric offset or backstop', obwohl _fm_status_presentation_offset_is_canonical seit b8c49aa zwei zusaetzliche, ausdruecklich numerische Faelle ablehnt: eine fuehrende Null und mehr als FM_STATUS_PRESENTATION_MAX_OFFSET_DIGITS Stellen. Direkt am aktuellen Stand verifiziert (Manifest 't1<TAB><echter-ident><TAB>X<TAB>0'): fuer X='010', X='007', X='1000000000000000000' und X='abc' ist die Meldung Zeichen fuer Zeichen identisch - 'malformed status-presentation-cursor row: non-numeric offset or backstop (expected 4 TAB-separated fields: task, ident, offset, backstop)'. Der Operator, der wegen genau dieser Aenderung erstmals eine Diagnose sieht, bekommt damit fuer '010' die Auskunft, der Wert sei nicht numerisch, und die angehaengte Formatangabe nennt nur die Spaltenzahl, nie die tatsaechlich verletzte Regel (kanonische Dezimalzahl, keine fuehrende Null, hoechstens 18 Stellen). Auftragspunkt 2 verlangt 'klare ... Fehlermeldung ... mit ... Grund ... und erwartetem Format' - der Grund ist hier nachweislich falsch und das erwartete Format unvollstaendig; der Kopfkommentar (Zeile 950-956) beschreibt die Regel bereits korrekt, nur die Meldung nicht. Kein falscher Wert und kein Datenverlust: alle vier Faelle scheitern korrekt geschlossen mit rc=1. Korrektur an der einen Stelle, an der die Regel geprueft wird: fuer den Kanonik-Verstoss einen eigenen Grund uebergeben (z.B. 'offset or backstop is not a canonical decimal byte count') und die Formatangabe in _fm_status_presentation_row_error (Zeile 968) um diese Regel ergaenzen; Kontrollfluss und Rueckgabewerte bleiben unveraendert. Achtung beim Nachziehen: test_leading_zero_offset_fails_loudly, test_out_of_range_offset_fails_loudly_like_any_non_numeric_offset und test_out_of_range_backstop_fails_loudly_instead_of_degrading_to_zero pruefen heute per grep -qi 'non-numeric' und muessen auf den neuen Grund umgestellt werden.bin/fm-wake-lib.sh:1801- Sachstandsnotiz, kein Handlungsbedarf in diesem Lauf. fm_wake_status_cursor_offset ruft den gehaerteten Leser als 'status_presentation_cursor_offset "$path" 2>/dev/null' auf und gibt bei rc=1 nur 'return 1' zurueck; der Aufrufer in Zeile 1906 bricht damit die Annotationsanreicherung still ab. Vor dieser Aenderung war das 2>/dev/null wirkungslos (der Leser schwieg ohnehin), jetzt verwirft es aktiv genau die neuen Manifest-Diagnosen: ein kaputtes Manifest ist auf dem Wake-Annotationspfad weiterhin komplett unsichtbar, waehrend es in bin/fm-teardown.sh und bin/fm-wake-drain.sh nun laut ist. Das widerspricht dem Auftrag nicht - Punkt 2 fordert Sichtbarkeit ausdruecklich 'in bin/fm-teardown.sh', und die Auftragsgrenze 'keine Aenderung an Aufsichts- oder Watcher-Logik' schliesst einen Eingriff hier aus. Nur festhalten, damit die verbleibende Luecke bekannt ist, falls dieselbe Fehlerklasse spaeter auf dem Watcher-Pfad auftaucht.🔧 Fix: name the violated offset rule in manifest row diagnostics
1 info still open:
bin/fm-classify-lib.sh:981- Der Kommentar über FM_STATUS_PRESENTATION_MAX_OFFSET_DIGITS begründet die Grenze mit "beyond 19 digits[ "$offset" -gt "$size" ]aborts with 'integer expression expected'". Das ist um eins daneben: der Abbruch beginnt nicht jenseits von 19 Stellen, sondern bereits bei 19-stelligen Werten oberhalb von INT64_MAX. Direkt gemessen in genau dieser Shell:[ 9223372036854775807 -gt 5 ](19 Stellen) rc=0,[ 9223372036854775808 -gt 5 ](ebenfalls 19 Stellen) rc=2 mit "[: 9223372036854775808: integer expression expected",[ 999999999999999999 -gt 5 ](18 Stellen) rc=0. Der Code selbst ist korrekt und konservativ - 18 Stellen sind immer vergleichbar, und alle Leser scheitern für 19 Stellen laut und geschlossen (verifiziert: rc=1 plus "offset or backstop is wider than 18 digits" bei allen drei Lesern und bei status_new_lines_since_cursor). Es entsteht kein falscher Wert. Die Gefahr liegt allein in der Begründung: wer sie beim Wort nimmt, hebt die Konstante auf 19 an und holt sich genau die Klasse zurück, die dieser Lauf beseitigt hat (roher Bash-Fehler statt Diagnose, Leser gibt unbrauchbaren Offset mit rc=0 zurück). Korrektur reine Kommentarzeile 981-983: die Schwelle als "ab 19 Stellen kann der Wert INT64_MAX überschreiten, 18 Stellen passen immer" formulieren. Kein Kontrollfluss, kein Rückgabewert, kein Test betroffen.✅ **Test** - passed
✅ No issues found.
bash tests/fm-classify-status-presentation-manifest.test.sh— 23/23 ok on this branchSame test file run against a base-commit tree (git archive d22318e) — fails on the first case, proving the new tests are true regressionsEnd-to-end operator transcript: realbin/fm-teardown.sh task-x1via thetests/fm-teardown.test.shsandbox harness (make_case/write_meta/wt_commit/add_fork_with_pushed_branch/run_teardown) over 4 manifest shapes — surplus 5th column, surplus column hidden behind an empty column, legacy 3-field row, symlinked manifest — executed once withbin/from d22318e and once from 7b6f021bash tests/fm-teardown.test.sh— direct consumer ofstatus_retire_presentation_task, 58 ok, exit 0bash tests/fm-wake-drain-outcome-backstop.test.sh— 18 okbash tests/fm-wake-drain-open-decisions-cursor.test.sh— 7 okbash tests/fm-classify-decision-key.test.sh— 15 ok✅ **Document** - passed
✅ No issues found.
⏭️ **Lint** - skipped
✅ **Push** - passed
✅ No issues found.