Archived docs.You are viewing v0.13.0. For current guidance, useLatest. Report archive issues onGitHub.
Evaluate a registry-backed claim locally
This tutorial continues from the my-first-api project created in
Run a protected registry API locally.
Imagine that a benefits intake service has collected an applicant’s name and date of birth. It
needs to know whether the registry accepts that person’s registration under the current intake
policy, but it should not receive the registry row or a national identifier. The initial policy
accepts active registrations and rejects pending ones. You will add Registry Notary, evaluate both
cases, and then expand the policy to accept pending registrations too.
Later tutorials will show how to start from your own registry and author a custom integration. Here, reusing the first tutorial’s small workbook keeps the focus on making and changing a Notary claim.
This tutorial uses synthetic data and local demo credentials. Do not use the generated keys or database settings in production.
This tutorial requires registryctl, immutable Relay and Notary images, and the image lock from
the same Registry Stack v0.13.0 release. Do not combine a source-built CLI with images or an
image lock from another release.
This is an evaluation-only local tutorial. It does not issue a credential or prove wallet, credential-presentation, or OID4VCI interoperability.
Add Notary to the project
Section titled “Add Notary to the project”From the my-first-api directory left by the first tutorial, add the local Notary journey:
registryctl add notaryThe command reports the authored claim file, local Notary URL, and next command:
Added Registry Notary to "my-first-api". Claim: notary/project/registry-stack.yaml Notary API after start: http://127.0.0.1:4255
Next: registryctl startAdd --format json when another program needs the versioned add-on report.
The add-on creates an editable Registry Stack project under notary/project/. It also adds a
private consultation Relay that reads the same workbook. Relay still owns registry access. Notary
receives only the minimized consultation result needed to evaluate the claim.
Inspect the claim
Section titled “Inspect the claim”Read the evidence service before starting it:
sed -n '7,28p' notary/project/registry-stack.yamlThe important part is deliberately small:
registration-verification: kind: evidence version: 1 purpose: https://example.local/purpose/tutorial legal_basis: public-service-delivery consent: not_required access: { scopes: ["benefits_casework:evidence"] } consultations: enrollment: integration: person-demographics input: given_name: request.target.attributes.given_name family_name: request.target.attributes.family_name date_of_birth: request.target.attributes.date_of_birth claims: person-registration-accepted: cel: enrollment.matched && enrollment.registration_status == "active" disclosure: predicateThe caller supplies a name and date of birth already obtained through its intake process. Relay
uses all three values for an exact lookup and returns registration_status only to Notary. The
selector fields cannot also be projected as outputs, and the public result does not repeat the
name, date of birth, or status. This keeps matching inputs separate from the registry fact being
proved.
This is evidence that the registry contains exactly one matching record whose status satisfies the current acceptance policy. It is not proof that the caller is that person. Authentication, identity assurance, and authority to act for the person remain separate controls.
Names and dates of birth can collide or be recorded differently. Requiring exactly one match rejects ambiguity, but a production service still needs a jurisdiction-appropriate identity assurance and matching design.
Start Relay and Notary
Section titled “Start Relay and Notary”Start the combined local project:
registryctl startAfter the containers become ready, the command prints both APIs:
Relay API: http://127.0.0.1:4242API docs: http://127.0.0.1:4242/docsNotary API: http://127.0.0.1:4255Notary docs: http://127.0.0.1:4255/docsLoad the evaluator key
Section titled “Load the evaluator key”Load the generated local credentials into your current shell:
set -a. ./secrets/local.envset +atest -n "$TUTORIAL_EVALUATOR_RAW"The raw key stays in the ignored secrets/local.env file. Do not paste it into the request body or
commit it.
Evaluate an accepted active registration
Section titled “Evaluate an accepted active registration”Evaluate an active registration from the workbook:
curl -sS -X POST \ -H "x-api-key: $TUTORIAL_EVALUATOR_RAW" \ -H "Data-Purpose: https://example.local/purpose/tutorial" \ -H "Content-Type: application/json" \ -H "Accept: application/vnd.registry-notary.claim-result+json" \ --data '{ "target": { "type": "person", "attributes": { "given_name": "Jo", "family_name": "Elm", "date_of_birth": "2019-02-03" } }, "claims": ["person-registration-accepted"], "disclosure": "predicate", "format": "application/vnd.registry-notary.claim-result+json", "purpose": "https://example.local/purpose/tutorial" }' \ http://127.0.0.1:4255/v1/evaluationsThe response also includes dynamic evaluation, target-reference, timestamp, and provenance fields. The stable fields to look for are:
{ "results": [ { "claim_id": "person-registration-accepted", "value": true, "satisfied": true, "disclosure": "predicate", "format": "application/vnd.registry-notary.claim-result+json" } ]}true is the claim result. The response does not contain Jo, Elm, 2019-02-03, or the source
value active.
Reject a pending registration
Section titled “Reject a pending registration”Save a request for a person whose registry record is pending. You will repeat it after changing the policy:
cat > notary/pending-registration-request.json <<'JSON'{ "target": { "type": "person", "attributes": { "given_name": "Nia", "family_name": "Stone", "date_of_birth": "1998-03-05" } }, "claims": ["person-registration-accepted"], "disclosure": "predicate", "format": "application/vnd.registry-notary.claim-result+json", "purpose": "https://example.local/purpose/tutorial"}JSON
curl -sS -X POST \ -H "x-api-key: $TUTORIAL_EVALUATOR_RAW" \ -H "Data-Purpose: https://example.local/purpose/tutorial" \ -H "Content-Type: application/json" \ -H "Accept: application/vnd.registry-notary.claim-result+json" \ --data @notary/pending-registration-request.json \ http://127.0.0.1:4255/v1/evaluationsThe person matches exactly, but pending does not satisfy the initial policy:
{ "results": [ { "claim_id": "person-registration-accepted", "value": false, "satisfied": false, "disclosure": "predicate" } ]}This is a policy rejection, not a failed lookup. The public response still does not reveal the source status.
Try a non-matching date of birth
Section titled “Try a non-matching date of birth”Change only the request date for this call:
curl -sS -X POST \ -H "x-api-key: $TUTORIAL_EVALUATOR_RAW" \ -H "Data-Purpose: https://example.local/purpose/tutorial" \ -H "Content-Type: application/json" \ -H "Accept: application/vnd.registry-notary.claim-result+json" \ --data '{ "target": { "type": "person", "attributes": { "given_name": "Jo", "family_name": "Elm", "date_of_birth": "2019-02-04" } }, "claims": ["person-registration-accepted"], "disclosure": "predicate", "format": "application/vnd.registry-notary.claim-result+json", "purpose": "https://example.local/purpose/tutorial" }' \ http://127.0.0.1:4255/v1/evaluationsThe lookup finds no exact record, so the predicate is false:
{ "results": [ { "claim_id": "person-registration-accepted", "value": false, "satisfied": false, "disclosure": "predicate" } ]}No-match is a claim result, not a source error. Ambiguous matches are different: the private Relay rejects them rather than choosing a row.
Edit the claim rule
Section titled “Edit the claim rule”Open notary/project/registry-stack.yaml in your editor. Find this line:
cel: enrollment.matched && enrollment.registration_status == "active"Change the expression to accept either status and save the file:
cel: enrollment.matched && (enrollment.registration_status == "active" || enrollment.registration_status == "pending")The pending fixture is an executable example of the original policy. Open
notary/project/integrations/person-demographics/fixtures/pending.yaml and change its expected claim
from:
claims: { person-registration-accepted: false }to:
claims: { person-registration-accepted: true }Now restart:
registryctl restartregistryctl restart validates and rebuilds the generated Relay and Notary inputs from the
authored files before starting the services. The fixtures are executable examples of the claim, so
restart refuses to deploy when an expected result no longer agrees with the edited rule. Edit the
authored files under notary/project/, not generated files under .registry-stack/build/.
Evaluate the edited rule
Section titled “Evaluate the edited rule”Repeat the saved request:
curl -sS -X POST \ -H "x-api-key: $TUTORIAL_EVALUATOR_RAW" \ -H "Data-Purpose: https://example.local/purpose/tutorial" \ -H "Content-Type: application/json" \ -H "Accept: application/vnd.registry-notary.claim-result+json" \ --data @notary/pending-registration-request.json \ http://127.0.0.1:4255/v1/evaluationsThe same pending source record is now accepted by the expanded policy:
{ "results": [ { "claim_id": "person-registration-accepted", "value": true, "satisfied": true, "disclosure": "predicate" } ]}You changed claim semantics in the authored project, rebuilt the governed contract, and observed the new result through the live API. You did not edit the workbook or expose its row.
Stop the stack
Section titled “Stop the stack”When you are done:
registryctl stopThis keeps the workbook, authored Notary project, and local request file. To return to the original
claim later, restore the equality expression and change the pending fixture expectation back to
false before starting the project.
What you built
Section titled “What you built”You extended the first tutorial’s protected registry with a live Notary evaluation path. An authorized caller supplied name and date of birth instead of a national identifier. Relay required one exact record and released only registration status to Notary. Notary returned one predicate, with no source fields in the public result. You saw a matched pending registration rejected by the initial policy, then expanded the policy and watched the same request produce a different governed answer.
- Author an HTTP Registry Stack project to start adapting a custom registry while retaining synthetic success, no-match, and ambiguity evidence.
- Registry Notary operator configuration reference for the generated runtime contract and deployment gates.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Resolution |
|---|---|---|
registryctl add notary is unknown | The CLI predates Registry Stack v0.11.0 or does not match the installed release image lock. | Install registryctl, the immutable Relay and Notary images, and the image lock from the same Registry Stack v0.13.0 release. Do not substitute an ad hoc source build or a lock from another release. |
registryctl add notary says the project already has a Notary add-on | The command has already completed for this project. | Continue with registryctl start; do not run the add command twice. |
Notary returns 401 | The evaluator key was not loaded or is from another generated project. | Source secrets/local.env again in the current shell. |
Notary returns 403 | The key lacks the evidence scope or the purpose does not match the authored service. | Use TUTORIAL_EVALUATOR_RAW and the tutorial purpose shown above. |
registryctl restart reports a claim mismatch | The edited rule no longer agrees with the fixture’s expected result. | Update the expected claim in notary/project/integrations/person-demographics/fixtures/pending.yaml, then restart again. |
| The edited rule does not take effect | A generated file was edited, or the running services were not rebuilt. | Edit the authored claim and matching fixture under notary/project/, then run registryctl restart. |