Skip to content
Registry stack docs v0 · draft

Registry Notary overview

Registry Notary is the claim evaluation and credential issuance product of the registry stack. It evaluates configured eligibility claims against your registry data, serves signed delegated evaluation to trusted peer Notaries, and issues SD-JWT VC credentials with selective disclosure. It also issues wallet-compatible credentials over OpenID for Verifiable Credential Issuance (OID4VCI). The verification receipt Registry Notary produces is an evidence artifact, not an eligibility decision: the issuing program decides what an entitlement is.

Stack commitments: safeguards, reviewability.

Claims are declared in YAML under evidence.claims. Each claim has a stable id, a subject_type, a typed value, named inputs, optional depends_on ordering, one or more source_bindings that name the connector and connection to read data from, an evaluation rule (one of extract, exists, cel, plugin), a disclosure policy, and the output formats and credential_profiles the claim may produce. For the field-by-field schema, see the claim config reference. The demo at demo/config/registry-notary.yaml ships three claims: date-of-birth (Extract rule), farmed-land-size (Extract rule), and farmer-under-4ha (CEL rule that depends on farmed-land-size).

Registry Notary supports four rule types:

  • extract: reads a named field from a named source binding. The simplest rule type: no logic, one field.
  • exists: returns true if a record was found at the source, false otherwise.
  • cel: evaluates a Common Expression Language (CEL) expression with named bindings. Bindings can reference other claim results via bindings.claims, letting a CEL claim compose from upstream Extract results without re-fetching source data.
  • plugin: declares an external plugin name. Not yet implemented; the evaluator returns OperationUnsupported (HTTP 501) for any claim that uses it.

Claims that declare depends_on are evaluated in dependency order. The validator enforces that depends_on relationships form a directed acyclic graph (DAG).

Each claim carries a disclosure block that controls what the caller receives.

ModeWhat the caller receives
valueThe full evaluated value (for example, the literal date or number).
predicateA boolean: does the subject satisfy the rule. The underlying value is not returned.
redactedNo value and no predicate. The claim result is present in the response but carries no data.

The claim’s disclosure.default sets the mode used when the caller does not request a specific mode. disclosure.allowed lists the modes the caller may request. disclosure.downgrade controls what happens when the caller requests a mode not in allowed: deny returns a DisclosureNotAllowed error; default falls back to the claim’s default mode; redacted forces the result to Redacted.

The default for a new claim with no disclosure config is default: redacted, allowed: [redacted], downgrade: deny, which is fail-closed.

Registry Notary issues credentials in SD-JWT VC format via POST /credentials/issue. Reusable SD-JWT signing and holder-proof checks come from Registry Platform; Registry Notary owns the profile config, claim selection, disclosure decision, and API workflow.

Key properties:

  • Algorithm: EdDSA (Ed25519).
  • Token type header: dc+sd-jwt.
  • Media type: application/dc+sd-jwt.
  • Key source: the issuing key is an OKP Ed25519 JWK loaded from an environment variable named by credential_profiles.<id>.issuer_key_env in the YAML config (for example, REGISTRY_NOTARY_ISSUER_JWK).
  • Disclosures: each claim field is wrapped in an individual SD-JWT disclosure with a SHA-256 digest, allowing the holder to present only selected fields.
  • Holder binding: the credential profile can require a did:jwk holder proof. Proof of possession is only supported with did:jwk; other DID methods in allowed_did_methods cause a startup validation error.

The public verification key is exported at /.well-known/evidence/jwks.json. Credential profiles are declared under evidence.credential_profiles and each specifies a format, issuer DID, vct (Verifiable Credential Type URI), validity_seconds, holder_binding, allowed_claims, and disclosure.

Registry Notary emits OpenID for Verifiable Credential Issuance (OID4VCI) Draft 13 compatible routes. The Registry Lab citizen self-attestation scenario uses this surface to drive a wallet through eSignet authentication and into a Notary-issued SD-JWT VC. The four routes are:

  • GET /.well-known/openid-credential-issuer: returns the Credential Issuer Metadata document (issuer URL, credential endpoint, nonce endpoint, authorization servers, supported credential configurations). Media type: application/json.
  • GET /oid4vci/credential-offer: returns a CredentialOffer for one credential configuration (when credential_configuration_id is set in the query) or for every configuration the server knows about (when no query is set). Media type: application/json.
  • POST /oid4vci/nonce: returns a fresh c_nonce the holder uses when signing the proof of possession JWT. Media type: application/json.
  • POST /oid4vci/credential: returns the SD-JWT VC. The caller presents a bearer access token from the configured authorization server and a proof JWT signed with the holder’s did:jwk key carrying the c_nonce. Media type: application/json.

Holder binding uses did:jwk (Decentralized Identifier method jwk): the wallet derives a public key, encodes it as did:jwk:<base64url-jwk>, signs the proof JWT with the matching private key, and Registry Notary embeds the holder’s did:jwk into the cnf claim of the issued credential. The route is exercised from the lab by just citizen-oid4vci-login, just citizen-oid4vci-code, just citizen-oid4vci-token, just citizen-oid4vci-probe, and just citizen-oid4vci-report; the host-side Notary instance runs on port 4325. For the full request and response shapes, see OID4VCI routes in the Notary reference.

Registry Notary can serve a static-peer federation MVP for one operation: POST /federation/v1/evaluations. A trusted Notary sends a compact signed JWT request with typ = registry-notary-request+jwt; the serving Notary verifies the peer, audience, time window, profile, purpose, replay key, and local policy before reading any source data. The response is a compact signed JWT with typ = registry-notary-response+jwt.

Federation uses two config surfaces:

  • Registry Manifest publishes public discovery metadata: federation, evaluation_profiles, and registry-notary evidence offerings whose access.ruleset matches a public evaluation profile ruleset.
  • Registry Notary enforces private runtime policy: federation.peers, federation.evaluation_profiles, signing key config, pairwise subject hash secret, replay settings, response shaping, and emergency denylist entries.

This is delegated evaluation only. The MVP does not implement open federation, dynamic trust-chain discovery, audit checkpoint exchange, shared replay storage, or federated credential issuance. The operator-facing contract is in the federated evaluation MVP spec and federated evaluation operator guide.

When a caller requests application/ld+json; profile="cccev", Registry Notary renders claim evaluation results as Core Criterion and Core Evidence Vocabulary (CCCEV) JSON-LD.

The output includes:

  • A @context block with the cccev, dcterms, foaf, time, and xsd namespace prefixes.
  • Evidence nodes typed as cccev:Evidence.
  • CCCEV properties: cccev:isProvidedBy, cccev:supportsRequirement, cccev:supportsValue, cccev:validityPeriod, and cccev:isConformantTo.
  • Supported value structures using cccev:SupportedValue and cccev:providesValueFor.

The claim definition’s optional cccev.requirement_type field populates the cccev:InformationRequirement or other requirement type annotation in the rendered output. The CCCEV output aligns with the vocabulary but does not claim full profile conformance.

Registry Notary fetches source data over HTTP using one of two connector kinds:

  • registry_data_api: posts to {base_url}/{dataset}/{entity} with query parameters shaped for the Registry Data API. The connector authenticates with a per-connection bearer token.
  • dci: uses the Social Protection Digital Convergence Initiative (SP DCI) sync search protocol. The request body follows the DCI registry/sync/search shape; the response is parsed via a configurable JSON path to extract records. Defaults: search path /registry/sync/search, records path /message/search_response/0/data/reg_records, max results 2.

Both connectors use an HTTP client with a 10-second timeout and a strict outbound URL policy by default. Private-network HTTP source registries are allowed only when a source connection sets allow_insecure_private_network: true; the opt-in still blocks cloud metadata endpoints. Source connection credentials are bearer tokens loaded from environment variables named by the token_env field on each source_connections entry. No source credentials appear in the YAML config file itself.

Every request that touches claim evaluation is authenticated and audited. Audit events are serialized as platform audit envelopes in JSON Lines (JSONL). Fields: event_id, occurred_at, principal_id, decision, method, path, status, verification_id, claim_hash, row_count, error_code. verification_id and claim_hash are optional and may be omitted for redacted results.

The audit sink is configurable as stdout (default) or file/jsonl. When sink: file or sink: jsonl, audit.path must be set. audit.hash_secret_env is required so redacted audit identifiers use a deployment-specific secret. The demo config writes to demo/var/registry-notary-audit.jsonl.

Registry Notary is a standalone HTTP service. It is independently deployable and does not import or link Registry Relay application code. A Relay instance may publish metadata that points to a Notary instance, but the two services remain decoupled at the product boundary. Both services share Registry Platform primitives for security and operational behavior. For questions about which service owns which concern, see Boundaries and map.

Three boundaries to keep in mind:

  • Not an eligibility decision. The verification receipt is an evidence artifact. The issuing program, not Registry Notary, converts a verified claim into an entitlement.
  • Not a wallet. Registry Notary issues credentials over OID4VCI; the wallet that holds and presents the credential is a separate component.
  • Not a credential authority. The DID, the verification key, and the credential profile config belong to the deployment, not to Registry Notary as a product.

Registry Notary is at version 0.2.1. It was renamed from evidence-server; the rename is documented in docs/rename-2026-05-23/.

Known limitations at this version:

  • The Plugin rule type is declared in config but not yet implemented.
  • Proof of possession for holder binding only supports did:jwk. Other DID methods in allowed_did_methods cause a startup validation error.
  • Inbound auth supports either static API-key/bearer-token mode or OIDC mode. Both modes use shared Registry Platform primitives, but Registry Notary still owns its config shape and scope enforcement.
  • The W3C Verifiable Credentials Data Model envelope is not used. Issued credentials are SD-JWT format directly, without a VC Data Model wrapper.
  • OID4VCI route shapes are Draft 13 compatible. No conformance test fixture is pinned at this version, so the broader spec claim level is aligns_with.
  • Federation is static-peer delegated evaluation only. Active-active deployments need shared replay storage before privileged federation traffic is enabled across multiple serving instances.