ERC-8419: Know-Your-Agent (KYA) Framework

Assigned ERC-8419 (2026-09-20). Latest: revision 4 — see the replies below; current Sepolia addresses are in the repo README.

PR: ethereum/ERCs#2012 · Repo / reference implementation: garyyang-finchip/kya-standard · Status: Draft, ERC-8419

What this proposes

ERC-8004 gives agents portable identity and a place to accumulate raw trust signals (Reputation, Validation). It deliberately stops short of trust conclusions — a shared way to say “this agent was checked under principle X, by issuer Y, reached level L, valid until T, not revoked.”

This ERC standardises the container for such conclusions, not the conclusions themselves:

  • Scheme Registry — registers a KYA Scheme: an addressable, versioned, freezable pointer to a descriptor that says what is checked, how the result is expressed as a uint8 level, which evidence kinds are admissible, and how assertions are admitted (ATTESTED by an issuer, or PROVED by a verifier contract).
  • KYA Registry — records Assertions (subjectKey, schemeId, issuer, level, claimDigest, issuedAt, expiresAt, evidenceHash, status) with per-issuer supersession and revocation, and resolves them: resolve(subject, schemeId, issuers[]) / check(subject, schemeId, minLevel, issuers[]). issuers must be non-empty — same Sybil rationale as 8004’s getSummary requiring clientAddresses.
  • ZK-KYA profile — not a second registry. attestWithProof(subject, schemeId, publicInputs, proof) calls the scheme’s IKYAVerifier, which returns (ok, subjectKey, nullifier, level, claimDigest, expiresAt). The registry enforces subject binding and nullifier consumption and records the assertion with issuer = verifier. Any proving system enters via an adapter; the spec fixes only the public-input layout (kya-public-v1). Proofs can also be presented ephemerally in the handshake without ever touching the chain.
  • Handshake — EIP-712 KYAChallenge / KYAPresentation, /.well-known/kya.json discovery; mutual KYA is two exchanges.
  • Policy (minimal) — registerPolicy(uri, hash) gives relying-party requirements an id that can be named in a challenge; on-chain evaluation is optional.
  • ERC-8004 binding — erc8004 is the MUST-support subject type (chainId, identityRegistry, agentId). Optional supportedTrust: ["kya","zk-kya"], a KYA services entry, a "kya" metadata key, and a KYA Bridge that acts as an 8004 validator and mirrors levels to 0–100 under tag = "kya:<8 hex of schemeId>", so 8004-only clients see KYA outcomes with zero changes to 8004.

The framework never defines a KYA algorithm or a credit rule. That is the point: like 8004 with reputation math, fixing rules on-chain would be obsolete before Final; a container for rules is not.

What is in the repo

Full ERC text; reference contracts (KYASchemeRegistry, KYARegistry, KYAPolicyRegistry, KYABridge8004, a Groth16→IKYAVerifier adapter); JSON Schemas for scheme / policy / discovery documents; deterministic vectors (ids, EIP-712 digests, ERC-165 ids); a 17-case end-to-end suite on an in-process EVM covering attested and proved flows, revocation/expiry, policy evaluation and the full 8004 bridge round-trip.

Where I would most like feedback

  1. Subject abstraction — is (subjectType, subjectData) with erc8004 as MUST and account / erc721 / did as optional the right cut, or should v1 be 8004-only?
  2. Verifier-as-issuer in proved mode — relying parties trust a verifier address the same way they trust an attester. Is that vocabulary acceptable, or should proved assertions carry a separate verifier field?
  3. Bridge design — a curated validator with operator-configured issuers and level→0–100 map, deterministic requestHash bound to (agentId, schemeId). Is mirroring into Validation (not Reputation) the right choice?
  4. Nullifier scoping — scheme vs scheme-epoch; should domain separation (chainId + registry) be MUST rather than SHOULD?
  5. Relationship to ERC-8143 and to generic attestation services — this draft positions itself as the agent-KYA semantic layer that can sit on top of either. Push back welcome.

Prior work from the same author: ERC-8338 (Token-Bound Executable Skills), ERC-8414 (Token-Bound Task Tenders). Those appear here only as informative use cases; nothing in this ERC depends on them.

2 Likes

Update: revision 2 of the draft after an external design review, plus a live Sepolia reference deployment

Since the OP we had the whole design reviewed with an adversarial eye. Several points were right and are now in the text and the reference implementation (PR updated in place; the kya-standard repo is in sync). The short version of what changed, and why:

What the framework is (and is not). KYA is a registry for trust conclusions about agents — not an identity system, not a reputation score, not an anonymity layer. Each conclusion is addressed by a scheme whose semantics are now immutable per schemeId (only the descriptor URI can be re-pointed; any semantic change is a new scheme with predecessor). Every scheme declares what it binds to — the identity record, the current controller, or a specific instance — because an ERC-8004 agent is a transferable ERC-721 and “this agent is accountable” does not automatically survive a sale. Relying parties get a rule they can implement (check for transfers since issuedAt); the registry stays ignorant of ERC-721 mechanics.

level is still a uint8, but ordering is now a profile. resolve’s “highest wins”, check’s minLevel and the bridge’s responseMap all assume a total order. That assumption is isolated as the Ordered-Level Profile (result.kind = "ordered-level"); pass/fail and categorical results use level as a code or leave it 0 with the result committed in claimDigest.

ZK-KYA, stated precisely. The profile hides the fact issuer and the underlying facts. It does not hide the subject — subjectKey is public — and we no longer describe it as anonymous. Two roles are now explicit: the fact issuer (attestor, never named on-chain) and the admitting verifier (recorded as issuer); the assertion carries an anchor (for the reference circuit, the issuer-set root) so relying parties pin (verifier, anchor) rather than an address alone. Three concrete fixes came out of the review:

  • the attestor’s credential now includes schemeId, and the circuit constrains it to the adapter’s argument — otherwise a credential issued under a lenient scheme was presentable under a strict one that shares attestors (cross-scheme replay);
  • the epoch is enforced on-chain from block.timestamp (current − grace ≤ epoch ≤ current), not supplied by the prover — otherwise “re-prove per epoch” was “mint a fresh nullifier at will”;
  • the 256-bit → (hi, lo) split uses an alias-checked decomposition, so a nullifier has exactly one encoding.

Bridge. Explicitly an optional, lossy snapshot of the KYA Registry: requestHash now includes the bridge address, a level outside the response map makes sync revert instead of guessing upward, and the text says plainly that a transfer after sync leaves a stale mirror until someone re-syncs.

Handshake. Acceptance rules are normative now (single-use challenge, presented assertions must belong to the challenge’s schemeIds, policy decides partial satisfaction, empty presentation only satisfies an empty challenge). The policy interface is split into a required base and an optional on-chain evaluator, discoverable via ERC-165.

Sepolia (revision 2), against the official ERC-8004 IdentityRegistry 0x8004A818…BD9e, agentId 10387

contract address
KYASchemeRegistry 0xAc7B642a5760467178F20c984f02cc72CCCa8E3f
KYARegistry 0xeBf357c87639aCcC0DaA505fAAf3F6E9b079AB9f
KYAPolicyRegistry 0x9B53BAD4346AacB34Bac328F1d856dfa2F246cfF
ValidationRegistry8004 (spec-conforming stand-in; canonical one not yet deployed) 0x4aD87d7D55C753c53A801ca9a23495fb4f9bAd53
KYABridge8004 0xCcd683927d1fC03A0163B3e9FbE7fC44afFbA5e4
Groth16Verifier (circuit rev 2, test ceremony) 0xBaE26c2a9b23c37Da76c4905F9F09004dFDBc7a9
Groth16KYAVerifierAdapter (30-day epochs, grace 1, pinned issuer-set root) 0xbC2E1A0FF358B64288a7D4157B996C72b6Ad44cD

Worked examples, every one read back from chain: setMetadata(10387, "kya", …) on the official registry (tx) · attested scheme, attest(level 2) → check(≥2) true (tx) · attestWithProof with a real Groth16 proof (scheme-bound credential, epoch 690 enforced by the adapter, recorded anchor = the pinned issuer-set root; which of the three demo attestors signed is not recoverable on-chain) → check(≥4, [adapter]) true (tx) · policy allOf[attested ≥ 2, proved ≥ 4], evaluate() true (tx) · ERC-8004 bridge: agent owner files validationRequest with the deterministic requestHash, anyone calls sync, Validation Registry reports 60/100 under tag kya:3673f050, getSummary(10387, [bridge], tag) → count 1 / average 60 — request tx · sync tx

Everything — tx hashes, the proof, public signals, descriptors, and the superseded revision-1 deployment for the record — is in deployments/ of garyyang-finchip/kya-standard. npm test runs 18 end-to-end cases on an in-process EVM; npm run test:zk runs 14 more with the real circuit, including the adversarial ones (prover-chosen future/stale epoch, cross-scheme replay, tampered signals, aliased encodings, attestor outside the pinned set). The Groth16 keys are a single-party test ceremony and say so everywhere.

Two things we would still like input on: whether binding should be a per-scheme declaration (current) or a per-assertion field, and whether the epoch window rule belongs in the ERC text as MUST for every epoch-scoped scheme (current) or should be left to the verifier’s descriptor.

1 Like

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:

  • scheme X >= 4
  • issuer A

while the executable rules supplied on-chain require:

  • scheme X >= 1
  • issuer A

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:

  1. Policy projection mismatch — a strict committed policy and weaker executable rules cannot share one policyId.

  2. Controller transfer — a controller-bound assertion cannot produce a complete policy-satisfied result after transfer unless binding validity was actually evaluated.

  3. Bridge configuration mutation — changing trusted issuers or responseMap changes the committed interpretation identity.

  4. Verifier semantic mutation — an existing schemeId cannot acquire different proof-admission behavior through verifier mutation.

  5. Tag collision — distinct scheme IDs sharing the same shortened prefix remain distinguishable in the ERC-8004 projection.

  6. 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.

1 Like

Clarification: the ZK profile covers two usage patterns, and the text now says so

Re-reading the draft, §5 described ZK-KYA as if every proved scheme were a credential: a hidden fact issuer signs a verdict in advance, and the proof transports it privately. That is what the reference circuit does (hence issuerSetRoot, anchor, issuerPolicy: verifier-only), but it is one usage, not the definition. Three things have been clarified in the PR, interfaces unchanged:

  1. Predicate pattern added (§5.1, informative). The scheme can be the decision rule: a relying party (or a community agreeing an underwriting standard) publishes its rule as a circuit or zkVM program and deploys the verifier; the counterparty evaluates the rule over its own authenticated private data inside the proof and presents only the verdict. No third party issues a credit verdict beforehand, and the relying party is never sent the raw data. The two patterns combine — one circuit can check source-signed data and evaluate the rule over it. The section is explicit about what this does not give you: the protocol does not destroy data or verify that anyone deletes proofs; inputs must be authenticated (storage/receipt proofs against a block hash, zkTLS-style notarised transcripts, or source-signed data, in decreasing order of trustlessness, with anchor committing to the source); and authenticity is not completeness, so a verdict is “satisfies rule P over the named sources, window and coverage”, never an unqualified “is creditworthy”.

  2. Scope of the scheme-binding MUST narrowed. The draft required that “whatever the fact issuer signs” include the schemeId. That is right for a signed object that carries a pre-made verdict — it is what stops a level issued under a lenient scheme being presented under a strict one — but wrong for signed inputs (a bank statement, an exchange export) that carry no verdict and legitimately predate any scheme. The rule now reads: the proof’s statement MUST be bound to the verifier’s schemeId (adapters feed it as a public input); additionally, verdict-carrying credentials MUST embed it. Signed inputs are exempt from the second half; their binding to the rule is the computation itself. This is a semantic clarification with no ABI change, but it does change the applicability of a MUST, so flagging it rather than calling it editorial.

  3. issuer in PROVED mode restated. It identifies the admitting verifier — which verification logic accepted the proof — not the source of the facts and not a guarantor. What stands behind the verifier differs by pattern and is what anchor commits to (attestor set in the credential pattern; block hash / notary set / data-signer set in the predicate pattern). Relying parties pin (verifier, anchor).

Also added to Security Considerations: query composition. A zero-knowledge proof bounds what one answer reveals, not what a sequence reveals (“above 100? above 50? above 75?” recovers a balance from perfectly ZK answers). The prover’s disclosure decision is the scheme’s outputs, parameters and query shape, and the set of schemes it will answer for one counterparty — not the proof system — and an audit of an immutable scheme is a judgement at a point in time, not a permanent one.

What has not changed and is not claimed: the reference circuit still demonstrates only the credential pattern; there is no predicate-pattern circuit yet; and there is no mechanism for a relying party to ship an ad-hoc rule inside a challenge. That last one is noted as a future extension — it needs a binding among request, program, parameters and data scope (a program hash alone is insufficient), and adding a member to KYAChallenge changes its EIP-712 type hash, so it would be a new message type or versioned extension rather than a compatible edit. Views on whether that belongs in this draft or a follow-up are welcome.

@chugarchugarr Thanks, this is a useful and concrete review. Your emphasis on keeping committed semantics aligned with executable behavior fits the intended scope of KYA: a framework for expressing and verifying trust conclusions, rather than a standard that dictates trust itself.

The split between scheme-level binding kind, assertion-level binding witness, and resolution-time binding validity makes sense. I agree that a registry-local check must not be presented as complete policy satisfaction when required binding predicates or other policy conditions have not been evaluated.

The policy-projection and bridge-configuration cases also identify important consistency requirements. For policies, committing the executable rules is only part of the solution: the supported projection and its correspondence to the declared policy must also be defined. Two committed hashes alone do not establish semantic equivalence. Unsupported conditions must not silently disappear from an evaluation that claims to satisfy the full policy.

One point I would refine is the distinction between immutable semantics and explicitly authorized live state. A scheme may intentionally depend on changing revocation status or authenticated chain state under fixed, declared rules. Such state updates should not automatically require a successor scheme. Changing the meaning, authority, or interpretation of those rules is different. Complete satisfaction should require evaluation of all necessary conditions, including declared live dependencies; otherwise the result should be explicitly scoped as partial or registry-local.

For epochs, I agree that replay eligibility must be verifier-validated under the profile’s defined authority, rather than created by an unconstrained prover-selected value. I also agree with explicit chain and registry domain separation for the canonical registry-admission profile. That should remain distinct from ephemeral peer-to-peer predicate presentations, whose request and session context needs its own explicit binding.

The separation between per-action verdicts and subject-trust assertions is consistent with the framework’s scope: they should compose through declared scheme semantics, not become interchangeable merely by encoding a verdict as a level.

The implementation-specific cases need to be checked against the current PR revision before being marked resolved. Your proposed regression cases provide a concrete checklist for that review. The follow-up should distinguish clarified requirements from changes actually implemented and tested.

Checked rev3 against the regression cases. Most of what I raised looks implemented now rather than just clarified. I think there are two boundaries left that are worth checking.

The first is the registry replay boundary. The new schemeId binds the chain and SchemeRegistry, so I think the current test correctly closes replay between two independently deployed SchemeRegistries. What I am having trouble reconciling is that the state which actually consumes the nullifier lives one level lower, inside KYARegistry.

Suppose one SchemeRegistry S has two KYARegistry instances A and B pointing to it. The scheme is the same in both, so the verifier receives the same schemeId. The proof and nullifier can also be identical. But A and B each maintain their own (schemeId, nullifier) consumption map. A proof consumed in A therefore does not appear consumed in B.

That seems to mean the current regression proves SchemeRegistry-domain separation, but not necessarily KYARegistry admission-domain separation. If the intended claim is that one proof can only be admitted once per KYA registry domain, I think the KYARegistry that actually consumes the nullifier has to enter that domain somewhere, unless the architecture makes one KYARegistry per SchemeRegistry an invariant.

The other boundary is the bridge configuration. I agree with committing configHash; I’m less sure it belongs in requestHash.

If C0 produces response 100 and then a legitimate reconfiguration to C1 produces response 0, putting configHash in the request gives us two different ERC-8004 requests, R0 and R1, under the same bridge and kya:S tag. getSummary(agent, [bridge], “kya:S”) then sees both records. In the reference aggregation that can produce count 2 / average 50 even though the current KYA projection is 0.

That seems different from the semantic-versioning case. The KYA scheme did not become a different object. The bridge’s authorized live interpretation changed.

So I think the cleaner boundary may be to keep the request stable as the continuing mirror of (bridge, agent, scheme), and move the configuration commitment into the response provenance:

requestHash = H(REQUEST_TYPE, chainId, identityRegistry, bridge, agentId, schemeId)

responseHash = H(assertionId, level, configHash)

Then validationResponse() can update the same ERC-8004 record as the live interpretation changes, while each response still proves exactly which bridge configuration produced it. That also seems consistent with the distinction you made above: immutable semantics do not require freezing authorized live state, but the consequence of that live state should identify what produced it. Those are the only two cases I still see as open.

Revision 3 is in the PR — what changed for each point of your first review

@chugarchugarr thanks for checking rev3 against the regression cases directly. You have already verified most of this yourself, so this post is mainly the point-by-point record for other readers and for the editors. Your two follow-up boundaries (the registry replay domain, and whether configHash belongs in requestHash) touch points 7 and 4 below. I am not answering them in this post; I want to work both through properly and will come back on them separately.

The invariant you stated — 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 — is now the organising rule for §3, §4, §7 and §8. Here is what changed per point, what was clarified in text only, and what I deliberately did not do. Everything below is implemented and tested (test/kya.test.js 24 cases including the six regression cases R1–R6; test/zk.test.js 15) and redeployed on Sepolia (addresses in the repo README).

1. Binding — kind per scheme, witness per assertion, predicate at resolution. Adopted as you framed it, with the complete evaluation model. binding is now an on-chain field of Scheme (registerScheme takes it; immutable). Each assertion stores a bindingWitness captured at issuance: for controller-bound schemes on erc8004 subjects the registry itself reads ownerOf(agentId) from the identity registry named in subjectData and stores keccak256(abi.encode(owner)); instance uses claimDigest; identity stores nothing. resolve/check (and therefore evaluate) now evaluate the predicate and exclude VIOLATED or UNEVALUABLE assertions, so a non-zero result means status, expiry and binding were evaluated. A new resolveLocal is the registry-local view and is specified as MUST NOT be presented as complete satisfaction; bindingStatus(assertionId) exposes the predicate. Where the registry cannot evaluate a controller binding (foreign-chain subject, other subject types without a resolver) it refuses to record the assertion rather than record one it can never check. R2 reproduces your t0/t1/t2 case: after the transfer, check and evaluate are false while resolveLocal still returns the assertion; a re-attestation to the new controller carries the new witness and satisfies again. Subject-type resolvers beyond erc8004 are left modular for a follow-up.

2. Epoch — MUST on authority, mechanism profile-defined. Text change only: “the prover MUST NOT control the epoch that determines replay eligibility; the verifier MUST derive it from system state” is the invariant; floor(block.timestamp / epochSeconds) with [current − grace, current] remains normative for the scheme-epoch profile; future profiles may derive it differently but the authority rule is not descriptor-optional.

3. Policy identity commits the executable projection. policyId = keccak256(abi.encode(owner, policyHash, rulesHash, nonce)) with rulesHash = keccak256(abi.encode(Rule[] allOf)) (0x0 when no projection). The supported projection is defined exactly — top-level allOf over ordered-level schemes with minLevel and non-empty issuers — and registerPolicyWithRules rejects empty rule sets and empty issuer lists; nothing else is expressible through the interface. The descriptor schema gained onchain: {evaluator, rulesHash, sufficient} so a document that also contains anyOf, categorical equality or anchors must declare sufficient: false. One refinement to your framing that I kept explicit in the text: two committed hashes do not by themselves establish semantic equivalence — a strict document can still be paired with a lenient projection under a fresh id — so the spec states that the correspondence is the descriptor author’s claim, verifiable by comparing the document’s top-level allOf with getRules, and that unsupported conditions must not silently disappear from an evaluation claiming full satisfaction. R1 covers the id separation.

4. Bridge configuration identity. configHash = keccak256(abi.encode(schemeId, trustedIssuers, responseMap)); it enters requestHash (… bridge, configHash, agentId, schemeId) and responseHash = keccak256(abi.encode(assertionId, level, configHash)). Reconfiguring yields a new configHash, hence a new requestHash; a request filed under the old configuration can no longer be synced and its recorded response stays exactly as committed. sync uses complete resolution, so a binding-violated assertion drives the mirror to responseMap[0] on the next sync. R3 covers both the map change and the issuer-set change. This describes rev3 as it stands; your follow-up on requestHash is open (see top).

5. Tag. Full schemeId ("kya:" + 64 hex, 68 bytes). R5 constructs two ids sharing the old 32-bit prefix and checks the tags differ.

6. Verifier immutability — with one refinement. Normative now: every code path, verification key, pinned issuer set or other dependency capable of changing proof admission MUST be semantically immutable for the life of a schemeId; upgradeable proxies and verifiers reading admission parameters from undeclared mutable storage MUST NOT be registered. Mechanically, the registry pins the verifier’s EXTCODEHASH at registration and refuses admission when the code at the address differs; the text is honest that this catches redeployment behind an address but not delegation, which is why the normative rule exists in addition. The refinement: I did not write “all state capable of changing acceptance must be frozen”. A scheme may intentionally depend on state that changes under rules it declares — a revocation source, an issuer set with a declared rotation mechanism, authenticated chain state — and such updates are part of the scheme’s meaning, not a change of it. What must not change under one schemeId is the meaning, the authority or the interpretation of those rules. Complete satisfaction then requires evaluating those declared live dependencies; otherwise the result is scoped as partial. R4 checks that a changed code hash is refused and documents the proxy limit.

7. Chain + registry domain separation — MUST for registry admission, by construction. schemeId = keccak256(abi.encode(chainId, registry, controller, schemeHash, nonce)). Since adapters already feed schemeId into the circuit as a public input, a proof for one registry’s scheme is not valid for another’s even with identical controller, descriptor and verifier, and nullifier scopes are disjoint by design rather than by accident — no circuit change was needed. The text scopes this to registry admission; ephemeral peer-to-peer predicate presentations are bound to a request, not a registry, and portability across registries, if ever wanted, is an explicit profile. R6 (in-process) and a real-circuit case in the ZK suite cover it. This describes rev3 as it stands; your follow-up on the SchemeRegistry / KYARegistry boundary is open (see top).

8. Per-action judgment. Agreed; unchanged: verdicts compose as evidence for schemes, they do not become durable subject-trust state by being encoded as a level.

Interface ids changed accordingly (IKYASchemeRegistry 0x50cb46d8, IKYARegistry 0x9cdd4647, IKYAPolicyEvaluator 0x173ff2b6; IKYAPolicyRegistry and IKYAVerifier unchanged). What remains open: the two boundaries from your follow-up, which get their own reply; and from your original list, resolvers for non-erc8004 subject types, and whether complete evaluation should be extensible to declared live dependencies beyond binding (revocation sources) through the same predicate mechanism. The latter two are follow-ups rather than v1 blockers, but I would take a view on the second.

1 Like

@chugarchugarr both boundaries are closed in revision 4 (PR updated in place, repo in sync, Sepolia redeployed — addresses in the repo README). Thank you for running the cases rather than reading the summary; both held exactly as you described them. The second one is adopted as you wrote it. For the first one I took a different shape than “fold the admitting registry into schemeId”, and I want to say why, because it is a design choice rather than the only fix.

Registry replay domain. You were right: rev3 bound the proof to the SchemeRegistry namespace while the nullifier is consumed one level lower, and nothing made one KYARegistry per SchemeRegistry an invariant. Putting the admitting KYARegistry into schemeId would close that with no circuit change, but it turns a scheme into “a rule for one registry”: two markets that want the same underwriting rule must register two schemes, and — since a credential in the credential pattern signs the schemeId — attestors must issue one credential per registry. That gives up the property the framework exists for. So rev4 keeps schemeId as the identity of the rule and gives the proof an explicit second context:

admissionDomain = keccak256(abi.encode(keccak256("erc-kya-registry-admission-v1"), chainId, schemeRegistry, kyaRegistry))

The admitting KYARegistry computes this from its own state (there is no calldata parameter for it) and passes it to IKYAVerifier.verify(schemeId, admissionDomain, publicInputs, proof). The reference adapter feeds it as public signals (13 → 15) and the reference circuit derives the nullifier from it: Poseidon(secret, schemeId, admissionDomain, epoch). The credential the attestor signs is unchanged and names no registry.

Two consequences, both intended and both stated in the text: a proof made for A does not verify at B — whether B shares A’s SchemeRegistry or not — and B cannot make it verify; and the same credential can be re-proved for B with a B-scoped nullifier. This is per-proof domain separation, not “one credential, one use anywhere”; a use case needing global single-use needs shared consumption state and its own profile, and the spec says so rather than letting it be inferred. Ephemeral peer-to-peer presentations use a request-bound domain instead of a registry’s.

What the regression covers, with the real Groth16 circuit end to end rather than a mock: one SchemeRegistry with two KYARegistries A and B; A’s admitted proof refused at B; a fresh proof made for B refused at A before anything has been consumed anywhere (so the refusal is the domain, not a nullifier); repeat at B refused by the nullifier rule, with a re-randomised proof; the adapter accepting the same proof under B’s domain and rejecting it under A’s; and the same credential re-proved for A admitted with a different nullifier. The rev3 two-SchemeRegistry case is kept alongside. The cost is honest: this is a public-input change, so circuit, verification key, verifier contract and vectors moved to circuit revision 3 rather than being patched, and IKYAVerifier / IKYARegistry have new interface ids (IKYASchemeRegistry is back to its rev3 id, since the rev4 draft’s registry parameter is gone).

Bridge configuration. Adopted as you wrote it:

requestHash = H(REQUEST_TYPE, chainId, identityRegistry, bridge, agentId, schemeId)
responseHash = H(assertionId, level, configHash)

The request is the continuing mirror of (bridge, agent, scheme) and its meaning is now written down as that — “keep this mirror current, naming the configuration in each response” — not a one-time judgment under a frozen configuration; the committed payload is the canonical ABI encoding, with the JSON at requestURI as a readable copy. A reconfiguration updates the same ERC-8004 record on the next sync; each response still proves which configuration produced it; configuration events, queries and the Synced event keep the commitment. The regression asserts the actual summary, not just the hashes: your 100 → 0 case reads count 1 / average 0 under getSummary(agent, [bridge], "kya:S"), requestHash unchanged, responseHash changed.

Two boundaries this leaves, stated in §8 rather than hidden: between a reconfiguration and the next sync the mirror shows the previous configuration’s result (the test checks that too); and the one-record guarantee is per bridge — a superseded bridge’s records stay in the Validation Registry under the same tag and are excluded by filtering on the current bridge, which is the query path the spec prescribes, and included only if a client lists both. The Sepolia demo is a fresh deployment for exactly that reason; the earlier ones are marked superseded in the repo.

If you see anything else open after this, I would rather hear it before a number is assigned.

1 Like

The draft has been assigned ERC-8419 (thank you @abcoathup). The PR now carries ERCS/erc-8419.md and assets/erc-8419/; the content is revision 4, unchanged apart from the number and the links that contain it.

One note for anyone verifying the Sepolia examples: the demo scheme descriptors were registered before the number was assigned, so their type URI still reads eip-9999 and their on-chain hashes are unchanged. The repo README says so.

Does a controller-bound assertion become valid again if the agent returns to its previous owner?

The implementation described above stores a hash of ownerOf(agentId) at issuance. Suppose user 1 receives an assertion and then transfers the agent to user 2. The assertion stops satisfying the binding check. User 2 later transfers it back to user 1.

Would the original assertion now pass again without a fresh assessment?

That seems to distinguish “same owner address today” from “no ownership change since assessment.” Which guarantee is intended? If any intervening transfer should invalidate the assertion then would the binding need an ownership epoch rather than just an address hash?

2 Likes

Nice piece of work, and the semantic-immutability rules are the part I’d keep hardest, pinning verifierCodehash and stating the normative rule because the hash catches redeployment but not delegation is exactly the right pair. Most proposals in this space leave the upgradeable-verifier hole wide open.

I’m one of the co-authors of ERC-8294 (Validation Network extension for ERC-8004), and I think 8419 composes with two existing proposals in ways that would be worth stating explicitly, because right now an implementer reading 8419 wouldn’t learn either exists.

1. ERC-8294 - the registry you mirror into. 8419 mirrors conclusions into ERC-8004’s Validation
Registry under a kya: tag. 8294 extends that same registry so a validatorAddress can be a network of independent validators, with operator-diversity as a policy parameter and a standardised attestation envelope. These look complementary rather than competing: 8294 is the producer side (who checked, how many independent parties, how the response is aggregated), 8419 the conclusion vocabulary side (what was concluded, under which scheme, until when). A KYA assertion produced by a conforming validation network seems like a natural fit, and it would give a PROVED-adjacent third option where the strength comes from operator diversity rather than from a proof. Worth a line either way, even if the answer is “out of scope for v1”.

2. ERC-8354, an adjacent ZK construction already in the repo. ERC-8354 (Confidential Agent
Policy Verdicts, created 2026-07-16) already does ZK-proof-in-place-of-issuer-signature for agent
verdicts over ERC-8004: a committed policy root, a verifier contract, an expiry and a single-use
nullifier. Structurally very close to your PROVED mode trusted as (verifier, anchor).

The objects differ, and I think that’s the interesting part rather than a problem: 8354 authorises
one specific proposed action pre-execution, bound to an action commitment and a permitted executor; 8419 records a standing conclusion about the agent, with a validity window. “May this action proceed?” versus “what has been concluded about this agent?” Those are the per-action and the standing halves of the same need, and saying so would help implementers who currently have to work it out.

3. A naming question, stated as a fact rather than a claim. A kya.* namespace for agent key
bindings has been public since 2026-07-30 in trustless-ai/recompute-kit kya.pq_key_binding.v0, with kya-l4-* binding identifiers, and is still live in its conformance vectors. That predates this draft by about seven weeks and is unrelated to it, which is exactly why I’d rather ask than assume: does 8419 intend kya: / kya.* as a reserved framework prefix? If it does, we should rename ours before both are deployed and the collision becomes expensive for everyone. If it doesn’t, a sentence scoping the tag to the Validation Registry bridge would prevent the ambiguity.

Happy to help on the 8294↔8419 seam specifically, a worked composition example, or conformance vectors for the mirrored-assertion path, if that’s useful to you.

@SergeevDmitry good catch, and the text currently says two different things. The predicate the registry evaluates is “the current controller equals the controller at issuedAt” — witness equality against ownerOf(agentId) read now. The table in §2.1 describes the invalidating event as “a change of controller”, which reads as “any intervening transfer”. Those are not the same guarantee, and under the implemented one a 1 → 2 → 1 round trip does make the original assertion satisfy check again without a fresh assessment. One narrowing: resolution considers each issuer’s latest assertion only, so if the issuer re-attested to user 2 in between, that assertion supersedes the original and the round trip revives nothing. Both cases are now in the regression suite (R2), so the behaviour is pinned rather than described.

The implemented semantics are the intended ones for the controller kind, and the text is tightened to say so: controller binds to who controls the subject now relative to issuance, not to an unbroken chain of custody. An ownership epoch would give the stronger property, but a KYA registry can only claim a predicate it can check, and neither ERC-721 nor the ERC-8004 identity registry as deployed today exposes a transfer count or last-transfer time that a contract can read. Relying parties that need “no transfer since issuance” establish it from the identity registry’s Transfer events off-chain; issuers of controller-bound schemes are already told to keep expiresAt short, which bounds the window in which a round trip matters. Security Considerations now states the exposure directly: an attacker who briefly acquires and returns an agent gains nothing from the round trip itself, while a seller who buys the agent back regains the conclusions issued to them, including any the interim owner’s changes may have made stale. If a later revision wants an on-chain form of the stronger binding, it belongs as a distinct binding kind rather than a change to this one.

The wording fix and the round-trip regression cases are in the PR (text and tests only — no interface, contract or deployment change).

1 Like

Thanks @TMerlini — these are useful connections, and I agree that the relationship should be clearer to implementers.

1. ERC-8294: complementary, without requiring another admission mode

I agree with the producer/conclusion distinction. My preference is to keep the admission modes separate from the mechanism that gives a conclusion its assurance.

A validation-network contract with KYA issuing support, or a scheme-specific adapter, could issue through the existing ATTESTED path. The contract actually calling attest() would be the recorded issuer. That gives us an integration point, rather than implying that every existing 8294 deployment is already a drop-in KYA issuer.

Any quorum, operator-diversity and aggregation requirements would need to be enforced by the network/adapter and made explicit in the composition. The optional on-chain KYA evaluator combines rules with allOf; a rule’s issuers[] is an accepted-issuer set, not a native quorum or operator-diversity check.

The existing erc8004-validation evidence kind also provides a place to reference the source result. The mapping should preserve its provenance and distinguish a substantive validation result from an operational failure: a timeout must not silently become a negative trust conclusion about the agent.

So I see a useful additional assurance model here, but not a need for a third admission mode in v1.

2. ERC-8354: action authorization versus subject-trust conclusions

Agreed. I would describe the boundary primarily by the object being evaluated, rather than by duration. 8354 authorizes a specific proposed action; 8419 expresses a trust conclusion about a subject. KYA also supports ephemeral peer-to-peer predicate presentations, so its conclusions need not always be standing on-chain records.

There is a privacy distinction too: 8354 explicitly protects the policy, whereas the predicate use case discussed here typically protects the subject’s underlying data.

The two could compose in either direction: a KYA conclusion could inform an action policy, and an action verdict could become evidence under a declared KYA scheme. Neither mapping should be implicit. A subject-trust conclusion is not itself an execution authorization, and one permitted action is not automatically a general trust conclusion.

3. Naming

No global reservation is intended for kya.* or kya:. The metadata, discovery, bridge-tag and optional credential-view conventions have meanings within their specified protocol contexts; they do not claim unrelated application namespaces.

There is no need to rename kya.pq_key_binding.v0 or the kya-l4-* identifiers on account of this proposal. Implementations should interpret those identifiers under their respective schemas, rather than infer compatibility or equivalent levels from the prefix. An explicit scope note is a good addition.

A small 8294→8419 composition example and test vectors would be very welcome. I suggest starting with an issue or focused PR in the kya-standard companion repository: one successful network-result-to-assertion mapping, rejection cases such as a wrong subject or an operational failure, and the mirrored result with source provenance retained.

That would let us test the integration while keeping the normative core network-agnostic and avoiding a new required mode or dependency.

Both informative notes — the action-authorization/subject-trust boundary in Motivation and the naming scope in §8 — are in the PR branch as of this reply; 8294 itself will be referenced from the text once it is merged.

1 Like

Thanks @garyyang-finchip and specifically for the §8 scope note. “Interpret those identifiers under
their respective schemas rather than infer compatibility or equivalent levels from the prefix” is
exactly the sentence that was needed, and it resolves the question better than a reservation would
have. Nothing to rename on our side, which is the outcome I was hoping for.

Both of your refinements are improvements on what I wrote, so taking them as read: describing the
8354 boundary by the object evaluated rather than by duration is sharper, my per-action/standing
split doesn’t survive your ephemeral predicate presentations, and the privacy axis is a distinction
I’d missed entirely. 8354 protects the policy; the predicate case protects the subject’s data.
Those are different things being hidden, and saying so is more useful than either framing alone.

One more connection, and this one is a convergence rather than an overlap, which I think makes it more interesting than the first two.

Your Rationale says:

Identities commit what executes. Three identifiers were tightened for the same reason: a
policyId now commits the executable projection (rulesHash), a bridge response commits the configuration that produced it (configHash), and a proof commits the admission domain that consumes it (admissionDomain). In each case the earlier derivation allowed an executable path … to exercise authority its committed identity did not name.

That is the principle ERC-8299 is named for — What You Read Is What You Execute. 8299
applies it to inference inputs: a triple-hash chain (raw_input_hash→sanitization_pipeline_hash → input_hash) so that the input a model acted on is provably the
input that was committed. You’ve applied the same rule to policy identity, and, reading the thread, arrived at it three separate times under review pressure rather than from a general principle.

I mention it because independent convergence is the strongest evidence a principle is real, and
because having a name for it is useful when the next identifier comes up: the question becomes “does this identity commit what executes?” rather than three separate catches.

There’s also a concrete composition, if you want one. 8419 defines a provenance dimension and a provenance-verified level — “origin, code or model lineage verified by a third party” and
deliberately leaves admissible evidence to scheme descriptors. An 8299 input-provenance attestation is a natural evidence kind for exactly that level: it says something checkable about what the agent actually read, which is a stronger claim than a third-party assertion that lineage was reviewed.
That needs no change to your normative core, it is a scheme descriptor someone writes.

Last, a note of recognition rather than a request: your point that “a timeout must not silently become a negative trust conclusion about the agent” is the same distinction we spend most of our conformance effort on, couldn’t check and checked and failed are different verdicts, and collapsing them is the failure mode that survives every test that doesn’t look for it. Good to see it stated as a requirement rather than left to implementers.

1 Like

I think the convergence is real. I’d only narrow one part of the composition before treating it as closed.

8299 by itself establishes the committed input-provenance chain and makes that chain recomputable/challengeable. As you put it in the 8299 thread, V1 is a commitment layer rather than an execution-proof layer. So I would be careful with saying an 8299 attestation alone proves “what the agent actually read.”

For a KYA scheme, I think the rule is the same one we just landed on for 8294 and 8354: the evidence can only authorize the conclusion it actually establishes.

An 8299 attestation can directly support a scheme conclusion like “input provenance committed and recomputable under this pipeline.” If the desired conclusion is stronger — “this exact input was actually consumed by this model invocation” — then the scheme should require whatever receipt or attestation binds that input_hash to the invocation as well.

That actually makes the convergence cleaner to me. “Does this identity commit what executes?” remains the right question, but the answer has to include the whole executable path needed for the claim being made. The commitment should not acquire the missing execution fact merely because KYA consumes it

Thanks @garyyang-finchip — that is a clearer boundary than the one I offered, on both counts. Taking them as read: the object evaluated rather than duration (my per-action/standing split doesn’t survive your ephemeral predicate presentations), and the privacy axis I’d missed entirely 8354 protects the policy, the predicate case protects the subject’s data. Different things being hidden.

And thank you for the §8 scope note. “Interpret those identifiers under their respective schemas
rather than infer compatibility or equivalent levels from the prefix” settles it better than a
reservation would have.

On the 8294 composition — rather than describe it, it’s up as a PR:

It follows your preference exactly: the existing ATTESTED path, no third admission mode, with an adapter as the recorded issuer so the contract calling attest() is what a relying party’s issuers[] names. Quorum is enforced in the adapter, since a rule’s issuers[] is an accepted-issuer set rather than a native quorum check.

The case the set is built around is yours. You wrote that a timeout must not silently become a negative trust conclusion; C3 is that made executable — pending, failed and below-floor runs cause a revert, and the case asserts no assertion is left behind. Completion is checked before any response value is read, so an incomplete run cannot be mapped to a level at all. C4 is its discriminating partner: a completed run with a poor verdict is recorded at level 0, so “could not check” and “checked and found wanting” stay distinguishable. Nine cases, each mutation-checked each guard deleted in turn, with the case claiming to cover it required to be the one that fails.

Two things it deliberately does not establish, both stated in the code rather than left to be assumed: the mock network constructs its own status, so the vectors establish how the adapter handles a reported result and not that the result is honest; and the floor is a count, not operator diversity — status() exposes only validatorCount, so ERC-8294’s minOperators is not observable through that interface and is not enforced. Seven validators under one operator satisfy it, and C8 pins that the assertion carries no diversity claim.

One more connection, and this one is a convergence rather than an overlap. Your Rationale says:

Identities commit what executes. Three identifiers were tightened for the same reason: a
policyId now commits the executable projection (rulesHash), a bridge response commits the configuration that produced it (configHash), and a proof commits the admission domain that consumes it (admissionDomain). In each case the earlier derivation allowed an executable path … to exercise authority its committed identity did not name.

That is the principle ERC-8299 is named for — What You Read Is What You Execute. 8299 applies it to inference inputs: a triple-hash chain (raw_input_hash → sanitization_pipeline_hash → input_hash) so the input a model acted on is provably the input that was committed. You have applied the same rule to policy identity, and, reading the thread, arrived at it three separate times under review pressure rather than from a general principle.

Worth naming because independent convergence is the strongest evidence a principle is real, and
because having a name for it turns the next occurrence into one question, does this identity commit
what executes?
instead of a fourth separate catch.

There is a concrete composition in it too, needing no change to your core: 8419 defines a provenance dimension with a provenance-verified level (“origin, code or model lineage verified by a third party”) and leaves admissible evidence to scheme descriptors. An 8299 input-provenance attestation is a natural evidence kind for exactly that level, it says something checkable about what the agent actually read, which is a stronger claim than an assertion that lineage was reviewed. That is a scheme descriptor someone writes, not a spec change.

@chugarchugarr you’re right, and “what the agent actually read” was a sloppier phrase than I’d have accepted from someone else. 8299 V1 commits the input-provenance chain and makes it recomputable; it does not by itself establish that the committed input was the one a given invocation consumed.

Your rule is the same one the 8294 adapter in the PR is built around, which is a good sign it generalises: the evidence authorizes only the conclusion it actually establishes. There it is a network that timed out not becoming a negative verdict, and a reported status not becoming a proven one. Here it is a commitment not acquiring an execution fact by being consumed downstream.

So the narrower mapping, which I’d take as the corrected version: an 8299 attestation supports a scheme conclusion of the form “input provenance committed and recomputable under this pipeline”. Anything stronger, this input, that invocation, needs the scheme to require the receipt that binds input_hash to the invocation as well, and that binding is a separate artifact that has to be named in the descriptor rather than assumed from the chain’s presence.

Which I think makes the composition more useful, not less: it gives a scheme author two distinct levels to write against instead of one overloaded one.

@TMerlini thank you for turning the composition into code rather than prose. I will review kya-standard#1 on GitHub and keep the code discussion there. The scope you stated is the right one: the adapter as the recorded issuer through the ATTESTED path, completion checked before any response value is read so an incomplete run cannot become a level, and no diversity claim, since validatorCount is not minOperators. Pinning C3 against C4 is exactly the “could not check” versus “checked and found wanting” distinction.

@chugarchugarr agreed on 8299, and it is the same rule the 8294 adapter follows: evidence authorises only the conclusion it establishes. An 8299 attestation supports “input provenance committed and recomputable under this pipeline”; “this exact input was consumed by this invocation” needs the receipt that binds input_hash to the invocation.

One precision on the level mapping. In the informative ladder, “provenance-verified” refers to the agent’s origin, code or model lineage, a property of the agent, while an 8299 attestation is about the inputs of one invocation. On its own it is evidence a scheme can consume rather than a level-3 conclusion; a scheme that draws a standing conclusion from it would do so over a set of attestations and a declared window, and say so in its descriptor.

On the name: “does this identity commit what executes?” is a good question to keep asking, with the narrowing above as part of the answer. The identity has to commit the whole path the claim depends on.

1 Like

I have additional question regarding the Ordered-Level Profile: which result values should be considered as valid?

The descriptor lists scheme’s levels and §3.1 defines level 0 as “not verified / failed”. But attest() accepts any uint8 level and check() returns true when resolve() finds a qualifying assertion with level >= minLevel.

That results in two edge cases:

  1. Undeclared level can satisfy a threshold. Suppose a scheme defines only levels {0, 2, 4}. A trusted ATTESTED issuer can still record level = 255. If the assertion otherwise qualifies, check(..., minLevel = 4, ...) returns true, accepting a result the descriptor assigns no meaning to.

  2. Explicit failed/not-verified result can satisfy a zero threshold. An otherwise qualifying level-0 assertion passes check(..., minLevel = 0, ...), while no qualifying assertion returns false. So zero means “a qualifying assertion exists”, even if its result is failed/not verified.

If levels enumerates valid results, I think the invariant discussed earlier applies here too: values used in executable policy evaluation should belong to the result domain committed by the scheme.

The registry does not interpret the descriptor, so it cannot enforce membership in levels. One option would be a normative requirement that issuers/verifiers MUST NOT emit undeclared levels. That would define valid behavior, but check() would still have no way to distinguish an undeclared level from a valid one.

A stronger option would be to commit the valid-level set on-chain for ordered schemes. A uint256 bitmap would cover the full uint8 range allowing the registry to reject undelcared levels at admission or exclude them during resolution. Separately, minLevel == 0 could be rejected for policy/check semantics or explicitly documented as an “assertion exists” query.

What I’d like to clarify first is intended meaning of levels: are its entries the complete valid result domain or just named points in an unrestricted uint8 ordering?

1 Like

@Revinand both edge cases hold against the current text: nothing stops an issuer recording an undeclared level, and a level-0 assertion satisfies check at minLevel == 0. Whether levels is the complete valid result domain is exactly the question to settle, and committing it on-chain would follow the same rule the thread has already applied to policyId and the bridge configuration. I will come back with the decision and the text, rather than answer from memory.

1 Like