Let a Substitute site synthesize a miss value, and observe what it did - #133
Merged
Merged
Conversation
This was referenced Sep 10, 2026
maverox
added this pull request to stack #142
September 11, 2026 10:20
maverox
force-pushed
the
work/on-miss-synthesis
branch
2 times, most recently
from
September 14, 2026 10:42
02c8bad to
7a604f7
Compare
`Reconstructed` gains `Synthesized(T)` and `NoValue`, so one closure answers both halves of a Substitute lookup: rebuild a hit, or derive a value from the query alone on a miss. `on_miss = <expr>` becomes sugar for the miss arm (`Synthesized(expr)`); declaring nothing becomes `NoValue`, the pre-existing fail-stop. Every existing site compiles unchanged. The value a miss arm returns must be a function of the query and nothing else. Determinism is the load-bearing property, ahead of honesty: replay needs `same query -> same value, every run`, or two replays of one candidate against one tape disagree with each other and "the candidate changed" cannot be separated from "the fabrication changed". It is also what keeps two DIFFERENT candidates comparable past the edge of the tape. Answering a miss by running the real computation is the most honest option available and the worst one -- at a `deja::id` seam it reintroduces exactly the entropy the seam exists to remove, on the calls the seam failed to cover. That forces the accounting to change. `MissPolicy` rode the query because the observation was emitted INSIDE the lookup, before the seam decided, so the only thing available to stamp was what the boundary had DECLARED. That was sound only while a declared `on_miss` could not decline. `NoValue` makes declaration and outcome disagree, so the Substitute path becomes two-phase -- `substitute_peek` -> decide -> `substitute_observe` -- mirroring the execute-shadow lifecycle that has always worked this way. Deferring the emission is safe because the SEAM owns both fail-stops: it emits, then panics, in that order. No Drop guard, no unwind-safety question. Both seam families share one `substitute_decide`, so the emit-before-stop ordering lives in exactly one place. `MissPolicy`, `dispatch_or_miss` and `dispatch_async_or_miss` are gone; both shapes call one seam and differ only in what the closure returns. Two things fall out. A hit whose payload will not rebuild is now stamped `Stopped` while staying `resolved: true` -- it used to emit `resolved: true, Provenance::Recorded` and only then panic, so the ledger showed a cleanly served call for a request that died, a combination the two booleans cannot express. And the macro no longer clones the args image per active call at every boundary with an `on_miss`: the seam builds the miss marker itself, from the spec it already holds, so only a genuine miss pays. `absorbed` (what the scorer reads) and `synthesized` (the V2 scaffold, until now always false) are derived from `outcome` and stamped in one place, with a test that they never drift. Collapsing them onto `outcome` is an orchestrator change and is not in here. The seam's doc block had drifted onto `Reconstructed` -- a plain `//` note between it and the code left `dispatch` rendering undocumented and the enum rendering with the seam's control flow on top of its own. Re-homed and updated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FmXkygUmueraF9oR4oqwS
maverox
force-pushed
the
work/on-miss-synthesis
branch
from
September 14, 2026 13:38
7a604f7 to
222f36e
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.
What
ReconstructedgainsSynthesized(T)andNoValue, so one closure answers both halves of a Substitute lookup: rebuild a hit, or derive a value from the query alone on a miss.Value(T)Synthesized(T)Failed(String)NoValueon_miss = <expr>becomes sugar for the miss arm (Synthesized(expr)); declaring nothing becomesNoValue, the pre-existing fail-stop. Every existing site compiles unchanged.Why determinism, not honesty
The value a miss arm returns must be a function of the query and nothing else. Replay needs
same query -> same value, every run, or two replays of one candidate against one tape disagree with each other and "the candidate changed" cannot be separated from "the fabrication changed". It is also what keeps two different candidates comparable past the edge of the tape.Answering a miss by running the real computation is the most honest option available and the worst one — at a
deja::idseam it reintroduces exactly the entropy the seam exists to remove, on precisely the calls the seam failed to cover.Why the accounting had to change with it
MissPolicyrode the query because the observation was emitted inside the lookup, before the seam decided — so the only thing available to stamp was what the boundary had declared. That was sound only while a declaredon_misscould not decline.NoValuemakes declaration and outcome disagree. So the Substitute path becomes two-phase —substitute_peek→ decide →substitute_observe— mirroring the execute-shadow lifecycle that has always worked this way. Deferring the emission is safe because the seam owns both fail-stops: it emits, then panics, in that order. NoDropguard, no unwind-safety question. Both seam families share onesubstitute_decide, so the emit-before-stop ordering lives in exactly one place.MissPolicy,dispatch_or_missanddispatch_async_or_missare gone.Two things that fall out
A hit that stopped the request is now visible. A
Failedhit used to emitresolved: true, Provenance::Recordedand only then panic, so the ledger showed a cleanly-served call for a request that died. It is now stampedStoppedwhile stayingresolved: true— a combination the two booleans structurally cannot express.A hot-path cost disappears. The macro used to clone the args image per active call at every boundary with an
on_miss. The seam builds the marker from the spec it already holds, so only a genuine miss pays.This is one of three instances of the same principle
DefaultEach replaces something the system decided on the author's behalf with something the author has to say. The reason it keeps paying is that the implicit version is indistinguishable in the source from an oversight.
Evidence
just verifygreen. Five tests, mutation-checked because all passed first try and the pre-existing tests could not fail if the core claim were wrong — they were written when declaration and outcome could not disagree:absorbeddecoupledTwo mutations are killed by exactly one test each, so neither is redundant, and the marker test is not vacuous.
Deliberately not in here
deja::synthhelpers (#134). Collapsingoutcome/absorbed/synthesized— an orchestrator change. A conditional decline from the macro: with the sugar a site always returnsSynthesized; only a hand-built seam can inspect the query and returnNoValue. The sugar is kept because it preserves every existing site.🤖 Generated with Claude Code