Skip to content
Merged
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
10 changes: 9 additions & 1 deletion .github/workflows/release-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -856,11 +856,19 @@ jobs:
printf '%s\n' "${grype_binary_sha256}" > dist/candidate/dist/grype/grype-binary.sha256
for name in registry-notary registry-relay; do
digest_ref="$(cat "inputs/build-a/dist/images/${name}.digest")"
expected_label_args=()
if [[ "${name}" == "registry-relay" ]]; then
relay_features="$(<crates/registry-relay/canonical-release-features.txt)"
Comment thread
jeremi marked this conversation as resolved.
Comment thread
jeremi marked this conversation as resolved.
Comment thread
jeremi marked this conversation as resolved.
expected_label_args+=(
--expected-label "org.registrystack.registry-relay.features=${relay_features}"
)
fi
python3 release/scripts/check-release-image-oci-labels.py \
"${digest_ref}" \
--source "https://github.com/${GITHUB_REPOSITORY}" \
--revision "${{ needs.validate.outputs.source_sha }}" \
--version "${{ needs.validate.outputs.version }}"
--version "${{ needs.validate.outputs.version }}" \
"${expected_label_args[@]}"
syft "${digest_ref}" \
-o "spdx-json=dist/candidate/dist/sbom/${name}.spdx.json" \
-o "syft-json=dist/candidate/dist/sbom/${name}.syft.json"
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ axum-test = { version = "20" }
base64 = { version = "0.22" }
bytes = { version = "1" }
calamine = { version = "0.36" }
cel = { version = "0.13" }
chrono = { version = "0.4" }
clap = { version = "4", features = ["derive", "env"] }
crc32fast = { version = "1.4" }
Expand Down
9 changes: 9 additions & 0 deletions crates/registry-relay/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Unreleased

- BREAKING: Promote governed attribute release to the stable default and
canonical release feature set. Require purpose, exact release-scope, and
subject-type bindings; remove inert pre-1.0 configuration fields; evaluate
release expressions only over redacted rows; prevent profile enumeration;
disable response storage; and publish compiled feature evidence through the
admin capabilities document and Relay image metadata. Profile versions use a
portable URL-path grammar, and resolve JSON rejections now return Relay
Problem Details with non-storable headers.

## 0.13.0 - 2026-07-25

- BREAKING: Remove the inert six-field `provenance.consent` member from the
Expand Down
18 changes: 11 additions & 7 deletions crates/registry-relay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ workspace = true
[package.metadata.cargo-machete]
ignored = ["humantime-serde"]

[build-dependencies]
toml = { version = "1.1" }

[features]
# The 1.0 default build keeps the beta attribute-release surface out of the
# shipped API. Opt in with `--features attribute-release` to compile the
# CEL-backed adapter, routes, config surface, and observability buckets.
default = []
attribute-release = ["crosswalk-runtime"]
# The 1.0 default build includes the stable attribute-release surface. Release
# recipes still select the exact canonical features explicitly so changing this
# developer default cannot silently expand a published binary.
default = ["attribute-release"]
Comment thread
jeremi marked this conversation as resolved.
Comment thread
jeremi marked this conversation as resolved.
Comment thread
jeremi marked this conversation as resolved.
attribute-release = ["crosswalk-runtime", "dep:cel"]
crosswalk-runtime = ["dep:crosswalk-core"]
standards-cel-mapping = ["crosswalk-runtime"]
spdci-api-standards = ["dep:jsonschema"]
Expand Down Expand Up @@ -66,6 +69,7 @@ native-tls = { version = "0.2" }
# relying on a re-export path.
futures = { version = "0.3" }
bytes = { version = "1" }
cel = { workspace = true, optional = true }
# Atomic state swap for IngestPlan readiness (single-writer, many-reader).
arc-swap = { version = "1" }
# Implements DataFusion's async TableProvider delegation for atomic swaps.
Expand All @@ -79,8 +83,8 @@ serde_json = { version = "1" }
schemars.workspace = true
rhai.workspace = true
crosswalk-functions = { workspace = true, features = ["date", "redaction"] }
# Optional Crosswalk CEL mapping support. Disabled by default so the core VC
# path does not pull in the mapping dependency graph.
# Crosswalk CEL support for canonical attribute release and optional mapping
# adapters.
crosswalk-core = { workspace = true, optional = true }
jsonschema = { version = "0.18", optional = true }
# Pure Rust YAML parser used instead of the previous unmaintained YAML stack.
Expand Down
14 changes: 11 additions & 3 deletions crates/registry-relay/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# syntax=docker/dockerfile:1.7

ARG REGISTRY_RELAY_FEATURES="attribute-release,crosswalk-runtime"

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 Preserve an explicit empty feature override

After making the Docker ARG non-empty by default, REGISTRY_RELAY_FEATURES= can no longer select the Dockerfile's explicit minimal build path through scripts/build-image.sh: that helper only adds --build-arg when the value is non-empty (lines 75–77), so an explicitly empty value is indistinguishable from an unset variable and silently builds the canonical features instead. Detect whether the variable is set rather than whether it is non-empty so operators can still request a no-feature image.

Useful? React with 👍 / 👎.


# Keep the tag for humans and the digest for reproducible pulls.
FROM rust:1.95-trixie@sha256:f49565f188ee00bc2a18dd418183f2c5f23ef7d6e691890517ed341a598f67c3 AS builder
ARG REGISTRY_RELAY_FEATURES
WORKDIR /workspace/registry_relay

COPY Cargo.toml Cargo.lock ./
Expand All @@ -13,16 +16,19 @@ COPY --from=crosswalk /Cargo.toml /Cargo.lock /workspace/crosswalk/
COPY --from=crosswalk /crates /workspace/crosswalk/crates
COPY benches ./benches
COPY resources ./resources
COPY build.rs build_support.rs ./
COPY scripts/validate-feature-profile.sh ./scripts/validate-feature-profile.sh
COPY src ./src

ARG REGISTRY_RELAY_FEATURES=""
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/workspace/registry_relay/target \
find src benches resources -type f -exec touch {} + && \
sh scripts/validate-feature-profile.sh "$REGISTRY_RELAY_FEATURES" && \
if [ -n "$REGISTRY_RELAY_FEATURES" ]; then \
cargo build --release --locked --features "$REGISTRY_RELAY_FEATURES"; \
REGISTRY_RELAY_FEATURES="$REGISTRY_RELAY_FEATURES" \
cargo build --release --locked --no-default-features --features "$REGISTRY_RELAY_FEATURES"; \
else \
cargo build --release --locked; \
REGISTRY_RELAY_FEATURES="" cargo build --release --locked --no-default-features; \
fi && \
cp /workspace/registry_relay/target/release/registry-relay /usr/local/bin/registry-relay && \
cp /workspace/registry_relay/target/release/registry-relay-rhai-worker /usr/local/bin/registry-relay-rhai-worker && \
Expand All @@ -35,11 +41,13 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \

# Distroless cc keeps glibc and CA certificates while dropping shell/package tools.
FROM gcr.io/distroless/cc-debian13:nonroot@sha256:d97bc0a941b8d4be647dc0ee75b264ddbb772f1ac5ba690a4309c00723b23775 AS runtime
ARG REGISTRY_RELAY_FEATURES

COPY --from=builder --chown=65532:65532 /workspace/runtime-root/ /
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}"
Comment thread
jeremi marked this conversation as resolved.

WORKDIR /var/lib/registry-relay

Expand Down
26 changes: 20 additions & 6 deletions crates/registry-relay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ Standards integrations such as DCAT-AP, OGC API Records, OGC API Features, Regis

## 1.0 Support Roster

The canonical 1.0 release has no optional Relay Cargo features enabled. Stable
support covers OpenAPI 3.x and RFC 9457 errors; RFC 9727 and portable DCAT,
DCAT-AP, BRegDCAT-AP, JSON-LD, SHACL, JSON Schema, ODRL, and link-free OGC
Records metadata; CSV and XLSX source input; and JSON aggregate output.
The canonical 1.0 release enables the `attribute-release` and
`crosswalk-runtime` Relay Cargo features. Stable support covers governed
attribute release; OpenAPI 3.x and RFC 9457 errors; RFC 9727 and portable
DCAT, DCAT-AP, BRegDCAT-AP, JSON-LD, SHACL, JSON Schema, ODRL, and link-free
OGC Records metadata; CSV and XLSX source input; and JSON aggregate output.

CSV, XLSX, and Parquet are source decoders. Aggregate output supports JSON,
CSV, and SDMX-JSON. The live OGC API Records adapter, OGC API Features, OGC API
EDR, SP DCI routes, standards-CEL mapping, CSV and SDMX-JSON aggregate output,
attribute release, and Parquet source input are experimental and feature-frozen.
and Parquet source input are experimental and feature-frozen.
Experimental surfaces are outside the 1.0 compatibility promise. Feature-gated
source and all-feature tests remain available. Non-feature-gated experimental
formats remain shipped but unstable to avoid breaking existing configurations.
Expand Down Expand Up @@ -105,7 +106,20 @@ registry-stack root.
scripts/build-image.sh registry-relay:local
```

The production image is distroless, non-root, and built with no optional Cargo features; standards-enabled images opt in through `REGISTRY_RELAY_FEATURES`. Build steps, sibling-checkout requirements, and promotion gates are in [docs/ops.md](docs/ops.md#build-and-release); image publication, tagging, and signing policy are in [docs/security-assurance.md](docs/security-assurance.md). Release images publish to `ghcr.io/registrystack/registry-relay` from stable `vX.Y.Z` tags and `registry-stack-technical-preview-<date-or-version>` tags; consume release tags or digests, not `latest`, for rollback guarantees. `Dockerfile.demo` is demo-only and is not release evidence.
The production image is distroless, non-root, and built with the feature set in
[`canonical-release-features.txt`](canonical-release-features.txt), currently
`attribute-release,crosswalk-runtime`. A custom
`REGISTRY_RELAY_FEATURES` value replaces that exact set. Custom profiles must
list product features in alphabetical order and include Cargo's complete
transitive feature closure. Incomplete, unordered, or duplicate profiles fail
the image build before an inaccurate feature label can be published. Build steps, sibling-checkout
requirements, and promotion gates are in
[docs/ops.md](docs/ops.md#build-and-release); image publication, tagging, and
signing policy are in [docs/security-assurance.md](docs/security-assurance.md).
Release images publish to `ghcr.io/registrystack/registry-relay` from stable
`vX.Y.Z` tags and `registry-stack-technical-preview-<date-or-version>` tags;
consume release tags or digests, not `latest`, for rollback guarantees.
`Dockerfile.demo` is demo-only and is not release evidence.

## Operating With Registry Notary

Expand Down
44 changes: 44 additions & 0 deletions crates/registry-relay/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0

mod build_support;

use std::env;
use std::fs;
use std::path::PathBuf;

fn main() {
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-env-changed=REGISTRY_RELAY_FEATURES");

let manifest =
fs::read_to_string("Cargo.toml").expect("registry-relay Cargo.toml must be readable");
let declared = build_support::declared_feature_names(&manifest)
.expect("registry-relay Cargo features must be readable");
let mut enabled = declared
.iter()
.filter(|feature| {
let environment_name =
format!("CARGO_FEATURE_{}", feature.replace('-', "_").to_uppercase());
env::var_os(environment_name).is_some()
})
.cloned()
.collect::<Vec<_>>();
enabled.sort();

if let Ok(requested) = env::var("REGISTRY_RELAY_FEATURES") {
build_support::validate_requested_profile(&requested, &declared, &enabled)
.unwrap_or_else(|error| panic!("invalid Registry Relay feature profile: {error}"));
}

let generated = format!(
"const COMPILED_CARGO_FEATURES: &[&str] = &[\n{}\n];\n",
enabled
.iter()
.map(|feature| format!(" {feature:?},"))
.collect::<Vec<_>>()
.join("\n")
);
let output = PathBuf::from(env::var_os("OUT_DIR").expect("Cargo must provide OUT_DIR"))
.join("compiled_cargo_features.rs");
fs::write(output, generated).expect("compiled feature inventory must be writable");
}
82 changes: 82 additions & 0 deletions crates/registry-relay/build_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: Apache-2.0

use std::collections::BTreeSet;

pub fn declared_feature_names(manifest: &str) -> Result<Vec<String>, String> {
let document = manifest
.parse::<toml::Table>()
.map_err(|error| format!("Cargo.toml is not valid TOML: {error}"))?;
let features = document
.get("features")
.and_then(toml::Value::as_table)
.ok_or_else(|| "Cargo.toml must contain a [features] table".to_string())?;
Ok(features
.keys()
.filter(|feature| feature.as_str() != "default")
.cloned()
.collect())
}

pub fn validate_requested_profile(
requested: &str,
declared: &[String],
enabled: &[String],
) -> Result<(), String> {
if requested
.bytes()
.any(|byte| !matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'-' | b','))
{
return Err("use comma-separated Cargo feature names".to_string());
}
if requested.starts_with(',') || requested.ends_with(',') || requested.contains(",,") {
return Err("feature list must not contain empty entries".to_string());
}

let requested = if requested.is_empty() {
Vec::new()
} else {
requested.split(',').map(str::to_string).collect::<Vec<_>>()
};
let requested_set = requested.iter().collect::<BTreeSet<_>>();
if requested_set.len() != requested.len() {
return Err("feature list must not contain duplicates".to_string());
}

let mut canonical = requested.clone();
canonical.sort();
if requested != canonical {
return Err(format!(
"feature list must use canonical order: {}",
canonical.join(",")
));
}

let declared = declared.iter().collect::<BTreeSet<_>>();
let unknown = requested
.iter()
.filter(|feature| !declared.contains(feature))
.cloned()
.collect::<Vec<_>>();
if !unknown.is_empty() {
return Err(format!("unknown Cargo features: {}", unknown.join(",")));
}

let enabled = enabled.iter().collect::<BTreeSet<_>>();
let missing = enabled
.difference(&requested_set)
.map(|feature| feature.as_str())
.collect::<Vec<_>>();
let inactive = requested_set
.difference(&enabled)
.map(|feature| feature.as_str())
.collect::<Vec<_>>();
if !missing.is_empty() || !inactive.is_empty() {
return Err(format!(
"requested profile does not match Cargo's effective feature set \
(missing: [{}], inactive: [{}])",
missing.join(","),
inactive.join(",")
));
}
Ok(())
}
1 change: 1 addition & 0 deletions crates/registry-relay/canonical-release-features.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
attribute-release,crosswalk-runtime
11 changes: 7 additions & 4 deletions crates/registry-relay/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ ignore = [
# through Crosswalk -> phonenumber -> postcard -> heapless 0.7. Phonenumber
# uses postcard at build time and runtime, but heapless selects
# `atomic-polyfill` only for AVR, riscv32i/riscv32imc, thumbv6m, and
# xtensa-esp32s2, not Relay's supported service targets. Review when the
# chain removes it, before supporting an affected embedded target, or
# before making CEL mapping part of a hardened default runtime.
# xtensa-esp32s2, not Relay's supported x86_64/aarch64 Linux or x86_64
# macOS service targets. Reviewed 2026-07-25 for the canonical
# attribute-release runtime. Review when the chain removes it, before
# supporting an affected embedded target, or if the advisory scope changes.
"RUSTSEC-2023-0089",
# RUSTSEC-2024-0436: `paste` is an unmaintained build-time procedural macro
# pulled in by Relay's DataFusion and Parquet graph. It does not remain in
Expand Down Expand Up @@ -69,7 +70,9 @@ workspace-default-features = "allow"
external-default-features = "allow"
allow = []
allow-workspace = false
deny = []
deny = [
{ name = "cel", deny-multiple-versions = true },
]
skip = []
skip-tree = []

Expand Down
Loading
Loading