feat(economic): class 0x0029 AuthorizedIssuanceV1 — the missing source predicate - #748
Merged
Merged
Conversation
…e predicate A user-created asset had no way for its supply to enter a validated lineage. Every credit must name a funded source, and the arms that existed all sourced value that already existed elsewhere. `token.mint` filled the gap by signing its own authorization with the caller's device key, which is a caller asserting an authorization rather than holding one. 0x0029 replaces that with a policy-authorized issuance the verifier reruns from scratch. There is no caller-supplied `policy_passed`. The 0x0023 arm is a conjunction over the AUTHENTICATED operation, never the descriptor: the verified op is a Mint/CreateToken for non-zero units; the evidence bundle resolves by exact content address and re-hashes; its carried policy bytes re-hash to the OPERATION's policy_commit; the body states this issuance, lineage, position and op digest; the committed policy parses and permits THIS issuance; k distinct signers match the policy-named set BEFORE their signatures are verified; asset and amount equal what the authorization names. The signatures cover the operation's own digest, so they cannot ride inside it. A Mint carrying a non-empty proof_of_authorization is refused, keeping the evidence bundle the only channel instead of leaving an ignored one beside it. The V1 matrix refuses rather than ignores: a finite SupplyCap (its circulating-supply input is per-device, so N authorized devices would each mint to the ceiling), an authority its own signer set cannot satisfy, a policy with mint/burn disabled, and a recipient device outside a committed allowlist — the authenticated DevID, never a caller-chosen `authorized_by`. It reads the committed v3 blob, the exact form policy_commit commits; there is deliberately no second matrix over the PolicyCondition model, whose conditions that blob cannot encode. Capped creation is refused at token.create, after request validation and before any side effect — no anchor, no registry row, no ERA fee debit, no advance. A policy is immutable once anchored, so anchoring one whose supply can never enter R_econ would create an asset that looks supported and is permanently unissuable, discoverable only at mint time. The finite-cap encoding and its parser are left intact; this is a capability fence. Consume-once is proven by replaying the IDENTICAL bytes at the next economic position with the operation digest held constant, so the test passes on the position binding rather than on a differing digest. Three mutation controls run red and restored: policy-key matching, the position binding, and the capped-creation gate. The initial-supply guard is DIAGNOSTIC and its doc says so — the policy parser, the CreateToken write-set rule and the accepting layer each already refuse supply-at-creation, so removing the guard costs the caller its reason, not the refusal. Five absence-asserting tests fired as designed and are re-cut to the narrower property that replaced the absence. Both IssuancePredicateUndefined variants are deleted (an unconstructed public variant raises no dead_code warning), and CreateTokenInitialSupplyRequiresIssuancePredicate — still correct, for a new reason — has its message corrected. The uncalled condition-level matrix is deleted rather than left as a parallel path. Boards on the final tree: workspace 3918/0 across 72 suites, node 270/0, make lint exit 0, production safety checks exit 0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Class
0x0029is the source predicate that was missing. Before this, auser-created asset had no way for its supply to enter a validated lineage:
every credit needs a funded source, and the only arms that existed sourced
value that already existed somewhere else.
token.mintfilled that gap bysigning its own authorization with the caller's device key — which is not an
authorization, it is a caller asserting one.
0x0029replaces that with a policy-authorized issuance the verifier rerunsfrom scratch. There is no caller-supplied
policy_passed.What proves an issuance
The
0x0023arm is a nine-step conjunction over the AUTHENTICATED operation,never the descriptor:
Mint/CreateTokenfor non-zero unitspolicy_commitkdistinct signers, each matched against the POLICY-NAMED set BEFORE itssignature is verified
The signatures cover the operation's own digest, so they cannot live inside
the operation. A
Mintcarrying a non-emptyproof_of_authorizationisrefused outright, keeping the evidence bundle the only channel rather than
leaving an ignored one beside it.
What the V1 matrix refuses rather than ignores
A finite
SupplyCapis refused: its circulating-supply input is derivedper-device, so N authorized devices would each mint to the ceiling. An
authority whose own signer set cannot meet its threshold is refused. A policy
with
mint_burn_enabled == falseis refused. A non-emptyallowlist_device_idsbinds the DEVICE RECEIVING the credit — theauthenticated DevID, never an
authorized_bybyte string the caller chose.The matrix reads the committed v3 blob, the exact form
policy_commitcommits. There is deliberately no second matrix over the richer
PolicyConditionmodel: the blob cannot encode those conditions, so a secondone would be an unreachable half that drifts unnoticed.
Capped creation is refused at the route
A token policy is immutable once anchored. Anchoring one whose positive supply
can never enter
R_econcreates an asset that looks supported and ispermanently unissuable, discoverable only at mint time. Both refusals happen
after request validation and before the first side effect — no policy anchor,
no registry row, no ERA fee debit, no advance — so a malformed capped request
still gets the precise reason it is malformed instead of a capability refusal
that hides it.
The finite-cap encoding, its parser and its policy-condition meaning are left
intact. This is a capability fence, not a redefinition.
Consume-once, proven on the binding that carries it
the_same_authorization_bytes_cannot_fund_a_second_creditreplays theIDENTICAL bytes at the next economic position with the operation digest held
constant, so it cannot pass because "the digest differs" — it passes on the
position binding, which is the thing actually doing the work.
Mutation controls (each red, then restored)
a_signature_from_an_unnamed_key_does_not_countredthe_same_authorization_bytes_cannot_fund_a_second_creditreda_capped_token_is_refused_at_creation_and_leaves_nothing_behindredOne honest note: the initial-supply guard is DIAGNOSTIC, not load-bearing.
Removing it does not make supply-at-creation possible — the policy parser, the
CreateToken write-set rule and the accepting layer each refuse it. What
removing it costs is the reason the caller is given. Its doc comment says so,
so it is not later mistaken for the fourth enforcement point.
Absence-asserting tests re-cut, not deleted
Five tests asserted the absence this work removes. Each was a deliberate
tripwire and each fired correctly; all five are re-cut to the narrower
property that replaced the absence, recording what they used to prove.
IssuancePredicateUndefinedis deleted from both enums (an unconstructedpublic variant produces no
dead_codewarning, so the sweep found these, notthe compiler), and
CreateTokenInitialSupplyRequiresIssuancePredicate— stillcorrect, for a NEW reason — has its message corrected.
A fixture sweep across eight files follows from the creation gate. Two of those
fixtures asserted on cap semantics and were re-cut rather than flipped; the
cap-scaling arithmetic is still pinned through the capped path's own refusals,
which now assert on the REASON so they cannot silently re-target to the
capability gate.
Verification