Skip to content

feat(dlv): the live market path binds, and stops at bound-but-unrealized (RE-OPENED — lost from #834's merge) - #836

Merged
cryptskii merged 2 commits into
mainfrom
feat/live-market-bind-to-bound-unrealized
Sep 10, 2026
Merged

feat(dlv): the live market path binds, and stops at bound-but-unrealized (RE-OPENED — lost from #834's merge)#836
cryptskii merged 2 commits into
mainfrom
feat/live-market-bind-to-bound-unrealized

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

Why this is being opened again

This work was already reviewed, verified and pushed, and it did not reach main.

#834's merge took commit 3f0e5db7 — the G1G4 enforcement only. The second commit on that branch, e804e662, carrying the live market path, never appeared on the pull request object at all: pulls/834 recorded head.sha = 3f0e5db7 and its commit list has exactly one entry.

The push itself succeeded. origin still holds the branch at e804e662 — which is how this is recoverable at all. What failed is the thing this repository already documents:

A push can land on the branch ref while the PR object stays pinned to the previous head, and gh pr merge takes the PR object's head — this merged a stale commit onto main twice (#698, #700).

That is now three times.

My part in it: after pushing, I printed git rev-parse HEAD — my local head — and treated it as evidence of what the PR would merge. It is not, and the repo rule says so in as many words. I did run check-pr-head-sync.sh 834, but in the same command as the PR body edit, and its output was cut off by the pipe. So the one check that would have caught this produced no visible result and I proceeded anyway.

main is not broken by this: it simply lacks the change, and the old refusal is still live there.

What the change is

Unchanged from its review. The market emission refusal is lifted, and the route carries a trade through signing, a pure prepare, production, publication, the trader-parent fence and QuorumBind to a committed binding — stopping at bound-but-unrealized.

Requirement 4 needed no new code. Requirement 5 fences the trader's own chain, never the vault's. Requirement 2's operands are plumbed out of the composition block rather than re-derived.

Nothing realizes: no balance moves, no head advances, no reserves move, no receipt publishes, the fence is not released.

Verification

merge-tree confirms it still merges cleanly onto current main, which has since taken #835.

Re-verified before this PR was opened, and the head-sync check will be run on its own this time, with its output read.

…nforce them on

Step 4 requirement 3. 2c-B froze four conjuncts over MarketTerms field 6 and deferred
them for a stated reason: their operand did not exist, and nothing was permitted to
fabricate one to make a vector pass. The producer supplied the operand; this is the
half that checks it, and the half a foreign verifier runs.

  G1  operation_bytes decode under DlvSettleOperationPreimageV1, all bytes consumed
  G2  canonical re-encode reproduces the carried bytes exactly
  G3  discriminator 26 and mode Unilateral
  G4  relationship_chain_tip_v2 over the carried inputs == trader_successor

Placement follows the two rulings that constrain it rather than convenience. NOT the
generic CCB decoder: the owner ruled the chain-tip recomputation is successor-evidence
validity rather than byte decoding, and putting it there would make every decode of any
bundle hash a foreign grammar. NOT the C4 walk: 2c-C4 says these gate the bundle's
structural validity, not the economic walk, which treats the bytes as opaque. The
sibling conjunct that IS in-bundle structure, embedded_parent == trader_parent, stays
where it was, enforced at construction and decode.

Errors are structured, one arm per conjunct, so the arm IS the finding. G4 carries both
the carried and the recomputed successor, because "these differ" is the whole content
of that refusal.

The discriminator is read before decoding, so a close preimage arriving where a settle
belongs reports as a wrong tag rather than as an undecodable blob.

The producer now verifies its own output. G1-G4 hold there by construction, so the
check can only fire if the construction stops being what it claims — which is exactly
when a silent divergence would otherwise begin. It also means this is not a verifier
sitting uncalled: a producer that cannot pass the check a foreign party runs has no
business emitting a bundle.

Teeth, including the two requirement 10 names:
- a single altered operation byte is rejected. The test flips byte 10, inside vault_id,
  deliberately: the bytes stay decodable and canonically re-encodable so G1-G3 all pass
  and G4 is the conjunct actually under test. My first attempt flipped the last byte,
  which is mode, and proved G3 instead — the failure is worth recording because a
  mutation test that fires the wrong gate looks identical to one that works.
- a supplied successor different from the recomputed one is rejected, and the refusal
  reports both values.
- trailing bytes, a wrong discriminator, empty bytes, and a bilateral settle are each
  refused separately. The bilateral case re-derives the tip first so ONLY the mode is
  wrong, or G4 would fire and the test would prove nothing about G3.

dsm dlv::market 15/15, ccb 18/18, conformance 12/12. make lint green; production safety
checks green.

Scope: this does NOT lift the market emission refusal. Requirements 1, 2 and 4-10 —
the live bind path to bound-but-unrealized — are next, and realization stays unreachable
until 2c-D.
Step 4, requirements 1, 2, 4, 5, 8 and part of 10. The refusal that stood here was
correct for exactly as long as its reason held: a canonical market bundle needs the
trader's prepared successor and its evidence, and no producer could make them. 5c-2
Step 2 built that producer, so the reason is spent and keeping the refusal would itself
have become the stale thing.

What the route now does, in this order and not another:

  sign the settle       the signature is INSIDE the bytes the chain tip hashes, so a
                        settle cannot be signed after the advance
  prepare, purely       no writes, no head install — it exists to learn the embedded
                        parent and the entropy the tip covers, both the device's own
  produce               prepare_market_successor RECOMPUTES the successor from those
                        exact bytes; market_terms runs G1-G4 over its own output
  cross-check           the producer's successor must equal the prepare's own. They
                        compute it by the same rule from the same inputs, so a
                        disagreement means one is not doing what it says, and binding a
                        successor this device would not advance to is worth refusing
                        hardest
  derive the successor  from the AUTHENTICATED V_n, by the rule every verifier applies
  publish, fence, bind  all inside bind_settlement, which publishes the canonical bundle
                        to a quorum of the vault's COMMITTED storage set and refuses
                        before any fence or binding round if that is not durable

Requirement 4 needed no new code: publication-before-binding with a quorum, and a failed
publication leaving no bind, no fence and no rival excluded, were already implemented
inside bind_settlement ahead of place_fence. The live path uses that ordering rather
than reimplementing it.

Requirement 5: the fence is keyed on the TRADER's own chain and parent, never the
vault's. A market settle consumes the trader's sovereign chain position; reusing the
close path's vault-keyed identity would fence the wrong thing. Both tests assert the
absence of a vault-keyed row, which is the checkable half of that.

Requirement 2 needed plumbing, because the operands died at a scope boundary. The
composition's owner identity and the vault's fee are now carried on SettleTerms, and the
checked amounts come from the re-simulation that proved them rather than a second
narrowing — the narrowing's own comment says a second site is how the difference gets
minted. AmmVerifyOutcome returns them instead of discarding them.

WHAT THIS DOES NOT LIFT. Realization. No balance moves, no head advances, no reserves
move, no receipt is published, and the fence is not released. Ruling R1 and V3 put the
acceptance witness, fence release, receipt publication and the realized frontier behind
2c-D, and nothing here reaches any of them. Binding a trade is not settling it.

The two tests that asserted the refusal now assert the end state instead, both halves
separately, because "it bound" and "it did not realize" fail in opposite directions.
The foreign-trader test is the one that matters most: a device holding no owner record
and no owner signature binds a trade against the owner's liquidity while the owner is
offline, and still cannot realize it.

A re-submission of the same trade is now accepted rather than refused, and that is
asserted: the generation is bound by that very trade, and a trader retrying after a
dropped response must not be told the vault is taken by someone else.

The three operator docs banded earlier said market settlement was deployment-blocked.
That is no longer true, so their banners are rewritten rather than left to mislead in
the opposite direction from before.

dsm_sdk: dlv_routes 37/37, vault_state_composition 22/22, route_commit_sdk 43/43,
settlement_bind 7/7, binding_occupancy 10/10, settlement_resume 2/2 — all serial.
make lint green; production safety checks green.

Still owed in Step 4: requirement 6's second half (running the accepted-successor and
economic-admission path after COMMIT), and the remaining requirement 10 controls. The
trader's advance is deliberately NOT committed here, so nothing it would credit exists
yet to be mistaken for realization.
@cryptskii
cryptskii merged commit 65b35ae into main Sep 10, 2026
16 checks passed
@cryptskii
cryptskii deleted the feat/live-market-bind-to-bound-unrealized branch September 10, 2026 03:06
cryptskii added a commit that referenced this pull request Sep 10, 2026
…ndle is READ (#838)

An adversarial review of the wired Step 4 path against the ten requirements found two
real gaps in requirement 3, both in work I wrote. Both are confirmed from source, not
taken on the reviewer's word.

FIRST: G1 was implemented as "decodes and consumes all bytes" and stopped there. 2c-B
lists two more conjuncts in the same normative block — "every fixed-length semantic
field has its required length" and "signature has the schema-1 SPX256f signature
length" — and its hostile-case clause names the attack directly:

    An operation_bytes with trailing bytes after a valid decode, a wrong
    discriminator, or a short sigma — whose trader_successor was computed over
    those same bytes so the chain-tip equality passes — must be refused by the
    conjunct. Owed at implementation.

It was owed and it was not paid. The decoder width-checks the digest-shaped fields and
sigma via get_arr32, but reads vault_id, both public keys and the signature with an
unbounded get_bytes. Those four are checked here or nowhere, and the grammar writes them
with a length prefix, so a SHORT field re-encodes to exactly the bytes it came from: G2
passes, and G4 passes too whenever the attacker computed the successor over those same
short bytes. A truncated settler key or signature was self-consistent.

Two tests build precisely that: truncate the field inside operation_bytes, RECOMPUTE the
tip over the result so G4 cannot be what fires, and require the width refusal. Mutation
control: removing the width rule turns exactly those two red and nothing else, which is
the evidence that G2 and G4 genuinely cannot catch it.

SECOND: check_market_evidence had one non-test caller — the producer's own self-check.
No consumer ran it. That was survivable while the market route refused; #836 lifted the
refusal, so market bundles now reach consumers for the first time, and a producer that
means harm simply does not self-check. A self-check gates nothing on the consuming side.

It now runs in binding_occupancy, where a foreign bundle first becomes readable: fetched
from the register, canonically decoded, hashed to the record's identity. That is exactly
the point 2c-C4 §2.1 describes — "refused before any verifier reads it" — and the
surrounding comment already said so about the checks beside it.

A test binds a bundle that is perfect in every other respect (canonical, hashing to the
record, naming this parent, right generation) and wrong only in its carried successor,
and requires it to be Unresolvable/Invalid. Mutation control: removing the consuming-side
call turns exactly that test red.

Also from the review, smaller and all confirmed:

- Three doc comments still said dlv.unlockRouted "refuses at emission", which stopped
  being true when #836 landed. Rewritten to say what each thing now is, including
  market_bundle_at, which is no longer a stand-in for a missing producer but a
  deliberate test shortcut past the trader advance.
- The foreign-trader test asserted only the ABSENCE of a vault-keyed fence, which no
  fence at all would also satisfy. It now asserts the positive half too: the fence is on
  the foreign trader's own chain and is still CommittedAwaitingAcceptance after its
  advance.
- A comment reading "NOTHING WAS EMITTED ... the refusal came before the first mutating
  op" sat directly above assertions that the trade bound and the trader advanced.
- EvidenceInvalid::TrailingBytes is unreachable, because from_bytes refuses trailing
  input itself and a successful decode always re-encodes to the same length. Kept as a
  guard on both facts, but the docs now say it is unreachable and that a trailing-byte
  preimage reports Undecodable — rather than implying it fires.

dsm dlv::market 17/17, conformance 12/12, settle provenance 15/15; dsm_sdk dlv_routes
37/37, binding_occupancy 11/11, settlement_bind 7/7, settlement_resume 2/2 — all serial.
make lint green; production safety checks green.

The review was INCOMPLETE and this is not a clean bill: 18 of its 39 agents died on
usage credits, and an unverified finding is dropped rather than promoted, so an unknown
number went unexamined. Two of requirement 10's controls also remain owed — publication
below quorum driven through the route, and a rival bundle after COMMIT.
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.

1 participant