Detect every judgment step a session can skip - #30
Merged
Conversation
Pass records exist so "ran and promoted nothing" is distinguishable from "never ran", but only one side had a reader: _stale_passes flags records whose rules version is old, and an item with NO harvest record was never examined — a session that skipped harvest left an item digested and cited on the strength of the shared link alone, on no surface. lint now lists "harvest these (enrichment landed, no harvest pass on record)": a live item that owes no further work, has at least one fetched page on record — a done unit that is not via media/extract-asset, the URL cap's own reading of "page" — and no harvest pass under any rules version. The bounds follow the ingest procedure's: an item still owing a unit is raw and has not reached the harvest step; a no-source capture and an item whose every unit died have no page to read the subject rule over and owe description + digest instead, which the run report already names. A pass covers its item by trailing shortid — the match exclusions already use across renames — so a renamed item's standing record never reads as a skipped judgment, and a migration reseed keeps answering to the rules-version check, not this one. The finding fires beside the digest backstop rather than deferring to it: that backstop's repair is digest → place → wiki, which never runs harvest, so deferring would carry the skipped judgment straight through the repair. Like the stale-pass row it never exits 1 — no later mechanical stage breaks on a missing pass the way the wiki layer breaks on a malformed digest, and the repair is judgment, the report's business. Lives beside digest_orphans in run.py, on the same ownership seam; torn pass lines stay lint's loud parse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dex-run step 8 backstop told the session to complete an interrupted item's "digest → place → wiki steps now" — a repair that skips harvest, the step that comes before digest in the per-item procedure. An interruption can predate harvest just as easily as digest, and the `enrich status` surface the step reads has no never-harvested row, so a harvest skipped by this path stayed invisible until the next health check flagged it (the lint check this branch already carries). The lint catch is the backstop's backstop; the instruction must stop causing the state it catches. The step now sends the session through the full remaining per-item procedure — harvest → digest → place → wiki, per references/ingest-item.md — and says why: the listing states that a digest is owed, never that harvest ran. The dex-lint fix-list's own "finish digest → place → wiki" line is left as it stands: the lint report lists a skipped harvest as its own "harvest these" row beside the digest row, so a lint session covers harvest through that row rather than through this one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dex-query told the owner that syntheses are refreshed by the health check as new material lands. Nothing does that: lint's staleness check reads topic pages against their taxonomy members, and a synthesis has no topic, so it is never compared against anything. Nor will such a check be built — a synthesis is question-shaped and dated on purpose, and a mechanical check cannot tell one gone stale from one deliberately scoped to its date; that distinction is judgment. The skill now says what is true: refreshing happens in the query path itself, when a session reads a synthesis whose generated: date predates material that changes its answer, and rewrites it. The design doc makes no synthesis-refresh claim, so it needed no correction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Writing a digest and recording its pass were two commands, and only the first had a product the session could see. A forgotten `enrich pass --stage digest` cost the staleness backstop its comparand silently: a digest with no pass record is dated by nothing, so no later enrichment ever reads as newer than it, and the item escapes "Digest these" forever. One motion, one verb: `enrich item digest` now records the pass itself, through the same record_pass path the CLI's pass verb calls — one pass-writing path, not two. Ordering is chosen by which crash residue has a reader. The record goes in after validation and before the file write: a refused payload records nothing, and a crash between the two writes leaves a pass with no digest file, which digest_orphans' no-digest branch lists loudly. The opposite order leaves a digest with no pass — the very unreadable state this change removes — so the record goes first. The confirmation line says the pass was recorded, so a session never doubts it into a duplicate `enrich pass`. The skills drop the separate digest pass step (the ingest procedure's §6 now says the verb records it; dex-run's state-writes rule and the contract say the same), while `enrich pass --stage digest` stays as the manual re-record and the harvest/wiki recorder. Design §4/§10/§14 updated to the same account. Updated assertions: test_writes_the_canonical_digest, test_a_rewrite_says_so_and_replaces_ every_field and test_item_digest_writes_the_digest now expect the "digest pass recorded" confirmation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
state-formats.md states the invariant: every corpus item appears in at least one topic's items or in uncategorized-shares. lint only ever read one face of it — orphans fires on an item that is uncited AND unledgered — so an item a page cites that no topic records was invisible on every surface: a citation is not a placement, and nothing asked the placement question about the cited. The check shares the derivations the orphan logic already computes: the cited set is the wiki scan's, the uncategorized set stays the orphan check's own, and the new placed set is the union of every topic's items (uncategorized-shares included, being a topic in the same dict), read with the same tolerance for hand-mangled shapes. It renders beside the orphan row in the wiki section, capped and elided like every listing, and like orphans it is a finding, never exit 1 — the repair is judgment. The dex-lint fix-list names it: append the id to each matching topic's items, or ledger it into uncategorized-shares. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A media unit parked blocked/waiting/error rests untouched while
media_fetch is `none` — the drain defers every media job before spending
anything — yet the standing view and run report filed it under "Waiting
on the engine — N entries it retries by itself", with "attempt n of m"
promising retries that are switched off. What actually unblocks it is
the owner flipping the config.
The classification is a rendering fact, not a ledger one, so no status
changes: the row builder — _parked_row, the one place both surfaces'
rows are made and the one with the config in hand, since payloads are
self-contained by design — marks such a row `resting`, gives it the
reason that names the unblock ("media_fetch is `none` — stays parked
until it is turned back on", the drain's own note vocabulary), and drops
the attempt framing. The surfaces' ownership split reads the mark: a
resting row is the owner's, rendered under Needs you with no retry note.
`manual` media rows are excluded — they wait on the owner either way,
and turning media back on resumes nothing for them, so their stated
reason stands. Design §11's split account and vocabulary table updated
to carry the exception.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Template evolution across releases can replace a synced file with a
same-named directory, or fold a directory down to one file. Sync only
knew how to mirror content: mkdir(parents=True) over the standing file
raised FileExistsError, read_text over the standing directory raised
IsADirectoryError, and either one killed the whole sync — an instance
that could not take the release that shipped the change.
Inside the dex-* directories sync owns, the template is authoritative —
the rule the prune step already established — so a shape conflict is now
converged, not crashed on: the conflicting entry is removed, the removal
reported as a machinery change ("template ships a directory here now" /
"template ships a file here now"), and the copy writes the template's
shape. Both directions are handled, and a symlink is unlinked rather
than followed, as everywhere in sync. Non-dex trees are untouched, as
before: nothing instance-owned is ever removed to make room. All three
new tests crash the unfixed sync (FileExistsError on file→directory,
IsADirectoryError on the other two). Design §12's mirror account gains
the shape sentence.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
leeovery
marked this pull request as ready for review
August 23, 2026 21:48
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.
The verification layer: the engine cannot force a session to do judgment work, so it must always detect the work skipped.
enrich item digestrecords its own digest pass (record before file write, so a crash reads loud on the digest backstop rather than silently defeating staleness); the separate instruction is gone from the skillsmedia_fetch: nonefiles under "Needs you" with the honest reason, not "the engine retries by itself"🤖 Generated with Claude Code