Rev2 is close. I think the remaining work reduces to one invariant:
Every value or transformation capable of changing a KYA decision must either be committed by the identity of the object being evaluated, or be explicitly declared live/external and excluded from claims of complete policy satisfaction.
Applying that gives fairly direct answers to the two open questions and exposes a few remaining closure points.
1. Binding stays per scheme
I would not make the binding kind per assertion.
If scheme S means controller-bound, assertions under S should not independently decide whether they are identity-, controller-, or instance-bound. Otherwise schemeId no longer identifies one semantic rule.
The useful split is:
- binding kind = scheme-level semantics
- binding witness = assertion-level committed state
- binding validity = resolution-time predicate
For example, a controller-bound scheme can require the assertion to commit the controller at issuance. An instance-bound scheme can commit the relevant code/model/configuration instance.
Conceptually:
valid(A, now) = active + unexpired + binding predicate satisfied
The reason this distinction matters is already present in rev2.
The text says a controller-bound assertion MUST be treated as invalid if the ERC-8004 token transfers after issuance. But KYARegistry.check() and the optional KYAPolicyRegistry.evaluate() currently do not evaluate that transfer boundary.
Concrete case:
- t0: Alice owns agent 10387.
- A controller-bound assertion gives the agent level 4.
evaluate(policy) returns true.
- t1: Alice transfers agent 10387 to Bob.
- t2: the assertion remains ACTIVE and unexpired.
A relying party implementing the binding rule returns false.
The current on-chain evaluator can still return true.
So two consumers can derive different answers to “does this subject satisfy policy P?” from the same KYA registry state.
I think v1 should resolve this explicitly in one of two ways:
Complete evaluation: an assertion carries the concrete binding witness, and a subject-profile binding resolver participates before a result can mean “policy satisfied.”
Registry-local evaluation: check() / evaluate() only establish registry-local assertion conditions and MUST NOT claim complete policy satisfaction when the declared binding predicate has not been evaluated.
I prefer the first semantic model while keeping subject-specific resolvers modular.
2. Keep the epoch MUST, but put the MUST on authority
The invariant I would preserve is:
The prover MUST NOT control the epoch that determines replay eligibility.
For the current scheme-epoch profile, the existing rule should remain normative:
current = floor(block.timestamp / epochSeconds)
and the accepted epoch remains within:
current - epochGrace <= epoch <= current
What can be generalized later is the mechanism by which a profile derives its epoch, not who gets authority to choose it.
So:
- epoch authority = MUST be system/verifier determined
- epoch mechanism = profile-defined
A later profile could use block ranges, finalized periods, or another deterministic source.
What should not become descriptor-optional is the property that the prover cannot manufacture a fresh replay domain by choosing another epoch.
3. Policy identity should commit the policy that actually executes
This looks like the largest remaining closure point.
Today registerPolicyWithRules(policyURI, policyHash, allOf) derives policyId from the owner, policyHash, and nonce, while the executable allOf rules are supplied separately.
Nothing currently proves that the executable rules correspond to the document committed by policyHash.
For example, the committed policy document could require:
while the executable rules supplied on-chain require:
An off-chain evaluator verifying policyHash gets one policy.
The on-chain evaluator executes another.
Both still refer to the same policyId.
That means the identifier does not uniquely identify the decision rule.
I think the executable projection needs a commitment of its own, e.g. a canonical rulesHash, and policyId should bind both the policy document and the exact executable projection.
Conceptually:
rulesHash = H(canonical executable rules)
policyId = H(owner, policyHash, rulesHash, nonce)
If the policy descriptor advertises an on-chain projection, it should carry the same rulesHash.
There is a second related boundary.
The general policy language supports semantics such as anchor restrictions and categorical equality, while the current on-chain Rule is effectively only schemeId + minLevel + issuers, with evaluation through level >= minLevel.
I would not make the optional evaluator much heavier.
Instead, define its supported projection precisely — for example:
- top-level
allOf
- ordered-level schemes
- issuer constraints
- no unsupported categorical/anchor semantics
and reject anything outside that subset.
The important invariant is:
One policyId must identify one policy meaning and one exact advertised executable projection.
4. The ERC-8004 bridge needs a configuration identity
Adding the bridge address to requestHash fixed ambiguity between different bridges.
There is still ambiguity between different configurations of the same bridge.
The operator can change trustedIssuers and responseMap, while the current response commitment binds the assertion, level, and issuers but not the response mapping.
Consider an assertion A that resolves to level 2.
Configuration C0:
- issuers = [I]
- responseMap = [0, 50, 100]
- sync produces response 100
Configuration C1:
- issuers = [I]
- responseMap = [0, 20, 70]
- sync produces response 70
The request can remain the same.
The assertion remains the same.
The existing response commitment can remain the same.
But the projected ERC-8004 conclusion changes.
Repeated ERC-8004 validation responses are not the problem. The issue is that the interpretation producing the result changed without changing its committed identity.
I would give the bridge configuration its own collision-resistant commitment, something equivalent to:
configHash = H(schemeId, trustedIssuers, responseMap)
Then bind that configuration identity into the request and response provenance.
Changing issuer authority or level-to-ERC-8004 interpretation then creates a new interpretation identity rather than silently changing what an existing request means.
5. The shortened ERC-8004 tag should not become a semantic collision point
The current bridge uses "kya:" + first 8 hex characters of schemeId.
That leaves only a 32-bit discriminator.
Two distinct schemes can intentionally share that prefix and therefore share the same ERC-8004 tag.
If a consumer aggregates by bridge + tag, semantically different KYA schemes can become indistinguishable.
I would use the complete schemeId, or another collision-resistant identifier derived from the complete scheme/configuration identity.
6. Semantic immutability also constrains verifier behavior
Rev2 establishes the right principle: one schemeId should denote one meaning, and semantic changes require a successor scheme.
But a PROVED scheme currently commits a verifier address.
If that address is an upgradeable proxy, the address can remain constant while verify() changes behavior.
Then the same schemeId has acquired new proof-admission semantics without becoming a new scheme.
I would make the invariant normative:
Every verifier code path, verification key, issuer set, configuration, or other dependency capable of changing proof acceptance for an existing schemeId MUST remain semantically immutable for that scheme. Any semantic change MUST use a successor scheme.
Upgradeable infrastructure can still exist. It simply cannot retroactively change the meaning of an already-registered scheme.
The current immutable reference adapter already has the right shape.
7. Chain and registry domain separation should be mandatory for canonical ZK admission
Nullifiers are consumed within one registry.
If a proof does not bind its chain and KYA registry, a second registry has an independent nullifier set and can admit the same proof.
For the canonical ZK-KYA registry-admission profile, I would therefore make chain + registry domain separation a MUST.
If proof portability across registries is desirable later, that should be an explicit portability profile rather than accidental replay caused by an omitted domain.
8. Per-action judgment should stay separate from durable KYA state
On the adjacent per-action-verdict question, I think the boundary is clean.
A per-action judgment asks:
Was action E correct under procedure P?
A KYA assertion asks:
What trust conclusion currently holds about subject A under scheme S?
Those should compose, not collapse.
A per-action judgment can be evidence consumed by a KYA scheme.
A KYA policy can also constrain who is eligible to issue or adjudicate such a judgment.
But the event verdict itself should not become durable subject-trust state merely by encoding its result as a level.
Regression cases
Before considering the semantic boundary closed, I would add six regression cases:
-
Policy projection mismatch — a strict committed policy and weaker executable rules cannot share one policyId.
-
Controller transfer — a controller-bound assertion cannot produce a complete policy-satisfied result after transfer unless binding validity was actually evaluated.
-
Bridge configuration mutation — changing trusted issuers or responseMap changes the committed interpretation identity.
-
Verifier semantic mutation — an existing schemeId cannot acquire different proof-admission behavior through verifier mutation.
-
Tag collision — distinct scheme IDs sharing the same shortened prefix remain distinguishable in the ERC-8004 projection.
-
Cross-registry replay — a canonical ZK-KYA proof bound to registry A cannot be admitted into registry B.
If those invariants hold, I think the model closes cleanly:
schemeId → one semantic rule
- assertion → one subject + one concrete binding state
policyId → one policy + one exact executable projection
- proof → one authorized replay domain
- bridge request → one exact projection configuration
- response → one traceable consequence of those commitments
That keeps the design where I think it is strongest: KYA remains a container for trust semantics rather than trying to standardize trust itself, while preventing an executable path from exercising authority that its committed identity did not already name.