Files
DogFatherGitandClaude Opus 5 603319a145 Arbeitsschloss: zwei Sitzungen sehen sich jetzt
HEUTE ZWEIMAL NUR GUTGEGANGEN. Zwei Claude-Sitzungen arbeiteten
gleichzeitig in diesem Verzeichnis, ohne voneinander zu wissen.
Keine hat etwas falsch gemacht — sie konnten es nicht wissen.

  * Beide haben `git add -A` benutzt. Haette die eine
    unfestgeschriebene Arbeit der anderen im Baum gehabt, waere sie
    mitcommittet worden — unter fremdem Namen, in einer fremden
    Begruendung, und niemandem waere es aufgefallen. Nachgesehen:
    diesmal war nichts dabei.
  * Beide haben den Stempellauf gestartet. Der schreibt 45 Dateien
    um. Wer dort eine offen hatte, bekam sie unter den Haenden weg
    geaendert.

Dasselbe hat in RunOne am 03.09.2026 sieben Minuten Ausfall
gekostet. Dort gibt es seitdem `arbeitsschloss.sh`; diese Fassung
uebernimmt seine Lehren.

WAS ES IST UND WAS NICHT. Es ist kein Riegel — wer wirklich muss,
kommt vorbei. Es beantwortet die eine Frage, die heute niemand
beantworten konnte: „arbeitet hier gerade sonst jemand?"

Die teuren Fehler liegen bei einem Schloss alle in derselben
Richtung: Es blockiert zu viel und wird deshalb abgeschafft. Also:

  * Es blockiert NICHT, wenn das Schloss DEINES ist. RunOnes erste
    Fassung fragte „ist abgeschlossen" statt „haelt es jemand
    anders" — damit haette, wer ordentlich abschliesst, nie mehr
    ausliefern koennen. Dafuer gibt es `fremd`.
  * Es VERFAELLT nach zwei Stunden, und dass da jemand war, steht
    beim Uebernehmen dabei.
  * Es blockiert NICHT, wenn es selbst unlesbar ist — dritter
    Ausgang, kein Stillstand.
  * Notausgang: SCHLOSS_ZWANG=ja git commit …

DAS PROBLEM, AN DEM RUNONE HAENGT, IST HIER GELOEST. Dort faellt die
Kennung im Zweifel auf den Systembenutzer zurueck, und zwei
Claudian-Sitzungen laufen BEIDE als `claudian` — die Sicherung griff
ausgerechnet zwischen den zwei Faellen nicht, fuer die sie gebaut
wurde. Deshalb ist dort `export ARBEITER=…` Pflicht, und Pflicht
heisst: man vergisst es.

Hier steht `CLAUDE_CODE_SESSION_ID` in jeder Sitzung und ist je
Sitzung verschieden (nachgesehen, 36 Zeichen UUID). Zwei Sitzungen
auf demselben Windows-Benutzer unterscheiden sich damit von selbst,
ohne dass jemand etwas tun muss. Reihenfolge: ARBEITER, dann
Sitzungskennung, dann Benutzername MIT Warnung.

NIEMAND MUSS DARAN DENKEN:
  * die beiden Stempelwerkzeuge nehmen es selbst und geben es selbst
    frei — auch nach einem Absturz und bei Strg+C (wie `bauen.sh` im
    Shop; eines, an das man denken muss, wird vergessen und ab da
    umgangen)
  * `tools/git-haken/pre-commit` bricht jeden Commit ab, solange
    jemand ANDERS das Schloss haelt. Das ist die Stelle, die heute
    gefehlt hat: `git add -A` ist der Griff, den man ohne Nachdenken
    macht, und gegen einen Reflex hilft keine Regel auf Papier.
  * `core.hooksPath` statt `.git/hooks` — letzteres ist nicht
    versioniert und waere nach einem Klon genau dann leer, wenn es
    gebraucht wird.

GEPRUEFT, server/pruef-arbeitsschloss.mjs: 33 Pruefungen, 0 Fehler —
in einem WEGWERF-Verzeichnis mit eigenem git, damit kein echtes
Schloss angefasst wird. Darunter am echten git:

    ohne Schloss committen        -> geht      (0)
    mit dem EIGENEN Schloss       -> geht      (0)
    mit einem FREMDEN Schloss     -> bricht ab (1), nennt wer und warum
    und es stehen genau ZWEI Commits da, nicht drei
    SCHLOSS_ZWANG=ja              -> kommt vorbei
    ohne das Schloss-Werkzeug     -> laesst durch

Dazu: verfallenes Schloss laesst durch, mit laengerer Frist blockt
dasselbe Schloss wieder (Gegenprobe), unlesbarer Inhalt und
unlesbarer Zeitstempel blockieren nicht.

Portnummern nach der neuen Pruefdatei nachgemessen: Pruefbereich bis
5415, 462 Nummern, 0 Kollisionen. pruef-struktur 75/0,
pruef-fingermass 5/0, pruef-ports 10/0.

In DEPLOY.md steht es jetzt an erster Stelle — eine Sicherung, von
der nur der weiss, der sie gebaut hat, ist die erste, die umgangen
wird.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-01 20:04:28 +02:00

157 lines
6.6 KiB
JavaScript

/* =====================================================================
workspace-stempel.mjs — Versionsstempel an die Workspace-Dateien.
WARUM ES DAS GIBT:
Cloudflare speichert JS und CSS vier Stunden im Browser -- es
ueberschreibt damit das "no-cache", das der Server korrekt schickt.
Nach einem Deploy sah man deshalb bis zu vier Stunden lang die ALTE
Oberflaeche, obwohl der Server laengst die neue auslieferte. Genau so
ist es passiert: eine neue Kachel war live, aber nicht sichtbar.
Die HTML-Seiten kommen dagegen immer frisch an (no-cache, DYNAMIC).
Deshalb genuegt es, in der HTML einen Stempel an die Adressen zu
haengen: Neue Seite -> neue Adresse -> Cloudflare kennt sie nicht ->
frische Datei. Ohne Cloudflare-Einstellung, ohne Zutun von aussen.
Der Stempel ist die Minute des Aufrufs. Er wird bei jedem Lauf neu
gesetzt, auch wenn schon einer dasteht.
AUFRUF: node tools/workspace-stempel.mjs
Gehoert vor jeden Deploy, der JS oder CSS im Workspace anfasst.
===================================================================== */
import { readdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
/* ==== NIEMAND STEMPELT, WAEHREND EIN ANDERER ARBEITET ===========
(01.10.2026.) Dieses Werkzeug schreibt Dutzende Dateien um.
Am 01.10. haben zwei Claude-Sitzungen es gleichzeitig laufen
lassen, ohne voneinander zu wissen -- wer dort eine Datei
offen hatte, bekam sie unter den Haenden weg geaendert.
Das Schloss wird hier SELBST genommen und beim Programmende
selbst freigegeben. Eines, an das man denken muss, wird
vergessen -- und dann umgangen, und ab da ist es nichts mehr
wert. Dieselbe Entscheidung wie bei `bauen.sh` im Shop. */
import { schlossKurz } from "./arbeitsschloss.mjs";
schlossKurz("Stempel setzen (Workspace)");
const HIER = dirname(fileURLToPath(import.meta.url));
const ORDNER = join(HIER, "..", "workspace");
const d = new Date();
const p = (n) => String(n).padStart(2, "0");
const STEMPEL = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}`;
/* Trifft href="assets/..." und src="assets/..." -- mit oder ohne
vorhandenen Stempel, damit wiederholte Laeufe nicht anhaengen. */
const MUSTER = /(href|src)="(assets\/(?:css|js)\/[A-Za-z0-9._-]+)(?:\?v=\d+)?"/g;
/* ZWEITES MUSTER: DIE APP-SYMBOLE (09.09.2026).
Sie stehen mit absolutem Pfad in der Seite (`/assets/img/app-symbole/…`)
und fielen deshalb durch das Muster oben durch. Der Server gibt Bildern
einen Tag Haltbarkeit -- ein neu gezeichnetes Symbol war damit bis zu
24 Stunden lang unsichtbar, obwohl es ausgeliefert wurde. Genau die
Luecke, die der Stempel eigentlich schliessen soll, nur fuer Bilder
statt fuer Stilvorlagen.
Die Reiter-Kennung und das Apple-Symbol bekommen deshalb denselben
Stempel wie alles andere. Dass es DERSELBE ist, ist wichtig: Die
Pruefung `pruef-zwischenspeicher` verlangt genau einen Stempel im
ganzen Haus, und eine zweite Zahl waere sofort rot. */
const MUSTER_SYMBOL =
/(href)="(\/assets\/img\/app-symbole\/[A-Za-z0-9._-]+)(?:\?v=\d+)?"/g;
let dateien = 0;
let stellen = 0;
for (const name of readdirSync(ORDNER)) {
if (!name.endsWith(".html")) continue;
const pfad = join(ORDNER, name);
const alt = readFileSync(pfad, "utf8");
let n = 0;
const neu = alt
.replace(MUSTER, (_, was, datei) => {
n += 1;
return `${was}="${datei}?v=${STEMPEL}"`;
})
.replace(MUSTER_SYMBOL, (_, was, datei) => {
n += 1;
return `${was}="${datei}?v=${STEMPEL}"`;
});
if (neu !== alt) {
writeFileSync(pfad, neu);
dateien += 1;
stellen += n;
console.log(` ${name.padEnd(20)} ${n} Verweise`);
}
}
/* DAS MANIFEST GEHOERT DAZU (09.09.2026).
Aus ihm holt sich das Betriebssystem das Symbol der installierten App.
Ohne Stempel zeigen Android und Windows nach einer Neuzeichnung
weiter das alte Bild -- die Adresse hat sich ja nicht geaendert, und
ein einmal installiertes Symbol wird nicht von sich aus nachgeladen.
Das Manifest selbst wird ohne Haltbarkeit ausgeliefert, kommt also
frisch an; nur die Adressen darin waren starr.
Derselbe Stempel wie in den Seiten -- `pruef-zwischenspeicher`
verlangt im ganzen Haus genau eine Zahl und wuerde eine zweite sofort
melden. */
/* DIE MANIFESTE WERDEN GEFUNDEN, NICHT AUFGEZAEHLT (11.09.2026).
Hier stand eine Liste mit zwei Namen und daneben der Satz, warum eine
Liste besser sei als ein kopierter Block. Beim DRITTEN Manifest
(treff.webmanifest, fuer die Community) hat sich das genau so
bewahrheitet, wie es vorausgesagt war -- nur eine Stufe weiter oben:
Der kopierte Block kam nicht, aber der neue Name fehlte in der Liste.
Das Manifest bekam keinen Stempel, und die Symbole der neuen App
waeren beim naechsten Wechsel bis zu vier Stunden lang die alten
geblieben. Kein Fehler, keine Warnung -- nur eine App mit dem
falschen Zeichen.
Jetzt wird gesucht. Wer eine vierte Tuer baut, muss hier nichts mehr
nachtragen; das Werkzeug findet sie.
NULL MANIFESTE SIND EIN FEHLER und kein Ueberspringen: Faende die
Suche nichts (falscher Ordner, umbenannte Endung), liefe der Stempel
gruen durch und haette nichts getan -- der gruene Haken ueber der
Null, gegen den in diesem Haus die halbe Dokumentation steht. */
const MANIFESTE = readdirSync(ORDNER)
.filter((n) => n.endsWith(".webmanifest"))
.sort();
if (!MANIFESTE.length) {
console.error(`FEHLER: kein einziges Manifest in ${ORDNER} gefunden.`);
process.exit(1);
}
const MUSTER_MANIFEST =
/("src":\s*")(\/assets\/img\/app-symbole\/[A-Za-z0-9._-]+)(?:\?v=\d+)?(")/g;
for (const name of MANIFESTE) {
const pfad = join(ORDNER, name);
if (!existsSync(pfad)) {
console.error(`FEHLER: ${name} fehlt -- erwartet in ${ORDNER}.`);
process.exit(1);
}
const alt = readFileSync(pfad, "utf8");
let n = 0;
const neu = alt.replace(MUSTER_MANIFEST, (_, vor, datei, nach) => {
n += 1;
return `${vor}${datei}?v=${STEMPEL}${nach}`;
});
/* Findet das Muster nichts, ist das ein FEHLER und kein Achselzucken:
Dann hat jemand das Manifest umgebaut, und ab da laeuft das Symbol
still auf dem alten Stand weiter. */
if (n === 0) {
console.error(`FEHLER: in ${name} keine Symboladresse gefunden -- Muster pruefen.`);
process.exit(1);
}
if (neu !== alt) { writeFileSync(pfad, neu); dateien += 1; stellen += n; }
console.log(` ${name.padEnd(20)} ${n} Verweise`);
}
console.log(`\nStempel ${STEMPEL} auf ${stellen} Verweise in ${dateien} Dateien gesetzt.`);
if (!dateien) console.log("Nichts geaendert -- stimmt das Muster noch?");