Skip to content

deja::synth — deterministic values a Substitute miss can be answered with - #134

Merged
maverox merged 1 commit into
mainfrom
work/synth-helpers
Sep 14, 2026
Merged

maverox merged 1 commit into
mainfrom
work/synth-helpers

Conversation

@maverox

@maverox maverox commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #133. Review that first; this diff is only the new module plus the two fields it needs.

What

deja::synth gives a site the sanctioned way to total the recording's partial function: uuid_v8, id, bytes, u64, monotonic — each a pure function of the SubstituteMiss handed to the miss arm.

Keyed the way the lookup is keyed

Every helper hashes the same canonical image the lookup key was built from (hash_value, now pub(crate)). So:

  • {"a":1,"b":2} and {"b":2,"a":1} synthesize the same value — they address the same recorded entry.
  • [1,2] and [2,1] synthesize different values — a permuted array misses at every address rank, so it is a different call.

Both halves are asserted. This is the property that makes "same query → same value" true by construction rather than by coincidence.

Non-collision, made structural

This is the property that is easy to skip and expensive to skip. Deja substitutes results, so a synthesized value flows into downstream args. If a synthesized uuid could equal a recorded one, the next Substitute lookup keyed on it would HIT — a false resync on fabricated data, silently.

So uuid_v8 draws from RFC 9562's version-8 custom space, which real code (v4, v7) never produces, and id carries a reserved deja-synth- prefix. The version and variant nibbles are asserted, not assumed.

Two things the helpers guarantee about each other

  • Domain separation — a site using two helpers is never handed the same bits twice under different names.
  • bytes extends rather than rewrites — one digest per 8-byte block, so bytes::<8> is a prefix of bytes::<32>, and a 32-byte draw is not one 8-byte value repeated four times. The second half needs its own test: without it, a single reused digest passes the extension assertion unchanged.

monotonic is named for what it guarantees

Successive misses at one call site advance. Two different sites both start from the origin — a miss carries a per-site occurrence and no global counter, and deriving one would mean advancing shared replay state from the miss path, perturbing the very keys the lookup is built on.

A caller that needs cross-site ordering has nothing derivable and should return NoValue. There is a test asserting that limit, so nobody reads a stronger promise into the name.

SubstituteMiss gains two fields

occurrence and correlation_id, built lazily on the miss branch from the identity the seam already holds and the same ambient correlation fallback the record seam uses. new keeps its four arguments; the context arrives through with_call_context.

Correlation is safe in the digest: the orchestrator replays the same correlation ids to every candidate, so including it separates two requests without separating two candidates — which is exactly what keeps them comparable past the edge of the tape.

Evidence

just verify green. 12 new tests. Four mutations, four kills, each by a distinct test:

mutation killed by
no domain separator the_helpers_are_domain_separated_from_each_other, the_blocks_of_a_byte_draw_differ_from_each_other
uuid stamped v4 instead of v8 a_synthesized_uuid_is_in_the_reserved_version_8_space
occurrence dropped from the digest a_different_miss_synthesizes_a_different_value
one digest for every byte block the_blocks_of_a_byte_draw_differ_from_each_other

🤖 Generated with Claude Code

https://claude.ai/code/session_019FmXkygUmueraF9oR4oqwS

let rendered = uuid_v8(&miss(json!({"key": "k1"})));

let groups: Vec<&str> = rendered.split('-').collect();
assert_eq!(groups.len(), 5, "must be uuid-shaped: {rendered}");
assert_eq!(
groups.iter().map(|g| g.len()).collect::<Vec<_>>(),
vec![8, 4, 4, 4, 12],
"must be uuid-shaped: {rendered}"
);
assert!(
rendered.chars().all(|c| c == '-' || c.is_ascii_hexdigit()),
"must be parseable: {rendered}"
Comment on lines +251 to +252
"version nibble must be 8 (RFC 9562 custom), NOT 4 or 7 — that is what \
makes the synthesized space disjoint from the recorded one: {rendered}"
assert_eq!(
variant & 0b1100,
0b1000,
"variant must be RFC 4122, or the value is not a valid uuid: {rendered}"
@maverox maverox self-assigned this Sep 14, 2026
Base automatically changed from work/on-miss-synthesis to main September 14, 2026 13:57
`deja::synth` gives a site the sanctioned way to total the recording's partial
function: `uuid_v8`, `id`, `bytes`, `u64` and `monotonic`, each a pure function
of the `SubstituteMiss` handed to the miss arm.

Every helper hashes the SAME canonical image the lookup key was built from
(`hash_value`, now `pub(crate)`: object keys sorted, array order significant).
Two calls that address the same recorded entry therefore synthesize the same
value by construction rather than by coincidence, and a permuted array -- which
misses at every address rank -- synthesizes a different one. Both halves are
asserted.

Non-collision gets the most attention because it is the property that is easy
to skip and expensive to skip. Deja substitutes RESULTS, so a synthesized value
flows into downstream ARGS; if a synthesized uuid could equal a recorded one,
the next Substitute lookup keyed on it would HIT -- a false resync on
fabricated data, silently. So `uuid_v8` draws from RFC 9562's version-8 custom
space, which real code (v4, v7) never produces, and `id` carries a reserved
prefix. The version and variant nibbles are asserted, not assumed.

Helpers are domain-separated from each other, so a site using two of them is
not handed the same bits twice under different names, and `bytes` draws one
digest per 8-byte block so lengthening a draw extends it rather than rewriting
it -- and so a 32-byte draw is not one 8-byte value repeated four times.

`monotonic` is named for what it guarantees and documented for what it does
not: successive misses at ONE call site advance, two different sites both start
from the origin. A miss carries a per-site occurrence and no global counter,
and deriving one would mean advancing shared replay state from the miss path,
perturbing the very keys the lookup is built on. A caller that needs cross-site
ordering has nothing derivable and should return `NoValue`; there is a test
asserting the limit so nobody reads a stronger promise into the name.

`SubstituteMiss` gains `occurrence` and `correlation_id`, built lazily on the
miss branch from the identity the seam already holds and the same ambient
correlation fallback the record seam uses. Correlation is safe in the digest:
the orchestrator replays the same correlation ids to every candidate, so it
separates two requests without separating two candidates -- which is what keeps
them comparable past the edge of the tape.

Four mutations, four kills, each by a distinct test: no domain separator, uuid
stamped v4, occurrence dropped from the digest, one digest for every byte block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FmXkygUmueraF9oR4oqwS
@maverox
maverox merged commit 585f796 into main Sep 14, 2026
9 of 10 checks passed
@maverox
maverox deleted the work/synth-helpers branch September 14, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants