commit 97fdfe0efa84fb2cb5a9bd0984c978550b133be7
parent 4ac891a625014e3044ab343ac3a704c20882335d
Author: triesap <tyson@radroots.org>
Date: Mon, 27 Jul 2026 12:48:29 +0000
ci: enforce crates release architecture
- add the repository-owned architecture and freshness aggregate
- pin the coordinated library baseline for migration checks
- isolate workflow caching from outputs and build artifacts
- document separately authorized status-check activation
Diffstat:
6 files changed, 134 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/architecture.yml b/.github/workflows/architecture.yml
@@ -0,0 +1,53 @@
+name: Architecture
+
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: architecture-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ architecture:
+ name: architecture
+ runs-on: ubuntu-24.04
+ timeout-minutes: 30
+ defaults:
+ run:
+ working-directory: sdk
+ steps:
+ - name: Check out SDK source
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ path: sdk
+ persist-credentials: false
+
+ - name: Check out coordinated library baseline
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: radrootslabs/lib
+ ref: bb9832fa4c33f68b4262140599c111abb5d5480d
+ path: lib
+ persist-credentials: false
+
+ - name: Restore Cargo download cache
+ uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: |
+ ~/.cargo/registry/index
+ ~/.cargo/registry/cache
+ ~/.cargo/git/db
+ key: ${{ runner.os }}-architecture-${{ hashFiles('sdk/Cargo.lock', 'sdk/rust-toolchain.toml', 'lib/Cargo.lock', 'lib/rust-toolchain.toml') }}
+
+ - name: Install the governed Rust toolchain
+ run: rustup toolchain install 1.97.1 --profile minimal --component llvm-tools-preview --target wasm32-unknown-unknown
+
+ - name: Validate crates-release architecture
+ run: cargo run --locked -p radroots_sdk_xtask -- architecture-ci
diff --git a/docs/engineering/ci.md b/docs/engineering/ci.md
@@ -0,0 +1,33 @@
+# Architecture continuous integration
+
+The pull-request architecture lane is a thin GitHub adapter over the
+repository-owned dispatcher:
+
+```sh
+cargo xtask architecture-ci
+```
+
+The command validates the synchronized release specification, workspace and
+package metadata, production dependency paths, the Cargo-resolved package-tier
+graph, public API implementation leakage, SDK feature boundaries, publication
+freeze, facade scaffold, language contracts, and generated-source freshness.
+The workflow invokes the same dispatcher with `cargo run --locked` so lockfile
+drift fails rather than being resolved implicitly.
+
+Until the lower public packages are available from the registry, the checked-in
+developer patch configuration requires a coordinated `radrootslabs/lib`
+checkout. The workflow pins that public source to
+`bb9832fa4c33f68b4262140599c111abb5d5480d`; update the pin only after a
+replacement commit is publicly reachable and passes the library architecture
+lane. This checkout does not alter any production dependency declaration.
+
+The workflow grants only read access to repository contents. Action
+dependencies are pinned to full commit identifiers. It caches only Cargo
+registry and Git downloads, keyed by both repositories' lockfiles and governed
+toolchains; generated outputs and build artifacts are never restored from the
+cache.
+
+Repository administrators may require the `Architecture / architecture`
+status after the pinned library commit and this workflow commit are publicly
+reachable. Changing branch protection or other repository administration
+remains a separate authorized operation.
diff --git a/docs/implementation/deviations.toml b/docs/implementation/deviations.toml
@@ -6,7 +6,18 @@ id = "RCRV1-DEV-001"
date = "2026-07-27"
status = "active"
approval = "Explicit user correction dated 2026-07-27."
-affected_steps = ["015", "016", "017", "018", "019", "020", "021", "022", "023"]
+affected_steps = [
+ "015",
+ "016",
+ "017",
+ "018",
+ "019",
+ "020",
+ "021",
+ "022",
+ "023",
+ "026",
+]
spec_anchors = [
"docs/specs/radroots_crates_release_v1.md#repository-ownership",
"docs/specs/radroots_crates_release_v1.toml#repository_policy",
@@ -19,6 +30,7 @@ replacement_action = "Retain the two existing standalone repositories; replace i
verification = [
"Both repository-local architecture validators resolve every spec anchor.",
"The synchronized architecture catalog enforces the exact 17/2 ownership partition.",
+ "Each standalone repository owns a required architecture CI adapter over its repository-local command surface.",
]
unresolved_risk = "Parent gitlinks cannot advance until the new standalone commits are public-remote reachable under separate authorization."
normative_architecture_change = false
diff --git a/tools/xtask/src/architecture.rs b/tools/xtask/src/architecture.rs
@@ -182,6 +182,12 @@ pub fn validate_api_boundaries(workspace_root: &Path) -> Result<(), String> {
api_leakage::validate_public_api(workspace_root)
}
+pub fn validate_ci(workspace_root: &Path) -> Result<(), String> {
+ validate(workspace_root)?;
+ dependency_boundary::validate_resolved_boundaries(workspace_root)?;
+ api_leakage::validate_public_api(workspace_root)
+}
+
fn validate_workspace_toolchain(
workspace_root: &Path,
architecture: &ArchitectureIdentity,
diff --git a/tools/xtask/src/check.rs b/tools/xtask/src/check.rs
@@ -156,6 +156,18 @@ pub fn check() -> Result<(), String> {
Ok(())
}
+pub fn architecture_ci(root: &Path) -> Result<(), String> {
+ validate_package_matrix()?;
+ check_publication_policy(root)?;
+ check_radroots_facade_scaffold(root)?;
+ validate_sdk_contracts(root)?;
+ check_sdk_feature_matrix(root)?;
+ check_forbidden_packages(root)?;
+ check_binding_crate_sources(root)?;
+ check_package_source_metadata(root)?;
+ check_generated_outputs(root)
+}
+
fn check_radroots_facade_scaffold(root: &Path) -> Result<(), String> {
let manifest_path = root.join("crates/radroots/Cargo.toml");
let manifest_raw = fs::read_to_string(&manifest_path)
diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs
@@ -19,6 +19,7 @@ mod wasm_declarations;
enum CommandAction<'a> {
Architecture,
+ ArchitectureCi,
CheckApiBoundaries,
CheckDependencyBoundaries,
GenerateAll,
@@ -41,6 +42,11 @@ fn run(args: impl IntoIterator<Item = String>) -> Result<(), String> {
let args = args.into_iter().collect::<Vec<_>>();
match command_action(&args)? {
CommandAction::Architecture => architecture::validate(&fs::workspace_root()?),
+ CommandAction::ArchitectureCi => {
+ let root = fs::workspace_root()?;
+ architecture::validate_ci(&root)?;
+ check::architecture_ci(&root)
+ }
CommandAction::CheckApiBoundaries => {
architecture::validate_api_boundaries(&fs::workspace_root()?)
}
@@ -60,6 +66,7 @@ fn run(args: impl IntoIterator<Item = String>) -> Result<(), String> {
fn command_action(args: &[String]) -> Result<CommandAction<'_>, String> {
match args {
[command] if command == "architecture" => Ok(CommandAction::Architecture),
+ [command] if command == "architecture-ci" => Ok(CommandAction::ArchitectureCi),
[command] if command == "check-api-boundaries" => Ok(CommandAction::CheckApiBoundaries),
[command] if command == "check-dependency-boundaries" => {
Ok(CommandAction::CheckDependencyBoundaries)
@@ -83,7 +90,7 @@ fn command_action(args: &[String]) -> Result<CommandAction<'_>, String> {
}
fn usage() -> String {
- "usage: cargo xtask architecture | cargo xtask check-api-boundaries | cargo xtask check-dependency-boundaries | cargo xtask generate | cargo xtask generate ts | cargo xtask generate wasm [--package <key>] | cargo xtask generate package-metadata | cargo xtask check | cargo xtask smoke knowledge-rust-local | cargo xtask coverage run"
+ "usage: cargo xtask architecture | cargo xtask architecture-ci | cargo xtask check-api-boundaries | cargo xtask check-dependency-boundaries | cargo xtask generate | cargo xtask generate ts | cargo xtask generate wasm [--package <key>] | cargo xtask generate package-metadata | cargo xtask check | cargo xtask smoke knowledge-rust-local | cargo xtask coverage run"
.to_owned()
}
@@ -101,6 +108,15 @@ mod tests {
}
#[test]
+ fn accepts_architecture_ci() {
+ let args = ["architecture-ci".to_owned()];
+ assert!(matches!(
+ command_action(&args).expect("action"),
+ CommandAction::ArchitectureCi
+ ));
+ }
+
+ #[test]
fn accepts_api_boundary_check() {
let args = ["check-api-boundaries".to_owned()];
assert!(matches!(