raw fetch: sanktionierter Intake für eine URL nach incoming/ #120

Closed
opened 2026-09-18 21:38:48 +00:00 by torben · 4 comments
Owner

Ausgearbeitet aus einem status/incoming-Stub am 2026-10-02 (Stand 8.0.0-beta.24, c0f324f). Der Stub steht wörtlich im ersten Kommentar.

Abgeschlossen 2026-10-02. Gebaut und publiziert in 8.0.0-beta.25, Commit fba263a, verifiziert durch CI-Runs 502 und 503. Nachzug aus der Abschlussprüfung in 8.0.0-beta.26, Commit c4dcff7, verifiziert durch CI-Runs 504 und 505. Alle vier Runs grün.

Befund (gegen den Baum geprüft, Stand vor dem Bau)

Für "Nutzer nennt eine URL, die ingested werden soll" gab es weder ein Tool noch eine Prozedur:

  • raw accept (tools/chemenu/commands/raw_cmd.py) nimmt nur existierende Dateien unter incoming/.
  • raw/CONTRACT.md kannte zwei Eingänge - incoming/ (lokale Datei, § Getting a file in) und mcp-upload/ (MCP-submit) -, keinen für eine URL.
  • instructions/wiki-ingest/SKILL.md Schritt 1 setzte eine Datei in incoming/ oder raw/ voraus; der einzige URL-Bezug war --set source_url=<upstream URL> auf der Source-Seite (Schritt 6, types/source.md).

Eine Session baute sich deshalb jedes Mal ihre eigene Kette (curl, Zeichensatz raten, lynx/pandoc je nach Verfügbarkeit, Boilerplate per Zeilennummer schneiden, Header frei erfinden). Zwei Sessions erzeugten aus demselben Artikel verschiedene raw/-Dateien - genau das, was der Intake durch Werkzeug ausschließen soll. Der Harness-eigene Web-Fetch scheidet aus: Er liefert eine LLM-Zusammenfassung, keine Erfassung nahe am Original.

Entscheidungen (alle umgesetzt)

  1. Ein Tool, keine reine Prozedur. wikitool raw fetch <url> lädt, behandelt den Zeichensatz, leitet Text ab und schreibt nach incoming/. Danach läuft raw accept unverändert - der Fetch landet nicht direkt in raw/, damit die Reihenfolge aus wiki-ingest (Verpflichtungsfrage vor raw accept) erhalten bleibt und --fidelity/--authority weiter genau dort erfragt werden.
  2. Die Rohquelle ist ein Bundle aus HTML und abgeleitetem Text (vom Betreiber entschieden 2026-10-02). raw fetch schreibt incoming/<stem>.html - die Bytes exakt wie empfangen - und incoming/<stem>.md (Header + abgeleiteter Text). raw accept nimmt beide in einem Aufruf als Bundle (raw/<YYYY>/<MM>/<stem>/), beide stehen in raw_files:. Begründung: raw/CONTRACT.md verlangt "kept exactly as received", und empfangen wurde das HTML; der Text ist eine Ableitung des Tools. Nur mit dem HTML bleibt eine Behauptung in kb/ byte-genau gegen das Original prüfbar, auch wenn die Ableitung Inhalt verliert oder später verbessert wird. Gelesen und zitiert wird die .md. Verworfen: nur den Text ablegen - kleiner, aber was die Ableitung verwirft, wäre unwiederbringlich, und der Contract bräuchte eine Ausnahme von seinem Qualitätsziel.
  3. Nur Standardbibliothek (urllib.request, html.parser, email.message für den Content-Type). Präzedenz: version.py lädt bereits per urllib. Begründung: Eine neue Abhängigkeit in tools/requirements.txt fällt durch die Vorwärtshälfte des Drop-in-Tests (instructions/dev/version-parts.md) - nach dem Kopieren von tools/ fehlt sie im venv, bis jemand pip install ausführt. Die schwächere Boilerplate-Entfernung ist hinnehmbar, weil der abgeleitete Text nicht der Beleg ist (Entscheidung 2).
  4. Zeichensatz, deterministisch in dieser Reihenfolge: BOM -> charset im HTTP-Content-Type -> <meta charset> / <meta http-equiv> in den ersten 4 KiB -> UTF-8. Ein Label, das Python nicht kennt, wird wie ein fehlendes übersprungen. Undekodierbare Bytes werden ersetzt, nicht verworfen, und der Fall wird in der Ausgabe gemeldet. Die verwendete Kodierung steht im Header (Entscheidung 6). Bei --html (Entscheidung 14) entfällt die HTTP-Stufe.
  5. Textableitung (HTML -> Markdown-naher Text), deterministisch: Inhaltswurzel ist <main>, sonst genau ein <article>, sonst <body>. Darunter fallen script, style, noscript, nav, header, footer, aside, form, template, svg weg. Überschriften -> #, Listen -> -/1., pre/code -> Codeblock/Backticks, Links -> [text](absolute-url) (relativ aufgelöst gegen final_url bzw. --url), Absätze durch Leerzeile. Keine Heuristik über Zeilennummern oder Textdichte. Gleiche Bytes ergeben dieselbe Ausgabe. Im Bau ergänzt, ohne die Regel zu ändern: Tabellen -> Markdown-Tabelle, blockquote -> >, Bilder -> ![alt](absolute-url) (data:-URIs nur als Alt-Text), Emphasis wird nicht ausgezeichnet. Folge der Regel, bewusst so gelassen: ein <header> innerhalb von <main>/<article> (oft mit dem Artikel-<h1>) fällt ebenfalls weg - der Titel steht dann nur in title:, das HTML behält ihn.
  6. Header-Format - ein fester Block am Anfang der abgeleiteten .md-Datei, vom Tool geschrieben, nie von Hand. Die .html bleibt unberührt.
    ---
    fetched_by: wikitool raw fetch
    url: <wie angefordert>
    final_url: <nach Redirects>
    retrieved: <ISO-8601, UTC>
    http_status: 200
    content_type: text/html; charset=utf-8
    charset: "utf-8 (from: header|meta|bom|default)"
    title: <aus <title>, sonst leer>
    derived_from: <stem>.html
    ---
    
    Im Bau geändert: Werte werden YAML-gequotet, wo YAML sie sonst nicht unverändert zurückliest (JSON-String-Form). Das betrifft immer charset (: in (from: ...)) und z.B. einen Titel mit : oder #. Grund: Die ----Zäune weisen den Block als Frontmatter aus; ungültiges YAML darin bricht jeden Viewer, der ihn liest. Inhaltlich bleibt das Format gleich.
    Bei --html (Entscheidung 14): fetched_by: wikitool raw fetch --html, retrieved/final_url/http_status/content_type entfallen, dafür derived: <ISO-8601, UTC> (Zeitpunkt der Ableitung - wann der Mensch die Seite gespeichert hat, weiß das Tool nicht und behauptet es nicht).
    Die Felder sind Erfassungsmetadaten, keine Seiten-Frontmatter: raw/ hat keine Typen (raw/CONTRACT.md Abs. 3), nichts im Stack liest diesen Block maschinell. author: wird bewusst nicht geführt - der Stub nannte ihn, aber aus HTML ist er nicht verlässlich bestimmbar; ein geratener Autor wäre eine Behauptung über die Quelle. Er gehört auf die Source-Seite, wenn die Quelle ihn trägt.
  7. Dateiname: Stamm aus dem letzten Pfadsegment der URL (ohne Endung), sonst aus dem Hostnamen, sluggifiziert (ASCII, Kleinbuchstaben, -), auf 60 Zeichen gekürzt; --name <stem> überschreibt (wörtlich übernommen; Pfadtrenner und leer -> exit 1). Existiert in incoming/ schon <stem>.html oder <stem>.md, verweigert der Befehl (exit 1) - kein Überschreiben, keine Suffixe; die Dateien werden zusätzlich exklusiv (x-Modus) angelegt. Bei --html ist der Stamm der der übergebenen Datei, die .md entsteht neben ihr. Die Pfadbudget- und Stammkollisionsprüfung gegen raw/ bleibt raw accepts Sache; raw fetch dupliziert sie nicht.
  8. Nicht-HTML-Antworten: text/plain, text/markdown -> Bytes unverändert als .txt/.md, ohne Ableitung. Alles andere (PDF, Bilder, ...) -> Bytes unverändert mit Endung aus dem Content-Type (Pythons eingebaute Tabelle, nicht /etc/mime.types; unbekannt oder fehlend -> .bin), ohne Ableitung; die Ausgabe sagt das. Ein Header entfällt dann (er hätte keine Datei, in die er gehört, ohne die Bytes zu verändern); url/retrieved stehen in der Erfolgsmeldung und gehen über source_url auf die Source-Seite.
  9. Grenzen: nur http/https, auch auf jedem Redirect-Schritt (file:, ftp: u.a. -> exit 1 - ein file:-Fetch wäre ein Weg an incoming/ vorbei). Timeout 30 s für den ganzen Transfer, Größenlimit 25 MiB (Abbruch beim Überschreiten, nichts geschrieben). Eigener User-Agent mit Versionsnummer (chemenu-wikitool/<version> (raw fetch)). Keine Cookies, kein JavaScript: Eine clientseitig gerenderte Seite ergibt fast leeren Text - unter 200 Zeichen abgeleitetem Text warnt der Befehl ausdrücklich, schreibt aber trotzdem; ob die Erfassung taugt, entscheidet der Mensch.
  10. Erfolgsmeldung nennt die geschriebenen Dateien, die nächste Zeile und den Paywall-Hinweis:
    OK Fetched https://example.org/post to incoming/post.html, incoming/post.md
      Next (after the commitment question in wiki-ingest):
      tools/wikitool raw accept --fidelity published --authority <value> incoming/post.html incoming/post.md
      and on the source page: --set source_url=https://example.org/post
      If the text stops at a teaser (paywall, login, script-rendered page): save the page from a
      logged-in browser to incoming/ and run  tools/wikitool raw fetch --html incoming/<file>.html --url <url>
    
  11. Invariante 4 bleibt unberührt: raw fetch wird nur auf eine URL angewandt, die der Nutzer selbst nennt - nie auf eine URL, die in einer Rohquelle steht. Das steht in raw/CONTRACT.md, in wiki-ingest und im NEVER-Teil des Kommando-Records.
  12. Nicht über MCP. Der MCP-Server bekommt kein Fetch-Tool: Ein Server, der beliebige URLs im Auftrag eines entfernten Aufrufers lädt, ist ein SSRF-Vektor. Der entfernte Weg bleibt submit -> mcp-upload/. Die Begründung steht in raw/CONTRACT.md § mcp-upload.
  13. Kein --fidelity/--authority bei raw fetch. Die Werte gehören zu raw accept (dort sind sie Pflicht); sie zweimal zu erfragen wäre eine zweite Stelle für dieselbe Entscheidung.
  14. Paywall und Login: kein Login im Tool, stattdessen --html (vom Betreiber entschieden 2026-10-02).
    • raw fetch --html incoming/<stem>.html --url <url> leitet aus einer HTML-Datei, die der Mensch aus seinem eingeloggten Browser nach incoming/ gespeichert hat ("Seite speichern unter", nur HTML), dieselbe .md ab (Header siehe Entscheidung 6). Kein Netzzugriff. Die .html bleibt byte-unverändert; das Bundle ist dasselbe wie in Entscheidung 2. Deckt Paywall und clientseitig gerenderte Seiten gleichermaßen ab. --html außerhalb von incoming/ -> exit 1 (dieselbe Prüfung wie bei raw accept). --url ist Pflicht: Sie steht im Header, löst relative Links auf und geht als source_url auf die Source-Seite.
    • Keine Zugangsdaten, Cookies oder Login-Adapter in wikitool. Das Tool geht an jede Instanz; Zugangsdaten im Arbeitsbaum widersprechen raw/CONTRACT.md § What does not belong here, und ein Adapter pro Paywall-Site ist Wartung, die kein Wissenskompiler tragen soll. Ein Auth-Dienst entsteht außerhalb des Stacks (#87); die Andockstelle dafür ist #169, nicht dieses Issue.
    • Teaser-Prüfung ist ein Urteil in wiki-ingest, nicht im Tool: Ein Teaser ist allgemein nicht erkennbar und hat oft mehr als die 200 Zeichen aus Entscheidung 9. Schritt 1 liest die Quelle ohnehin vollständig; bricht der Text erkennbar ab ("Weiterlesen mit ...", Abo-Hinweis, Login-Aufforderung), stoppt die Session und schlägt dem Nutzer den --html-Weg vor, statt einen Teaser als Quelle zu ingesten.

Betroffene Dateien

Bau (fba263a):

  • tools/chemenu/web_capture.py (neu) - Fetch, Zeichensatz, Ableitung, Header, Stamm; Kern ohne typer/rich, wirft ValidationError/BackendError, damit #169 dort andocken kann.
  • tools/chemenu/commands/raw_cmd.py - neues Kommando raw fetch samt Kommando-Record (Synopsis für beide Formen, network: yes, Exit-Status, ON FAILURE, NEVER).
  • tools/chemenu/cli_contract.py - raw fetch in GROUPS ("Raw material and uploads"); ohne den Eintrag rendert docs contract das Kommando nicht.
  • tools/CONTRACT.md - aus dem Record generiert (nicht von Hand).
  • tools/chemenu/tests/test_raw_fetch.py (neu, 36 Tests) - gegen einen lokalen http.server (Präzedenz: test_dist_upgrade.py), kein echtes Netz.
  • tools/chemenu/tests/test_cli.py - die gepinnte network: yes-Menge um raw fetch ergänzt.
  • tools/chemenu/tests/test_portability.py - opener (urllibs OpenerDirector) in NOT_TEXT_OPEN: Der AST-Scan las opener.open(request, ...) als Text-Datei-Open ohne encoding=.
  • raw/CONTRACT.md - neuer Abschnitt "Getting a URL in: raw fetch" zwischen incoming/ und mcp-upload/: Bundle-Regel, Header-Format, --html und Paywall-Regel, Invariante-4-Satz; im mcp-upload/-Abschnitt der SSRF-Satz (Entscheidung 12).
  • instructions/wiki-ingest/SKILL.md - description/Trigger um "ingest "; Schritt 1: URL -> raw fetch, Teaser-Prüfung mit --html; Kommandoliste.
  • tools/README.md (Modulliste: web_capture.py), README.md (§ Adding Knowledge: URL-Ingest und --html).
  • CHANGES.md über version bump (Changeset "raw fetch: a sanctioned intake for a URL into incoming/ (#120)", Impact medium).

Abschlussprüfung (c4dcff7):

  • kb/CONTRACT.md § Provenance: Der Satz, die lokale Kopie eines externen Artikels liege "under raw/articles/", war seit dem Datums-Shard falsch und hätte genau den URL-Pfad fehlgeleitet. Jetzt: unter raw/, bei einem raw fetch mit .html und .md.
  • instructions/wiki-ingest/SKILL.md - eine URL als zweites Beispiel-Trigger.
  • CHANGES.md über version bump --patch (Impact low).
  • Geprüft und unverändert gelassen: docs/pipeline-rationale.md (das Argument "incoming/ hält, was ein Mensch gewählt hat; die Grenze ist die Promotion nach raw/" gilt für raw fetch unverändert, die Begründung des Änderungsinhalts trägt raw/CONTRACT.md), types/source.guidance.md, INSTALL.md, INSTALL-MCP.md, instructions/ingest-queue.md, instructions/mcp-read-server.md. Keine Installationsinstruktion berührt.

Akzeptanzkriterien

  • tools/wikitool raw fetch <url> gegen eine lokale HTML-Testseite schreibt incoming/<stem>.html und incoming/<stem>.md und gibt exit 0; raw/ ist danach unverändert. (test_fetch_writes_html_and_md_to_incoming_and_leaves_raw_untouched)
  • Die .html in incoming/ ist byte-identisch mit dem, was der Server ausgeliefert hat. (test_the_html_is_byte_identical_to_what_the_server_sent, inkl. CRLF und Nicht-ASCII-Bytes)
  • Zwei Läufe gegen dieselben Server-Bytes ergeben byte-identischen abgeleiteten Text (bis auf retrieved:). (test_two_fetches_of_the_same_bytes_derive_identical_text)
  • Eine als ISO-8859-1 ausgelieferte Seite (Charset nur im <meta>) ergibt korrekt dekodierte Umlaute, und der Header nennt charset: "iso-8859-1 (from: meta)" (gequotet, Entscheidung 6). (test_latin1_declared_only_in_meta_is_decoded)
  • Inhalt in nav/footer/aside/script einer Testseite erscheint nicht im abgeleiteten Text; Inhalt in <main> erscheint vollständig. (test_boilerplate_is_dropped_and_main_is_kept_whole)
  • raw accept mit beiden Dateien aus der Erfolgsmeldung legt das Bundle unter raw/<YYYY>/<MM>/<stem>/ an. (test_the_bundle_is_accepted_into_raw_as_one_source)
  • Existiert incoming/<stem>.html oder .md bereits, endet der Befehl mit exit 1 und die vorhandene Datei ist unverändert. (test_an_existing_target_is_never_overwritten, beide Fälle)
  • file:///etc/passwd, eine Antwort über 25 MiB (mit und ohne Content-Length) und ein Timeout enden je mit exit 1 und hinterlassen keine Datei in incoming/. (test_a_file_url_…, test_a_response_over_the_size_limit_…, test_a_slow_server_times_out; zusätzlich Redirect auf ftp: und HTTP 404)
  • Eine application/pdf-Antwort wird byte-identisch als .pdf abgelegt, ohne abgeleitete Datei. (test_a_pdf_is_stored_byte_identical_without_a_derivation)
  • raw fetch --html incoming/<stem>.html --url <url> schreibt nur incoming/<stem>.md, macht keinen Netzzugriff, die .html bleibt byte-identisch, und der abgeleitete Text gleicht dem eines Online-Abrufs derselben Bytes (bis auf den Header). (test_html_derives_only_the_md_without_network_and_matches_a_fetch - Netz per Monkeypatch gesperrt)
  • --html mit einem Pfad außerhalb von incoming/ oder ohne --url endet mit exit 1, ohne etwas zu schreiben. (test_html_outside_incoming_is_refused, test_html_without_url_is_refused)
  • Der MCP-Server bietet kein Fetch-Tool an (Tool-Liste unverändert - gehalten von test_mcp_server.py::test_the_four_tools_are_there_and_nothing_that_writes, das die Menge exakt pinnt).
  • raw/CONTRACT.md dokumentiert Bundle-Regel, Header-Format, --html und Paywall-Regel; wiki-ingest führt eine URL über raw fetch und enthält die Teaser-Prüfung.
  • pytest, docs verify, instructions verify grün; CI grün auf dem publizierten Commit. - Lokal: volle Suite hermetisch (env -i) 2095 passed, 3 skipped; docs verify, instructions verify OK. CI: Runs 502/503 auf fba263a, 504/505 auf c4dcff7, alle success.

Versionsteil

--minor - neues Kommando, Drop-in in beide Richtungen: kein Flag/Kommando entfernt oder umbenannt, keine neue Abhängigkeit (Entscheidung 3), kein maschinell gelesenes Dateiformat geändert, keine Content-Migration; ein Downgrade verliert nur raw fetch. Der laufende Kandidat stand bereits auf MAJOR; der Bump hob ihn auf 8.0.0-beta.25 (fba263a), der Patch-Bump der Abschlussprüfung auf 8.0.0-beta.26 (c4dcff7).

Bezug

  • #169 - externer Abrufbefehl pro Instanz (.wikitool-fetch.json), die Andockstelle für einen Auth-Dienst; baut auf diesem Issue auf, prio/waiting. Andockpunkt ist jetzt tools/chemenu/web_capture.py.
  • #87 - Notizen zur Infrastruktur außerhalb des Stacks (RSS-Poll, Auth-Dienst), wandert später in ein eigenes Repo. Dieses Issue übernimmt von dort nichts außer der Erkenntnis, dass Paywall-Inhalt nicht über das Tool selbst kommt.

Labels

area/kb (Intake in raw/ ist Pipeline/Provenienz), kind/build, prio/planned - bis zu diesem Bau erzeugte jeder URL-Ingest eine ad hoc geformte raw/-Datei, die unveränderlich bleibt. size/M.

> Ausgearbeitet aus einem `status/incoming`-Stub am 2026-10-02 (Stand `8.0.0-beta.24`, `c0f324f`). Der Stub steht wörtlich im ersten Kommentar. > > **Abgeschlossen 2026-10-02.** Gebaut und publiziert in `8.0.0-beta.25`, Commit `fba263a`, verifiziert durch CI-Runs [502](https://gitea.nehmer.net/torben/chemenu/actions/runs/502) und [503](https://gitea.nehmer.net/torben/chemenu/actions/runs/503). Nachzug aus der Abschlussprüfung in `8.0.0-beta.26`, Commit `c4dcff7`, verifiziert durch CI-Runs [504](https://gitea.nehmer.net/torben/chemenu/actions/runs/504) und [505](https://gitea.nehmer.net/torben/chemenu/actions/runs/505). Alle vier Runs grün. ## Befund (gegen den Baum geprüft, Stand vor dem Bau) Für "Nutzer nennt eine URL, die ingested werden soll" gab es weder ein Tool noch eine Prozedur: - `raw accept` (`tools/chemenu/commands/raw_cmd.py`) nimmt nur existierende Dateien unter `incoming/`. - `raw/CONTRACT.md` kannte zwei Eingänge - `incoming/` (lokale Datei, § Getting a file in) und `mcp-upload/` (MCP-`submit`) -, keinen für eine URL. - `instructions/wiki-ingest/SKILL.md` Schritt 1 setzte eine Datei in `incoming/` oder `raw/` voraus; der einzige URL-Bezug war `--set source_url=<upstream URL>` auf der Source-Seite (Schritt 6, `types/source.md`). Eine Session baute sich deshalb jedes Mal ihre eigene Kette (curl, Zeichensatz raten, lynx/pandoc je nach Verfügbarkeit, Boilerplate per Zeilennummer schneiden, Header frei erfinden). Zwei Sessions erzeugten aus demselben Artikel verschiedene `raw/`-Dateien - genau das, was der Intake durch Werkzeug ausschließen soll. Der Harness-eigene Web-Fetch scheidet aus: Er liefert eine LLM-Zusammenfassung, keine Erfassung nahe am Original. ## Entscheidungen (alle umgesetzt) 1. **Ein Tool, keine reine Prozedur.** `wikitool raw fetch <url>` lädt, behandelt den Zeichensatz, leitet Text ab und schreibt nach `incoming/`. Danach läuft `raw accept` unverändert - der Fetch landet **nicht** direkt in `raw/`, damit die Reihenfolge aus `wiki-ingest` (Verpflichtungsfrage vor `raw accept`) erhalten bleibt und `--fidelity`/`--authority` weiter genau dort erfragt werden. 2. **Die Rohquelle ist ein Bundle aus HTML und abgeleitetem Text** (vom Betreiber entschieden 2026-10-02). `raw fetch` schreibt `incoming/<stem>.html` - die Bytes exakt wie empfangen - und `incoming/<stem>.md` (Header + abgeleiteter Text). `raw accept` nimmt beide in einem Aufruf als Bundle (`raw/<YYYY>/<MM>/<stem>/`), beide stehen in `raw_files:`. Begründung: `raw/CONTRACT.md` verlangt "kept exactly as received", und empfangen wurde das HTML; der Text ist eine Ableitung des Tools. Nur mit dem HTML bleibt eine Behauptung in `kb/` byte-genau gegen das Original prüfbar, auch wenn die Ableitung Inhalt verliert oder später verbessert wird. Gelesen und zitiert wird die `.md`. Verworfen: nur den Text ablegen - kleiner, aber was die Ableitung verwirft, wäre unwiederbringlich, und der Contract bräuchte eine Ausnahme von seinem Qualitätsziel. 3. **Nur Standardbibliothek** (`urllib.request`, `html.parser`, `email.message` für den Content-Type). Präzedenz: `version.py` lädt bereits per `urllib`. Begründung: Eine neue Abhängigkeit in `tools/requirements.txt` fällt durch die Vorwärtshälfte des Drop-in-Tests (`instructions/dev/version-parts.md`) - nach dem Kopieren von `tools/` fehlt sie im venv, bis jemand `pip install` ausführt. Die schwächere Boilerplate-Entfernung ist hinnehmbar, weil der abgeleitete Text nicht der Beleg ist (Entscheidung 2). 4. **Zeichensatz, deterministisch in dieser Reihenfolge:** BOM -> `charset` im HTTP-`Content-Type` -> `<meta charset>` / `<meta http-equiv>` in den ersten 4 KiB -> UTF-8. Ein Label, das Python nicht kennt, wird wie ein fehlendes übersprungen. Undekodierbare Bytes werden ersetzt, nicht verworfen, und der Fall wird in der Ausgabe gemeldet. Die verwendete Kodierung steht im Header (Entscheidung 6). Bei `--html` (Entscheidung 14) entfällt die HTTP-Stufe. 5. **Textableitung (HTML -> Markdown-naher Text), deterministisch:** Inhaltswurzel ist `<main>`, sonst genau ein `<article>`, sonst `<body>`. Darunter fallen `script`, `style`, `noscript`, `nav`, `header`, `footer`, `aside`, `form`, `template`, `svg` weg. Überschriften -> `#`, Listen -> `-`/`1.`, `pre`/`code` -> Codeblock/Backticks, Links -> `[text](absolute-url)` (relativ aufgelöst gegen `final_url` bzw. `--url`), Absätze durch Leerzeile. Keine Heuristik über Zeilennummern oder Textdichte. Gleiche Bytes ergeben dieselbe Ausgabe. Im Bau ergänzt, ohne die Regel zu ändern: Tabellen -> Markdown-Tabelle, `blockquote` -> `>`, Bilder -> `![alt](absolute-url)` (`data:`-URIs nur als Alt-Text), Emphasis wird nicht ausgezeichnet. Folge der Regel, bewusst so gelassen: ein `<header>` *innerhalb* von `<main>`/`<article>` (oft mit dem Artikel-`<h1>`) fällt ebenfalls weg - der Titel steht dann nur in `title:`, das HTML behält ihn. 6. **Header-Format** - ein fester Block am Anfang der *abgeleiteten* `.md`-Datei, vom Tool geschrieben, nie von Hand. Die `.html` bleibt unberührt. ``` --- fetched_by: wikitool raw fetch url: <wie angefordert> final_url: <nach Redirects> retrieved: <ISO-8601, UTC> http_status: 200 content_type: text/html; charset=utf-8 charset: "utf-8 (from: header|meta|bom|default)" title: <aus <title>, sonst leer> derived_from: <stem>.html --- ``` **Im Bau geändert:** Werte werden YAML-gequotet, wo YAML sie sonst nicht unverändert zurückliest (JSON-String-Form). Das betrifft immer `charset` (`: ` in `(from: ...)`) und z.B. einen Titel mit `: ` oder ` #`. Grund: Die `---`-Zäune weisen den Block als Frontmatter aus; ungültiges YAML darin bricht jeden Viewer, der ihn liest. Inhaltlich bleibt das Format gleich. Bei `--html` (Entscheidung 14): `fetched_by: wikitool raw fetch --html`, `retrieved`/`final_url`/`http_status`/`content_type` entfallen, dafür `derived: <ISO-8601, UTC>` (Zeitpunkt der Ableitung - wann der Mensch die Seite gespeichert hat, weiß das Tool nicht und behauptet es nicht). Die Felder sind Erfassungsmetadaten, keine Seiten-Frontmatter: `raw/` hat keine Typen (`raw/CONTRACT.md` Abs. 3), nichts im Stack liest diesen Block maschinell. `author:` wird bewusst **nicht** geführt - der Stub nannte ihn, aber aus HTML ist er nicht verlässlich bestimmbar; ein geratener Autor wäre eine Behauptung über die Quelle. Er gehört auf die Source-Seite, wenn die Quelle ihn trägt. 7. **Dateiname:** Stamm aus dem letzten Pfadsegment der URL (ohne Endung), sonst aus dem Hostnamen, sluggifiziert (ASCII, Kleinbuchstaben, `-`), auf 60 Zeichen gekürzt; `--name <stem>` überschreibt (wörtlich übernommen; Pfadtrenner und leer -> exit 1). Existiert in `incoming/` schon `<stem>.html` oder `<stem>.md`, verweigert der Befehl (exit 1) - kein Überschreiben, keine Suffixe; die Dateien werden zusätzlich exklusiv (`x`-Modus) angelegt. Bei `--html` ist der Stamm der der übergebenen Datei, die `.md` entsteht neben ihr. Die Pfadbudget- und Stammkollisionsprüfung gegen `raw/` bleibt `raw accept`s Sache; `raw fetch` dupliziert sie nicht. 8. **Nicht-HTML-Antworten:** `text/plain`, `text/markdown` -> Bytes unverändert als `.txt`/`.md`, ohne Ableitung. Alles andere (PDF, Bilder, ...) -> Bytes unverändert mit Endung aus dem Content-Type (Pythons eingebaute Tabelle, nicht `/etc/mime.types`; unbekannt oder fehlend -> `.bin`), ohne Ableitung; die Ausgabe sagt das. Ein Header entfällt dann (er hätte keine Datei, in die er gehört, ohne die Bytes zu verändern); `url`/`retrieved` stehen in der Erfolgsmeldung und gehen über `source_url` auf die Source-Seite. 9. **Grenzen:** nur `http`/`https`, auch auf jedem Redirect-Schritt (`file:`, `ftp:` u.a. -> exit 1 - ein `file:`-Fetch wäre ein Weg an `incoming/` vorbei). Timeout 30 s für den ganzen Transfer, Größenlimit 25 MiB (Abbruch beim Überschreiten, nichts geschrieben). Eigener `User-Agent` mit Versionsnummer (`chemenu-wikitool/<version> (raw fetch)`). Keine Cookies, kein JavaScript: Eine clientseitig gerenderte Seite ergibt fast leeren Text - unter 200 Zeichen abgeleitetem Text warnt der Befehl ausdrücklich, schreibt aber trotzdem; ob die Erfassung taugt, entscheidet der Mensch. 10. **Erfolgsmeldung** nennt die geschriebenen Dateien, die nächste Zeile und den Paywall-Hinweis: ``` OK Fetched https://example.org/post to incoming/post.html, incoming/post.md Next (after the commitment question in wiki-ingest): tools/wikitool raw accept --fidelity published --authority <value> incoming/post.html incoming/post.md and on the source page: --set source_url=https://example.org/post If the text stops at a teaser (paywall, login, script-rendered page): save the page from a logged-in browser to incoming/ and run tools/wikitool raw fetch --html incoming/<file>.html --url <url> ``` 11. **Invariante 4 bleibt unberührt:** `raw fetch` wird nur auf eine URL angewandt, die der Nutzer selbst nennt - nie auf eine URL, die in einer Rohquelle steht. Das steht in `raw/CONTRACT.md`, in `wiki-ingest` und im NEVER-Teil des Kommando-Records. 12. **Nicht über MCP.** Der MCP-Server bekommt kein Fetch-Tool: Ein Server, der beliebige URLs im Auftrag eines entfernten Aufrufers lädt, ist ein SSRF-Vektor. Der entfernte Weg bleibt `submit` -> `mcp-upload/`. Die Begründung steht in `raw/CONTRACT.md` § mcp-upload. 13. **Kein `--fidelity`/`--authority` bei `raw fetch`.** Die Werte gehören zu `raw accept` (dort sind sie Pflicht); sie zweimal zu erfragen wäre eine zweite Stelle für dieselbe Entscheidung. 14. **Paywall und Login: kein Login im Tool, stattdessen `--html`** (vom Betreiber entschieden 2026-10-02). - `raw fetch --html incoming/<stem>.html --url <url>` leitet aus einer HTML-Datei, die der Mensch aus seinem eingeloggten Browser nach `incoming/` gespeichert hat ("Seite speichern unter", nur HTML), dieselbe `.md` ab (Header siehe Entscheidung 6). Kein Netzzugriff. Die `.html` bleibt byte-unverändert; das Bundle ist dasselbe wie in Entscheidung 2. Deckt Paywall und clientseitig gerenderte Seiten gleichermaßen ab. `--html` außerhalb von `incoming/` -> exit 1 (dieselbe Prüfung wie bei `raw accept`). `--url` ist Pflicht: Sie steht im Header, löst relative Links auf und geht als `source_url` auf die Source-Seite. - **Keine Zugangsdaten, Cookies oder Login-Adapter in `wikitool`.** Das Tool geht an jede Instanz; Zugangsdaten im Arbeitsbaum widersprechen `raw/CONTRACT.md` § What does not belong here, und ein Adapter pro Paywall-Site ist Wartung, die kein Wissenskompiler tragen soll. Ein Auth-Dienst entsteht außerhalb des Stacks (#87); die Andockstelle dafür ist #169, nicht dieses Issue. - **Teaser-Prüfung ist ein Urteil in `wiki-ingest`, nicht im Tool:** Ein Teaser ist allgemein nicht erkennbar und hat oft mehr als die 200 Zeichen aus Entscheidung 9. Schritt 1 liest die Quelle ohnehin vollständig; bricht der Text erkennbar ab ("Weiterlesen mit ...", Abo-Hinweis, Login-Aufforderung), stoppt die Session und schlägt dem Nutzer den `--html`-Weg vor, statt einen Teaser als Quelle zu ingesten. ## Betroffene Dateien Bau (`fba263a`): - `tools/chemenu/web_capture.py` (neu) - Fetch, Zeichensatz, Ableitung, Header, Stamm; Kern ohne `typer`/`rich`, wirft `ValidationError`/`BackendError`, damit #169 dort andocken kann. - `tools/chemenu/commands/raw_cmd.py` - neues Kommando `raw fetch` samt Kommando-Record (Synopsis für beide Formen, `network: yes`, Exit-Status, ON FAILURE, NEVER). - `tools/chemenu/cli_contract.py` - `raw fetch` in `GROUPS` ("Raw material and uploads"); ohne den Eintrag rendert `docs contract` das Kommando nicht. - `tools/CONTRACT.md` - aus dem Record generiert (nicht von Hand). - `tools/chemenu/tests/test_raw_fetch.py` (neu, 36 Tests) - gegen einen lokalen `http.server` (Präzedenz: `test_dist_upgrade.py`), kein echtes Netz. - `tools/chemenu/tests/test_cli.py` - die gepinnte `network: yes`-Menge um `raw fetch` ergänzt. - `tools/chemenu/tests/test_portability.py` - `opener` (urllibs `OpenerDirector`) in `NOT_TEXT_OPEN`: Der AST-Scan las `opener.open(request, ...)` als Text-Datei-Open ohne `encoding=`. - `raw/CONTRACT.md` - neuer Abschnitt "Getting a URL in: `raw fetch`" zwischen `incoming/` und `mcp-upload/`: Bundle-Regel, Header-Format, `--html` und Paywall-Regel, Invariante-4-Satz; im `mcp-upload/`-Abschnitt der SSRF-Satz (Entscheidung 12). - `instructions/wiki-ingest/SKILL.md` - `description`/Trigger um "ingest <url>"; Schritt 1: URL -> `raw fetch`, Teaser-Prüfung mit `--html`; Kommandoliste. - `tools/README.md` (Modulliste: `web_capture.py`), `README.md` (§ Adding Knowledge: URL-Ingest und `--html`). - `CHANGES.md` über `version bump` (Changeset "raw fetch: a sanctioned intake for a URL into incoming/ (#120)", Impact medium). Abschlussprüfung (`c4dcff7`): - `kb/CONTRACT.md` § Provenance: Der Satz, die lokale Kopie eines externen Artikels liege "under `raw/articles/`", war seit dem Datums-Shard falsch und hätte genau den URL-Pfad fehlgeleitet. Jetzt: unter `raw/`, bei einem `raw fetch` mit `.html` und `.md`. - `instructions/wiki-ingest/SKILL.md` - eine URL als zweites Beispiel-Trigger. - `CHANGES.md` über `version bump --patch` (Impact low). - Geprüft und unverändert gelassen: `docs/pipeline-rationale.md` (das Argument "`incoming/` hält, was ein Mensch gewählt hat; die Grenze ist die Promotion nach `raw/`" gilt für `raw fetch` unverändert, die Begründung des Änderungsinhalts trägt `raw/CONTRACT.md`), `types/source.guidance.md`, `INSTALL.md`, `INSTALL-MCP.md`, `instructions/ingest-queue.md`, `instructions/mcp-read-server.md`. Keine Installationsinstruktion berührt. ## Akzeptanzkriterien - [x] `tools/wikitool raw fetch <url>` gegen eine lokale HTML-Testseite schreibt `incoming/<stem>.html` und `incoming/<stem>.md` und gibt exit 0; `raw/` ist danach unverändert. (`test_fetch_writes_html_and_md_to_incoming_and_leaves_raw_untouched`) - [x] Die `.html` in `incoming/` ist byte-identisch mit dem, was der Server ausgeliefert hat. (`test_the_html_is_byte_identical_to_what_the_server_sent`, inkl. CRLF und Nicht-ASCII-Bytes) - [x] Zwei Läufe gegen dieselben Server-Bytes ergeben byte-identischen abgeleiteten Text (bis auf `retrieved:`). (`test_two_fetches_of_the_same_bytes_derive_identical_text`) - [x] Eine als ISO-8859-1 ausgelieferte Seite (Charset nur im `<meta>`) ergibt korrekt dekodierte Umlaute, und der Header nennt `charset: "iso-8859-1 (from: meta)"` (gequotet, Entscheidung 6). (`test_latin1_declared_only_in_meta_is_decoded`) - [x] Inhalt in `nav`/`footer`/`aside`/`script` einer Testseite erscheint nicht im abgeleiteten Text; Inhalt in `<main>` erscheint vollständig. (`test_boilerplate_is_dropped_and_main_is_kept_whole`) - [x] `raw accept` mit beiden Dateien aus der Erfolgsmeldung legt das Bundle unter `raw/<YYYY>/<MM>/<stem>/` an. (`test_the_bundle_is_accepted_into_raw_as_one_source`) - [x] Existiert `incoming/<stem>.html` oder `.md` bereits, endet der Befehl mit exit 1 und die vorhandene Datei ist unverändert. (`test_an_existing_target_is_never_overwritten`, beide Fälle) - [x] `file:///etc/passwd`, eine Antwort über 25 MiB (mit und ohne `Content-Length`) und ein Timeout enden je mit exit 1 und hinterlassen keine Datei in `incoming/`. (`test_a_file_url_…`, `test_a_response_over_the_size_limit_…`, `test_a_slow_server_times_out`; zusätzlich Redirect auf `ftp:` und HTTP 404) - [x] Eine `application/pdf`-Antwort wird byte-identisch als `.pdf` abgelegt, ohne abgeleitete Datei. (`test_a_pdf_is_stored_byte_identical_without_a_derivation`) - [x] `raw fetch --html incoming/<stem>.html --url <url>` schreibt nur `incoming/<stem>.md`, macht keinen Netzzugriff, die `.html` bleibt byte-identisch, und der abgeleitete Text gleicht dem eines Online-Abrufs derselben Bytes (bis auf den Header). (`test_html_derives_only_the_md_without_network_and_matches_a_fetch` - Netz per Monkeypatch gesperrt) - [x] `--html` mit einem Pfad außerhalb von `incoming/` oder ohne `--url` endet mit exit 1, ohne etwas zu schreiben. (`test_html_outside_incoming_is_refused`, `test_html_without_url_is_refused`) - [x] Der MCP-Server bietet kein Fetch-Tool an (Tool-Liste unverändert - gehalten von `test_mcp_server.py::test_the_four_tools_are_there_and_nothing_that_writes`, das die Menge exakt pinnt). - [x] `raw/CONTRACT.md` dokumentiert Bundle-Regel, Header-Format, `--html` und Paywall-Regel; `wiki-ingest` führt eine URL über `raw fetch` und enthält die Teaser-Prüfung. - [x] `pytest`, `docs verify`, `instructions verify` grün; CI grün auf dem publizierten Commit. - Lokal: volle Suite hermetisch (`env -i`) 2095 passed, 3 skipped; `docs verify`, `instructions verify` OK. CI: Runs 502/503 auf `fba263a`, 504/505 auf `c4dcff7`, alle `success`. ## Versionsteil **`--minor`** - neues Kommando, Drop-in in beide Richtungen: kein Flag/Kommando entfernt oder umbenannt, keine neue Abhängigkeit (Entscheidung 3), kein maschinell gelesenes Dateiformat geändert, keine Content-Migration; ein Downgrade verliert nur `raw fetch`. Der laufende Kandidat stand bereits auf MAJOR; der Bump hob ihn auf **`8.0.0-beta.25`** (`fba263a`), der Patch-Bump der Abschlussprüfung auf **`8.0.0-beta.26`** (`c4dcff7`). ## Bezug - #169 - externer Abrufbefehl pro Instanz (`.wikitool-fetch.json`), die Andockstelle für einen Auth-Dienst; baut auf diesem Issue auf, `prio/waiting`. Andockpunkt ist jetzt `tools/chemenu/web_capture.py`. - #87 - Notizen zur Infrastruktur außerhalb des Stacks (RSS-Poll, Auth-Dienst), wandert später in ein eigenes Repo. Dieses Issue übernimmt von dort nichts außer der Erkenntnis, dass Paywall-Inhalt nicht über das Tool selbst kommt. ## Labels `area/kb` (Intake in `raw/` ist Pipeline/Provenienz), `kind/build`, `prio/planned` - bis zu diesem Bau erzeugte jeder URL-Ingest eine ad hoc geformte `raw/`-Datei, die unveränderlich bleibt. `size/M`.
torben added the area/workflowkind/decisionstatus/incoming labels 2026-09-18 21:38:48 +00:00
Author
Owner

Changelog: Stub ausgearbeitet und gegen den Baum (Stand 8.0.0-beta.24, c0f324f) geprüft. Befund bestätigt: Es gibt weiterhin keinen URL-Intake - raw accept nimmt nur Dateien unter incoming/, wiki-ingest setzt eine Datei voraus, nur source_url auf der Source-Seite existiert. Body neu geschrieben als Spec für wikitool raw fetch <url> (Variante "Bevorzugt" des Stubs); eine Frage offen (was als Rohquelle in raw/ landet), daher kind/decision. area/workflow -> area/kb (Intake in raw/ ist Pipeline/Provenienz, nicht Git/Publish). Labels prio/planned, size/M gesetzt, status/incoming entfernt.

Der ursprüngliche Stub, wörtlich:

Befund

raw/CONTRACT.md Abschnitt "Getting a file in: incoming/" beschreibt ausschliesslich den
Fall, dass ein Mensch bereits eine lokale Datei in incoming/ ablegt; tools/wikitool raw accept verlangt dementsprechend einen existierenden lokalen Dateipfad. Die wiki-ingest-Skill
setzt in Schritt 1 ebenfalls voraus, dass die Datei bereits in incoming/ oder raw/ liegt.
Fuer den Fall "Nutzer nennt eine URL, die ingested werden soll" gibt es weder ein Tool noch eine
dokumentierte Prozedur.

Ursache

Die Pipeline ist architektonisch auf Datei-Intake ausgelegt (incoming/ -> raw accept); ein
URL-Intake wurde offenbar nie mitgedacht. Es existiert kein wikitool raw fetch <url> o.ae.,
das eine URL laedt, Zeichensatz behandelt und das Ergebnis in ein raw/-taugliches Format
bringt.

Reproduktion

Bei einer Anfrage "Ingest " hat die Session keinen sanktionierten Tool-Call zur Verfuegung
und musste sich selbst eine Prozedur zusammenbauen:

  1. curl -sL -A "..." "<url>" -o page.html
  2. Zeichensatz pruefen (grep -oi charset= im HTML) statt ihn blind anzunehmen
  3. HTML->Text-Konvertierung mit lynx -dump -nolist -assume_charset=UTF-8 -display_charset=UTF-8 page.html (verfuegbares Tool im Container; pandoc, w3m, Python
    bs4/readability waren nicht installiert)
  4. Navigation/Footer/Kommentare/Tag-Wolke manuell anhand von Zeilennummern (grep -n, sed -n)
    heraustrennen
  5. Von Hand eine raw/-Datei mit einem Source:/Captured:-Header schreiben, in Anlehnung an
    ein vorhandenes Beispiel (raw/CONTRACT.md selbst schreibt kein Format fuer diesen Header
    vor)
  6. Erst danach tools/wikitool raw accept --fidelity published --authority <wert> incoming/<datei> - ab hier greift der normale, dokumentierte Ablauf wieder

Jeder dieser sechs Schritte ist eine Ad-hoc-Entscheidung der Session (Tool-Wahl, Trennheuristik,
Header-Format), nicht das Ergebnis einer dokumentierten Vorgabe. Zwei Sessions, die denselben
Artikel ingesten, wuerden voraussichtlich unterschiedliche raw/-Dateien erzeugen.

Loesungsvorschlag

  • Bevorzugt: ein wikitool raw fetch <url> (oder aequivalent), das Fetch + Zeichensatz-
    Handling + HTML->Text/Markdown-Konvertierung buendelt und das Ergebnis nach incoming/
    schreibt - danach greift raw accept unveraendert. Damit bleibt "eine URL ist am Ende eine
    Datei" gewahrt, ohne dass jede Session ihre eigene Toolchain waehlt.
  • Minimal: falls kein neues Tool gebaut wird, zumindest eine dokumentierte Prozedur (Tool-
    Wahl, Header-Format Source:/Captured:/Author:) in raw/CONTRACT.md oder einer neuen
    instructions/ingest-url.md, damit der Ad-hoc-Teil wenigstens konvergiert.

Verworfen: den eingebauten Web-Fetch-Tool-Call der Harness direkt als Quelle zu nutzen - der
gibt die Seite nur als LLM-Zusammenfassung zurueck, nicht als Text nahe am Original. Das
widerspricht dem Qualitaetsziel von raw/CONTRACT.md ("kept exactly as received") fuer eine
published-Fidelity-Erfassung.

Akzeptanzkriterien

  • Eine URL-Quelle laesst sich ueber einen einzigen dokumentierten/getoolten Schritt
    ingesten, ohne Ad-hoc-Shell-Bastelei pro Session.
  • Das Header-Format der erzeugten raw/-Datei (Quelle, Erfassungsdatum) ist standardisiert
    und in raw/CONTRACT.md oder einer verlinkten Instruction dokumentiert.
  • docs verify / instructions verify clean.

Herkunft

Beobachtet in einer privaten Instanz beim Ingest eines oeffentlichen Web-Artikels auf
ausdrueckliche URL-Anfrage des Nutzers. Kein Bezug zu Instanzinhalten - reiner
Workflow-/Tooling-Befund der Pipeline selbst.

**Changelog:** Stub ausgearbeitet und gegen den Baum (Stand `8.0.0-beta.24`, `c0f324f`) geprüft. Befund bestätigt: Es gibt weiterhin keinen URL-Intake - `raw accept` nimmt nur Dateien unter `incoming/`, `wiki-ingest` setzt eine Datei voraus, nur `source_url` auf der Source-Seite existiert. Body neu geschrieben als Spec für `wikitool raw fetch <url>` (Variante "Bevorzugt" des Stubs); eine Frage offen (was als Rohquelle in `raw/` landet), daher `kind/decision`. `area/workflow` -> `area/kb` (Intake in `raw/` ist Pipeline/Provenienz, nicht Git/Publish). Labels `prio/planned`, `size/M` gesetzt, `status/incoming` entfernt. Der ursprüngliche Stub, wörtlich: > ## Befund > > `raw/CONTRACT.md` Abschnitt "Getting a file in: incoming/" beschreibt ausschliesslich den > Fall, dass ein Mensch bereits eine lokale Datei in `incoming/` ablegt; `tools/wikitool raw > accept` verlangt dementsprechend einen existierenden lokalen Dateipfad. Die `wiki-ingest`-Skill > setzt in Schritt 1 ebenfalls voraus, dass die Datei bereits in `incoming/` oder `raw/` liegt. > Fuer den Fall "Nutzer nennt eine URL, die ingested werden soll" gibt es weder ein Tool noch eine > dokumentierte Prozedur. > > ## Ursache > > Die Pipeline ist architektonisch auf Datei-Intake ausgelegt (`incoming/` -> `raw accept`); ein > URL-Intake wurde offenbar nie mitgedacht. Es existiert kein `wikitool raw fetch <url>` o.ae., > das eine URL laedt, Zeichensatz behandelt und das Ergebnis in ein `raw/`-taugliches Format > bringt. > > ## Reproduktion > > Bei einer Anfrage "Ingest <URL>" hat die Session keinen sanktionierten Tool-Call zur Verfuegung > und musste sich selbst eine Prozedur zusammenbauen: > > 1. `curl -sL -A "..." "<url>" -o page.html` > 2. Zeichensatz pruefen (`grep -oi charset=` im HTML) statt ihn blind anzunehmen > 3. HTML->Text-Konvertierung mit `lynx -dump -nolist -assume_charset=UTF-8 > -display_charset=UTF-8 page.html` (verfuegbares Tool im Container; `pandoc`, `w3m`, Python > `bs4`/`readability` waren nicht installiert) > 4. Navigation/Footer/Kommentare/Tag-Wolke manuell anhand von Zeilennummern (`grep -n`, `sed -n`) > heraustrennen > 5. Von Hand eine `raw/`-Datei mit einem `Source:`/`Captured:`-Header schreiben, in Anlehnung an > ein vorhandenes Beispiel (`raw/CONTRACT.md` selbst schreibt kein Format fuer diesen Header > vor) > 6. Erst danach `tools/wikitool raw accept --fidelity published --authority <wert> > incoming/<datei>` - ab hier greift der normale, dokumentierte Ablauf wieder > > Jeder dieser sechs Schritte ist eine Ad-hoc-Entscheidung der Session (Tool-Wahl, Trennheuristik, > Header-Format), nicht das Ergebnis einer dokumentierten Vorgabe. Zwei Sessions, die denselben > Artikel ingesten, wuerden voraussichtlich unterschiedliche `raw/`-Dateien erzeugen. > > ## Loesungsvorschlag > > - **Bevorzugt:** ein `wikitool raw fetch <url>` (oder aequivalent), das Fetch + Zeichensatz- > Handling + HTML->Text/Markdown-Konvertierung buendelt und das Ergebnis nach `incoming/` > schreibt - danach greift `raw accept` unveraendert. Damit bleibt "eine URL ist am Ende eine > Datei" gewahrt, ohne dass jede Session ihre eigene Toolchain waehlt. > - **Minimal:** falls kein neues Tool gebaut wird, zumindest eine dokumentierte Prozedur (Tool- > Wahl, Header-Format `Source:`/`Captured:`/`Author:`) in `raw/CONTRACT.md` oder einer neuen > `instructions/ingest-url.md`, damit der Ad-hoc-Teil wenigstens konvergiert. > > Verworfen: den eingebauten Web-Fetch-Tool-Call der Harness direkt als Quelle zu nutzen - der > gibt die Seite nur als LLM-Zusammenfassung zurueck, nicht als Text nahe am Original. Das > widerspricht dem Qualitaetsziel von `raw/CONTRACT.md` ("kept exactly as received") fuer eine > `published`-Fidelity-Erfassung. > > ## Akzeptanzkriterien > > - [ ] Eine URL-Quelle laesst sich ueber einen einzigen dokumentierten/getoolten Schritt > ingesten, ohne Ad-hoc-Shell-Bastelei pro Session. > - [ ] Das Header-Format der erzeugten `raw/`-Datei (Quelle, Erfassungsdatum) ist standardisiert > und in `raw/CONTRACT.md` oder einer verlinkten Instruction dokumentiert. > - [ ] `docs verify` / `instructions verify` clean. > > ## Herkunft > > Beobachtet in einer privaten Instanz beim Ingest eines oeffentlichen Web-Artikels auf > ausdrueckliche URL-Anfrage des Nutzers. Kein Bezug zu Instanzinhalten - reiner > Workflow-/Tooling-Befund der Pipeline selbst.
torben changed title from Kein sanktionierter Weg fuer Ingest per URL - Ad-hoc-Scraping noetig to raw fetch: sanktionierter Intake für eine URL nach incoming/ 2026-10-02 19:28:07 +00:00
torben added prio/plannedsize/Marea/kb and removed area/workflowstatus/incoming labels 2026-10-02 19:28:08 +00:00
torben added kind/build and removed kind/decision labels 2026-10-02 20:02:38 +00:00
Author
Owner

Changelog: Beide offenen Fragen vom Betreiber entschieden. Entscheidung 2 neu: Rohquelle ist das Bundle HTML + abgeleiteter Text (Variante A; B "nur Text" verworfen). Entscheidung 14 neu: Paywall/Login über raw fetch --html aus einer im eingeloggten Browser gespeicherten Seite, kein Login und keine Credentials im Tool, Teaser-Prüfung als Urteil in wiki-ingest. Header, Dateiname, Erfolgsmeldung und Kriterien entsprechend ergänzt. Andockstelle für einen externen Auth-Dienst als #169 ausgelagert (prio/waiting); #87 bleibt Notizzettel für die Infrastruktur außerhalb des Stacks. Titel umbenannt. kind/decision -> kind/build.

**Changelog:** Beide offenen Fragen vom Betreiber entschieden. Entscheidung 2 neu: Rohquelle ist das Bundle HTML + abgeleiteter Text (Variante A; B "nur Text" verworfen). Entscheidung 14 neu: Paywall/Login über `raw fetch --html` aus einer im eingeloggten Browser gespeicherten Seite, kein Login und keine Credentials im Tool, Teaser-Prüfung als Urteil in `wiki-ingest`. Header, Dateiname, Erfolgsmeldung und Kriterien entsprechend ergänzt. Andockstelle für einen externen Auth-Dienst als #169 ausgelagert (`prio/waiting`); #87 bleibt Notizzettel für die Infrastruktur außerhalb des Stacks. Titel umbenannt. `kind/decision` -> `kind/build`.
Author
Owner

Changelog: Gebaut und publiziert (stack-build): 8.0.0-beta.25, Commit fba263a, CI-Runs 502/503 grün; alle Akzeptanzkriterien abgehakt, jeweils mit dem Test, der sie hält. Im Bau korrigiert: Header-Werte werden YAML-gequotet, wo nötig (charset: "utf-8 (from: header)"; Entscheidung 6 und Kriterium 4 angepasst). Entscheidung 5 nennt jetzt die Ergänzungen (Tabellen, Zitate, Bilder) und die bewusst gelassene Folge, dass ein <header> innerhalb von <main> mit wegfällt. Entscheidungen 7-9 präzisiert (--name wörtlich, exklusives Anlegen, Endungstabelle, Redirect-Schritte, Gesamt-Timeout, User-Agent). Betroffene Dateien um cli_contract.py (GROUPS), test_cli.py (Netzwerk-Pin), test_portability.py (opener) und README.md ergänzt.

**Changelog:** Gebaut und publiziert (`stack-build`): `8.0.0-beta.25`, Commit `fba263a`, CI-Runs 502/503 grün; alle Akzeptanzkriterien abgehakt, jeweils mit dem Test, der sie hält. Im Bau korrigiert: Header-Werte werden YAML-gequotet, wo nötig (`charset: "utf-8 (from: header)"`; Entscheidung 6 und Kriterium 4 angepasst). Entscheidung 5 nennt jetzt die Ergänzungen (Tabellen, Zitate, Bilder) und die bewusst gelassene Folge, dass ein `<header>` innerhalb von `<main>` mit wegfällt. Entscheidungen 7-9 präzisiert (`--name` wörtlich, exklusives Anlegen, Endungstabelle, Redirect-Schritte, Gesamt-Timeout, User-Agent). Betroffene Dateien um `cli_contract.py` (`GROUPS`), `test_cli.py` (Netzwerk-Pin), `test_portability.py` (`opener`) und `README.md` ergänzt.
Author
Owner

Changelog: Abgeschlossen (stack-close). Body auf Endstand: Kopf nennt beide Commits und alle vier grünen CI-Runs, Befund in der Vergangenheit, Entscheidungen als umgesetzt markiert. Die Abschlussprüfung fand einen veralteten Satz in kb/CONTRACT.md (lokale Kopie eines externen Artikels "under raw/articles/") und korrigierte ihn mit einem eigenen Patch-Bump: 8.0.0-beta.26, c4dcff7, CI 504/505 grün. Dazu kam ein URL-Beispiel-Trigger in wiki-ingest. Geprüft und unverändert: docs/pipeline-rationale.md und die übrigen Intake-Dokumente (Liste im Body).

Übergabe:

Phase Modell Effort Eigene Session? Kontext übergelaufen oder kompaktiert?
1 Design (stack-dev) unbekannt (nicht protokolliert) unbekannt ja - vor dem /clear, mit dem der Bau begann unbekannt
2 Bau (stack-build) Claude Opus 5.5 unbekannt - für die Session nicht sichtbar ja (nach /clear, kalt vom Body gestartet) nein
3 Abschluss (stack-close) Claude Opus 5.5 unbekannt - für die Session nicht sichtbar nein - in der Bau-Session fortgesetzt nein

size/M.

**Changelog:** Abgeschlossen (`stack-close`). Body auf Endstand: Kopf nennt beide Commits und alle vier grünen CI-Runs, Befund in der Vergangenheit, Entscheidungen als umgesetzt markiert. Die Abschlussprüfung fand einen veralteten Satz in `kb/CONTRACT.md` (lokale Kopie eines externen Artikels "under `raw/articles/`") und korrigierte ihn mit einem eigenen Patch-Bump: `8.0.0-beta.26`, `c4dcff7`, CI 504/505 grün. Dazu kam ein URL-Beispiel-Trigger in `wiki-ingest`. Geprüft und unverändert: `docs/pipeline-rationale.md` und die übrigen Intake-Dokumente (Liste im Body). **Übergabe:** | Phase | Modell | Effort | Eigene Session? | Kontext übergelaufen oder kompaktiert? | |---|---|---|---|---| | 1 Design (`stack-dev`) | unbekannt (nicht protokolliert) | unbekannt | ja - vor dem `/clear`, mit dem der Bau begann | unbekannt | | 2 Bau (`stack-build`) | Claude Opus 5.5 | unbekannt - für die Session nicht sichtbar | ja (nach `/clear`, kalt vom Body gestartet) | nein | | 3 Abschluss (`stack-close`) | Claude Opus 5.5 | unbekannt - für die Session nicht sichtbar | nein - in der Bau-Session fortgesetzt | nein | `size/M`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#120