Skip to content

fix(registryctl): permit tutorial purpose in generated notary policy#189

Merged
jeremi merged 4 commits into
mainfrom
fix/notary-tutorial-purpose-policy
Jul 3, 2026
Merged

fix(registryctl): permit tutorial purpose in generated notary policy#189
jeremi merged 4 commits into
mainfrom
fix/notary-tutorial-purpose-policy

Conversation

@jeremi

@jeremi jeremi commented Jul 3, 2026

Copy link
Copy Markdown
Member

Problem

The "Evaluate a claim with Registry Notary" tutorial fails at its central step on both released v0.8.3 and current main. registryctl notary smoke fails at "notary evaluator can verify starter claim" (expected 200, actual 403), and the tutorial's own evaluate curl returns 403 pdp.purpose_not_permitted (matching_policy_id notary.source_binding.relay.benefits_casework.person) for the tutorial's own purpose URI. The 409 evidence.not_available teaching point is unreachable for the same reason. Found by a full black-box reader run of the published tutorial.

Root cause

The benefits template (crates/registryctl/src/templates/notary_config.yaml.tmpl) declared the person source binding with no matching.allowed_purposes. Source-binding PDP policies are built fail-closed (permit_unconstrained: false in registry-notary-server/src/runtime.rs; empty purpose_constraints denies in registry-platform-pdp), so the generated sample denied every purpose, including its own documented one. The OpenCRVS/DCI template already allow-lists the same tutorial purpose, confirming the server behavior is intended and the fix belongs in the generated config.

Changes

  • fix(registryctl): permit tutorial purpose in generated notary policy: add a matching.allowed_purposes block containing only https://example.local/purpose/tutorial to the benefits notary template. Test-first: local_relay_notary_config_permits_tutorial_purpose parses and validates the generated config and asserts the allow-list.
  • fix(registryctl): always print docs URL for open commands: registryctl open / notary open printed nothing in headless sessions because macOS open exits 0 even with no display, so the documented fallback never fired. The commands now always print the URL(s), then best-effort launch a browser. Output extracted into unit-testable helpers with tests.

Security review notes (authorization/policy defaults)

  • The change permits exactly one additional purpose in the local sample project that registryctl generates. It does not alter production defaults, shared PDP behavior, or any committed runtime config.
  • Purpose is the only gate added; no legal-basis, consent, jurisdiction, or assurance constraints are relaxed.
  • The server keeps failing closed on empty allow-lists (unchanged behavior, re-verified).

Verification

  • cargo fmt --check, cargo check --locked --workspace --all-targets, cargo clippy -p registryctl --all-targets -- -D warnings, cargo test --locked -p registryctl: all green.
  • End to end against the pinned notary image: all 7 registryctl notary smoke checks pass; the tutorial's evaluate curl for per-2001 returns 200 with satisfied: true; per-9999 now returns the documented 409 evidence.not_available.

Known related issues, deliberately not in this PR

  • Current-main registryctl no longer emits the commitment fingerprint field the pinned released relay image requires, so a project generated by a locally built registryctl fails to start the pinned relay (missing field commitment). Separate relay-config drift.
  • Live problem-type URIs use dead domains (registry-relay.dev, docs.registry-notary.dev); domain consolidation is tracked separately.
  • The v0.8.3 registryctl asset self-reports 0.1.0; already fixed on main via version.workspace = true.

@jeremi

jeremi commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Two follow-up commits after a template audit:

  • The standalone notary template (init notary, used for registry-data-api and FHIR sidecar sources) had the same defect: no matching.allowed_purposes, so the shared notary smoke evaluate check (which always sends the tutorial purpose) was denied fail-closed. Fixed test-first (standalone_notary_config_permits_tutorial_purpose). The OpenCRVS/DCI template was already correct.
  • Reworded the generated-config comments for the adopter reading the file rather than the reviewer of this change: the allow-list comment now states the field's contract (deny-by-default, the pdp.purpose_not_permitted error code, what to edit), the three notary templates gained orientation headers matching the relay template's voice, and the DCI template warns that matching context constraints must stay in sync with the evaluator key's authorization_details.

Same security posture as before: one purpose permitted in generated local sample configs only.

jeremi added 4 commits July 3, 2026 15:12
The "Evaluate a claim with Registry Notary" tutorial failed at its central
step: `registryctl notary smoke` reported 6 PASS then
`FAIL notary evaluator can verify starter claim`, and the documented
evaluate curl returned 403 pdp.purpose_not_permitted instead of 200
satisfied.

Root cause: the generated benefits/local-relay notary config
(notary_config.yaml.tmpl) declared a source binding with no
matching.allowed_purposes. Source-binding PDP policies run with
permit_unconstrained = false, so purpose_gate_is_declared always returns
true; an empty purpose allow-list therefore denies every request with
pdp.purpose_not_permitted before any evidence lookup. This also made the
tutorial's 409 evidence.not_available teaching point (per-9999) unreachable.

Fix: add matching.allowed_purposes with the tutorial purpose to the
generated source binding, mirroring what the OpenCRVS/DCI sample template
already does and matching the purpose the smoke check and docs send.

Security review notes:
- The change only permits one additional purpose
  (https://example.local/purpose/tutorial) in the LOCAL sample project that
  registryctl generates from `init relay --sample benefits` + `add notary`.
  It does not touch production defaults, shared PDP behavior, or any
  committed runtime config; the server keeps failing closed on empty
  allow-lists.
- Purpose is the only gate added. No legal-basis, consent, jurisdiction,
  or assurance constraints are relaxed; the sample carries none of those,
  matching the tutorial's single-purpose evaluate request.

Test: added local_relay_notary_config_permits_tutorial_purpose, which
parses the generated notary config and asserts the person source binding
permits TUTORIAL_PURPOSE.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
`registryctl open` and `registryctl notary open` promised to "open or print"
the local API docs URL, but printed nothing in headless sessions. On macOS
`open <url>` returns exit 0 even over SSH with no display, so the conditional
fallback (print only when the launch failed) never fired and the user was left
with no URL.

Always print the docs URL(s) first, then best-effort launch a browser. Extract
relay_open_lines / notary_open_lines pure helpers so the surfaced URLs are unit
testable without spawning a browser during tests.

Tests: relay_open_always_reports_docs_url_for_headless_fallback and
notary_open_always_reports_docs_url_for_headless_fallback.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
registryctl notary smoke sends the tutorial purpose for standalone
projects too, and the source-binding PDP policy fails closed, so the
standalone template's empty allow-list denied every evaluation with
pdp.purpose_not_permitted, same defect as the benefits template.

Security review notes: permits exactly one purpose in the generated
local project config; no production defaults or shared PDP behavior
change, and the server still fails closed on empty allow-lists.

Also gives the generated file an orientation header comment matching
the relay template's voice.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The generated configs are read by tutorial followers, not maintainers.
Reword the purpose allow-list comment to state the field's contract
(deny-by-default, the error code, what to edit) instead of justifying
the change, add orientation headers matching the relay template, and
warn in the OpenCRVS/DCI template that matching context constraints
must stay in sync with the evaluator key's authorization_details.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi jeremi force-pushed the fix/notary-tutorial-purpose-policy branch from adc6bc0 to 293076b Compare July 3, 2026 08:13
@jeremi jeremi enabled auto-merge (squash) July 3, 2026 08:15
@jeremi jeremi merged commit e09f64c into main Jul 3, 2026
10 checks passed
@jeremi jeremi deleted the fix/notary-tutorial-purpose-policy branch July 3, 2026 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant