Pipeline: media wedge, unhealable units, premature deletion - #17
Open
leeovery wants to merge 6 commits into
Open
Pipeline: media wedge, unhealable units, premature deletion#17leeovery wants to merge 6 commits into
leeovery wants to merge 6 commits into
Conversation
A page-relative og:image reached the media stage verbatim, where the transport refuses it as a ValueError — outside the connection-failure lifecycle. The inline download sat outside the drain's single broad except, so the failure superseded the PARENT's done line and the next run died unhandled, permanently. Three layers, all of them: - the web driver absolutizes og:image against the page, unescapes entities, and emits nothing that is not http(s); its regex stops at a line break so a wrapped content attribute cannot yield a multi-line URL; - the media stage validates every URL before the birth line — anything unfetchable parks manual as its own media unit — and runs the download through _process, so any exception class is charged to the media unit; - the via:media dispatch moves inside the one broad except, restoring the exactly-one-except contract. The media slot comes from the unit's position among the item's media units in ledger order rather than a disk scan: the crash window between the file write and the outcome line now overwrites media-0 instead of writing media-1 beside an orphan. Also: LedgerEntry rejects a multi-line url (the item-status surface renders it verbatim), and seeding's media read — the one unguarded read left — notes an unreadable file instead of dying. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three heals the pipeline could not perform: - `mark` canonicalized before looking a unit up, while bad seeds and every via:media line are keyed on the URL verbatim — the two hashes could never meet, so 100% of that class was unhealable and the contract forbids hand-editing the ledger. It now resolves canonically first, then by the exact stored key, and the healed line keeps the entry's own hash/url. - A redetection unlinked the old kind's output at correction time. When the corrected fetch then parked, the item's enrichment directory was empty, its status fell back to raw and its digest was orphaned with nothing left to re-derive from. The stale file now leaves on the success that replaces it. - `_refresh_item_frontmatter` missed UnicodeDecodeError (a ValueError, not an OSError), so one non-UTF-8 corpus item made mark and pass exit 1 after their write had already landed — and the retry duplicated it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five surfaces that swallowed what the owner needed to see: - a cross-item URL raised out of `enrich fetch` mid-batch, aborting URLs already ledgered and losing the report with them; it is reported now, naming the owning item, and the batch continues; - a capped fetch refusal rendered as a bare `skipped 1`, leaving the --force route unreachable — it appends a report note carrying the stored reason, and a repeat refusal no longer appends a byte-identical line; - `_load_env` sat one line outside enrich.main's try, so an unreadable .env (non-UTF-8, or a directory) came back as a raw traceback; it also kept surrounding quotes on values, and `KEY="sk-…"` 401s every call with a baffling waiting reason; - the enrichment `url:` line bypassed `_yaml_value`, so a work key like `file:media/plan: v2.pdf` made the whole frontmatter unparseable; - `enrich item new` silently turned a capture still carrying `asset:` / `name:` frontmatter into a text item, losing the binary's provenance at exit 0. It refuses loudly and points at `dex inbox`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A redetection's stale output left the disk by pattern: every `*-<hash6>.md` in the item directory that was not the file just written. `hash6` is six hex digits, so two units under one item collide often enough that a real pair exists (`web-6968e3.md` beside `file-6968e3.md`) — and whichever landed second deleted the other's enrichment, leaving a `done` ledger line pointing at nothing. Candidates are now the closed `<kind>-<hash6>.md` set, and each must prove it belongs to this unit by the `url:` it records before it is unlinked. Nothing is dropped for a replacement that is not itself on disk. The drop also runs on the mark route. The prescribed recovery for a web→file correction whose corrected fetch parks — a scanned PDF, no extractor — is a hand-written enrichment closed with `enrich mark <url> done --path …`, and that output is one of the unit's own. Without the drop the item kept two files for one unit, both listed in frontmatter, and served the stale pre-correction view to the digest and query layers permanently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The media slot became the unit's position among the item's media units, which fixed the crash-window duplicate — and silently made that position decide the cap too. A unit's parked, dead and skipped siblings then spent cap they had put no file on disk for. An X thread pooling six photos whose first two 404 ended as `p2 done, p3 done, p4 skipped "media cap (4 files) reached", p5 skipped` with two files in the item: `skipped` is terminal, so p4 and p5 were lost forever, under a reason that was false. The position still names the file — reruns overwrite, never duplicate — but the cap decision counts media-family files that exist, as `_media_file_count` does and as the docstring already claimed. An index past the cap is ordinary: that thread now lands its four survivors at `media-2` through `media-5`. The "this slot already holds my own file" shortcut returned before the cap check on a glob that matched `media-<n>.md` — the session's written description of a media capture, which `_media_file_count` deliberately excludes. One `_is_media_file` predicate now answers both questions, so an item can no longer end with five media-family files and a downloaded `media-0.png` beside a `media-0.md` describing something else. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The capture stopped at the line break, so a wrapped `content=` yielded the truncated head of the URL — `https://cdn.example.test/` — which is a perfectly well-formed request for a resource that does not exist. That became a real media unit and a guaranteed junk fetch, and the test pinned it as the intended outcome rather than a defensible one. The value must now run from the opening quote to the closing one: a URL this driver cannot read whole is not a URL it hands on. The page still enriches; it just carries no media. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
leeovery
marked this pull request as ready for review
August 22, 2026 14:14
leeovery
force-pushed
the
fix2/pipeline
branch
from
August 22, 2026 14:14
1bbfcf1 to
1c4f123
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Eleven confirmed pipeline defects from the sweep, including two blockers.
og:imagebrickedenrich runpermanently. The media redrain sat outside the pipeline's single broad except, so an unfetchable media URL charged its failure to the parent (superseding a gooddoneand filing a spurious issue), and every subsequent run died before doing any work. Media URLs are now absolutised and validated before they can enter the queue, and the dispatch runs inside the same protection as everything else.markcould not heal any raw-keyed unit. Bad seeds and everyvia: medialine are keyed on the URL verbatim, whilemarkcanonicalised first, so the hashes could never meet and the contract forbids hand-editing.marknow resolves canonical-first, then by the exact stored key.fetchbatch mid-way; it now reports and continues.skipped, hiding the--forceaffordance the design says the owner is owed..envwas read outside the CLI's error wrapper (raw traceback on an unreadable file) and kept literal quotes around values.url:line bypassed YAML quoting, so a work key containing": "made the whole frontmatter unparseable._refresh_item_frontmattermissedUnicodeDecodeError, somark/passexited 1 after their write had landed.LedgerEntryrejects multi-line URLs, and a capture still carrying a staged asset is refused loudly instead of silently losing the binary reference.Gates: 1082 tests, ruff and ty clean.
🤖 Generated with Claude Code