Conversation
Phase 1c — per-item repair. Audit §3.1, and the binding constraint on multi-file value since H5 made multi-item bundles reachable. Full reasoning in DECISIONS §47. Validation is bundle-scoped and fallback was all-or-nothing. Measured on the frozen 45-file Python corpus at targetReductionRatio 0.3, the stages achieved 42.52% and the run emitted 0.00%: 26 CONSTRAINT_DIRECTIVE_LOST errors across 14 items reverted all 45, with drift at 0.0359 against a 0.40 gate and AST clean. 45 Python files 0.00% -> 22.73% 14 of 45 reverted 61 TypeScript files 0.00% -> 19.47% 21 of 61 reverted Three pieces, in dependency order. Attribution became data. It already existed as prose — `ValidationIssue` carried "…in item [<id>]…" interpolated into `message` and nothing else, which is unusable by anything that has to act on it, and recovering it by regex would have been audit M5b exactly. `ValidationIssue.itemId` is now a field, populated by the AST and constraint checks that knew it all along. `validate()` says what it can and cannot attribute, via `FailureAttribution`. Drift splits: SEMANTIC_DRIFT_UNMEASURABLE is attributable, because the measurement gate refuses specific items and `unwitnessedItemIds` names them (§33); SEMANTIC_DRIFT_EXCEEDED is not, being a set comparison over the whole bundle. The engine reverts named items, re-validates through the **same** `validate`, and adopts the result only if it passes. Deliberately shaped like, and placed after, the automated-rehydration attempt already there — it is the same move. The load-bearing property is that repair changes which bundle is offered, never what counts as valid; nothing here decides an item is acceptable. The refusal gate was tried too strict and corrected by measurement. "Refuse if any error is unattributable" reads as the conservative choice and is wrong: the TypeScript bundle fails on both attributable constraint losses and SEMANTIC_DRIFT_EXCEEDED at 0.4122, so the failure naming nothing discarded the attribution naming 21 items and the run stayed at 0.00%. The question is not "is every error attributed?" but "is there a principled subset to revert?" — safe because the candidate is re-validated regardless, and reverting items lowers semantic loss, which is what drift measures: drift fell 0.4122 -> 0.0056 (TypeScript) and 0.0359 -> 0.0141 (Python). Reverting a subset when *no* error names anything would be guessing, and still refuses. Repair declines, routing to the real fallback, when every changed item would be reverted. That is a fallback wearing a different name, and the distinction is load-bearing: fallback echoes `request.rawInput` and the CLI writes the original Buffer, while repair renders from items — §35 exists because those are not the same bytes for input that is not valid UTF-8. Measured: 14 of 14 single-file fallbacks remain byte-identical and none reports itemsReverted. "Every item" accounts for pruning, since a bundle whose survivors were all reverted is still a real reduction if the planner dropped items. One pass, not a loop to fixpoint. A loop terminates — each pass reverts at least one more item — but costs a whole-bundle AST pass per iteration, so worst case is O(n²) validations on the bundles that need it most. One pass sufficed on both corpora; a second round of attributable failures falls back rather than iterating. `trace.itemsReverted` names what was put back, present only on a partial success. Without it the outcome is a reduction with fallbackUsed false and no indication anything was restored — invariant 10's shape. 574 of 574 corpus rows are identical to the pre-1c engine, same frozen corpus, varying only dist/. That is the expected result: the harness measures single-file runs, where repair cannot fire. Phase 1c adds value where the audit said it was missing and nowhere else. One measurement caution, now in the status doc: the TypeScript bucket read 19.76% against a previous 23.03% and looked like a regression. The pre-1c engine reads 19.76% on the same frozen corpus too — the corpus changed because this change edits src/, and src/ *is* the TypeScript bucket. Suite: 606 passing (was 599), typecheck and lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Phase 1c — per-item repair. The audit's §3.1, and the binding constraint on multi-file value
since H5 made multi-item bundles reachable. Full reasoning in DECISIONS §47.
The problem, measured
Validation is bundle-scoped and fallback was all-or-nothing. On the frozen 45-file Python corpus
at
--target-reduction-ratio 0.3:Fourteen items reverted forty-five.
Result
How
Three pieces, in dependency order.
Attribution became data. It already existed — as prose.
ValidationIssuecarried"…in item [<id>]…"interpolated intomessageand nothing else, which is unusable by anythingthat has to act on it; recovering it by regex would have been audit M5b exactly.
ValidationIssue.itemIdis now a field, populated by the checks that knew it all along.validate()reports what it can attribute. Drift splits:SEMANTIC_DRIFT_UNMEASURABLEisattributable (the measurement gate refuses specific items and
unwitnessedItemIdsnames them,§33);
SEMANTIC_DRIFT_EXCEEDEDis not, being a whole-bundle set comparison.The engine repairs before giving up. Named items are reverted, and the candidate goes back
through the same
validate, adopted only if it passes. Shaped like — and placed after — theautomated-rehydration attempt already there, because it is the same move. The load-bearing
property: repair changes which bundle is offered, never what counts as valid.
Two things worth reviewing closely
The refusal gate was tried too strict, and measurement corrected it. The first rule was
"refuse if any error is unattributable" — it reads as conservative and is wrong. The TypeScript
bundle fails on both attributable constraint losses and
SEMANTIC_DRIFT_EXCEEDEDat 0.4122, sothe failure that named nothing discarded the attribution that named 21 items, and the run stayed
at 0.00%. The question is not "is every error attributed?" but "is there a principled subset to
revert?" — safe because the candidate is re-validated regardless, and reverting items lowers
semantic loss, which is what drift measures:
Reverting a subset when no error names anything would be guessing, and still refuses.
Repair declines when every changed item would be reverted, routing to the real fallback
instead. Not cosmetic: fallback echoes
request.rawInputand the CLI writes the originalBuffer, while repair renders from items — §35 exists because those are not the same bytesfor input that isn't valid UTF-8. "Every item" accounts for pruning, since a bundle whose
survivors were all reverted is still a real reduction if the planner dropped items.
Verification
dist/. Expected: the harness measures single-file runs, where repair cannot fire. Phase 1cadds value where the audit said it was missing and nowhere else.
itemsReverted— aone-item bundle can never be a partial success, and the code says so rather than relying on it
not arising.
One pass, not a loop to fixpoint: a loop terminates but costs a whole-bundle AST pass per
iteration (O(n²) on the bundles that need it most). One pass sufficed on both corpora.
trace.itemsRevertednames what was put back, present only on a partial success — without it areduction with
fallbackUsed: falseconceals that anything was restored, which is invariant 10'sshape.
A measurement caution
The TypeScript bucket read 19.76% against a previous 23.03% and looked like a regression.
The pre-1c engine reads 19.76% on the same frozen corpus too — the corpus changed because this
change edits
src/, andsrc/is the TypeScript bucket. Now recorded in the status doc'straps.
Still open on this axis
Drift remains a bundle-scoped score. It is repairable in practice — reverting items lowers it —
but it never names an item itself, so a bundle failing on drift alone still falls back whole.
🤖 Generated with Claude Code