Gegenbuch: Pruefmodus schreibt nicht mehr, Widerspruch aufgeloest (R181)
R181-01: Der Pruefmodus sagte zu, nichts zu veraendern und kein Schreibrecht zu brauchen - und pushte trotzdem, weil ankerNachziehen() in jedem Modus lief. Ein read-only Audit mutierte damit still das geteilte Substrat. R181-02: Ein Widerspruch zwischen den eigenen Fixes. R180-01 erhebt die Serversperre auf refs/notary/* zur tragenden Pflicht, R180-02 verlangt dort Schreibrecht zur Selbstheilung. Sobald je ein Anker fehlte, bekam jeder read-only pruefende Auditor dauerhaft einen Fehler auf einer voellig gueltigen Kette, den er nicht beheben konnte. Fix: Reparieren nur im Notar-Schreiblauf, im Pruefmodus wird der fehlende Anker gemeldet. Eigener Rueckgabecode 5: "Anker unvollstaendig" ist nicht "nicht feststellbar" - die Kette ist gueltig, nur das Substrat-Gedaechtnis unvollstaendig, ein benannter reparierbarer Defekt. Dieselbe Trennung wie bei Genesis/Adoption. Empirisch beantwortet: Die Notar-Identitaet laesst sich eng auf das Anlegen von refs/notary/* beschraenken, ohne Loeschen oder Ueberschreiben - serverseitig unterscheidbar an der Null-OID. Mit pre-receive-Hook verifiziert: Backfill greift, Loeschen und Force-Overwrite bleiben abgewiesen. Hook als Beispiel in der README. Verifiziert: read-only --check mit fehlendem Anker -> Code 5, nichts gepusht; Notar-Schreiblauf unter derselben ACL -> Anker nachgetragen, exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,39 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🧮 Gegenbuch: Pruefmodus schreibt nicht mehr, Widerspruch aufgeloest (Pentest R181)** (2026-08-18)
|
||||
- **R181-01 (LOW→MED)** – Der Pruefmodus sagte zu, nichts zu veraendern und
|
||||
kein Schreibrecht zu brauchen – und pushte trotzdem: `ankerNachziehen()`
|
||||
lief in JEDEM Modus. Ein „read-only“ Audit mutierte damit still das
|
||||
geteilte Substrat. Fix: Nachziehen nur im Schreiblauf.
|
||||
- **R181-02 (MEDIUM) – ein Widerspruch zwischen meinen EIGENEN Fixes.**
|
||||
R180-01 erhebt die Serversperre auf `refs/notary/*` zur tragenden Pflicht;
|
||||
R180-02 verlangt genau dort Schreibrecht zur Selbstheilung. Ergebnis:
|
||||
Sobald je ein Anker fehlte, bekam jeder read-only pruefende Auditor
|
||||
dauerhaft einen Fehler auf einer **voellig gueltigen** Kette – und konnte
|
||||
ihn nicht beheben.
|
||||
Fix: Reparieren ist Sache des Notar-Laufs; im Pruefmodus wird der fehlende
|
||||
Anker gemeldet, nicht repariert.
|
||||
- **Eigener Rueckgabecode 5** (seine Frage b): „Anker unvollstaendig“ ist
|
||||
NICHT „nicht feststellbar“. Die Kette ist geprueft und gueltig, nur das
|
||||
Substrat-Gedaechtnis ist unvollstaendig – ein benannter, reparierbarer
|
||||
Defekt mit klarer Handlungsanweisung. Ihn in Code 3 zu werfen hiesse, eine
|
||||
Anweisung als Ungewissheit zu melden; dieselbe Trennung wie bei
|
||||
Genesis/Adoption.
|
||||
- **Seine Frage (a) empirisch beantwortet:** Die Notar-Identitaet laesst sich
|
||||
eng auf das ANLEGEN von `refs/notary/*` beschraenken, ohne ihr Loeschen
|
||||
oder Ueberschreiben zu geben – serverseitig unterscheidbar an der Null-OID
|
||||
beim Anlegen. Mit `pre-receive`-Hook verifiziert: Backfill greift (exit 0),
|
||||
Loeschen und Force-Overwrite werden weiterhin abgewiesen. Der Hook steht
|
||||
als Beispiel in der README.
|
||||
- Verifiziert: read-only `--check` mit fehlendem Anker → **Code 5, nichts
|
||||
gepusht** (Anker-Anzahl vorher/nachher identisch); Notar-Schreiblauf unter
|
||||
derselben ACL → „Fehlende Anker nachgetragen: 1“, exit 0.
|
||||
- Seine Non-Findings uebernommen: Die Backfill-Zuordnung ist gegen das
|
||||
PIN-Modell robust – jeder Commit mit echter Ledger-Aenderung ist non-merge
|
||||
und muss keyA-signiert sein, sig-exempte Merges tragen keinen Inhalt in die
|
||||
Zuordnungsliste.
|
||||
|
||||
- [x] **🛑 Gegenbuch: Anker belegen nichts mehr, Anker-Verlust wird laut (Pentest R180-01/-02)** (2026-08-18)
|
||||
- **R180-01 (MEDIUM)** – Genau die Unsicherheit, die ich selbst benannt
|
||||
hatte, bestaetigt: Der Code nahm den **hoechsten noch vorhandenen**
|
||||
|
||||
@@ -103,6 +103,19 @@ Standardzustand.
|
||||
> einen niedrigeren stehen lässt, senkt damit den Vergleichswert selbst – und
|
||||
> in-repo ist das von „gab es nie" nicht zu unterscheiden.
|
||||
>
|
||||
> **Die Notar-Identität braucht dort eine eng gefasste Ausnahme:** Sie muss
|
||||
> `refs/notary/*` **anlegen** dürfen, aber ausdrücklich **nicht löschen oder
|
||||
> überschreiben**. Sonst hebelt eine kompromittierte Notar-Identität genau den
|
||||
> Schutz aus, um den es hier geht. Server-seitig ist das unterscheidbar – ein
|
||||
> `pre-receive`-Hook sieht beim Anlegen die Null-OID als alten Wert:
|
||||
>
|
||||
> ```sh
|
||||
> # refs/notary/*: nur anlegen, niemals ändern oder löschen
|
||||
> case "$ref" in refs/notary/*)
|
||||
> [ "$old" = "0000000000000000000000000000000000000000" ] || exit 1 ;;
|
||||
> esac
|
||||
> ```
|
||||
>
|
||||
> Deshalb begründen Anker **keine** Unversehrtheit. Sie können ein
|
||||
> Zurückspulen *widerlegen* (wenn ein hoher Anker nicht mehr im Kopf steckt),
|
||||
> aber nie belegen, dass nichts fehlt. Ein `--check` aus frischem Klon erkennt
|
||||
@@ -198,6 +211,7 @@ Deshalb gilt jetzt:
|
||||
| 0 | alles in Ordnung, Checkpoint angehängt (bzw. Prüfung bestanden) |
|
||||
| 1 | Betriebsfehler (Konfiguration, Commit oder Push fehlgeschlagen) |
|
||||
| 2 | **Befund** – Widerspruch zwischen CRM und Gegenbuch, oder ungültige Signatur |
|
||||
| 5 | **Anker unvollständig** – die Kette ist gültig, aber `refs/notary/seq-N` fehlt. Reparierbar durch einen Notar-Schreiblauf |
|
||||
| 4 | **Wächter-Gedächtnis fehlt** – Erstinbetriebnahme unbestätigt, oder Speicher nach der Etablierung verloren |
|
||||
| 3 | beglaubigter Stand nicht abschließend feststellbar – Remote fehlt/unerreichbar, erste Beobachtung, Zurückspulen nicht ausschließbar, **oder** Checkpoint erstellt aber nicht verankert |
|
||||
|
||||
@@ -210,8 +224,14 @@ Code 3 heißt „ich weiß es nicht" – und das ist ausdrücklich kein Freibrie
|
||||
node notary.mjs --check
|
||||
```
|
||||
|
||||
Führt alle Kontrollen aus, hängt aber nichts an und braucht kein Schreibrecht.
|
||||
Geeignet für jemanden, der die Kette unabhängig nachvollziehen will.
|
||||
Führt alle Kontrollen aus, **verändert nichts** und braucht kein Schreibrecht –
|
||||
auch dann nicht, wenn dabei ein fehlender Anker auffällt. Der wird gemeldet
|
||||
(Code 5), nicht repariert; Reparieren ist Sache des Notar-Laufs.
|
||||
|
||||
Das ist kein Detail: Die Serversperre für `refs/notary/*` lehnt Schreibzugriffe
|
||||
ja gerade ab. Würde der Prüfmodus dort nachtragen wollen, bekäme jeder
|
||||
read-only prüfende Auditor dauerhaft einen Fehler auf einer völlig gültigen
|
||||
Kette – und könnte ihn nicht beheben.
|
||||
|
||||
## Anhängen ist selbst ein Beglaubigungsakt
|
||||
|
||||
|
||||
@@ -497,30 +497,58 @@ const beobachtungMerken = () => {
|
||||
// senkte damit den Substrat-Hoechststand still um eins und riss genau das
|
||||
// Fenster auf, das R180-01 ausnutzt. Deshalb wird vor allem anderen
|
||||
// nachgezogen, was fehlt.
|
||||
let fehlendeAnker = [];
|
||||
|
||||
function ankerNachziehen(bisherLaenge) {
|
||||
if (NUR_LOKAL || !PUSH) return;
|
||||
const vorhanden = new Set(ankerRefs.map((a) => a.seq));
|
||||
const fehlend = [];
|
||||
for (let n = 1; n <= bisherLaenge; n++) if (!vorhanden.has(n)) fehlend.push(n);
|
||||
if (!fehlend.length) return;
|
||||
fehlendeAnker = [];
|
||||
for (let n = 1; n <= bisherLaenge; n++) if (!vorhanden.has(n)) fehlendeAnker.push(n);
|
||||
if (!fehlendeAnker.length) return;
|
||||
|
||||
// Im PRUEFMODUS wird NICHT nachgezogen (Pentest R181-01/-02).
|
||||
//
|
||||
// Zwei Gruende, und beide sind grundsaetzlich:
|
||||
// 1. Der Pruefmodus sagt zu, nichts zu veraendern und kein Schreibrecht zu
|
||||
// brauchen. Ein Audit, das dabei still das Substrat mutiert, bricht
|
||||
// genau diese Zusage.
|
||||
// 2. `refs/notary/*` MUSS serverseitig gegen Schreibzugriff gesperrt sein
|
||||
// (R180-01) – dieselbe Sperre lehnt den Nachtrag ab. Ein read-only
|
||||
// pruefender Auditor bekaeme dadurch dauerhaft einen Fehler auf einer
|
||||
// voellig gueltigen Kette, den er gar nicht beheben kann.
|
||||
//
|
||||
// Der fehlende Anker wird gemeldet, nicht repariert. Reparieren ist Sache
|
||||
// des Notar-Schreiblaufs, dessen Identitaet in der Serversperre eine
|
||||
// Ausnahme fuer das ANLEGEN von `refs/notary/*` braucht (nicht fuers
|
||||
// Loeschen oder Ueberschreiben – sonst hebelt eine kompromittierte
|
||||
// Notar-Identitaet den Schutz aus R180-01 selbst aus).
|
||||
if (NUR_PRUEFEN || NUR_LOKAL || !PUSH) return;
|
||||
|
||||
// Die n-te Aenderung am Gegenbuch entspricht Checkpoint n.
|
||||
const commits = gitStill('log', '--reverse', '--format=%H', ref, '--', LEDGER_NAME);
|
||||
const liste = commits.ok ? commits.out.split('\n').filter(Boolean) : [];
|
||||
let nachgezogen = 0;
|
||||
for (const n of fehlend) {
|
||||
const nachgetragen = new Set();
|
||||
for (const n of fehlendeAnker) {
|
||||
const sha = liste[n - 1];
|
||||
if (!sha) continue;
|
||||
if (gitStill('push', REMOTE, `${sha}:refs/notary/seq-${n}`).ok) nachgezogen++;
|
||||
if (gitStill('push', REMOTE, `${sha}:refs/notary/seq-${n}`).ok) {
|
||||
nachgezogen++;
|
||||
nachgetragen.add(n);
|
||||
}
|
||||
}
|
||||
if (nachgezogen) console.log(`Fehlende Anker nachgetragen: ${nachgezogen}`);
|
||||
if (nachgezogen < fehlend.length) {
|
||||
fehlendeAnker = fehlendeAnker.filter((n) => !nachgetragen.has(n));
|
||||
if (fehlendeAnker.length) {
|
||||
console.error(
|
||||
`${fehlend.length - nachgezogen} Anker-Ref(s) fehlen und ließen sich nicht nachtragen.\n` +
|
||||
`${fehlendeAnker.length} Anker-Ref(s) fehlen und ließen sich nicht nachtragen: ` +
|
||||
`${fehlendeAnker.join(', ')}\n` +
|
||||
'Ohne sie ist der beglaubigte Höchststand nicht aus dem Server rekonstruierbar –\n' +
|
||||
'genau die Eigenschaft, auf der dieser Schutz beruht.',
|
||||
'genau die Eigenschaft, auf der dieser Schutz beruht.\n' +
|
||||
'Wahrscheinlichste Ursache: Die Serversperre für refs/notary/* lässt auch die\n' +
|
||||
'Notar-Identität nicht anlegen. Sie braucht dort eine Ausnahme zum ANLEGEN –\n' +
|
||||
'ausdrücklich nicht zum Löschen oder Überschreiben.',
|
||||
);
|
||||
process.exit(3);
|
||||
process.exit(5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,6 +694,22 @@ if (letzter) {
|
||||
// ---------------------------------------------------------------------------
|
||||
if (NUR_PRUEFEN) {
|
||||
const sicher = abschliessendSicher(bisher.length);
|
||||
// Eigener Code fuer den fehlenden Anker (Pentest R181-02, Frage b).
|
||||
//
|
||||
// „Anker fehlt“ ist NICHT dasselbe wie „nicht feststellbar“: Die Kette ist
|
||||
// geprueft und gueltig, nur das Substrat-Gedaechtnis ist unvollstaendig –
|
||||
// ein bekannter, benannter, durch einen Notar-Lauf reparierbarer Defekt.
|
||||
// Ihn in Code 3 zu werfen hiesse, eine konkrete Handlungsanweisung als
|
||||
// Ungewissheit zu melden; dieselbe Trennung wie bei Genesis/Adoption.
|
||||
if (fehlendeAnker.length) {
|
||||
console.log(
|
||||
`Anker unvollständig: refs/notary/seq-${fehlendeAnker.join(', seq-')} fehlt/fehlen.\n` +
|
||||
' Die Kette selbst ist geprüft und gültig – aber der beglaubigte Höchststand ist\n' +
|
||||
' ohne diese Refs nicht vollständig aus dem Server rekonstruierbar.\n' +
|
||||
' Behebbar durch einen Notar-Schreiblauf; im Prüfmodus wird bewusst nichts\n' +
|
||||
' gepusht (dieser Modus verändert nichts und braucht kein Schreibrecht).',
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
`OK: ${bisher.length} Checkpoint(s) geprüft – Signaturen gültig, Reihe lückenlos, ` +
|
||||
`Arbeitsdatei deckungsgleich mit dem signierten Stand` +
|
||||
@@ -673,6 +717,7 @@ if (NUR_PRUEFEN) {
|
||||
OHNE_ANKER() + REWIND_VORBEHALT(),
|
||||
);
|
||||
beobachtungMerken();
|
||||
if (fehlendeAnker.length) process.exit(5);
|
||||
process.exit(sicher ? 0 : 3);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user