Skip to content

feat(relay): promote attribute release to default#488

Open
jeremi wants to merge 4 commits into
mainfrom
agent/issue-487-relay-default
Open

feat(relay): promote attribute release to default#488
jeremi wants to merge 4 commits into
mainfrom
agent/issue-487-relay-default

Conversation

@jeremi

@jeremi jeremi commented Jul 25, 2026

Copy link
Copy Markdown
Member

What

  • promote governed attribute release and Crosswalk runtime to the canonical Relay feature set
  • remove inert or misleading pre-1.0 config fields and require purpose, subject type, and the exact release scope
  • apply governed redaction before release CEL, hide unauthorized profiles, and make every response non-storable
  • publish compiled feature evidence in admin capabilities and Relay image labels
  • align Registryctl authoring, generated schemas and OpenAPI, the support roster, docs, and release checks
  • derive exposure stability and default OpenAPI coverage from the Relay Cargo default-feature graph

Why

Attribute release is the maintained Notary/Relay integration path. Shipping it as an optional variant would create multiple production profiles and leave the official image unable to exercise that path. The hardening in this change closes the gaps found while reviewing promotion readiness.

The initial CI run exposed a stale assurance invariant that treated every feature-gated route as optional and experimental. The checker now permits stable routes only behind Cargo features enabled by default, while preserving the experimental requirement for optional feature gates.

Security

  • release scope and purpose remain mandatory before source reads
  • unauthorized known profiles return the same public 404 as unknown profiles
  • release predicates and computed claims see only the governed-redacted row
  • CEL expressions are limited to the projected source authority, including dotted and bracketed access checks
  • subject identifiers remain keyed audit hashes and never appear in responses
  • release responses and denials are private, no-store
  • optional feature gates remain fail-closed as experimental in the exposure manifest

Verification

  • just ci in Registry Relay
  • just ci-preflight in Registry Relay
  • just exposure-check in Registry Relay
  • python3 -m unittest tests/security_assurance_check_test.py
  • python3 scripts/check_security_assurance.py openapi-coverage
  • just openapi-contract origin/main
  • cargo test --locked -p registryctl project_authoring
  • python3 -m unittest discover -s release/scripts -p 'test_*.py'
  • npm test and npm run check in docs/site
  • supported-target cargo tree review for RUSTSEC-2023-0089

Refs #487

Follow-up release evidence

After the next Registry Stack version is published, Solmara Lab still needs to pin the official Relay image by digest and record the live journey evidence. This PR prepares and verifies the canonical binary and image contract but cannot produce that future release digest.

@jeremi
jeremi marked this pull request as ready for review July 25, 2026 15:27

@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: dbd9f2f5d6

ℹ️ 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 on lines +306 to +309
if &expression[start..index] == "context"
|| matches!(bytes.get(member_index), Some(b'.' | b'['))
{
roots.insert(expression[start..index].to_string());

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 Track only the first member in each CEL chain

When a valid source-only expression contains nested dotted access or a method call, such as source.person.name or source.name.startsWith('A'), this scanner also records the intermediate identifier (person or name) because it is followed by a dot. validate_expression_authority then sees roots other than source and rejects the profile during configuration loading, even though the expression never leaves the projected source authority. Stop scanning the remainder of a member chain as independent roots, and cover nested and method access in the authority tests.

Useful? React with 👍 / 👎.

local digest
digest="$(jq -r '."containerimage.digest"' "${metadata}")"
local digest_ref="${REGISTRY}/${IMAGE_NAMESPACE}/${name}@${digest}"
local -a expected_label_args=()

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

Commit b3d09322b137cac437b5d394d9a633403b3d7b32 has no Signed-off-by trailer, so it violates the repository's DCO requirement and will be rejected by the contribution gate; recreate the commit with the required sign-off before merging.

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

Useful? React with 👍 / 👎.

Comment on lines +4164 to +4165
"Profile not found or not visible to the authenticated principal. \
Does not confirm whether the profile ever existed."

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 Declare the mandatory Data-Purpose header in OpenAPI

Every promoted attribute-release profile now requires an exactly matching Data-Purpose header, but the resolve operation's generated parameter list contains only the two path parameters and optional trust-context headers. Clients generated from this newly canonical OpenAPI contract therefore omit Data-Purpose and receive 400 auth.purpose_required for every otherwise valid resolution. Add the required header parameter to this operation and regenerate the committed OpenAPI output.

AGENTS.md reference: AGENTS.md:L82-L86

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: 4e796a9fc2

ℹ️ 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".

local digest
digest="$(jq -r '."containerimage.digest"' "${metadata}")"
local digest_ref="${REGISTRY}/${IMAGE_NAMESPACE}/${name}@${digest}"
local -a expected_label_args=()

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 sign-off to the rewritten commit

Fresh evidence after the prior DCO comment: the rewritten commit d2bb4d66df8dcc6d7baf22c8f7cc16f2c202c947 itself still has no Signed-off-by trailer. The repository's DCO workflow checks every commit and will reject this one, so recreate it with git commit -s before merging.

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

Useful? React with 👍 / 👎.

COPY --from=builder /usr/local/bin/registry-relay /usr/local/bin/registry-relay
COPY --from=builder /usr/local/bin/registry-relay-rhai-worker /usr/local/bin/registry-relay-rhai-worker
COPY LICENSE /licenses/registry-relay/LICENSE
LABEL org.registrystack.registry-relay.features="${REGISTRY_RELAY_FEATURES}"

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 Record effective features in the image label

For a documented custom build such as REGISTRY_RELAY_FEATURES=attribute-release, Cargo also activates crosswalk-runtime transitively via attribute-release = ["crosswalk-runtime"], but this label records only the literal build argument. The resulting image therefore claims one feature while /admin/v1/capabilities reports both, making the new feature-provenance evidence inaccurate; normalize the transitive feature closure or reject incomplete override lists before applying the label.

AGENTS.md reference: AGENTS.md:L79-L81

Useful? React with 👍 / 👎.

}
}
},
"description": "Invalid request: malformed body, unknown id_type, empty claims list, or unsupported media type."

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 Document the runtime 415 response

When the resolve request has a missing or non-JSON Content-Type, the Json extractor returns 415, as asserted by resolve_rejects_non_json_content_type, but this newly canonical contract classifies an unsupported media type as 400 and declares no 415 response. Generated clients will therefore handle the actual rejection incorrectly; add a 415 response in the OpenAPI generator and regenerate this artifact.

AGENTS.md reference: AGENTS.md:L82-L86

Useful? React with 👍 / 👎.

Comment on lines +1289 to +1290
"description": "Subject identifier value. Never logged or echoed in responses.",
"type": "string"

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 Allow every supported subject scalar in OpenAPI

For profiles backed by numeric or boolean subject fields, the runtime explicitly accepts JSON strings, numbers, and booleans in scalar_subject_value, while the canonical request schema restricts subject.value to a string. OpenAPI validators and generated clients will reject valid numeric/boolean requests or coerce them to strings, potentially changing equality matching against the source field; declare the value as a string/number/boolean union in the generator and regenerate the artifact.

AGENTS.md reference: AGENTS.md:L82-L86

Useful? React with 👍 / 👎.

support_owner: none
feature_frozen: true
canonical_release: false
stability_tier: stable

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 across owning areas

This single change modifies independently owned surfaces under crates/registry-relay, crates/registryctl, docs/site, and release, despite the repository rule that a change remain scoped to one owning area. Split the promotion into owner-scoped changes so each area's contract and required gates can be reviewed independently.

AGENTS.md reference: AGENTS.md:L77-L78

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: 8ce6d9c04e

ℹ️ 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".

local digest
digest="$(jq -r '."containerimage.digest"' "${metadata}")"
local digest_ref="${REGISTRY}/${IMAGE_NAMESPACE}/${name}@${digest}"
local -a expected_label_args=()

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

Fresh evidence after the prior DCO comments: the latest rewritten commit 0a50fc5784766cf82fcb816a43c96e85897b364b still has no Signed-off-by trailer. The repository's DCO workflow checks every commit and will reject this one, so recreate it using git commit -s before merging.

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

Useful? React with 👍 / 👎.

Comment on lines +3704 to +3707
&& !value.contains(',')
&& !value
.chars()
.any(|character| character.is_control() || character.is_whitespace())

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 Restrict header-bound purposes to visible ASCII

When a mandatory profile purpose contains a non-ASCII token such as identité, this validator accepts it because the characters are neither whitespace nor control characters. However, purpose_header_value reads Data-Purpose through HeaderValue::to_str(), which rejects every non-visible-ASCII byte, so no request can ever match the configured purpose and every resolution returns auth.purpose_required. Validate the header-bound purpose as visible ASCII (or define a reversible wire encoding) so an accepted profile remains usable.

Useful? React with 👍 / 👎.

Comment on lines +3801 to 3805
if !entity.api.required_filters.is_empty() {
return release_error(
"attribute_release_profiles cannot use an entity with required_filters because the caller-supplied subject cannot satisfy a principal-bound filter",
);
}

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 Mirror release prerequisites in Registryctl validation

When a Registryctl records service combines an attribute-release profile with nonempty required_principal_filters, the authoring schema and validate_record_attribute_release_profiles accept it and generate the entity, but this new Relay validation rejects that generated configuration during Registryctl's production-loading check. The same mismatch occurs when the authored pagination max_limit is 1 because the adjacent Relay check requires at least 2. Add these cross-field prerequisites to Registryctl's authoring validation so schema-valid projects fail early with an actionable authoring error instead of always failing generated-product activation.

Useful? React with 👍 / 👎.

Comment on lines +106 to +108
enabled: set[str] = set()
pending = list(defaults)
while pending:

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 stability from canonical release features

When a future developer-default feature is added to Cargo.toml, this checker will treat its routes as eligible for stable status and canonical OpenAPI coverage solely because it starts from features.default. The release recipe deliberately builds with --no-default-features and an explicit attribute-release,crosswalk-runtime list so developer defaults cannot expand the published binary; therefore the assurance checks could approve a stable route that is absent from the release. Derive this set from the canonical release feature profile instead of the mutable developer default.

Useful? React with 👍 / 👎.

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