Skip to content

feat(economic): provenance wire freeze — inline credit sources in 0x001D - #726

Merged
cryptskii merged 1 commit into
mainfrom
feat/provenance-wire-freeze
Aug 25, 2026
Merged

feat(economic): provenance wire freeze — inline credit sources in 0x001D#726
cryptskii merged 1 commit into
mainfrom
feat/provenance-wire-freeze

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

Step 3, sub-PR 3.2 of 6 — the provenance wire freeze. Exact field tables for the six credit-source descriptors and for the witness that carries them. Encoders and decoders only; no acceptance semantics.

Field tables — the four marked ⚠ contain choices I made

Your spec gave credit_mutation_index plus ... for four arms. Those fills are mine; flagging them so review lands on the invented parts rather than the dictated ones.

Class Fields beyond credit_mutation_index
0x0023 AuthorizedIssuance issuance_authorization_addr
0x0024 SameTransitionMove debit_mutation_index as specified
0x0025 ValidatedPeerDebit peer_genesis, peer_devid, peer_economic_position, peer_debit_mutation_index, acceptance_evidence_addr
0x0026 DlvReserveConsumption vault_id, parent_sequence, x, reserve_consumption_evidence_addr
0x0027 ValidatedDlvSettlementPayment vault_id, settlement_receipt_id, parent_sequence, trader_genesis, trader_devid, payment_evidence_addr
0x0028 VerifiedOfflineReentry prior_boundary_id, unload_boundary_id, branch_evidence_addr

0x0025 carries peer coordinates because a debit must be locatable in a specific position of a specific identity's lineage — "some peer debited something" is not a source. 0x0028 leads with prior_boundary_id because that is the anti-fork field: deriving from the terminal offline state instead is the inflation bug where two forks of one branch both reenter and 100 exported returns as 130.

Deliberate omissions, all three arms-wide

  • No source_id. Every SourceId is derived from authenticated facts; a descriptor able to supply one could name a source it never established.
  • No policy_commit / amount. They live in the credit mutation being pointed at. Duplicating them creates a second place for one fact to disagree with itself.
  • 0x0026 carries x, not receipt_id — the latter derives from (vault_id, x), and a derived name beside its own inputs is a place for the two to diverge.

The bijection turned out fully structural

is_positive_credit() reads one mutation — post amount > pre amount, absent counting as zero. No identity, no tree, no fetch. So all four frozen rules are decidable from the witness bytes before retrieving a single blob, exactly as you predicted:

credit_sources strictly ascending by credit_mutation_index
exactly one source for every positive credit
no duplicate credit_mutation_index          (implied by strict ascent)
no source for a non-credit mutation

Settlement receipts and consumed-source markers report no amount: they are records, not credits. Demanding a funding source for a bookkeeping entry would be incoherent, and treating an insertion as a credit would have forced exactly that.

The manifest index is now derived

manifest.provenance_evidence_addrs
    == sort_unique(every direct external evidence address referenced by
                   witness.credit_sources)     // mismatch REJECTS

A publication and durability index, not a second description of provenance. SameTransitionMove references nothing external, so a transition funded entirely by internal moves has an empty index — correct, not missing.

One narrowing: 0x0029 stays reserved

Overrule this if you disagree. Its field table would encode who may issue what, and this protocol has no authenticated issuance predicate — the same absence behind the builtin ERA/dBTC mint repair, where the accepting layer refuses builtin issuance precisely because nothing exists to validate against. Nothing is blocked: 0x0023 references its authorization by address, so the credit source is complete on the wire while the object behind it stays undefined until the predicate does.

0x001D and 0x00230x0028 were promoted out of ccb::reserved into ccb::class. Having to edit the reserved-set test is the deliberate-diff signal that reservation exists to produce.

Decoders

Rebuilt through the encoder's own validating constructors, so a zero-amount balance leaf and a forged receipt_id both fail to decode — a laxer decoder would be a second, weaker definition of the protocol. Reuses ccb::decode::Cursor (now pub(crate), plus peek_class for heterogeneous inline sequences) rather than a second reader whose truncation semantics could drift from the first.

Verification

16 new tests. Five gates mutation-proven, each with a positive control asserting the mutation edit actually landed before the run — the control added after 3.1, where a mutation reported green against a control test an earlier edit had silently deleted:

Gate removed Test that goes red
every-credit-is-funded a_credit_with_no_source_is_refused
no-source-for-a-non-credit a_source_for_a_debit_is_provenance_for_nothing
strict ascent credit_sources_must_be_strictly_ascending
manifest index equality the_manifest_index_must_equal_what_the_sources_reference
decoder receipt_id recomputation a_receipt_cannot_assert_a_receipt_id_its_contents_do_not_produce

Scoring the work surfaced a gap I closed rather than logged: nothing crossed the 3.2 wire object to the 3.1 verifier, so a witness could have round-tripped carrying unusable Merkle paths. a_witness_that_survived_the_wire_still_verifies_against_a_real_tree builds a real tree, encodes, decodes, and confirms the decoded witness still recomputes its post-root.

Board — both halves, on this exact tree

cargo test --locked --workspace --exclude dsm_storage_node -- --nocapture --test-threads=1
  65 suites, 3777 passed, 0 failed, 19 ignored, WORKSPACE_EXIT=0

make lint   (repo root; fmt --check + clippy --all-targets as a pair)
  LINT_EXIT=0, 0 errors, "Lint passed."

Step 3 sub-PR 3.2 of 6. Exact field tables for the six credit-source
descriptors and for the transition witness that carries them. Encoders and
decoders only; no acceptance semantics.

The shape
---------
Credit-source CCBs are INLINE inside the witness; only bulky proof material is
content-addressed:

    manifest -> witness -> inline CreditSource descriptors -> heavy evidence

Addressing the descriptors themselves would mean fetching an object just to
learn that mutation 3 is funded by mutation 1 — absurd for SameTransitionMove,
which is intra-transition by definition. So the witness commits directly to
which source type funds which credit, and the small descriptors carry
addresses only where the evidence behind them is heavy.

0x001C.provenance_evidence_addrs becomes DERIVED and non-authoritative:

    manifest.provenance_evidence_addrs
        == sort_unique(every direct external evidence address referenced by
                       witness.credit_sources)

Mismatch rejects. It is a publication and durability index, not a second
description of provenance — the semantics live in the inline sources, so
there is one description and an index over it rather than two things that can
disagree. SameTransitionMove references nothing external, so a transition
funded entirely by internal moves has an empty index, correctly.

The bijection is fully structural
---------------------------------
is_positive_credit() reads ONE mutation — post amount > pre amount, absent
counting as zero. No identity, no tree, no fetch. So all four frozen rules are
decidable from the witness bytes before retrieving a single blob:

    credit_sources strictly ascending by credit_mutation_index
    exactly one source for every positive credit
    no duplicate credit_mutation_index      (implied by strict ascent)
    no source for a non-credit mutation

Settlement receipts and consumed-source markers report no amount: they are
RECORDS, not credits. Demanding a funding source for a bookkeeping entry
would be incoherent.

Deliberate omissions
--------------------
No descriptor carries source_id — every SourceId is derived from
authenticated facts, and a descriptor able to supply one could name a source
it never established. None carries policy_commit or amount either; those live
in the credit mutation being pointed at, and duplicating them creates a second
place for one fact to disagree with itself. 0x0026 carries x but not
receipt_id, which derives from (vault_id, x).

0x0029 stays reserved
---------------------
Its field table would encode WHO MAY ISSUE WHAT, and this protocol has no
authenticated issuance predicate — the same absence behind the builtin
ERA/dBTC mint repair, where the accepting layer refuses builtin issuance
precisely because nothing exists to validate against. Nothing is blocked:
0x0023 references its authorization by ADDRESS, so the credit source is
complete on the wire while the object behind it stays undefined until the
predicate does.

0x001D and 0x0023-0x0028 were promoted out of ccb::reserved into ccb::class.
Having to edit the reserved-set test is the deliberate-diff signal that
reservation exists to produce.

Decoders
--------
Rebuilt through the encoder's own validating constructors, so a zero-amount
balance leaf and a forged receipt_id both FAIL to decode. A laxer decoder
would be a second, weaker definition of the protocol. Reuses
ccb::decode::Cursor (now pub(crate), plus peek_class for heterogeneous inline
sequences) rather than a second reader whose truncation semantics could drift.

Verification
------------
16 new tests. Five gates mutation-proven, each with a positive control
asserting the mutation edit actually landed before the run:

  every-credit-is-funded removed -> a_credit_with_no_source_is_refused
  no-source-for-non-credit       -> a_source_for_a_debit_is_provenance_for_nothing
  strict ascent                  -> credit_sources_must_be_strictly_ascending
  manifest index equality        -> the_manifest_index_must_equal_what_the_
                                    sources_reference
  decoder receipt_id recompute   -> a_receipt_cannot_assert_a_receipt_id_its_
                                    contents_do_not_produce

Board: 65 suites, 3777 passed, 0 failed, 19 ignored, exit 0.
make lint: exit 0, 0 errors.
@cryptskii
cryptskii merged commit 614835a into main Aug 25, 2026
18 checks passed
@cryptskii
cryptskii deleted the feat/provenance-wire-freeze branch August 25, 2026 06:25
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