Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 3 additions & 36 deletions crates/registry-notary-server/src/relay_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,21 +1010,6 @@ fn result_decoder(profile: &VerifiedRelayProfile) -> Result<ClosedJsonDecoder, R
],
)?,
)?,
field(
"consent",
true,
object(
false,
vec![
field("outcome", true, string(false, 32)?)?,
field("verifier_id", true, string(true, 128)?)?,
field("verifier_revision", true, string(true, 128)?)?,
field("checked_at", true, string(true, 64)?)?,
field("expires_at", true, string(true, 64)?)?,
field("revocation_status", true, string(false, 32)?)?,
],
)?,
)?,
];
provenance_fields.push(field(
"snapshot",
Expand Down Expand Up @@ -1073,17 +1058,11 @@ fn result_decoder(profile: &VerifiedRelayProfile) -> Result<ClosedJsonDecoder, R
projection("r10", &["provenance", "acquisition_class"])?,
projection("r11", &["provenance", "integration", "id"])?,
projection("r12", &["provenance", "integration", "revision"])?,
projection("r16", &["provenance", "consent", "outcome"])?,
projection("r17", &["provenance", "consent", "verifier_id"])?,
projection("r18", &["provenance", "consent", "verifier_revision"])?,
projection("r19", &["provenance", "consent", "checked_at"])?,
projection("r20", &["provenance", "consent", "expires_at"])?,
projection("r21", &["provenance", "consent", "revocation_status"])?,
projection("r22", &["provenance", "snapshot", "generation_id"])?,
projection("r23", &["provenance", "snapshot", "published_at"])?,
projection("r13", &["provenance", "snapshot", "generation_id"])?,
projection("r14", &["provenance", "snapshot", "published_at"])?,
];
let mut presence = Vec::new();
let output_projection_offset = 24;
let output_projection_offset = 15;
let VerifiedRelayResult::OutputMap(outputs) = &profile.result;
for (index, name) in outputs.keys().enumerate() {
projections.push(projection(
Expand Down Expand Up @@ -1187,12 +1166,6 @@ fn parse_result(
{
return Err(RelayClientError::InvalidResult);
}
fields.require_string("not_required")?;
fields.require_null()?;
fields.require_null()?;
fields.require_null()?;
fields.require_null()?;
fields.require_string("not_applicable")?;
let snapshot = match (fields.scalar()?, fields.scalar()?) {
(ProjectedJsonScalar::String(generation_id), ProjectedJsonScalar::String(published_at)) => {
if generation_id.is_empty() {
Expand Down Expand Up @@ -1352,12 +1325,6 @@ impl FieldCursor {
.ok_or(self.error)
}

fn require_null(&mut self) -> Result<(), RelayClientError> {
matches!(self.scalar()?, ProjectedJsonScalar::Null)
.then_some(())
.ok_or(self.error)
}

fn exhausted(&self) -> bool {
self.fields.as_slice().is_empty()
}
Expand Down
38 changes: 30 additions & 8 deletions crates/registry-notary-server/src/relay_client/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,14 +553,6 @@ fn result_value() -> Value {
"integration": {
"id": "dhis2.tracker.enrollment-status",
"revision": 1
},
"consent": {
"outcome": "not_required",
"verifier_id": null,
"verifier_revision": null,
"checked_at": null,
"expires_at": null,
"revocation_status": "not_applicable"
}
}
})
Expand Down Expand Up @@ -1380,6 +1372,36 @@ async fn result_union_requires_outputs_only_for_match() {
server.shutdown().await;
}

#[tokio::test]
async fn result_rejects_retired_provenance_consent_member() {
let token_file = TestTokenFile::new(&test_token());
let mut result = result_value();
assert!(result["provenance"].get("consent").is_none());
result["provenance"]["consent"] = json!({
"outcome": "not_required",
"verifier_id": null,
"verifier_revision": null,
"checked_at": null,
"expires_at": null,
"revocation_status": "not_applicable"
});
let server = FakeRelay::start(
metadata_response(),
WireResponse::ok(serde_json::to_vec(&result).unwrap()),
)
.await;
let client = verified(&server, &token_file).await;

assert_eq!(
client
.execute(EVALUATION_ID, Zeroizing::new(INPUT_VALUE.to_string()))
.await
.expect_err("retired consent member fails closed"),
RelayClientError::InvalidResult
);
server.shutdown().await;
}

#[tokio::test]
async fn batch_execute_propagates_one_bounded_opaque_child_identity() {
let token_file = TestTokenFile::new(&test_token());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,6 @@ async fn test_relay_execute(State(state): State<TestRelayState>, request: Reques
"integration": {
"id": "dhis2.tracker.enrollment-status",
"revision": 1
},
"consent": {
"outcome": "not_required",
"verifier_id": null,
"verifier_revision": null,
"checked_at": null,
"expires_at": null,
"revocation_status": "not_applicable"
}
}
}))
Expand Down
14 changes: 14 additions & 0 deletions crates/registry-relay/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

## Unreleased

- BREAKING: Remove the inert six-field `provenance.consent` member from the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the missing DCO sign-off

This commit does not contain any Signed-off-by: trailer, and the DCO workflow scans every PR commit for that trailer before allowing CI to pass, so this PR remains unmergeable until the commit is recreated or amended with a valid sign-off.

AGENTS.md reference: AGENTS.md:L70-L70

Useful? React with 👍 / 👎.

pre-1.0 `registry.relay.consultation-result.v1` response and its closed
batch-terminal replay representation. The PostgreSQL terminal constraint is
replaced with the reduced exact shape, so earlier terminal payloads are not
accepted. Consent verification, policy enforcement, legal-basis handling,
evidence commitments, scopes, and trust headers are unchanged. A Relay
consultation state plane initialized with the earlier pre-release constraint
must be cleanly re-bootstrapped before running the updated binary. This is not
a rolling-compatible change: stop new consultations and let the old binary
serve retained terminal replays for their 15-minute retention window before
re-bootstrapping, or re-bootstrap immediately if discarding those pre-1.0
terminal results is acceptable. Do not run old and updated binaries against
one state plane.

## 0.12.2 - 2026-07-20

### Security
Expand Down
2 changes: 1 addition & 1 deletion crates/registry-relay/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ A successful response has the closed `registry.relay.consultation-result.v1` env
only `match`, `no_match`, or `ambiguous`. A `match` includes every declared typed output.
`no_match` and `ambiguous` omit `outputs`. Every outcome includes the generated consultation id,
required Notary evaluation id, exact profile id and `contract_hash`, acquisition time and class,
integration id and revision, snapshot evidence when applicable, and the closed consent result.
integration id and revision, and snapshot evidence when applicable.
Raw inputs, source credentials, source URLs, static policy digests, and source diagnostics are never
returned.

Expand Down
4 changes: 4 additions & 0 deletions crates/registry-relay/openapi/oasdiff-err-ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ GET /v1/datasets/{dataset_id}/entities/{entity}/schema added the new path reques
GET /v1/datasets/{dataset_id}/entities/{entity}/schema added the new path request parameter 'entity'
GET /v1/datasets/{dataset_id}/measures added the new path request parameter 'dataset_id'
GET /v1/datasets/{dataset_id}/measures/{item_id} added the new path request parameter 'dataset_id'

# One-time accepted diff for issue #362 on 2026-07-20: remove the inert
# provenance.consent member from the pre-1.0 consultation result.
POST /v1/consultations/{profile_id}/execute added `subschema #1, subschema #2, subschema #3` to the response body `oneOf` list for the response status `200`
Loading
Loading