Repository navigation
feat(sofi): the vault head past its genesis - #957
Conversation
A vault's leaves were only fetchable at R_0. acquire_evidence gave up on
any vault whose core named another root, so a second trade against the
same vault could never be validated by anyone — and it gave up silently,
because the check defaulted to "at genesis" for a vault no core names.
Owner ruling §44.4: replace the responsibility. What a later acquisition
consumes is leaf PREIMAGES, which is why a node store could never have
served this and why R14 deleted it rather than wiring it.
CORE/sofi/validation.rs vault_post_states: every vault's state after the
operation, recomputed from the pre states the evidence holds and the
settlement's own terms, bound to the post value each V° states, with the
root taken from the fold against the pre-root the core states — the same
value the verdict used and then discarded. It is the vault side of
trader_post_states and decides no canonicality; advance_resolved already
selected the state. Each record carries BOTH ends of its chain link,
because a store of post roots alone is a set, and a parent's status is
asked about a generation.
SDK client_db/sofi_vault_head.rs: sofi_vault_root holds (vault,
generation) -> root forever; sofi_vault_leaf holds the current head's
leaves with their preimages, replaced wholesale. record_resolved_with_conn
runs inside the admit transaction, so a head and the position that chose
it cannot disagree, and it reads the previous leaves through the CALLER'S
transaction because the admit path already holds the connection.
leaves_at_head rebuilds the tree from every stored leaf and requires the
recomputed root to equal the recorded one. That equality is what detects
an INCOMPLETE record — a vault another trader moved between this device's
trades leaves it missing their leaf — and an incomplete record yields
nothing, so Core answers Unavailable and the position waits rather than
standing on a state this device never established.
acquire_evidence serves R_0 from the genesis and past it from that store,
requiring the record to be the head the core was built on. A vault named
by no core is now skipped rather than loaded as if at genesis.
Controls, each alone for attribution, each restored byte-identical:
M1 the post state is not bound to what V° states
-> a_vault_core_stating_another_post_state_yields_no_head
M2 the root is the pre-root rather than the fold's
-> vault_post_states_are_recomputed_and_bound_to_the_stated_values
M3 leaves_at_head skips the root check
-> a_vault_record_that_cannot_reproduce_its_own_root_is_not_evidence
M4 the predecessor row is not written, so the chain is a set
-> a_resolved_route_leaves_the_vault_head_the_next_trade_stands_on
M1 did not land on its first run: its marker did not match after
formatting, so the passes were the unmutated tree. Re-run grep-verified.
STILL OWED, and visible in the type: ParentStatus::Orphaned has no
producer. Deciding it needs the vault's chain complete to an open head,
which is the forward walk over successor cells. Until that lands a leg
whose parent this verifier has not established is Unavailable and the
position waits — never silently "not refuted".
Spec §44.4 gains the code, §34 places vault_post_states at stage 10.
Correction to this PR's "still owed" paragraphThe body says the producer for That criterion is unsound, and I would rather say so before it is merged as a description of the next change. A head is open precisely so that the next root can still arrive. Take a chain The sound criterion is the one in the ruling, per generation (§44.4 ruling 3): for a leg claiming This refutes only against an established Two consequences worth recording, both of which make the next change smaller rather than larger:
Nothing in the code in this PR changes — |
The vault head past its genesis
A vault's leaves were only fetchable at
R_0.acquire_evidencegave up on any vault whose core named another root, so a second trade against the same vault could never be validated by anyone — and it gave up silently for a vault no core names, loading genesis leaves as if that were the case.Per the ruling in §44.4: replace the responsibility. What a later acquisition consumes is leaf preimages, which is why a node store could never have served this and why R14 deleted it rather than wiring it.
Core
vault_post_states(CORE/sofi/validation.rs) is the vault side of R13'strader_post_states. For every vault the operation touches it recomputes the post state from the pre state the evidence holds and the settlement's own terms, binds it to the post value that vault's core states, and takes the root from the fold against the pre-root the core states — the same value the verdict computes and then discards.It decides no canonicality.
advance_resolvedalready selected the state; this only says what the state is.Each record carries both ends of its chain link,
pre_rootatpre_generationand the post root atgeneration. A store of post roots alone is a set, and a parent's status is asked about a generation.The store
SDK/storage/client_db/sofi_vault_head.rs:sofi_vault_root(vault, generation) → root, foreversofi_vault_leafrecord_resolved_with_connruns inside the admit transaction, so a head and the position that chose it cannot disagree. It reads the previous leaves through the caller's transaction, because the admit path already holds the connection and a second acquisition would deadlock rather than fail.A read is checked, never trusted.
leaves_at_headrebuilds the tree from every stored leaf and requires the recomputed root to equal the recorded one. That equality is what detects an incomplete record: a vault another trader moved between this device's trades leaves it missing their relationship leaf. An incomplete record yields nothing, so Core answersUnavailableand the position waits rather than standing on a state this device never established.acquire_evidenceservesR_0from the genesis and past it from that store, requiring the record to be the head the core was built on. A vault named by no core is now skipped rather than loaded as if at genesis.Controls
Each run alone for attribution, each restored byte-identical.
V°statesa_vault_core_stating_another_post_state_yields_no_headvault_post_states_are_recomputed_and_bound_to_the_stated_valuesleaves_at_headskips the root checka_vault_record_that_cannot_reproduce_its_own_root_is_not_evidencea_resolved_route_leaves_the_vault_head_the_next_trade_stands_onThe first did not land on its initial run: the marker did not match after formatting, so those passes were the unmutated tree. Re-run grep-verified, and recorded rather than dropped.
Two of the tests also caught my own errors before the mutations did. One asserted that the vault's relationship leaf sits at a different key from the trader's; it does not — one key derivation, two trees, and the values differ in shape. The other asserted a leg both final on this operation's commitment and consumed by another, which the ladder rightly called
Realized.Gates
sofi::156/0, two tests new;tests/sofi_v8_independent17/0. SDK (release,--features test-utils):sdk::sofi_*53/0, two tests new;storage::client_db388/0.make lintexit 0;ci/production_safety_checks.shpassed; G1 84 reachable, 0 baselined.A dead function this branch introduced, and removed
record_genesis_with_connhad exactly one occurrence in the tree: its own definition. No caller, not even a test.It also had no distinct responsibility.
record_resolved_with_connalready writes generation zero as the pre-side of the first resolved chain link, and a vault's evidence at its genesis is served byvault_leaves_at_genesisfrom the accepted genesis object rather than from this store — so the doc comment's claim, "without it the first trade's parent has no status", was false as written. That is the same shape as the dark tree store its sibling PR deleted: reachable from nothing, reading as live.The gate did not catch it and structurally cannot.
sofi_reachabilityscansCORE_SOFI = "/dsm/src/sofi/", so G1 proves Core production reachability, not SDK production reachability — a green G1 coexists with dead SDK capability, and as of today the whole SDK SoFi producer surface has no production caller. Closing that blind spot is ruled into the facade work (spec §44.6).Still owed, and visible in the type
ParentStatus::Orphanedhas no producer. Until one lands, a leg whose parent this verifier has not established isUnavailableand the position waits — never silently "not refuted", which is what the two booleans this replaced would have said.The producer is the forward walk over successor cells — extend the recorded chain from its last contiguous root, applying
vault_post_statesper consumed attempt. It is decided per generation, never by absence from a chain. An earlier draft of this paragraph said a root absent from a chain complete to an open head is refuted; that is wrong, and I would rather say so before it is merged as a description of the next change. A head is open precisely so the next root can still arrive, so an absent root may be one an in-flight operation is about to realize — and refuting it answersOrphaned, aRouteImpossiblearm, hence a permanentVoidon a route whose only defect is that the verifier looked early.The sound criterion is the ruling's own, against
root_at(v, g): equal isCanonical, different isOrphaned, absent isUnavailable.R*_gis permanent once established — either consumed to produceg+1, or it is the head — whichOneConsumerPerParentalready model-checks across crash and recover, so the walk inherits its uniqueness rather than needing a new concurrency argument.