Eingangskanäle generalisieren: mehrere konfigurierte Kanäle mit Handler, alle münden in die Quarantäne (MCP-submit einer davon, OneDrive-Ordner ein anderer)
#213
Reference in new issue
Block a user
Abgespalten aus der Diskussion zu #186 (2026-10-12).
Stand: erledigt (2026-10-13). Gebaut, veröffentlicht, mit Release 10.0.0 ausgeliefert und von Hand auf dem Windows-Arbeitsrechner geprüft.
10.0.0-beta.11in Commitcceb9a7, CI-Läufe 678 und 679 grün.10.0.0-beta.12in Commitecefe0a, CI-Läufe 680 und 681 grün.AGENTS.md§ Gates undinstructions/mcp-read-server.mdnannten noch nursubmitbzw. die Sektionupload.collectaus einem OneDrive-Ordner mit Dateien vom Handy, beide offenen Punkte aus Entscheidung 7 beantwortet,wiki-ingestohne Argument holt ab und lässt das Gate beim Menschen. Ergebnis in Entscheidung 7 und im letzten Kriterium.docs/how-to/collect-from-a-synced-folder.md§settle_seconds, Commitf2f251f, reinedocs/-Prosa ohne Bump, CI-Lauf 691 grün).folder-Kanal wird nicht gegenbackup.targetgeprüft; Export und Eingang angleichen) und #219 (Stub:wiki-ingestohne Argument arbeitet alle wartenden Dateien in einem Kontext ab).Anlass
Eine berufliche Instanz entsteht gerade, auf einem Windows-Rechner (pwsh 7). Dort soll man Dokumente von überall her über einen OneDrive-Ordner in die Instanz bringen können:
Heute schreibt vor allem der Betreiber selbst in den Ordner. Das muss aber offen bleiben, und was dort ankommt, gilt nicht automatisch als vertrauenswürdig.
Vor diesem Paket gab es genau einen Kanal von außen: das opt-in MCP-Werkzeug
submit. Es schrieb in die Quarantänemcp-upload/. Ins Wiki kam das Material nur über das Upload Review Gate (upload accept, Exit 42).Befunde aus dem Baum (Stand vor dem Bau)
Die Fundstellen sind mit
cceb9a7/ecefe0anachgezogen.mcp-uploadnennen heute nur nochCHANGES.md, die Migrationsanleitung und die Erkennung des alten Verzeichnisses (config.LEGACY_UPLOAD_DIRNAME,upload.py,upload_cmd.py,doctor.py,raw/CONTRACT.md§ Altverzeichnis).uploadwar unveröffentlicht.10.0.0(Settings-Tiers, #207,f6aaadc, 2026-10-10). 9.0.0 las noch.wikitool-upload.json.config migrateüberführte diese Datei in die Sektionupload.config migratekann nur aus den vier Altdateien umziehen. Sektionen baut es nicht um.version bumpwertet jeden Bump dieses Kandidaten als Überschreitung gegen das letzte Release (version_cmd.py,crossing). Ein weiteres--breakingwird angehängt..gitignore-Lücke gerissen. Die Wurzel-.gitignoreignorierte nur/mcp-upload/. Ohne passenden Eintrag hättepublish(git add -A) ein neues Verzeichnis mit committet. Deshalb trägtquarantine/heute eine eigene.gitignore(Entscheidung 5).mcp-uploadkam an vielen Stellen vor: in Code (config.py,upload.py,upload_cmd.py,mcp/server.py,raw/pending.py,doctor.py,backup.py/backup_cmd.py,docs_verify.py), in Instructions, in Docs und in Tests. Siehe „Betroffene Dateien“.upload collectliegt unterchemenu.commandsund inchemenu/collect.pyund ist damit, wieaccept/reject, aus dem MCP-Serverprozess nicht importierbar.digest_to_pending_id), Ledger undconfirm_token. Die Quote (_check_quota) hängt ansubmitterund gilt nur fürmcp.fetch_routes.run_fetcherruft ohne Shell auf, schließt stdin, setzt ein Timeout und gibt bei einem Fehler das Ende von stderr aus. Der Handlercommandfolgt dem Muster.Entscheidungen
Was ein Kanal liefert, landet immer in der Quarantäne, nie direkt in
incoming/.incoming/hält lautraw/CONTRACT.mdnur Material, das ein Mensch an der Tastatur bewusst eingeworfen hat. In die Pipeline kommt Material nur überupload accept(Gate, Mensch).Verworfen:
incoming/als NTFS-Junction oder Symlink in den OneDrive-Ordner.incoming/.gitkeepist eingecheckt. Ein Link an der Stelle des Verzeichnisses kann dazu führen, dasspublishdas Löschen von.gitkeepoder einen Link-Eintrag committet.raw acceptverschiebt mitrename/os.replace. Das scheitert über Laufwerksgrenzen.Mehrere Kanäle, jeder mit einem Handler. MCP-
submitist ein Kanal (mcp), der OneDrive-Ordner ein anderer (folder). Weitere Quellen schließt der Betreiber übercommandan, ohne Stack-Änderung.Abholen gibt kein Vertrauen, deshalb darf der Agent es auslösen.
upload collect [<kanal>...]holt aus den Pull-Kanälen in die Quarantäne. Aus der Quarantäne heraus führt nurupload acceptmit Gate.E1: Die Quarantäne heißt
quarantine/, und der Bruch kommt mit 10.0.0.Eigene
.gitignore: Wer die Quarantäne anlegt, legt darinquarantine/.gitignoremit dem Inhalt*an. So wird sie nie committet, auch nicht mit einer veralteten Wurzel-.gitignore. Die Wurzel-.gitignoreträgt/quarantine/statt/mcp-upload/.Altes Verzeichnis: Existiert noch ein
mcp-upload/mit Inhalt, dann:doctorFAILunterupload-intakemit dem Verschiebebefehl,upload list/show/accept/reject/collectmit Exit 1, bevor sie etwas tun, und nennen denselben Befehl,Es wird nichts automatisch verschoben.
Migration:
instructions/migrations/10.0.0-settings-tiers.mdhat einen Schritt für das Verschieben, mit Befehl für POSIX und pwsh.Sicherung: Die Maschinensicherung (#186) schließt
quarantine/aus.E2: Ein Handler ist nur eine Quelle. Er liefert Dateien unverändert. Konvertieren und Ableiten bleiben Sache von
raw derivenachupload accept. Material wird nie verändert, bevor ein Mensch es gesichtet hat.E3: Wann eine Datei als fertig synchronisiert gilt (
folder). Übernommen wird eine Datei nur, wenn beide Bedingungen gelten:st_birthtime, wo vorhanden, sonst nur die mtime) sind älter alssettle_seconds;Sonst bleibt sie liegen und wird als „noch in Bewegung“ gemeldet.
Belegt durch die Prüfung von Hand (2026-10-13, OneDrive for Business unter Windows 11):
CreationTime 07.10.2026 17:30:03undLastWriteTime 07.10.2026 17:30:04, also mit dem Stand der Datei, nicht mit dem Zeitpunkt der Ankunft (13.10.).settle_secondshält eine solche Datei also nicht zurück.Archive, ReparsePoint(nur online).collecthat sie gelesen, damit heruntergeladen und übernommen. Die Kopie in der Quarantäne trägt die Zeit des Abholens.settle_seconds. Das How-to nannte diese Grenze schon und nennt jetzt auch den beobachteten Fall. Eine Codeänderung folgt daraus nicht, wie vorab entschieden.E4:
wiki-ingestohne Argument holt zuerst ab.raw pending:upload collect(alle Pull-Kanäle),upload list,upload show/upload accept(instructions/ingest-queue.md).collectendet mit Exit 0 und „nothing to collect“.incoming/fort. Er wiederholt nichts.upload acceptnicht auf und gibt kein Token selbst ein. Das Gate bleibt beim Menschen.incoming/ist leer und Einreichungen warten: Der Lauf endet mit der Liste.collectBetreiber und Agent.E5: keine Quoten für
folder/command. Es giltmax_bytesje Datei, und eine Flut von Dateien fällt am Gate auf. Eine Höchstzahl je Lauf wäre ein eigenes Paket.E6: Die Sektion
uploaderscheint nie in einem Release. Erfüllt: 10.0.0 ist mitintakeerschienen.intakeersetzt.config migrateschreibt.wikitool-upload.jsondirekt nachintake.channels.mcpmithandler: "mcp".quota.*wird dabei flach zuquota_*.WARNfür eine alte Sektion.Konfiguration
Sektion
intakein.wikitool-local.json(config_shapes.INTAKE, ersetztconfig_shapes.UPLOAD). Das Vorbild istfetch.routes/fetch.fetchers. Die erzeugte Referenz steht indocs/reference/configuration.md§ Intake channels.channels[a-z0-9-]+) → Kanal. Der Name kommt ins Manifest und ist das Argument vonupload collect.channels.<n>.handlermcp,folderodercommand. Höchstens ein Kanal mitmcp. Ein zweiter ist ein Fehler der Sektion.max_bytesallowed_extensions., ohne Rücksicht auf Groß-/Kleinschreibungidentity_headermcp, DefaultX-Forwarded-Userquota_submissions_per_day,quota_bytes_per_daymcpmcppathfolderbackup.targetwird nicht geprüft: #216.)settle_secondsfolder, Default 60, ganze Zahl ≥ 0commandcommandcommandscommandsWie der Handler
commandläuft: Das Programm wird ohne Shell gestartet, stdin ist geschlossen, das Timeout beträgt 300 s. Als letztes Argument bekommt es ein leeres Staging-Verzeichnis unterquarantine/.staging/<zufall>/.Regeln für Fehler und Fehlen:
command-Verweis ohne Eintrag untercommands.submitnoch einen Pull-Kanal. Das ist ein gültiger Zustand,doctormeldetOK(absent).upload collectendet mit Exit 1,doctormeldetFAIL.gatebzw.names a command, deshalb lehntconfig setsie mit Exit 42 ab.Was gebaut wurde
config_shapes.INTAKEersetztUPLOAD.upload.read_configliest die Kanäle. Der MCP-Server registriertsubmitgenau dann, wenn ein Kanal mithandler: mcpexistiert, und liest dessen Grenzen.quarantine/(config.UPLOAD_DIR). Alle Fundstellen sind nachgezogen, inklusive der eigenen.gitignoreund der Erkennung des alten Verzeichnisses (Entscheidung 5).channel,handlerundorigin(Pfad der Quelldatei,command:<name>odermcp:submit). Beifolder/commandistsubmitter="unknown"undsubmitter_source= der Handler. Ledger-Einträge tragenchannel.wikitool upload collect [<kanal>...](schreibend;cli_contract-Eintrag in der Gruppe „Raw material and uploads“; Code inchemenu/collect.py):mcp-Kanal als Argument ist Exit 1, bevor etwas geschieht.folderwird sie an der Quelle trotzdem entfernt, weil der Inhalt in der Quarantäne gesichert ist.doctorupload-intake: Gemeldet werden die Kanäle mit ihrem Handler, die Zahl der wartenden Einreichungen und das altemcp-upload/(FAIL). Beifoldergibt esWARN, wennpathnicht existiert.config migrate: Das Ziel von.wikitool-upload.jsonistintake.channels.mcp.wiki-ingestSchritt 1 nach Entscheidung 8.instructions/ingest-queue.mdnenntcollectund die Herkunftsfelder des Manifests. Beisubmitter: unknowngibt es keinen Absender, dem man trauen könnte.docs/how-to/collect-from-a-synced-folder.md:foldervon Hand anlegen,settle_seconds,f2f251fmit dem in der Prüfung beobachteten Verhalten von OneDrive,commandmit einem Beispielskript.Was ein Abholen garantiert
folder). Die Quelldatei wird erst entfernt, wenn ihre Kopie in der Quarantäne vollständig geschrieben ist (atomar über_write_atomic_within) und Größe und sha256 der Quelle nach dem Kopieren noch denen der Kopie entsprechen. Andernfalls bleibt die Quelle unverändert, und die halbe Einreichung wird entfernt.collectnur eins: Es entfernt übernommene Dateien direkt inpath. Es schreibt nie dorthin, löscht nie ein Verzeichnis und verfolgt keinen Symlink. Ein Symlink inpathwird übersprungen und gemeldet..oder~$, oder unter Windows ist das Attribut hidden oder system gesetzt. Ebenso bleibt eine Datei übermax_bytesliegen.sanitize_filename. Ein Name, den es ablehnt, bleibt an der Quelle und wird gemeldet.incoming/oderraw/außer überupload accept.Akzeptanzkriterien
Tests in
test_upload.py/test_upload_cmd.py/test_mcp_server.py/test_config_shapes.py/test_legacy_settings.py, soweit nicht anders genannt. Die Tests des Abholens (folder,command,upload collect,.gitignoreder Quarantäne) stehen intest_collect.py.upload collectgegen einenfolder-Kanal mit zwei erlaubten Dateien liegen inquarantine/zwei Einreichungen, deren sha256 den Quelldateien entspricht. Der Quellordner ist danach leer. Die Manifeste tragenchannel,handler: folder,originundsubmitter: unknown..x.pdf, eine Datei~$a.docx, eine Datei mit nicht erlaubter Endung, eine Datei übermax_bytesund ein Symlink im Quellordner sind nachcollectbyte-gleich vorhanden und erscheinen in der Ausgabe als übersprungen.settle_seconds: Eine Datei mit mtime jünger alssettle_secondsbleibt byte-gleich liegen und wird nicht eingereicht.collect.source_matches), bleibt die Quelle byte-gleich, und inquarantine/entsteht keine Einreichung.command, Erfolg: Ein Programm, das zwei Dateien ins Staging-Verzeichnis legt und mit Exit 0 endet, ergibt zwei Einreichungen mitorigin: command:<name>.command, Misserfolg: Exit ≠ 0 bzw. Timeout ergibt null Einreichungen.quarantine/.staging/ist danach leer bzw. fehlt, und die Ausgabe enthält das Ende von stderr.collectmit Exit 0 und „nothing to collect“. Ein unbekannter Kanalname oder einmcp-Kanal als Argument ergibt Exit 1 ohne Wirkung..gitignoreder Quarantäne: Nach der ersten Einreichung (übersubmitund übercollect) existiertquarantine/.gitignoremit dem Inhalt*. In einem Test-Repo ohne Eintrag in der Wurzel-.gitignorezeigtgit status --porcelain --untracked-files=allnichts unterquarantine/.mcp-upload/mit Inhalt endenupload list/show/accept/reject/collectmit Exit 1 und nennen den Verschiebebefehl.doctormeldetupload-intakeFAIL, undmcp-upload/ist danach byte-gleich.mcp-Kanäle, ein Feld, das nicht zum Handler passt, eincommand-Verweis ohne Eintrag, ein relativerpathund einpathinnerhalb der Instanz sind jeweils ein Fehler der Sektion.config set intake.…endet mit Exit 42.mcp-Kanal registriert der Serversubmitmit dessen Grenzen. Ohnemcp-Kanal, aber mitfolder-Kanal, istsubmitnicht registriert. Einesubmit-Einreichung trägtchannel: mcp. Die bestehenden Tests zu Quote und Identität bleiben grün.config migratemit einer.wikitool-upload.json(verschachteltequota) ergibtintake.channels.mcpmithandler: mcpund den flachenquota_*-Feldern, und es entsteht keine Sektionupload.backup runliegt im Ziel keine Datei ausquarantine/(test_backup.py).docs/how-to/README.md.raw/CONTRACT.md,instructions/gates.md,instructions/ingest-queue.md,instructions/wiki-ingest/SKILL.md,instructions/mcp-read-server.md,docs/how-to/set-up-the-mcp-server.md,back-up-to-a-synced-folder.mdund die Migration nennenquarantine/undintake. Außerhalb vonCHANGES.md, der Migrationsanleitung und der Erkennung des alten Verzeichnisses nennt kein Dokumentmcp-upload/mehr als aktuellen Ort (grep).ecefe0a):AGENTS.md§ Gates (das Upload Review Gate gilt für jeden Kanal),instructions/mcp-read-server.md(Sektionintakestattupload), der Einleitungssatz indocs/reference/configuration.mdund der Docstring vondoctor.cceb9a7: lokalpytest(2997 bestanden, 4 übersprungen),docs verifyundinstructions verifygrün; CI-Lauf 678 (Jobsverifyundpwsh) und Lauf 679success.ecefe0a: lokal dieselben drei Prüfungen grün (2997 bestanden, 4 übersprungen); CI-Läufe 680 und 681success.f2f251f(How-to, Ergebnis von Entscheidung 7):docs verifylokal grün, CI-Lauf 691success.docs contract/docs referencesind neu erzeugt. Die Sektionintakesteht indocs/reference/configuration.md, die Sektionuploadnicht mehr.collectgegen einen Ordner unterOneDrive - <Firma>mit vom Handy hochgeladenen Dateien: übernommen in die Quarantäne, Quelle danach entfernt, Freigabe überupload acceptsauber.doctormeldetupload-intakemit dem Kanalonedrive (folder).wiki-ingestohne Argument holt aus dem Ordner in die Quarantäne, zeigt die wartenden Einreichungen und lässt die Freigabe beim Betreiber. Nebenbefund: Danach arbeitet es mehrere wartende Dateien in einem Kontext ab → #219.Versionsteil
10.0.0-beta.11: MAJOR auf dem laufenden Kandidaten,--impact high.--breaking "Die Quarantäne heißt quarantine/ statt mcp-upload/. Eine Instanz mit wartenden Einreichungen verschiebt das Verzeichnis einmal von Hand (Migrationsschritt); bis dahin enden die upload-Kommandos mit Exit 1."required(instructions/migrations/10.0.0-settings-tiers.md, ergänzt um den Schritt).10.0.0-beta.12: PATCH für den Nachzug beim Abschluss,--impact low.f2f251fist reinedocs/-Prosa außerhalb des Version-Gates, ohne Bump.Dass
uploaddurchintakeersetzt wird, ist gegenüber 9.0.0 kein eigener Bruch, denn der Weg aus.wikitool-upload.jsonendet nur an anderer Stelle.Betroffene Dateien und Flächen
tools/chemenu/upload.py,tools/chemenu/collect.py(neu, damit der MCP-Serverprozess Abholen, Löschen und Programmstart nicht importieren kann),tools/chemenu/commands/upload_cmd.py(neu:collect),tools/chemenu/mcp/server.py,tools/chemenu/config_shapes.py(UPLOAD→INTAKE),tools/chemenu/legacy_settings.py,tools/chemenu/config.py(UPLOAD_DIR),tools/chemenu/commands/doctor.py(check_upload_intake),tools/chemenu/commands/raw/pending.py,tools/chemenu/commands/backup_cmd.py,tools/chemenu/commands/docs_verify.py,tools/chemenu/reference_doc.py(Region der Sektion),tools/chemenu/cli_contract.py(Gruppe)..gitignore.AGENTS.md§ Gates,raw/CONTRACT.md§ Getting a file in from outside,instructions/gates.md,instructions/ingest-queue.md,instructions/wiki-ingest/SKILL.md,instructions/mcp-read-server.md,instructions/migrations/10.0.0-settings-tiers.md.docs/reference/configuration.md,docs/how-to/set-up-the-mcp-server.md,docs/how-to/back-up-to-a-synced-folder.md,docs/how-to/ingest-a-source.md,docs/how-to/collect-from-a-synced-folder.md(neu; Nachtragf2f251f),docs/how-to/README.md,docs/explanation/architecture.md,docs/explanation/pipeline-rationale.md,tools/README.md.tools/CONTRACT.md,docs/reference/commands/{README,git,instance-health,raw-material-and-uploads}.md, die Region indocs/reference/configuration.md.test_upload.py,test_upload_cmd.py,test_mcp_server.py,test_config_shapes.py,test_legacy_settings.py,test_backup.py,test_doctor.py,test_collect.py(neu, 22 Tests).CHANGES.mdüberversion bump.Kette
Kette „OneDrive/Microsoft 365“
Changelog: Body gegen den Baum geprüft. Neuer Abschnitt „Befunde aus dem Baum“:
uploadgibt es erst im unveröffentlichten Kandidaten 10.0.0..gitignore-Lücke.mcp-uploadsind aufgelistet.Entscheidung 5 ist gestrichen, weil ihre Prämisse nicht trägt, und durch die offene Frage E6 ersetzt. Die offenen Fragen heißen jetzt E1–E6, jede mit einer Empfehlung. E3 ist als nicht blockierende Regel formuliert. Neue Festlegungen in Konfiguration und Garantien:
mcp-Kanal[a-z0-9-]+pathabsolut und außerhalb der InstanzDer Versionsteil ist an E1/E6 gekoppelt, die Liste der betroffenen Dateien ist vervollständigt.
Changelog: Der Betreiber hat E1–E6 wie empfohlen entschieden (2026-10-12, „alles ok“). Sie stehen jetzt als Entscheidungen 5–10 im Body.
Neu im Body:
INTAKE-Shape, die Umbenennung inquarantine/mit eigener.gitignoreund Erkennung des alten Verzeichnisses, Herkunftsfelder im Manifest,upload collect,doctor,config migrate,wiki-ingest, How-to.command-Handlers: nach dem Muster vonrun_fetcher, mit 300 s Timeout.--breaking, Migrationsschritt).Labels:
kind/decision→kind/build, weil keine Betreiberentscheidung mehr offen ist.size/Lbleibt, denn das Paket braucht mehrere Sitzungen.Folge für das Release: 10.0.0 wird erst nach diesem Paket veröffentlicht.
Gebaut und veröffentlicht als
10.0.0-beta.11(Commitcceb9a7), CI grün (Läufe 678 und 679).intakein.wikitool-local.jsonersetztupload. Kanäle haben die Handlermcp,folderundcommand.quarantine/(vorhermcp-upload/). Das Verzeichnis trägt eine eigene.gitignore. Ein noch vorhandenesmcp-upload/führt zu Exit 1 in denupload-Kommandos und zuFAILindoctor. Der Migrationsschritt dafür steht ininstructions/migrations/10.0.0-settings-tiers.md.wikitool upload collect [<kanal>...]: Holt aus den Pull-Kanälenfolderundcommandin die Quarantäne. Die Quelldatei wird erst nach gesicherter Kopie entfernt. Fremdes bleibt liegen und wird gemeldet. Ein scheiternder Kanal stoppt die anderen nicht.channel,handlerundorigin.wiki-ingestholt ohne Argument zuerst ab und meldet wartende Einreichungen.upload acceptund das Gate bleiben beim Menschen.config migrateschreibt.wikitool-upload.jsondirekt nachintake.channels.mcp.docs/how-to/collect-from-a-synced-folder.md, Referenz und Verträge nachgezogen.--breaking(Verzeichnisname).Offen: die Prüfung von Hand auf dem Windows-Arbeitsrechner (OneDrive-Ordner, die beiden unbelegten Punkte aus Entscheidung 7,
wiki-ingestohne Argument).Changelog:
submitals einzigen Kanal bzw. die Sektionupload:AGENTS.md§ Gates,instructions/mcp-read-server.md, die Einleitung vondocs/reference/configuration.mdund der Docstring vondoctor. Sie sind als10.0.0-beta.12nachgezogen (Commitecefe0a), CI-Läufe 680 und 681 grün./stack-close.Changelog: Abschluss nach der Prüfung von Hand (2026-10-13, Release 10.0.0, Windows mit OneDrive for Business). Das letzte Kriterium ist abgehakt. Entscheidung 7 ist belegt: OneDrive behält die ursprünglichen Zeiten, und ein Platzhalter wird beim Lesen heruntergeladen. Bei OneDrive schützt also der Vergleich von Größe und sha256, nicht
settle_seconds. Das How-to ist inf2f251fnachgezogen, CI-Lauf 691 grün. Folgepakete: #216 und #219. Changelog-Zeile: Eingangskanäle (mcp,folder,command) münden inquarantine/;upload collectholt aus Sync-Ordner und Programm ab, mit 10.0.0 ausgeliefert und auf Windows mit OneDrive bestätigt.