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:
@@ -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