Skip to content

Commit 31c5fbb

Browse files
Axel051171claude
andcommitted
feat(schreibtor): unbekannt ist keine Erlaubnis — P3-298 (a)+(b) (MF-986)
Der Schreibschutz wurde nur geprueft, um das SCHREIBEN zu verhindern — nicht, um das ORIGINAL zu schuetzen. Pasti ST verweigert aus demselben Grund sogar das LESEN nicht schreibgeschuetzter Quellen. UFT hatte die Leitung und benutzte sie nur andersherum. (a) und (b) kommen zusammen, wie die Auflage aus P3-291 es verlangt: ein Vertrag, den niemand beantwortet, und ein Schalter ohne Schaltung waeren der dritte Fall nach `adaptive_passes` und `extended_tracks`. ## Der Bestand trug den Fehler bereits — das ist der eigentliche Fund Der Diagnose-Schritt des Tores endete mit } else { result->checks_passed |= UFT_CHECK_DRIVE; } Das Fehlen des Flags `UFT_DRIVE_DIAG_WRITE_PROTECT` hiess damit zweierlei: „gefragt, nicht geschuetzt" ODER „niemand hat gefragt". Eine genullte `uft_drive_diag_t` — genau das, was ein Backend ohne Sensor liefert — kam als **bestandene Pruefung** durch. Das ist der Bool-Fehler, gegen den der dreiwertige Zustand gebaut ist. Und er zeigt, warum drei Werte allein nichts loesen: sie loesen es nur, wenn der **Verbraucher** den dritten richtig behandelt. ## (b) Der Vertrag `SensesWriteProtect` in `include/uft/hal/concepts.h` — geschuetzter Pfad, vom Eigentuemer ausdruecklich freigegeben. Rueckgabe `uft_write_protect_t {UNKNOWN = 0, PROTECTED, UNPROTECTED}`; die Null ist Absicht, damit eine genullte Struktur ehrlich ist. Kein `*Outcome`-Typ, und der Grund steht im Header: Geraetefehler, fehlende Leitung und fehlender Sensor fuehren alle zu **derselben** ehrlichen Antwort. Ein Fehlerkanal daneben waere ein zweiter Weg fuer dieselbe Aussage. Greaseweazle erfuellt ihn ueber den vorhandenen Stift-Abruf. `!is_open()` liefert **UNKNOWN, nicht „ungeschuetzt"**. ## (a) Der Guard `uft_write_gate_precheck` verweigert jetzt: UNKNOWN -> UFT_GATE_WRITE_PROTECT_UNKNOWN UNPROTECTED ohne target_media_released -> UFT_GATE_TARGET_NOT_RELEASED Der zweite ist der unbequeme und der wichtigere: ein Medium ohne Schreibschutzkerbe ist noch kein freigegebenes Ziel. `target_media_released` ist an die Richtlinie ANGEHAENGT (ABI-Regel) und in den Vorgabe-Makros bewusst nicht gesetzt — wer eine Vorgabe uebernimmt, gibt nichts frei. ## Abnahme: vier Faelle, jeder per Mutation belegt UNKNOWN -> Absage M1 faellt genau ihn UNPROTECTED ohne Freigabe -> Absage M2 PROTECTED (beide Kanaele) -> Absage M3 UNPROTECTED MIT Freigabe -> geht durch (Gegenzweig) Der vierte ist keine Zugabe: ohne ihn bestuende ein Tor, das IMMER verweigert, die ersten drei und waere wertlos. Dazu `static_assert(SensesWriteProtect<GreaseweazleProviderV2>)` — M4 (Methode entfernt) laesst ihn feuern: der Vertrag ist **nicht leer**. Gemessen: `test_write_gate_unbekannt_verweigert` 4/4, `test_greaseweazle_v2` 0 errors, `test_write_gate` 22/22, `test_write_gate_snapshot_survives` 0 Abweichungen. ## Was das NICHT heisst Die sechs uebrigen Controller beantworten den Vertrag weiterhin nicht — sie liefern UNKNOWN, und das Tor verweigert. Das ist die richtige Richtung, aber es heisst: Schreiben ueber SCP, KryoFlux, FC5025, XUM1541, Applesauce und UFI braucht eine **ausdrueckliche Freigabe**, sobald eine Diagnose uebergeben wird. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 1c27e0f commit 31c5fbb

9 files changed

Lines changed: 420 additions & 3 deletions

File tree

docs/OPEN_ITEMS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ offen).
489489
| P3-295 | **Der versiegelte capsimage-Baum liegt ein zweites Mal im Rohmaterial — unter einem Namen, den keine Suche nach „capsimage“ findet.** `neue-ideen/SPStudio_Dev.zip` (776 KB, 80 Einträge) führt `CAPSImg/`, `LibIPF/` und `Codec/` samt `CAPSLib520a.pdf` — denselben Quellbaum, den P3-293 nach MF-973 gesperrt hat | **gemessen**: `CAPSImg/CapsFDCEmulator.cpp` (57 151 B) gegen die lose Fassung aus MF-973 (52 528 B) — **13 gemeinsame Bezeichner, 0 exklusive auf beiden Seiten** (`fdccall`, `fdccall_t1`, `fdccall_t2r`, …): dieselbe Datei in zwei Fassungen. Das Archiv enthält **gar keine** Lizenzdatei; der Vermerk, der es regiert, liegt laut capsimage in einem übergeordneten Archiv, das hier fehlt | ✅ **kein Leck, und das war Glück der richtigen Bauart.** MF-973 hat nicht die gefundene Datei ignoriert, sondern **das ganze Verzeichnis** — deshalb greift die Sperre auch für ein Archiv, das damals niemand gesehen hat. **Was bleibt, ist eine Lehre über Siegel:** P3-293 nennt „capsimage“, und dieser Baum heißt `SPStudio_Dev`. Ein Siegel auf einem **Namen** ist so gut wie die Namensliste — dieselbe Form wie die vierzehn Aufzählungen vor ihr. Wer künftig prüft, was gesperrt ist, sucht nach **Inhalt** (`CapsAPI.h`, `LibIPF/`, `fdccall`), nicht nach dem Verzeichnisnamen |
490490
| P3-296 | **Die Verwaisten-Grundlinie behauptete 14-mal etwas, das nicht mehr galt — und ihr eigener Kopf sagte, das Tor melde genau das.** `docs/orphan_baseline.txt` führte **223** Pfade; gemessen waren **209** ohne jeden Aufrufer. Der Kopf der Datei sagt wörtlich *„Wird eines behoben, gehört seine Zeile hier heraus — **das Tor sagt dann, welche**“* | **gemessen MF-979** mit `orphan_module_gate.measure()` selbst: 14 Einträge trafen nicht mehr zu, **fünf davon über Module mit PRODUKTIONS-Aufrufer** — `uft_stx_air.c` (seit **MF-854** der registrierte STX-Leser), `uft_kryoflux_checker.c` (seit **MF-919**), `adfcopy_serial_runners.cpp`, `applesauce_serial_runners.cpp`, `qprocess_subprocess_runner.cpp`; die übrigen neun haben Test-Aufrufer. **Warum es niemand sah:** `main()` druckt *„bitte dort streichen“* — aber ohne Fehlercode, und **`check()`**, das in der Kette hängt und bei jedem Commit läuft, meldete nur `now - base`. Die Zusage stand in der Datei, gehalten hat sie niemand | ✅ **behoben MF-979.** Grundlinie auf **209** gekürzt (zeilenerhaltend — der erste Versuch riss den `ERLEDIGT MF-663`-Block mit heraus, gefangen am Diff), `check()` meldet jetzt **beide** Richtungen. **Der Vorfall hat einen Vorläufer:** **P3-259** hat exakt diese Falschaussage über `uft_stx_air.c` im **Quarantäne**-Register gefunden und berichtigt — dort entscheidet die Spalte „Fähigkeit — 0 Aufrufer“ über den Löschweg. In der Verwaisten-Grundlinie blieb sie stehen: **eine Berichtigung an einer Stelle, während dieselbe Aussage an der zweiten weiterlebte** |
491491
| P3-297 | **Der Zensus „Tore ohne Selbsttest“ holte seine Torliste aus dem DATEINAMEN — 13 verdrahtete Tore kamen darin überhaupt nicht vor.** `audit_selbsttest.tor_skripte()` suchte in `check_consistency.py` nach `\baudit_[a-z_]+`. Ein Tor mit anderem Namen fiel aus **beiden** Spalten: weder geprüft noch ungeprüft, schlicht nicht vorhanden | **gemessen MF-979**: `check_consistency.py` verdrahtet **50** Tore, **13** davon heißen anders — `cbm_zone_gate`, `define_parity_gate`, `enum_macro_conflicts`, `extern_decl_conflicts`, `orphan_module_gate`, `packing_gate`, `platform_header_gate`, `plugin_registry_gate`, `probe_buffer_gate`, `quarantine_stand`, `shared_guard_gate`, `untracked_flood_gate`, `write_gate_caps_gate`. Der Zensus meldete **„16 von 36“**; ehrlich sind es **29 von 49** | ✅ **behoben MF-979** — die Torliste kommt jetzt aus der **Verdrahtung** (`import X as Y` mit gerufenem `Y.check(`), nicht aus dem Präfix. **Siebzehnter Fall von Aufzählung statt Messung**, und wieder an der teuersten Stelle: die Aufzählung stand in dem Werkzeug, das zählt, **wer ungeprüft ist**. Dass ausgerechnet `orphan_module_gate` zu den Unsichtbaren gehörte, ist kein Zufall — es ist dasselbe Tor, dessen Grundlinie in derselben Runde mit 14 veralteten Einträgen auffiel (P3-296) |
492-
| P3-298 | **Der Schreibschutz wird nur geprüft, um das SCHREIBEN zu verhindern — nicht, um das ORIGINAL beim Lesen zu schützen.** Pasti ST, das Werkzeug, das die STX-Abbilder erzeugt, verweigert das **Lesen**, wenn die Quelldiskette nicht schreibgeschützt ist (*„The imaging tool rejects source disks that are not write protected“*, `PastiST.txt`). UFT hat die Leitung, benutzt sie aber nur andersherum | **gemessen MF-979**: `uft_gw_is_write_protected()` wird an **einer** Stelle gerufen — `greaseweazle_provider_v2.cpp:448`, in `do_write_raw_flux()`. Je Controller: **Greaseweazle** fragt den Stift **vorab** ab und führt als einziger `HAL_CAP_WRITE_PROTECT`; **XUM1541** (CBM-Status 26), **Applesauce** und **UFI** (SCSI Sense Key 0x07) erfahren es erst **nachträglich** aus einem Schreib*ergebnis* (`translate_write_result`, REQUEST_SENSE); **SCP**, **KryoFlux**, **FC5025** nennen ihn in **null** Dateien | ⚠ **offen — zwei getrennte Fragen, und nur die zweite ist HAL-Arbeit.** **(a) Einstellung:** eine Richtlinie für die Lesequelle — *warnen* / *verweigern* / *ignorieren*. Sie gehört neben das bestehende Schreibtor (`src/policy/uft_write_gate.c`), das die Diagnose `UFT_DRIVE_DIAG_WRITE_PROTECT` bereits führt. **Auflage wie bei P3-291:** die Option darf nur **zusammen mit ihrer Wirkung** kommen — dieser Baum hat mit `adaptive_passes` (P3-284) und `extended_tracks` (P3-203) bereits zwei Schalter ohne Schaltung. **(b) HAL-Erweiterung:** eine **vorab**-Abfrage `sense_write_protect()` im Vertrag. Heute kann sie nur Greaseweazle beantworten; ohne sie lässt sich (a) auf keinem anderen Controller anbieten, und `HAL_CAP_WRITE_PROTECT` würde vier Backends **unterschätzen**, die den Zustand sehr wohl melden — nur zu spät. **Kennzahl (Regel 9):** keine der vier direkt; es ist ein Prinzipienfall („Keine stille Veränderung“ am **Original**) **NACHTRAG MF-984 — wo der Vertrag hingehoert, gemessen statt vermutet.** Der Eintrag sagte „eine vorab-Abfrage `sense_write_protect()` im Vertrag“, ohne zu sagen, in **welchem**. Gemessen: **nicht in den C-HAL.** `src/hal/uft_hal_unified.c` haelt fuer Greaseweazle nur `{hw_model, hw_submodel, fw_version, sample_freq_hz}` und einen eigenen seriellen Griff; ein `uft_gw_device_t*` gibt es dort **nicht**, und `uft_gw_is_write_protected()` verlangt genau den. Der C-HAL benutzt von Greaseweazle ausschliesslich die reinen Funktionen (`uft_gw_encode/decode_flux_stream`). Die einzige Stelle im Baum, die antworten **kann**, ist der C++-Provider (`greaseweazle_provider_v2.cpp:448`, `m_handle`). **Der Vertrag gehoert also zu den V2-Faehigkeiten** — ein Concept `SensesWriteProtect` neben `ReadsRawFlux`/`WritesRawFlux` in `include/uft/hal/concepts.h`, mit Mixin. **Und dort ist STOPP:** `.claude/CLAUDE.md` fuehrt `include/uft/hal/{outcomes,concepts,mixins}.h` als **geschuetzten Pfad** („keine Aenderung ohne Rueckfrage“, ausdruecklich Type-System-Architect-Arbeit). Das ist eine Eigentuemer-Entscheidung, keine Umsetzungsfrage. **Was der Entwurf mitbringen muss, wenn er kommt:** der Zustand ist **dreiwertig**, nicht `bool` — `UNBEKANNT` (0), `GESCHUETZT`, `UNGESCHUETZT`. Ein `bool` kann „nicht geschuetzt“ und „weiss es nicht“ nicht unterscheiden, und diese Verwechslung **erlaubt das Schreiben auf ein Original** — genau der Schaden, den (a) verhindern soll. Vorbild fuer die Regel steht im Baum: `read_flux_ex` (MF-954) — *„Fehlt der Zeiger, meldet die API NULL Umdrehungsgrenzen — es ERFINDET keine.“* **Und die Auflage aus P3-291 gilt hier genauso:** ein Vertrag, den heute **kein** Backend beantworten kann und **kein** Verbraucher ruft, waere der dritte Schalter ohne Schaltung nach `adaptive_passes` (P3-284) und `extended_tracks` (P3-203). Er kommt zusammen mit (a) oder gar nicht. |
492+
| P3-298 | **Der Schreibschutz wurde nur geprüft, um das SCHREIBEN zu verhindern — nicht, um das ORIGINAL beim Lesen zu schützen.** Pasti ST verweigert das **Lesen**, wenn die Quelldiskette nicht schreibgeschützt ist. UFT hatte die Leitung und benutzte sie nur andersherum | ✅ **(a) und (b) behoben MF-986 — zusammen, wie die Auflage aus P3-291 es verlangt.** **(b) Der Vertrag:** Concept `SensesWriteProtect` in `include/uft/hal/concepts.h` (geschützter Pfad, vom Eigentümer freigegeben), Rückgabe **dreiwertig** — `uft_write_protect_t {UNKNOWN=0, PROTECTED, UNPROTECTED}`. `UNKNOWN` ist **0**, damit eine genullte Struktur ehrlich ist. Greaseweazle erfüllt ihn über den vorhandenen Stift-Abruf; `!is_open()` liefert **UNKNOWN, nicht „ungeschützt“**. **(a) Der Guard:** `uft_write_gate_precheck` verweigert bei UNKNOWN (`UFT_GATE_WRITE_PROTECT_UNKNOWN`) und bei UNPROTECTED ohne ausdrückliche `target_media_released` (`UFT_GATE_TARGET_NOT_RELEASED`) | **Der Bestand trug den Fehler bereits, und das war der eigentliche Fund.** Der Diagnose-Schritt endete mit `} else { checks_passed |= UFT_CHECK_DRIVE; }` — das Fehlen des Flags `UFT_DRIVE_DIAG_WRITE_PROTECT` hieß „gefragt, nicht geschützt“ **oder** „niemand hat gefragt“. Eine genullte `uft_drive_diag_t`, also genau das, was ein Backend ohne Sensor liefert, kam als **bestandene Prüfung** durch. Drei Werte lösen das nur, wenn der **Verbraucher** den dritten richtig behandelt. **Abnahme** (`tests/test_write_gate_unbekannt_verweigert.c`), vier Fälle, jeder per Mutation belegt: UNKNOWN → Absage (M1 fällt genau ihn) · UNPROTECTED ohne Freigabe → Absage (M2) · PROTECTED → Absage über **beide** Kanäle, altes Flag und Tri-State (M3) · **Gegenzweig:** UNPROTECTED **mit** Freigabe geht durch — ohne ihn bestünde ein Tor, das immer verweigert, alle drei. Dazu `static_assert(SensesWriteProtect<GreaseweazleProviderV2>)`: M4 (Methode entfernt) lässt ihn feuern, der Vertrag ist also **nicht leer**. `test_write_gate` 22/22 und `test_write_gate_snapshot_survives` bleiben grün. **Was das NICHT heißt:** die sechs übrigen Controller beantworten den Vertrag weiterhin nicht — sie liefern UNKNOWN, und das Tor verweigert. Das ist die richtige Richtung, aber es heißt, dass Schreiben über SCP, KryoFlux, FC5025, XUM1541, Applesauce und UFI eine **ausdrückliche Freigabe** braucht, sobald eine Diagnose übergeben wird. **Kennzahl (Regel 9):** keine der vier — Prinzipienfall („Keine stille Veränderung“ am Original) |
493493
| P3-299 | **Vier Fremdquellen zerlegt — drei ohne Ertrag, und das ist das Ergebnis. Nebenbei ein Phantom mit falschem Feldnamen.** `AFCopy 4.4` (180 KB C), `pdi2iso`, `XPRCopy`, `FilecopyVerifyRetry` lagen unbearbeitet in `neue-ideen/` | **gemessen MF-979**: **AFCopy** hat **null** Treffer auf `trackdisk`, `td_`, `MFM`, `RawRead/Write`, `TD_FORMAT` — 5000 Zeilen reiner AmigaDOS-Dateimanager (die einzigen „Disk“-Stellen sind `IDCMP_DISKINSERTED` und freier Platz). **pdi2iso** ist ein **CD**-Werkzeug (`TRACK 1 MODE1/2352`, ISO-9660, GPL-2+), nicht das Disketten-`PDI` aus `src/samdisk/pdi.cpp` (`PDITYPE1`, RLE-komprimierte DOS-Abbilder); **UFTs Doku ist dazu ehrlich** — `FORMAT_CATALOG.md:188` führt `PDI` mit *„Plugin? **nein**“*. **XPRCopy** ist serieller Dateitransfer. **FilecopyVerifyRetry** ist Datei-Ebene, grenzt aber thematisch an UFT-59 | ⚠ **ein Rest, klein aber echt:** `include/uft/uft_formats_extended.h:94-102` deklariert `stx_header_t` mit Byte 0x0B als **`sides`**. Alle drei wirklichen STX-Leser des Baums lesen dort **`revision`** (`uft_stx_parser_v2.c:297`, `uft_stx_parser.c:93`, `uft_stx_air.c:121`) — und die haben recht. Die Struktur hat **null Benutzer**, ist also heute folgenlos; sie gehört zu den Phantomen aus **P3-254**. Gefährlich wäre sie erst, wenn jemand sie für bare Münze nähme — dieselbe Form wie das nachdeklarierte EDSK-Layout aus MF-796, das *live* war |
494494
| P3-300 | **23 Sektor-Leser gaben erfundene Bytes als gelesene Sektoren aus — mit „CRC gültig“.** Das Muster hieß überall gleich: `if (fread(...) != SS) memset(buf, 0xE5, SS);` und danach der unveränderte `uft_format_add_sector()`. Der setzt für **jeden** Sektor unbedingt `status = UFT_SECTOR_OK`, `uft_sector_set_crc(true)` und `uft_sector_set_id_crc(true)` (`uft_format_common.h`) | **Rotbeweis MF-980**, `tests/test_kurze_datei_erfindet_keine_sektoren.c`: `trd_open()` nimmt **jede** Dateigröße an — eine 1-KB-Datei öffnet als 40-Spur-Diskette. Der Test fiel mit *„Sektor 4 stand nicht in der Datei, gilt aber als gelesen (status=0x00, crc_ok=1, data[0]=0xE5)“*. Der Fall braucht keinen Kunstgriff — er entsteht bei jedem abgebrochenen Abzug. **`uft_atr.c` nannte die Zeile im Kommentar sogar „forensic fill on read error“**; eine Füllung ohne Kennzeichnung ist das Gegenteil davon | ✅ **behoben MF-980 in allen 23.** Neuer Helfer `uft_format_mark_last_missing()` setzt `UFT_SECTOR_MISSING` und beide CRC-Flags auf „nicht gut“; die Bytes **bleiben stehen** („Kein Bit verloren“ gilt auch für die Füllung), sie gelten nur nicht mehr als Messwert. **Tor 62** (`scripts/audit_erfundene_sektoren.py`, Grundlinie 0, Selbsttest 4/4) hält die Klasse. **Die Zählung stieg unterwegs von 19 auf 23**, weil das Tor schärfer misst als meine Handsuche: `adf_ext` füllt mit **0** statt 0xE5, `northstar` vergleicht gegen `(size_t)ss` — beide Formen fielen durch mein Muster. **Und es fand zwei Fehlalarme, die den Torentwurf korrigiert haben:** `uft_fds_plugin.c` und `uft_dsk_cpc.c` setzen `memset` zur **Initialisierung** und brechen bei kurzem Lesen ab (`return UFT_ERROR_IO` bzw. `break`). Das Tor prüft seither den **Fehlerzweig eines `fread`**, nicht „irgendwo im Rumpf steht ein memset“ | **376/376 Tests**, ein benannter Skip |
495495
| P3-301 | **Lese- und Schreibseite behandeln Koordinaten uneinheitlich — das Muster hat jetzt vier belegte Fälle.** MF-519/529 (negative Koordinaten), MF-905/931 (`head != 0` und `track_data[cyl]`). Beim baumweiten Nachmessen blieben nach Abzug der Absager **drei** Plugins, deren `read_track` und `write_track` sich unterscheiden — und bei zweien fehlt die Schranke auf der **Leseseite** | **gemessen MF-980**: `uft_g64.c` (Schreiben weist `head != 0` ab, Lesen nicht), `uft_jv1.c` und `uft_ssd_plugin.c` (Lesen ohne `< 0`-Deckel). **Kein Speicherfehler** — alle drei rechnen Datei-Versätze, ein negativer `fseek` schlägt fehl. **Aber `jv1_read_track` rechnet `(head * cylinders + cyl)`**: bei `head=1, cyl=-1` bleibt der Versatz **positiv**, der Leser liefert eine **andere Spur** und meldet `UFT_OK`. Dazu **13 Stellen**, die `cyl` nach `size_t`/`uint32_t` gießen, ohne vorher auf `< 0` zu prüfen — dort greift der `fseek`-Schutz gar nicht, weil der Versatz positiv wird | ⚠ **offen, und bewusst nicht in derselben Runde erledigt.** Die Klasse ist belegt, die Zahl gemessen, der Rotbeweis fehlt: ohne einen Test, der den falschen Spurinhalt **zeigt**, wären 13 Schranken 13 ungeprüfte Änderungen im Formatlayer (EINFRIER-REGEL). **Nächster Schritt:** ein Fuzz-Fall über `uft_disk_read_track()` mit negativen Koordinaten — dieselbe Bauart wie `tests/test_disk_open_fuzz.c`, das MF-519 gefunden hat. **Kennzahl (Regel 9):** keine der vier direkt; es ist „Keine erfundenen Daten“ wie P3-300 |

include/uft/hal/concepts.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@
4545

4646
#include "uft/hal/outcomes.h"
4747

48+
/* MF-986: `uft_write_protect_t` — der dreiwertige Zustand. Er lebt im
49+
* Policy-Header, weil ihn Sensor UND Guard brauchen und eine zweite
50+
* Definition genau die Doppelung waere, die dieser Baum sonst jagt. */
51+
#include "uft/policy/uft_write_gate.h"
52+
4853
namespace uft::hal {
4954

5055
/* ───────────────────────────────────────────────────────────────────────
@@ -180,6 +185,60 @@ concept DetectsDrive = HasIdentity<P> &&
180185
{ p.detect_drive() } -> std::same_as<DetectOutcome>;
181186
};
182187

188+
/* ───────────────────────────────────────────────────────────────────────
189+
* SensesWriteProtect — die VORAB-Abfrage (MF-986, P3-298b)
190+
*
191+
* Heute erfaehrt der Baum den Schreibschutz an vier Stellen, und nur
192+
* eine davon fragt vorher:
193+
*
194+
* Greaseweazle fragt den Stift VORAB (uft_gw_is_write_protected)
195+
* XUM1541 CBM-Status 26 \
196+
* Applesauce Fehlerantwort > erst aus einem SCHREIB-ERGEBNIS
197+
* UFI SCSI Sense 0x07 /
198+
* SCP, KryoFlux, FC5025 nennen ihn in NULL Dateien
199+
*
200+
* „Erst aus dem Ergebnis" heisst: die Diskette ist bereits angefasst.
201+
* Fuer ein forensisches Werkzeug ist das die falsche Reihenfolge —
202+
* darum dieser Vertrag.
203+
*
204+
* ── Warum der Rueckgabewert dreiwertig ist ──────────────────────────
205+
*
206+
* `uft_write_protect_t` (aus dem Policy-Header) kennt UNKNOWN,
207+
* PROTECTED, UNPROTECTED. Ein `bool` kann „nicht geschuetzt" und
208+
* „weiss es nicht" nicht unterscheiden, und diese Verwechslung
209+
* **erlaubt das Schreiben auf ein Original**.
210+
*
211+
* Drei Werte allein loesen das aber NICHT. Sie loesen es nur, wenn der
212+
* Verbraucher den dritten richtig behandelt: `uft_write_gate_precheck`
213+
* verweigert bei UNKNOWN, und bei UNPROTECTED ohne ausdrueckliche
214+
* Zielfreigabe ebenfalls (MF-986a, drei Testfaelle plus Gegenzweig in
215+
* `tests/test_write_gate_unbekannt_verweigert.c`, je mit Mutation
216+
* belegt). Ein Sensor, der UNKNOWN liefert, und ein Guard, der daraus
217+
* „dann eben schreiben" macht, waere der Bool-Fehler mit mehr Aufwand.
218+
*
219+
* ── Warum kein Outcome-Typ ──────────────────────────────────────────
220+
*
221+
* Die uebrigen Concepts liefern `*Outcome`, weil ihre Operation
222+
* scheitern kann. Diese kann es nicht: ein Geraetefehler, eine fehlende
223+
* Leitung und ein Controller ohne Sensor fuehren alle zu derselben
224+
* ehrlichen Antwort — UNKNOWN. Ein Fehlerkanal daneben waere ein
225+
* zweiter Weg fuer dieselbe Aussage.
226+
*
227+
* ── Wer es NICHT implementiert ──────────────────────────────────────
228+
*
229+
* Der Vertrag ist optional. Ein Provider ohne `sense_write_protect()`
230+
* erfuellt das Concept nicht, und der Aufrufer traegt UNKNOWN in die
231+
* Diagnose ein — was das Tor verweigern laesst. Das ist dieselbe Regel
232+
* wie bei `read_flux_ex` (MF-954): fehlt der Zeiger, meldet die API
233+
* NULL Umdrehungsgrenzen; sie ERFINDET keine.
234+
* ─────────────────────────────────────────────────────────────────────── */
235+
236+
template<class P>
237+
concept SensesWriteProtect = HasIdentity<P> &&
238+
requires(P p) {
239+
{ p.sense_write_protect() } -> std::same_as<::uft_write_protect_t>;
240+
};
241+
183242
/* ───────────────────────────────────────────────────────────────────────
184243
* Composite predicates (for codegen + GUI gating)
185244
*

0 commit comments

Comments
 (0)