Reference
Resolve Gate reference
Resolve before reliance from the relying side: turn a native identifier (an MCP endpoint, an agent ARN, a SPIFFE ID) into the ECZ-ID that holds it and its current evidence, then let your own policy decide.
Documented ahead of release. The native resolution route is built in ECZ-ID Core and is not yet switched on in production, so nothing on this page is callable today. This is the relying-side integration pattern, not a product package.
- Contract
- Documented · release-gated contractDocumented ahead of release. Nothing is callable, and field names may change at release.
- Version
ecz-resolve-gate-v1 · ecz.native_resolution.v1- Machine-readable
- Gate contract (JSON)
Accountability Chain:IdentityCurrent stateDecisionReceiptRe-verify
ECZ-ID supplies evidence; your policy decides
- You observe a native identity: an MCP server endpoint, an AWS role ARN, an Entra Agent ID, a SPIFFE ID.
- Reverse-resolve it: Core answers which ECZ-ID holds it through a proven binding, and what is true of that binding now.
- Direct-resolve the ECZ-ID it names for the current public record.
- Your own policy reads the evidence and decides. The gate has no default decision.
- Act, record your reliance as a receipt if you want one, and re-resolve before relying again.
Native resolution
POST https://api.ecocitizenz.com/api/v1/resolve/native with namespace, resource_type, identifier in a JSON body. A provider identifier (an ARN carries an account id) never sits in a URL or an access log. The answer is a ecz.native_resolution.v1 document.
| Outcome | Meaning |
|---|---|
RESOLVED | Exactly one ECZ-ID currently holds this identifier through a binding that publishes the link (a proven binding that is BOUND, DEGRADED or SUSPENDED and not past its expires_at, except that a PRIVATE_UNTIL_BOUND binding publishes the link only while BOUND; several live bindings of one ECZ-ID are one holder, the most recently proven is shown). ecz_id, the factual binding_state and current are present. |
NOT_FOUND | No ECZ-ID currently holds this identifier through a binding that publishes the link. A former holder (revoked, expired, mismatched or lapsed) is never named in an anonymous answer, and a binding that does not publish the link changes no answer at all: the answer is exactly what it would be if that binding did not exist. Shown as UNBOUND: an absence, not a state. |
CONFLICT | Two or more ECZ-IDs currently hold this identifier through bindings that publish the link. Resolution fails closed and names no ECZ-ID. |
INVALID_IDENTIFIER | The identifier is malformed for its namespace (for example an OCI tag instead of a digest, or a control character), or beyond the normaliser's work bounds, which are checked before any other rule: a non-ASCII identifier of more than 2807 bytes of UTF-8, or of more than 2048 characters once NFKC-normalised, has error_code NATIVE_IDENTIFIER_TOO_LONG; one holding more than 253 non-ASCII characters, or a non-ASCII host whose A-label cannot fit DNS (more than 127 labels, a label over 63 characters, or the host over 253 with its dots), has error_code NATIVE_IDENTIFIER_INVALID, and such a host is refused before any IDNA processing. This covers identifiers within the route's field limits. An identifier over 2048 characters, or a namespace or resource_type over 64, never reaches resolution: the route refuses it with HTTP 400 and {"ok": false, "error": "invalid_field", "field": <the field>}, not with a resolution answer. |
UNSUPPORTED_NAMESPACE | The namespace/resource type pair is not one of the supported pairs. |
UNAVAILABLE | Core could not read its index, a holder's canonical record, or the publication (A-45) decision for a current holder whose policy publishes the link, so it names no holder: the route answers HTTP 503 with this resolution document. Never read as NOT_FOUND. |
BOUND is reached only through a proof Core itself observed on the exact identifier, by a route listed for its namespace; Core records that route as native.proof_method. No claimant, console or provider adapter can state it. Reverse resolution names an ECZ-ID only for a binding whose projection policy publishes the link at that moment (PUBLIC_WHEN_BOUND, PUBLIC_SETUP_INCOMPLETE and HASH_ONLY while the binding holds the identifier; PRIVATE_UNTIL_BOUND only while it is BOUND, never while DEGRADED or SUSPENDED) and whose owner has not withdrawn publication (A-45). NEVER_PUBLIC and CUSTOMER_OPT_IN bindings are withheld until an explicit reverse-resolution publication/consent primitive exists; consent is never inferred from target_public_value or any other field. A withheld binding is never named or confirmed and changes no answer: there is no existence or liveness signal. Historical ownership is not part of anonymous resolution; it stays in the owner's authenticated records.
There is no search by native identifier: a caller resolves one exact identifier it already holds. The public Resolver offers no native-identifier search in this release.
URL identifiers (mcp/server_endpoint, web/api_origin) are https on the default port: http://, any other port, userinfo, a backslash, an IDNA deviation character, and a host, path or query that a client would read differently (compatibility, invisible or white-space characters) are refused as INVALID_IDENTIFIER, never repaired. A path and query have exactly one accepted spelling, the request target Core's own fetcher sends for them; any other spelling is refused as INVALID_IDENTIFIER with error_code NATIVE_IDENTIFIER_URL_PATH_NOT_CANONICAL: a '.' or '..' path segment or an empty one ('//'), a ';' or an escaped '/' or ';' in the path, a lower-case or malformed percent-escape or one of an unreserved character, a bare '?', a raw "'" in the query, or a character the fetcher percent-encodes left raw (non-ASCII, or one of '"', '<', '>', '[', ']', '^', '`', '{', '|', '}'). The normaliser's work bounds (see INVALID_IDENTIFIER) are checked before this rule, so a spelling beyond them has their error_code (NATIVE_IDENTIFIER_TOO_LONG or NATIVE_IDENTIFIER_INVALID) instead. Resolve exactly the identifier your client connects to, spelled as your client requests it.
While the route is switched on, every answer it gives after admitting a request, a resolution or a refusal, comes no sooner than 0.5 seconds after the request started. At capacity it answers at once with HTTP 503 {"ok": false, "error": "unavailable"} and Retry-After: 1, whatever was asked; an unexpected fault is the same 503 body, without Retry-After. A request body over 8192 bytes (8 KiB) is refused with HTTP 413 {"ok": false, "error": "body_too_large", "limit": 8192}. While the route is switched off it does not exist: an empty HTTP 404. None of these is a resolution document: the gate reads each as RESOLUTION_FAILED with resolution null, and it never retries. Whether, when and how often to retry is the relying party's choice, bounded and explicit in its own code.
A RESOLVED answer carries the binding block exactly as the Resolver projects it, current (BOUND and inside its expiry), and freshness times. Then read https://api.ecocitizenz.com/api/p/{ecz_id}.json. The anonymous machine record of the ECZ-ID a RESOLVED answer names (direct_resolve.machine_json). It is the one follow-up read a public resolution points to, and it needs no key.
Supported native identifiers
| Namespace | Resource type | Identifier | Proof routes | Live verification |
|---|---|---|---|---|
dns | domain | Domain name | DNS_TXT, DOMAIN_WELL_KNOWN | ECZ-ID Core observes control itself at the domain's root: a DNS TXT record or /.well-known/ecz-id.json on that exact host. |
web | api_origin | API origin | DNS_TXT, DOMAIN_WELL_KNOWN | ECZ-ID Core observes control itself at the origin's host root: a DNS TXT record or /.well-known/ecz-id.json. One OpenAPI document (API_ORIGIN) cannot prove an origin. |
mcp | server_endpoint | MCP server endpoint | DNS_TXT, DOMAIN_WELL_KNOWN, MCP_SERVER | ECZ-ID Core observes control itself: the MCP manifest under that exact endpoint (MCP_SERVER), or the endpoint's host root (DNS TXT or /.well-known/ecz-id.json). Another endpoint on the same host never proves it. |
mcp_registry | server_name | MCP Registry server name | MCP_REGISTRY_ENTRY | Core has a provider proof route for this namespace, switched off until its live provider behaviour is proven: it cannot be proven, bound or resolved yet. Not live-supported. |
github | repository | GitHub repository | GITHUB_OIDC | Core has a provider proof route for this namespace, switched off until its live provider behaviour is proven: it cannot be proven, bound or resolved yet. Not live-supported. |
package | coordinate | Package coordinate (ecosystem:name) | — | No proof route exists: it cannot be proven, bound or resolved. Not live-supported. |
oci | image_digest | OCI image digest | — | No proof route exists: it cannot be proven, bound or resolved. Not live-supported. |
aws | iam_role_arn | AWS IAM role ARN | AWS_STS_SIGNED_IDENTITY | Core has a provider proof route for this namespace, switched off until its live provider behaviour is proven: it cannot be proven, bound or resolved yet. Not live-supported. |
aws | agentcore_runtime_arn | AWS Bedrock AgentCore runtime ARN | AWS_AGENTCORE_DESCRIBE | Proof needs ECZ-ID's own provider registration or credentials, which are not configured in this build: it cannot be proven, bound or resolved yet. Not live-supported. |
aws | agentcore_workload_identity_arn | AWS Bedrock AgentCore workload identity ARN | AWS_AGENTCORE_DESCRIBE | Proof needs ECZ-ID's own provider registration or credentials, which are not configured in this build: it cannot be proven, bound or resolved yet. Not live-supported. |
microsoft_entra | service_principal | Microsoft Entra service principal | ENTRA_ACCESS_TOKEN | Proof needs ECZ-ID's own provider registration or credentials, which are not configured in this build: it cannot be proven, bound or resolved yet. Not live-supported. |
microsoft_entra | agent_identity | Microsoft Entra Agent ID | ENTRA_ACCESS_TOKEN | Proof needs ECZ-ID's own provider registration or credentials, which are not configured in this build: it cannot be proven, bound or resolved yet. Not live-supported. |
google_cloud | service_account | Google Cloud service account | GOOGLE_SERVICE_ACCOUNT_JWT | Core has a provider proof route for this namespace, switched off until its live provider behaviour is proven: it cannot be proven, bound or resolved yet. Not live-supported. |
google_cloud | agent_resource | Google Cloud agent resource name | GOOGLE_AGENT_RESOURCE_DESCRIBE | Proof needs ECZ-ID's own provider registration or credentials, which are not configured in this build: it cannot be proven, bound or resolved yet. Not live-supported. |
spiffe | spiffe_id | SPIFFE ID | SPIFFE_JWT_SVID | Core has a provider proof route for this namespace, switched off until its live provider behaviour is proven: it cannot be proven, bound or resolved yet. Not live-supported. |
Evidence status
| Status | When |
|---|---|
EVIDENCE_AVAILABLE | RESOLVED, binding_state BOUND, current true, and the direct record was read |
EVIDENCE_INCOMPLETE | NOT_FOUND, a BOUND binding whose direct record could not be read, a BOUND binding for a URL identifier whose target names no host the gate can compare (a hash, or withheld), or a binding re-observing after a mismatch |
STALE | RESOLVED and BOUND but past its expiry, or EXPIRED |
DEGRADED | RESOLVED and DEGRADED |
SUSPENDED | RESOLVED and SUSPENDED |
REVOKED | RESOLVED and REVOKED |
MISMATCH | RESOLVED and MISMATCH |
RESOLUTION_FAILED | CONFLICT, INVALID_IDENTIFIER, UNSUPPORTED_NAMESPACE, UNAVAILABLE, the request itself failed or was answered without a resolution document (at capacity, a body too large, or the route switched off), a URL identifier the gate refused before sending (not https, userinfo, a backslash, or a path or query character Core would fold or drop), or a RESOLVED binding whose target names a different host from the one the client connects to |
The gate loop
import { createResolveGate } from './resolve-gate.mjs';
const gate = createResolveGate({
// Your policy. ECZ-ID never decides for you.
policy: (evidence) =>
evidence.evidenceStatus === 'EVIDENCE_AVAILABLE' ? 'PROCEED' : 'HOLD',
});
const result = await gate.check({
namespace: 'mcp',
resourceType: 'server_endpoint',
identifier: 'https://mcp.example.com/mcp',
});
// result.decision is your policy's answer; result.decidedBy is 'RELYING_PARTY'.
const later = await gate.recheck(result);
// later.materialChanges lists what changed since the evidence you relied on.- The relying party supplies the policy. The gate has no default decision and returns whatever the policy returns.
- The policy is called for every evidence status, including RESOLUTION_FAILED: failing open or closed is the relying party's choice.
- Re-resolve before each consequential reliance, and on a schedule; a material change is a reason to re-decide.
- Reverse resolution is followed by direct resolution of the ECZ-ID it names.
- For a URL identifier (mcp/server_endpoint, web/api_origin) the gate sends the identifier exactly as a WHATWG fetch client requests it: the scheme and the host canonicalised by the WHATWG URL parser, so that ECZ-ID resolves the host your client connects to, and the path and query as that parser serialises them (dot segments removed, characters percent-encoded as it encodes them), without the fragment. Core accepts one spelling of a path and query, the request target its own fetcher sends, and refuses any other as INVALID_IDENTIFIER with error_code NATIVE_IDENTIFIER_URL_PATH_NOT_CANONICAL; the gate never repairs such an identifier or tries another spelling, so it is RESOLUTION_FAILED. It refuses, without sending a request, an identifier that is not https, that carries userinfo or a backslash, or whose path or query holds a character Core would fold or drop (an NFKC compatibility form, a control or format character, or trailing white space). It compares the resolved binding's target host with the host your client connects to.
Recording reliance
A DCI_RELIANCE receipt (ecz.receipt.v1) records that your organisation relied on resolved evidence. An OWNER or ADMIN of the relying organisation, through its signed-in TrustOps account. For a native resolution (resolution), Core recomputes the resolution digest from its contents, checks core_attestation when it holds the attestation key (refusing evidence it did not issue), and requires the binding it names to be one Core may confirm. For evidence Core attested, the binding must still exist for that same identifier, proven BOUND through a Core-observed route, under a projection policy that publishes the link, and not withdrawn; it need not still be current, and the facts recorded are Core's as of checked_at. For unattested evidence, public reverse resolution must name exactly that binding now (a current public holder), and the facts recorded are Core's at recording. Its checked_at must be no more than 24 hours old when Core records the reliance and no later than that recording time plus a 60-second clock leeway (else check CHECKED_AT_INVALID), and decided_at no earlier than checked_at less the same leeway; a decided_at in the future (a later second than the recording) is refused. For direct-resolution evidence (direct_resolution: an ecz_id and a digest), Core checks the format, and when the relying party states checked_at, it must be an RFC 3339 UTC timestamp held to the same three time rules. Core records that evidence as the relying party reported it (REPORTED_ONLY) and checks neither the digest nor a binding. Core never makes or endorses the decision. relying_party_decision is the relying party's own label, an upper-case token of 2 to 40 characters (A-Z, 0-9 or _, starting with a letter), and Core refuses one that is, or holds as a token in any case, a word in never_returned_by_ecz_id (check RESERVED_VERDICT_WORD).
- It carries the ECZ-ID relied on.
- It carries the resolution_digest relied on.
- It carries the relying party's own decision label and local policy reference.
- It carries the relying party's action reference.
Re-resolve and material change
A change to any of outcome, ecz_id, binding_state, current, binding_id between two resolutions is material. A re-check that only renews freshness is not.
Related
This page is explanation, not authority. Current state is only ever the live Resolver record. Live facts on this page were measured read-only on 2026-10-10.
