Skip to content
Registry stack docs v0 · draft

Demo flow walkthrough

This page walks you through the three narrated scenarios in scripts/demo-flow.py after the Registry Lab compose topology is running. The focus is on what each step exercises: which service receives the request, what the request demonstrates, and what you can inspect in logs or output artifacts.

For starting the topology from the beginning, see the First run with Registry Lab tutorial.

Use this walkthrough when the core topology is already up (services started with docker compose -f compose.yaml up -d) and you want to trace what the demo client does, understand how scope enforcement works across services, or verify that a change to a Relay or Notary configuration changes the client output in the expected way.

  • All background services started and healthy. Verify with scripts/smoke.sh before running the client.
  • output/ directory writable from the compose host. The demo client mounts it at /demo/output.
  • openssl on the host PATH (used by demo-flow.py for Ed25519 holder proof signing when run locally; the compose-based client uses the Python image’s openssl).
  • Generated .env present (from scripts/generate-demo-secrets.py).

Start the demo client as a one-shot container in the client profile:

Terminal window
docker compose -f compose.yaml --profile client run --rm demo-client

The client connects to services via their compose DNS names (for example, http://civil-registry-relay:8080), runs all three scenarios in order, and exits. Artifacts land in output/ on the host.

To override the correlation ID used on all requests:

Terminal window
DEMO_CORRELATION_ID=my-trace-id docker compose -f compose.yaml --profile client run --rm demo-client

The default correlation ID is decentralized-demo-correlation-001. Every request in the demo carries this value as x-request-id.

Scenario 1: Birth registration to child support

Section titled “Scenario 1: Birth registration to child support”

This scenario exercises civil-notary (port 4321 on the host, http://civil-notary:8080 inside compose).

The demo client:

  1. Calls GET /.well-known/evidence-service on civil-notary to discover the available claims. This endpoint requires the CIVIL_EVIDENCE_CLIENT_BEARER token.
  2. Sends POST /claims/evaluate to civil-notary with a subject identifier and a birth-registration claim. The claim uses predicate disclosure: the credential does not return the raw civil registry row, only whether the predicate (born in a registered district, caregiver present) is satisfied.
  3. Optionally sends the same request with Accept: application/dc+sd-jwt to receive a Selective Disclosure JWT Verifiable Credential (SD-JWT VC) signed with the issuer JWK.

What you’ll see / What this proves for your integration: The Notary fetches facts from the Relay on your behalf, using a configured bearer token. The civil Relay does not write any result back. The Notary applies the configured disclosure profile (predicate) and returns a structured JSON-LD credential body that calling systems can parse for the claim result. CCCEV stands for Core Criterion and Core Evidence Vocabulary.

What to take away: predicate disclosure lets you confirm a fact (born in a registered district, caregiver present) without exposing the underlying registry row to the caller.

What to inspect:

  • output/scenario1-civil-credential.json: the claim result artifact.
  • Relay logs: docker compose logs civil-registry-relay. Look for the evaluate event and the 200 response to the Notary source call.
  • Notary logs: docker compose logs civil-notary. Look for the evaluation event with the correlation ID.

Scenario 2: Household benefit review from registry data

Section titled “Scenario 2: Household benefit review from registry data”

This scenario exercises social-protection-registry-relay (port 4312 on the host, http://social-protection-registry-relay:8080 inside compose) and scope enforcement.

The demo client:

  1. Calls GET /datasets/social_protection_registry/household on the social protection Relay with the SOCIAL_ROW_READER_RAW token and a Data-Purpose header. This is a protected row read: only the row-reader scope allows it.
  2. Calls GET /datasets/social_protection_registry/household/aggregates/households_by_eligibility_band on the same Relay with the SOCIAL_AGGREGATE_READER_RAW token. This is the aggregate consultation path: only the aggregate-reader scope allows it.
  3. Attempts to call the aggregate endpoint with the row-reader token. Registry Relay returns a 403 response with auth.scope_denied.
  4. Writes a household-benefit-decision artifact locally based on the aggregate result. Registry Relay has no write-back path; the artifact exists only in output/.

What you’ll see / What this proves for your integration: Scope enforcement is route-level. The row-reader and aggregate-reader credentials have distinct scopes and cannot substitute for each other. All reads are protected; the Relay does not expose any route without a valid credential.

What to take away: you can issue purpose-scoped tokens so that a benefit-decision system can read aggregate bands but cannot reach individual household rows.

What to inspect:

  • output/scenario2-household-benefit-decision.json: the decision artifact, which includes boundary.relay_write_back = false.
  • Relay logs: docker compose logs social-protection-registry-relay. Look for auth.scope_denied on the rejected aggregate request and the 200 on the permitted reads.

Scenario 3: Cross-authority conditional support

Section titled “Scenario 3: Cross-authority conditional support”

This scenario exercises static-metadata-publisher (port 4331 on the host), shared-eligibility-notary (port 4323), and the three Relay instances acting as sources.

The demo client:

  1. Fetches GET /metadata/evidence-offerings.json from static-metadata-publisher. No authentication is required. The client uses this to discover that shared-eligibility-notary handles cross-authority claims.
  2. Calls GET /.well-known/evidence-service on shared-eligibility-notary using the SHARED_EVIDENCE_CLIENT_BEARER token.
  3. Sends POST /claims/evaluate to shared-eligibility-notary with a cross-authority claim that requires evidence from civil, social protection, and health registries.

What you’ll see / What this proves for your integration: The Notary fans out to all three Relay services using per-source bearer tokens configured in config/notary/shared-eligibility-notary.yaml. The result carries source_count >= 2 when at least two sources contributed evidence.

What to take away: a single Notary can aggregate evidence across independently operated registries and report how many authorities contributed, without merging or exposing the underlying rows.

What to inspect:

  • output/scenario3-cross-authority-result.json: the multi-source claim result.
  • Notary logs: docker compose logs shared-eligibility-notary. Look for source-fetch events to each Relay and the final evaluation result.
  • Relay logs for all three services: look for the Notary source-read requests, identifiable by the correlation ID header.

After the client exits, run the following checks to confirm the artifacts and logs match expectations.

Check that all three scenario artifacts exist:

Terminal window
ls output/

Confirm the cross-authority result has at least two sources:

Terminal window
python3 -c "import json,sys; d=json.load(open('output/scenario3-cross-authority-result.json')); sys.exit(0 if d.get('source_count', 0) >= 2 else 1)"

Confirm the household benefit decision records no write-back:

Terminal window
python3 -c "import json,sys; d=json.load(open('output/scenario2-household-benefit-decision.json')); b=d.get('boundary',{}); sys.exit(0 if b.get('relay_write_back') is False else 1)"

Confirm scope-denial events appear in Relay logs:

Terminal window
docker compose logs social-protection-registry-relay 2>&1 | grep auth.scope_denied

Run the full smoke suite as a post-run gate (it re-runs the demo client internally):

Terminal window
scripts/smoke.sh

To exercise the additional live-service stories, run:

Terminal window
scripts/demo-live-stories.sh

That script writes Postgres, Zitadel/OIDC, and OpenFn artifacts under output/live-stories/. It also writes the service-first discovery artifact that shows which Core Criterion and Core Evidence Vocabulary (CCCEV) evidence option Registry Atlas selected from the published Core Public Service Vocabulary Application Profile (CPSV-AP) catalogue.

Client exits immediately without producing artifacts. Confirm all background services are healthy before starting the client. Run docker compose ps and scripts/smoke.sh. A service not yet healthy causes the client to fail on its first HTTP request.

403 on evidence discovery (/.well-known/evidence-service). The bearer token in .env may be stale or mismatched. Regenerate credentials with scripts/generate-demo-secrets.py and restart the affected Notary service: docker compose restart civil-notary.

Missing source_count field in scenario 3 output. The shared eligibility notary received responses from fewer than two Relay sources. Check that all three Relay services are running and reachable inside the compose network:

Terminal window
docker compose exec shared-eligibility-notary wget -q -O- http://civil-registry-relay:8080/health

openssl not found when running demo-flow.py directly on the host. Install openssl or use the compose-based invocation, which uses the Python image’s built-in openssl.

Artifacts from a previous run are overwritten. demo-flow.py clears output/ on each invocation. Copy artifacts out before re-running if you need to compare runs.