stack: MCP submit-Tool mit Upload Review Gate und Quarantäne-Schreibpfad (schliesst #32)
CI / verify (push) Successful in 53s
Release / release (push) Successful in 36s

Files changed:
- .gitignore
- AGENTS.md
- CHANGES.md
- INSTALL-MCP.md
- README.md
- VERSION
- docs/why-gates-are-code.md
- instructions/gates.md
- instructions/ingest-queue.md
- instructions/mcp-read-server.md
- instructions/wiki-ingest/SKILL.md
- raw/CONTRACT.md
- tools/CONTRACT.md
- tools/chemenu/cli.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/upload_cmd.py
- tools/chemenu/config.py
- tools/chemenu/mcp/server.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_mcp_server.py
- tools/chemenu/tests/test_upload.py
- tools/chemenu/tests/test_upload_cmd.py
- tools/chemenu/upload.py
This commit is contained in:
2026-09-11 09:51:37 +02:00
parent 4781140375
commit 828521861d
24 changed files with 1866 additions and 56 deletions
+71 -1
View File
@@ -35,7 +35,7 @@ dev-checkout concern - readable here, never shipped as something to parse.
---
## 5.0.0-beta.10 - 2026-09-11 - tools/CONTRACT.md: raw accept Doku auf Datums-Shard und Capture-Felder nachgezogen (schliesst #89)
## 5.0.0-beta.11 - 2026-09-11 - MCP submit-Tool: Quarantäne-Schreibpfad mit Upload Review Gate (schliesst #32)
**Author:** Torben Nehmer
@@ -63,6 +63,7 @@ dev-checkout concern - readable here, never shipped as something to parse.
- CLAUDE.md-Importkette entdrifted, Modellwahl nach docs/ verschoben (schliesst #81)
- Telemetrie-Default nach Installationsform, Byte-Deckel und Session-Retention
- tools/CONTRACT.md: raw accept Doku auf Datums-Shard und Capture-Felder nachgezogen (schliesst #89)
- MCP submit-Tool: Quarantäne-Schreibpfad mit Upload Review Gate (schliesst #32)
<!-- /wikitool:bumps -->
@@ -1107,6 +1108,75 @@ vorbeigeschrieben worden - `raw/CONTRACT.md` selbst war korrekt, `docs verify`s
prüft nur Kommandonamen gegen die Tabelle, nicht deren Prosa gegen den Code. Reine
Doku-Korrektur, kein Verhalten geändert. Schließt #89.
Der MCP-Server bekommt ein sechstes, optionales Tool: `submit`, ein Schreibpfad für Dokumente
von einem Aufrufer, der nicht dieses Terminal ist. #19s Eigenschaft "kein Tool schreibt" galt
strukturell - nichts unter `chemenu.commands` war importierbar - und diese Formulierung wird mit
einem echten Schreibpfad falsch. Die tragfähige Ersatzformulierung ist eine **Positiv-Liste**
statt einer Abwesenheit: der Serverprozess darf in genau ein Verzeichnis schreiben,
`mcp-upload/`, erzwungen durch eine einzige Funktion (`chemenu.upload._write_atomic_within`),
die jeden aufgelösten Zielpfad gegen dieses eine Verzeichnis prüft - Tests decken `..`,
absolute Pfade und einen Symlink, der aus dem Verzeichnis hinausführt. Die alte
Abwesenheitseigenschaft bleibt daneben unverändert bestehen: die Reviewer-Kommandos
(`upload accept`/`upload reject`) liegen unter `chemenu.commands` und sind vom Server aus nicht
erreichbar.
**Zwei Stufen vor `raw/`, zwei verschiedene Grenzen.** `mcp-upload/<id>/` hält Material, das
niemand geprüft hat; `wikitool upload accept <id> --confirm <token>` befördert es nach
`incoming/`, wo es sich nicht mehr von einer lokal abgelegten Datei unterscheidet und
`wiki-ingest` Schritt 1 unverändert greift. Ohne Token verweigert `upload accept` mit **Exit
42** - der vierte Gate des Stacks, **Upload Review Gate**, gleiche Form wie der
Mass-Update-Gate: ein Token, der Id, Dateiname, Größe, Sha256 und Einreicher digestet, wird
also ungültig, sobald sich das Manifest ändert. `wikitool upload reject <id> --reason "<warum>"`
braucht keinen Gate - Ablehnen braucht keine Freigabe, nur Annehmen tut das - und löscht das
Material, behält aber Grund und Sha256 im append-only `mcp-upload/ledger.jsonl`.
**Die Einreicher-Identität kommt ausschließlich aus einem HTTP-Header, nie aus einem
Tool-Argument.** `identity_header` (Default `X-Forwarded-User`) in `.wikitool-upload.json`
nennt den Header; fehlt er auf der Anfrage, wird ohne jeden Schreibvorgang verweigert - eine
unzurechenbare Einreichung ist damit unmöglich, nicht nur unerwünscht. Das Manifest hält neben
`submitter` auch `submitter_source` (den Headernamen), damit der Datensatz sagt, worauf die
Behauptung ruht, statt sie als Tatsache zu führen. Die Middleware muss den Header selbst setzen
und eine vom Client mitgeschickte Kopie verwerfen - eine Deployment-Pflicht, dokumentiert in
`INSTALL-MCP.md` Schritt 5 und `instructions/ingest-queue.md`, die der Prozess selbst nicht
erzwingen kann.
**`.wikitool-upload.json` ist ein struktureller Opt-in, nicht bloß eine Konfiguration.** Fehlt
die Datei, wird das `submit`-Tool gar nicht erst registriert - anders als bei
`.wikitool-remotes.json`, wo Abwesenheit "unbeschränkt" heißt, heißt sie hier "der Schreibpfad
existiert nicht". Eine defekte Datei ist ein Startfehler des Servers (`ValidationError` beim
Aufbau) und ein `FAIL` in `wikitool doctor`s neuem `upload-intake`-Check - nie "keine
Beschränkung". Weitere Schutzschichten in `chemenu/upload.py`: eine Größenprüfung auf der
base64-Länge *vor* dem Dekodieren (mit einem Toleranzband von 2 Bytes für Padding, damit sie
keine an der Grenze liegende, legitime Einreichung fälschlich ablehnt - der Nachdekodier-Check
bleibt die exakte Durchsetzung), eine Endungs-Positivliste, ein rollierendes
24-Stunden-Kontingent pro Einreicher (Anzahl und Bytes, aus dem Ledger berechnet, nie aus
Verweigerungen), und eine Ablehnung doppelter Inhalte, solange die erste Einreichung noch
wartet - unter Nennung der wartenden Id.
**`incoming/` bleibt komplett unberührt als zweite, unabhängige Grenze.** Ein Unterverzeichnis
dort wird seit #67 toleriert und ignoriert, sodass eine Fremdeinreichung darunter still
promotierbar gewesen wäre - deshalb ein eigenes Top-Level-Verzeichnis `mcp-upload/`, gitignored,
ohne `.gitkeep` (die Schreibprimitive legt es selbst an). `docs_verify`s Ignore-Kanarien prüfen
jetzt auch `mcp-upload/probe.pdf` und `.wikitool-upload.json`.
**MINOR, kein neuer Boundary-Crossing:** additiv in beide Richtungen geprüft - eine bestehende
Instanz kopiert die neue Maschinerie über sich und bekommt ein neues, standardmäßig
unregistriertes Tool, ohne Hand-Arbeit oder Migration; kein `.wikitool-kb.json`-Feld, kein
Type-Spec, kein umbenanntes Kommando oder Flag, kein geändertes Dateiformat. Die alte Version
zurücklegen verliert nichts - der Schreibpfad existiert dort schlicht nicht. Der Kandidat trägt
seine `--breaking`-Zeile bereits aus einem früheren Bump (Confidence-Entfernung); diese Änderung
fügt keine neue hinzu.
Geändert: `tools/chemenu/upload.py` (neu), `tools/chemenu/commands/upload_cmd.py` (neu),
`tools/chemenu/tests/test_upload.py` (neu), `tools/chemenu/tests/test_upload_cmd.py` (neu),
`tools/chemenu/mcp/server.py`, `tools/chemenu/tests/test_mcp_server.py`,
`tools/chemenu/commands/doctor.py`, `tools/chemenu/tests/test_doctor.py`,
`tools/chemenu/commands/docs_verify.py`, `tools/chemenu/cli.py`, `tools/chemenu/config.py`,
`.gitignore`, `tools/CONTRACT.md`, `AGENTS.md`, `instructions/gates.md`,
`instructions/mcp-read-server.md`, `instructions/wiki-ingest/SKILL.md`,
`instructions/ingest-queue.md` (neu), `raw/CONTRACT.md`, `docs/why-gates-are-code.md`,
`INSTALL-MCP.md`, `README.md`. Schließt #32.
---
## 4.7.4 - 2026-09-04 - bootstrap.md nennt den session-id-WARN nach frischem Bootstrap explizit als erwartet