Skip to content

feat(economic): economic lineage and register - #727

Merged
cryptskii merged 1 commit into
mainfrom
feat/economic-lineage-register
Aug 25, 2026
Merged

feat(economic): economic lineage and register#727
cryptskii merged 1 commit into
mainfrom
feat/economic-lineage-register

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

Step 3, sub-PR 3.3 of 6. The pure protocol coordinate and register semantics. The durable state machine is 3.4 and is deliberately absent.

Registered is not validated, and the compiler enforces it

Writing a claim into a write-once cell establishes non-equivocation and nothing else: this identity named one root at this position and can never name a second. It says nothing about whether that root resulted from a valid transition — a malicious trader registers an arbitrary root perfectly consistently, and the register accepts it.

ValidatedEconomicRoot therefore has a private field and no public constructor taking a RegisteredEconomicRoot: no From, no into_validated, no assume_valid. accepted_root != valid_root is the load-bearing claim of the whole economic design, and a convenience conversion added later "just for this call site" is exactly how it would stop being true. Making the coercion unwritable costs nothing and deletes the failure mode.

No successor constructor, on purpose

Advancing a validated root requires the local acceptance substrate to verify and to bind the same operation_digest as the witness. Both are 3.4. Without the shared digest a trader presents a valid successor and a valid economic transition describing different operations, so a constructor missing that check would be strictly worse than none.

Nothing in the module can produce position k+1. The full predicate is written out in the module docs with the two missing conditions marked, so the gap is legible rather than implied.

Two separate properties, not one

K_root                identity-scopes the cell
claimant attribution  prevents third-party preemption of that cell

K_root = H(tag ‖ G ‖ DevID ‖ u64_be(position)) gives each identity its own coordinate space. It confers no exclusivity: every input is public, so K_root(G_victim, D_victim, k) is derivable by anyone, and write-once storage means one accepted value there burns the position forever. What makes that write impossible is the member-side attribution check — claimant_public_key and trader_devid must be the authenticated caller's — and that is only as strong as the P0–P6 proof of the caller's key and device.

An earlier draft of these docs credited K_root with the anti-squatting property. That was wrong, and wrong in a way worth naming: crediting the wrong component makes the real gate look redundant, which is how a load-bearing check gets deleted by a later reader.

Register resolution fails closed

An unknown network is refused rather than defaulted — a default register is one an attacker can steer traffic into. resolve_for_trader additionally refuses a network mismatch, which is what stops a trader minting a genesis under another network whose profile names a different register and presenting roots from there as though they came from here.

Member identities live in economic::register; dlv::beta_storage_profile keeps cardinality and threshold only, and the quorum is read from it rather than restated. Two modules asserting membership would be two places for the fleet to be described differently.

Claim envelope

EconomicRootClaimV1 carries the body's exact 0x001B CCB bytes, not a nested proto mirror — a mirror would give one object two canonical forms with the signature covering only one. Strict decode → re-encode equality, because at a write-once cell two byte-different encodings of "the same" claim are two different values, and a member storing a padded one holds bytes that can never win a quorum.

Activation

The only way to obtain a ValidatedEconomicRoot. Succeeds exactly when the device holds nothing, yielding position 0 at the canonical empty root — verifier-derived, never trader-chosen. A device already holding value cannot activate: calling its current holdings position 0 would let it assert its own opening balances, which is self-rooting at the base of the lineage. Migration is future work and must never be an implicit snapshot.

Deferred to 3.4 (agreed)

Economic position persistence. A durable economic_position has no correct transition semantics until 3.4 answers when it increments, what commits atomically with it, what happens on a crash between DSM acceptance and root registration, and whether a pending position can be reused.

Verification

11 tests. Six gates mutation-proven, each with a positive control asserting the mutation edit actually landed:

Gate removed Test that goes red
network mismatch a_trader_cannot_settle_under_a_network_it_did_not_commit_to
unknown-network fail-closed an_unknown_network_fails_closed_rather_than_defaulting
claimant attribution a_member_refuses_a_claim_that_is_not_the_callers
device attribution a_member_refuses_a_claim_that_is_not_the_callers
activation clean check a_device_already_holding_value_cannot_call_its_holdings_position_zero
envelope canonical re-encode a_decodable_but_noncanonical_envelope_is_refused

The canonical check initially survived its mutation — deletable with every test still green. The test was dead, not the gate: the tamper appended 0x00, which makes prost fail to decode, so the call returned from a layer above the check and never reached it. A negative test for a strictness gate must fail at that gate; the covering input is decodable-but-non-canonical, i.e. an unknown protobuf field that prost silently skips. Added, and the mutation now goes red.

make lint also caught a real one that tests could not: clippy type_complexity on a fn-pointer array. The two halves cover genuinely different failures.

Board — both halves, on this exact tree

cargo test --locked --workspace --exclude dsm_storage_node -- --nocapture --test-threads=1
  66 suites, 3788 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."

An earlier board went green before the wording correction and the lint fix landed, and was discarded rather than reported — it described a different tree.

Step 3 sub-PR 3.3 of 6. The pure protocol coordinate and register semantics.
The durable state machine is 3.4 and is deliberately not here.

Registered is not validated
---------------------------
Writing a claim into a write-once cell establishes NON-EQUIVOCATION and
nothing else: this identity named one root at this position and can never
name a second. It says nothing about whether that root resulted from a valid
transition — a malicious trader registers an arbitrary root perfectly
consistently, and the register accepts it.

That separation is enforced by the compiler. ValidatedEconomicRoot has a
private field and NO public constructor taking a RegisteredEconomicRoot: no
From, no into_validated, no assume_valid. accepted_root != valid_root is the
load-bearing claim of the whole economic design, and a convenience conversion
added later "just for this call site" is exactly how it would stop being true.
Making the coercion unwritable costs nothing.

No successor constructor
------------------------
Advancing a validated root requires the local acceptance substrate to verify
AND to bind the same operation_digest as the witness. Both are 3.4. Without
the shared digest a trader presents a valid successor and a valid economic
transition DESCRIBING DIFFERENT OPERATIONS, so a constructor missing that
check would be strictly worse than none. Nothing in the module can produce
position k+1, so nothing can claim validation it has not performed. The full
predicate is in the module docs with the two missing conditions marked.

Two separate properties, not one
--------------------------------
    K_root                identity-scopes the cell
    claimant attribution  prevents third-party preemption of that cell

K_root = H(tag || G || DevID || u64_be(position)) gives each identity its own
coordinate space. It confers NO exclusivity: every input is public, so
K_root(G_victim, D_victim, k) is derivable by anyone, and the register being
write-once means one accepted value there burns the position forever. What
makes that write impossible is the member-side attribution check —
claimant_public_key and trader_devid must be the authenticated caller's — and
that is only as strong as the P0-P6 proof of the caller's key and device.

Register resolution fails closed
--------------------------------
An unknown network is refused rather than defaulted: a default register is one
an attacker can steer traffic into. resolve_for_trader additionally refuses a
network mismatch, which is what stops a trader minting a genesis under another
network whose profile names a different register. Member IDENTITIES live here;
dlv::beta_storage_profile keeps cardinality and threshold only, and the quorum
is READ from it rather than restated.

Claim envelope
--------------
EconomicRootClaimV1 carries the body's EXACT 0x001B CCB bytes, not a nested
proto mirror — a mirror would give one object two canonical forms with the
signature covering only one. Strict decode -> re-encode equality, because at a
write-once cell two byte-different encodings of "the same" claim are two
different values.

Activation
----------
The only way to obtain a ValidatedEconomicRoot. Succeeds exactly when the
device holds nothing, yielding position 0 at the canonical empty root. A
device already holding value cannot activate: calling its current holdings
position 0 would let it assert its own opening balances, which is self-rooting
at the base of the lineage. Migration is future work and must never be an
implicit snapshot.

Verification
------------
11 tests. Six gates mutation-proven, each with a positive control asserting
the mutation edit landed:

  network mismatch      -> a_trader_cannot_settle_under_a_network_it_did_not_
                           commit_to
  unknown network       -> an_unknown_network_fails_closed_rather_than_
                           defaulting
  claimant attribution  -> a_member_refuses_a_claim_that_is_not_the_callers
  device attribution    -> a_member_refuses_a_claim_that_is_not_the_callers
  activation clean      -> a_device_already_holding_value_cannot_call_its_
                           holdings_position_zero
  envelope canonical    -> a_decodable_but_noncanonical_envelope_is_refused

The canonical check initially SURVIVED its mutation. The test was dead, not
the gate: the tamper appended 0x00, which makes prost fail to decode, so the
call returned from a layer above the check. A negative test for a strictness
gate must fail AT that gate — the covering input is decodable but
non-canonical, i.e. an unknown protobuf field that prost silently skips.

Board: 66 suites, 3788 passed, 0 failed, 19 ignored, exit 0.
make lint: exit 0, 0 errors.
@cryptskii
cryptskii merged commit 98788b5 into main Aug 25, 2026
15 checks passed
@cryptskii
cryptskii deleted the feat/economic-lineage-register branch August 25, 2026 07:39
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