Skip to content

feat(relay): clarify access profiles and add named spatial search - #700

Closed
jeremi wants to merge 24 commits into
mainfrom
codex/relay-v2-spatial
Closed

feat(relay): clarify access profiles and add named spatial search#700
jeremi wants to merge 24 commits into
mainfrom
codex/relay-v2-spatial

Conversation

@jeremi

@jeremi jeremi commented Aug 10, 2026

Copy link
Copy Markdown
Member

What changed

This refines Relay V2 around four distinct concepts and adds a deliberately bounded spatial capability:

  • An operation owns the callable route and complete query shape.
  • An access profile owns exact authorization and maximum disclosure.
  • fields can only reduce the selected access profile.
  • Accept and formatProfile select serialization only.

The business-registry example now exposes a separately authorized named Point-bbox search at /v2/resources/{resource}/searches/{search}. It supports governed JSON, JSON-LD, RFC 7946 GeoJSON, and the bounded JSON-FG profile over the same Registry Records. List and search access remain independent.

relayctl check --production --explain now returns a deterministic, value-free explanation of each operation, access profile, query capability, transform, handling floor, cache posture, and wire format. relayctl generate writes the same canonical operation-explanation.json bytes.

Product boundary

The spatial cut stays intentionally narrow: one reviewed Point per resource, CRS84, one fixed inclusive bounded bbox predicate, named Consultation search, and read-only SQLite comparisons. It does not add OGC API Features, CQL2, EDR, tiles, spatial joins, reprojection, GeoPackage decoding, SpatiaLite, extension loading, generic geometry, dynamic policies, or media-type-specific access rights.

Spatial output inherits the selected access profile's public or protected posture, exact OAuth scope, optional purpose, optional principal or verified-claim row binding, disclosure profile, field minimization, audit, quota, and cache rules.

Contract and security notes

  • Existing pre-release Registry contract, runtime, HTTP binding, audit, package, fixture, and cursor version identities remain unchanged.
  • Old representation, defaultRepresentation, single-profile, and list spatialQuery forms are rejected without aliases.
  • bbox is reserved for named spatial search and cannot compile as an unusable ordinary list filter.
  • Bbox, exact filters, and compiler-injected row authority remain conjunctive in one fixed read-only statement.
  • Cursor context binds the exact named operation, access profile, disclosure, bbox, fields, format, format profile, authorization, revisions, and expiry.
  • Attempt audit precedes source access. Terminal audit gates the exact held response bytes.
  • Public cache revalidation requires public access, public processing, and a snapshot source. Public live and all protected results remain no-store.
  • Selected fixture execution now includes the minimal prerequisite closure for cursors, ETags, and equivalence assertions.

Generated and public documentation

  • OpenAPI documents fresh named search versus cursor continuation without making bbox block cursor-only requests.
  • Capabilities, OpenAPI extensions, and operation explanations use one inclusive-point-within-bbox predicate identifier.
  • Configuration key-path output wildcards adopter-defined access-profile identifiers.
  • Product concepts, Definition of Done, examples, standards alignment, configuration guidance, operator guidance, and the runnable public tutorial are aligned with access profiles and named searches.
  • Standards evidence links use durable public documentation routes rather than feature-branch commit identifiers.

Verification

  • cargo fmt --check
  • cargo test --locked -p registry-relay-v2 --all-features
  • cargo test --locked -p registry-relayctl
  • cargo clippy --locked -p registry-relay-v2 -p registry-relayctl --all-targets --all-features -- -D warnings
  • products/relay-v2/scripts/check-contracts.sh
  • products/relay-v2/scripts/check-generated.sh
  • cargo deny check
  • cd docs/site && npm run check

Clean-context tutorial, security, and staff-engineering reviews were run after the final remediation.

jeremi added 3 commits August 10, 2026 03:04
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi
jeremi marked this pull request as ready for review August 10, 2026 03:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee04e649e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

[dev-dependencies]
async-trait.workspace = true
futures.workspace = true
jsonschema.workspace = true

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 required DCO sign-off

The reviewed commit has no Signed-off-by: trailer, so it does not satisfy the repository's mandatory DCO policy and will be rejected by DCO enforcement; recreate or squash the commit with git commit -s.

AGENTS.md reference: AGENTS.md:L266-L266

Useful? React with 👍 / 👎.

Comment thread crates/registry-relay-v2/src/api.rs Outdated
Comment on lines +2370 to +2373
} else if geojson && supports_representation(operation, ContractRepresentation::Geojson) {
Ok(WireRepresentation::GeoJson(GeoJsonProfile::Rfc7946))
} else if json && supports_representation(operation, ContractRepresentation::Json) {
Ok(WireRepresentation::Json)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor Accept quality before choosing GeoJSON

When a spatial client sends a weighted fallback such as Accept: application/json;q=1, application/geo+json;q=0.1, negotiation records only booleans and this fixed ordering selects GeoJSON despite JSON having the higher quality. That unexpectedly changes the response envelope for clients that explicitly prefer JSON; rank the operation's acceptable representations by their parsed quality and header order instead.

Useful? React with 👍 / 👎.

Comment on lines +827 to +831
json!(operation
.schema_reference
.strip_suffix(".schema.json")
.map(|base| format!("{base}.geojson.schema.json"))
.unwrap_or_else(|| format!("{}.geojson", operation.schema_reference))),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the served URI for the GeoJSON schema ID

For generated operation references such as /v2/artifacts/registered-premises--read-schema, the suffix test fails and assigns the GeoJSON schema an $id ending in -schema.geojson. The artifact is actually served under the identifier registered-premises--read-geojson-schema, so consumers resolving the schema's declared identity receive a nonexistent artifact; derive $id from the generated GeoJSON artifact identifier instead.

Useful? React with 👍 / 👎.

Comment on lines +1720 to +1723
let longitude_span = bbox.east - bbox.west;
let latitude_span = bbox.north - bbox.south;
if longitude_span > f64::from(spatial.maximum_longitude_span_degrees)
|| latitude_span > f64::from(spatial.maximum_latitude_span_degrees)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare bbox spans without binary-rounding rejection

When a publisher sets a one-degree maximum, a mathematically exact boundary box such as bbox=-128.8,0,-127.8,1 is rejected because the parsed f64 subtraction produces 1.0000000000000142. This makes valid requests at the documented inclusive span limit fail depending on their coordinate values; validate the decimal span without binary-rounding error, and keep the mirrored PointBbox::is_within check consistent.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 368d3295be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/registry-relay-v2/src/api.rs Outdated
Comment on lines +2343 to +2346
let Some(value) = headers.get(ACCEPT) else {
return Ok(Representation::Json);
return supports_representation(operation, ContractRepresentation::Json)
.then_some(WireRepresentation::Json)
.ok_or(ProblemCode::UnsupportedRepresentation);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Select a declared format when Accept is absent

When an operation explicitly declares only representations: [geojson] or [json-ld], which compile_representations permits, a request without an Accept header should accept any available representation, but this branch tries only JSON and returns 406. Generic clients sending Accept: */* or application/* fail similarly because those wildcards only enable JSON; choose among the operation's declared formats for absent or wildcard Accept values.

Useful? React with 👍 / 👎.

Comment thread docs/site/src/data/standards.yaml Outdated
- Relay V2 bounded Point collection responses
evidence_docs:
- label: Relay V2 spatial response acceptance tests
url: https://github.com/registrystack/registry-stack/blob/ee04e649e78c78b17ea9460f49c4f0e2906b39b9/crates/registry-relay-v2/tests/acceptance_http.rs

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a main-reachable evidence link

The four new GeoJSON/JSON-FG evidence URLs pin commit ee04e649..., but ancestry checks against both pushed main and reviewed commit ba0edfd1... fail; that SHA exists only in the local review history. After the change is squashed or merged these public documentation links can return 404, including their generated JSON copies, so replace the source YAML URLs with stable, main-reachable evidence and regenerate the data.

AGENTS.md reference: AGENTS.md:L269-L271

Useful? React with 👍 / 👎.

Comment on lines +1478 to +1482
if let Some(spatial_query) = &list.spatial_query {
let Some(geometry) = primary_geometry else {
self.error(
"list.spatial_query_without_geometry",
&format!("{location}.spatialQuery"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require bbox geometry in the disclosure profile

For a list declaring spatialQuery.bbox with only JSON/JSON-LD representations, this branch checks that a non-personal primary geometry exists but never checks that the selected disclosure profile contains it; the only such membership check is conditional on opting into GeoJSON. The compiler therefore accepts a contract that exposes record membership through bbox tests over a geometry excluded from the operation's stated disclosure maximum, contrary to the documented requirement in docs/site/src/content/docs/configure/relay.mdx; validate disclosure membership whenever bbox is enabled, independently of representation choice.

AGENTS.md reference: AGENTS.md:L64-L65

Useful? React with 👍 / 👎.

jeremi added 14 commits August 10, 2026 13:21
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi
jeremi force-pushed the codex/relay-v2-spatial branch from 368d329 to bc73b88 Compare August 10, 2026 09:17

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc73b883ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/registry-relay-v2/src/api.rs Outdated
return Err(ProblemCode::FieldsInvalid);
}
}
"profile" => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reserve the new bbox and profile query names

When an authored exact filter is named profile or bbox, compilation still accepts it because RESERVED_PARAMETERS contains neither name, but these new branches intercept the parameter before declared filters are processed. Such a contract therefore generates an unreachable filter (and may also emit duplicate OpenAPI parameters); reject both names during compilation as the Relay V2 product contract requires.

AGENTS.md reference: AGENTS.md:L62-L65

Useful? React with 👍 / 👎.

"semanticModelReference": representation.semantic_model_reference,
"contextReference": representation.context_reference,
"formats": response_format_documents(resource, representation),
"spatialQuery": operation.query.spatial_bbox.as_ref().map(|spatial| json!({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive capability inventory patterns from operation.pattern

For a list with spatial_bbox, the compiler sets operation.pattern to Search, and runtime metadata plus OpenAPI advertise consultation.search; however, capability_inventory still derives pattern from OperationKind::List, so every generated public, full, or operation-bound capability artifact emits pattern: list beside this new spatialQuery. Use the compiled pattern here so generated discovery agrees with the bounded-point-search contract.

AGENTS.md reference: AGENTS.md:L62-L65

Useful? React with 👍 / 👎.

an access or row boundary.
Registry Core remains present.

## Add a bounded Point profile

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 Split the change along owning-area boundaries

This commit combines runtime changes under crates/, product-contract changes under products/, and public-site changes under docs/site/, despite the repository requiring each change to remain within one owning area. Split these into separately scoped commits so each area's review and verification boundary remains enforceable.

AGENTS.md reference: AGENTS.md:L272-L274

Useful? React with 👍 / 👎.

parameters.push(json!({
"name": "bbox",
"in": "query",
"required": false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark bbox required when it is the sole query constraint

For a spatial list with no equality filters and allowUnfiltered: false—including the new registered-premises acceptance contract—the runtime rejects every request that omits bbox, but the generated OpenAPI declares this parameter optional. Generated clients and readers can therefore issue requests that the published contract says are valid but Relay always rejects; set required when bbox is the only reachable query constraint, or otherwise encode that requirement in the operation contract.

AGENTS.md reference: AGENTS.md:L62-L65

Useful? React with 👍 / 👎.

jeremi added 7 commits August 10, 2026 17:19
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi jeremi changed the title feat(relay): add governed spatial point profile feat(relay): clarify access profiles and add named spatial search Aug 10, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 15ba97b1e3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

),
},
properties,
primary_geometry,

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 Bind primary geometry into classification inventory

When a contract uses the new primaryGeometry and its semanticTerm or sourceRequired value changes after classification review, this compiled field is absent from classification_inventory_digest's ResourceInventory, while classification_inventory_report likewise enumerates only resource.properties. Because the carrier columns and disclosure profile can remain unchanged, the inventory digest does not change and a stale ClassificationReview can still pass production compilation even though the published geometry meaning or requiredness changed; include the geometry's governed binding in both canonical inventory paths.

Useful? React with 👍 / 👎.

"the primary geometry name must be URL-safe camelCase",
);
}
if property_names.contains(geometry.name.as_str()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reserve JSON-LD core names for primary geometry

When primaryGeometry.name is a valid camel-case transport name such as registryIdentifier, recordIdentifier, domainData, items, or meta, this check accepts it because it only tests scalar-property collisions. json_ld_context later inserts the geometry term into the same context map as those Registry Core and envelope terms, so one insertion overwrites the other and JSON-LD consumers interpret either the core value or geometry under the wrong semantic mapping; reject geometry names that collide with the generated JSON-LD vocabulary.

Useful? React with 👍 / 👎.

Comment on lines 1432 to +1433
if matches!(operation.kind, OperationKind::List)
&& representation.disclosure_handling >= Handling::Confidential
&& access_profile.disclosure_handling >= Handling::Confidential

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include searches in nonpublic collection review

When a protected named Point-bbox search discloses confidential data, compilation permits it, but this contextual-review condition only matches OperationKind::List. The generated review findings therefore omit the nonpublic collection-disclosure prompt for the newly added search operation even though the corresponding restricted-data compiler checks explicitly treat lists and searches alike; include OperationKind::Search here or emit an equivalent search-specific finding.

Useful? React with 👍 / 👎.

Base automatically changed from codex/relay-v2 to main August 10, 2026 18:52
@jeremi jeremi closed this Aug 11, 2026
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