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
4 changes: 4 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# pages.yml — GitHub Pages deployment via Ddraig SSG.
name: GitHub Pages (Ddraig SSG)
on:
push:
Expand Down
104 changes: 104 additions & 0 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# rust-ci.yml — Build and test the Aletheia microkernel (Rust).
#
# WHY THIS LIVES AT THE REPOSITORY ROOT
# ------------------------------------
# `aletheia/` is vendored as plain tracked files, not a submodule. GitHub Actions
# only reads workflows from `.github/workflows/` at the repository root, so the 16
# workflow files under `aletheia/.github/workflows/` have never executed — see
# `aletheia/.github/workflows/README.md`. Until this file landed, ~962 lines of Rust
# had no build, test, or format gate of any kind, and `main` sat uncompilable for
# over a month (broken 2026-06-17 by b5322c2, fixed in this change).
#
# SCOPE OF THIS GATE — read before trusting a green tick
# -----------------------------------------------------
# Gated here (all genuinely passing, all blocking):
# * debug + release build
# * the 26 unit tests
# * `cargo fmt --check`
#
# NOT gated here, because they are genuinely red today and a passing-but-hollow
# job is worse than no job:
# * `tests/integration_tests.rs` — 27 of 29 fail; they exercise a CLI surface
# (--help, --version, --format=, --badge, --html, --init-hook) that `src/main.rs`
# does not implement.
# * `cargo clippy -- -D warnings` — 25 findings, mostly dead code from modules
# that `main.rs` never wires up.
# Both are tracked as issues. Add them here as blocking jobs once they pass; do not
# add them with `continue-on-error`.
name: Rust CI

on:
pull_request:
branches: ['**']
push:
branches: [main, master]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: rust-ci-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
working-directory: aletheia

jobs:
build:
name: Build (debug + release)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
submodules: false

- name: Show toolchain
run: cargo --version && rustc --version

- name: Verify zero dependencies (RSR Bronze constraint)
run: |
if cargo tree --depth 1 | tail -n +2 | grep -q '[a-z]'; then
echo "::error::Aletheia must have zero dependencies (see aletheia/CLAUDE.md)"
cargo tree --depth 1
exit 1
fi
echo "Zero dependencies confirmed"

- name: Build (debug)
run: cargo build --locked --verbose

- name: Build (release)
run: cargo build --locked --release --verbose

test:
name: Unit tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
submodules: false

- name: Run unit tests
run: cargo test --locked --bins --verbose

format:
name: Formatting
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
submodules: false

- name: cargo fmt --check
run: cargo fmt --all --check
2 changes: 1 addition & 1 deletion absolute-zero
46 changes: 46 additions & 0 deletions aletheia/.github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!--
SPDX-License-Identifier: CC-BY-SA-4.0
SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
-->

# These 16 workflows do not run. They never have.

GitHub Actions only reads workflow files from `.github/workflows/` **at the root of a
repository**. This directory is nested inside `maa-framework`, so every YAML file here
is inert — including `rust-ci.yml`, `codeql.yml`, `cflite_pr.yml`, `cflite_batch.yml`,
`generator-generic-ossf-slsa3-publish.yml`, `scorecard.yml`, and `ghcr-publish.yml`.

## Why they are here

`aletheia` began as the standalone repository `hyperpolymath/aletheia` (created
2025-12-11), where these workflows *did* run. That repository was removed from GitHub —
its mirrors on GitLab, Codeberg and Bitbucket all stop by early January 2026. On
2026-02-21, commit `639f389` left behind a dangling gitlink with no `.gitmodules`
entry; on 2026-03-02, commit `25cf219` ("Fix stale submodule pointers after repo
cleanup") replaced that pointer by vendoring 361 files — these workflows among them —
into `maa-framework` as ordinary tracked files.

Nothing has executed them since.

## What that cost

Between 2026-06-17 and 2026-07-21, `aletheia` **did not compile**. Commit `b5322c2`
("security: remediate Track C and Track E findings") correctly added a 1 MiB read cap
to `Config::load_config`, but collapsed the block onto one line and dropped a closing
brace. A single `cargo build` would have caught it. Nothing ran one, so `main` stayed
broken for over a month.

## Where the real gate lives now

`/.github/workflows/rust-ci.yml`, at the repository root. It builds debug and release,
runs the 26 unit tests, checks formatting, and enforces the zero-dependency constraint
from `aletheia/CLAUDE.md`. Read its header comment for what it deliberately does *not*
gate yet.

## If you are changing CI for aletheia

Edit the root workflow. Editing anything in this directory has no effect. These files
are retained only as the source material for porting the capabilities that were lost —
ClusterFuzzLite fuzzing, SLSA3 provenance, GHCR publishing — back to root workflows.
Once a capability is ported, delete its file here so this directory shrinks toward
empty.
5 changes: 4 additions & 1 deletion aletheia/benches/verification_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,10 @@ fn main() {
} else if avg_ms < 10.0 {
println!("\n Status: ✅ TARGET MET ({:.2}ms < 10ms)", avg_ms);
} else {
println!("\n Status: ⚠️ NEEDS IMPROVEMENT ({:.2}ms >= 10ms)", avg_ms);
println!(
"\n Status: ⚠️ NEEDS IMPROVEMENT ({:.2}ms >= 10ms)",
avg_ms
);
}

// Memory info (if available on Linux)
Expand Down
45 changes: 31 additions & 14 deletions aletheia/src/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
//! RSR Compliance Verification Kernel.
//!
//! This module implements the deterministic checks used by Aletheia to
//! audit repository state. It performs physical filesystem analysis to
//! This module implements the deterministic checks used by Aletheia to
//! audit repository state. It performs physical filesystem analysis to
//! validate documentation, build system files, and security configurations.

use std::fs;
Expand All @@ -20,7 +20,7 @@ pub fn glob_match(pattern: &str, text: &str) -> bool {
}

/// SECURITY: Validates that a path does not contain malicious symlinks.
/// Specifically checks if a symlink "escapes" the repository root, which
/// Specifically checks if a symlink "escapes" the repository root, which
/// is a critical safety invariant for air-gapped or verified builds.
pub fn check_path_security(path: &Path, repo_root: &Path) -> PathCheckResult {
let metadata = match fs::symlink_metadata(path) {
Expand All @@ -29,19 +29,36 @@ pub fn check_path_security(path: &Path, repo_root: &Path) -> PathCheckResult {
};

if !metadata.file_type().is_symlink() {
return PathCheckResult { exists: true, ..Default::default() };
return PathCheckResult {
exists: true,
..Default::default()
};
}

// RESOLUTION: Determine the absolute target of the symlink.
let target = match fs::read_link(path) {
Ok(t) => t,
Err(_) => return PathCheckResult { exists: true, is_symlink: true, ..Default::default() },
Err(_) => {
return PathCheckResult {
exists: true,
is_symlink: true,
..Default::default()
}
},
};

// ESCAPE DETECTION: Canonicalize and verify prefix.
let canonical_root = repo_root.canonicalize().unwrap_or_else(|_| repo_root.to_path_buf());
let resolved_target = if target.is_absolute() { target } else { path.parent().expect("TODO: handle error").join(target) };
let canonical_target = resolved_target.canonicalize().unwrap_or_else(|_| resolved_target);
let canonical_root = repo_root
.canonicalize()
.unwrap_or_else(|_| repo_root.to_path_buf());
let resolved_target = if target.is_absolute() {
target
} else {
path.parent().expect("TODO: handle error").join(target)
};
let canonical_target = resolved_target
.canonicalize()
.unwrap_or_else(|_| resolved_target);

PathCheckResult {
exists: true,
Expand Down Expand Up @@ -85,11 +102,8 @@ pub fn check_spdx_headers(report: &mut ComplianceReport, repo_path: &Path) {
if path.extension().map_or(false, |ext| ext == "rs") {
checked += 1;
if let Ok(content) = fs::read_to_string(&path) {
let first_10_lines: String = content
.lines()
.take(10)
.collect::<Vec<_>>()
.join("\n");
let first_10_lines: String =
content.lines().take(10).collect::<Vec<_>>().join("\n");
if first_10_lines.contains("SPDX-License-Identifier") {
valid += 1;
}
Expand Down Expand Up @@ -121,7 +135,10 @@ pub fn check_workflow_pins(report: &mut ComplianceReport, repo_path: &Path) {
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
if path.extension().map_or(false, |ext| ext == "yml" || ext == "yaml") {
if path
.extension()
.map_or(false, |ext| ext == "yml" || ext == "yaml")
{
checked_files += 1;
if let Ok(content) = fs::read_to_string(&path) {
// Check if all 'uses:' lines have SHA pinning (40 hex chars)
Expand Down
9 changes: 7 additions & 2 deletions aletheia/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ impl Config {
let config_path = repo_path.join(".aletheia.toml");

if config_path.is_file() {
if let Ok(mut file) = fs::File::open(&config_path) { use std::io::Read; let mut content = String::new(); if file.take(1024 * 1024).read_to_string(&mut content).is_ok() {
return Self::parse_from_string(&content);
if let Ok(file) = fs::File::open(&config_path) {
use std::io::Read;
let mut content = String::new();
// Cap the read at 1 MiB so a hostile config cannot exhaust memory.
if file.take(1024 * 1024).read_to_string(&mut content).is_ok() {
return Self::parse_from_string(&content);
}
}
}

Expand Down
14 changes: 9 additions & 5 deletions aletheia/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
//! Aletheia — Authoritative RSR Compliance Verification.
//!
//! Named after the Greek concept of "unconcealment," Aletheia is the
//! gatekeeper for the Rhodium Standard Repository (RSR) ecosystem.
//! It provides automated, deterministic audits of repository state to
//! Named after the Greek concept of "unconcealment," Aletheia is the
//! gatekeeper for the Rhodium Standard Repository (RSR) ecosystem.
//! It provides automated, deterministic audits of repository state to
//! ensure adherence to safety, security, and documentation standards.
//!
//! COMPLIANCE DIMENSIONS:
Expand Down Expand Up @@ -70,7 +70,7 @@ fn main() {
Err(e) => {
eprintln!("Error: {}", e);
process::exit(1);
}
},
};

// 2. ENVIRONMENT VALIDATION
Expand All @@ -93,7 +93,11 @@ fn main() {
}

// 5. EXIT POLICY
let exit_code = if report.checks.iter().any(|c| !c.passed && c.required_for == ComplianceLevel::Bronze) {
let exit_code = if report
.checks
.iter()
.any(|c| !c.passed && c.required_for == ComplianceLevel::Bronze)
{
exit_codes::COMPLIANCE_FAILED
} else if report.warnings.iter().any(|w| w.level == "critical") {
exit_codes::SECURITY_WARNING
Expand Down
31 changes: 20 additions & 11 deletions aletheia/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
//! Aletheia Output and Reporting Engine.
//!
//! This module implements the presentation layer for compliance audits.
//! It provides multiple serialization formats (JSON, SARIF, HTML) and
//! This module implements the presentation layer for compliance audits.
//! It provides multiple serialization formats (JSON, SARIF, HTML) and
//! a high-fidelity human-readable CLI report.
//!
//! ZERO-DEPENDENCY DESIGN: To maintain RSR Bronze compliance, this module
//! implements its own timestamp formatting and string escaping rather than
//! ZERO-DEPENDENCY DESIGN: To maintain RSR Bronze compliance, this module
//! implements its own timestamp formatting and string escaping rather than
//! pulling in external crates like `chrono` or `serde_json`.

use std::time::SystemTime;
use crate::types::*;
use std::time::SystemTime;

/// The current version of the Aletheia tool.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

/// EXIT STRATEGY: Standardized exit codes for CI/CD integration.
pub mod exit_codes {
pub const SUCCESS: i32 = 0; // Bronze compliance achieved.
pub const SUCCESS: i32 = 0; // Bronze compliance achieved.
pub const COMPLIANCE_FAILED: i32 = 1; // Mandatory checks failed.
pub const SECURITY_WARNING: i32 = 2; // Critical security issues (e.g. symlink escape).
pub const SECURITY_WARNING: i32 = 2; // Critical security issues (e.g. symlink escape).
}

/// ALGORITHM: Manual timestamp formatter.
Expand All @@ -40,7 +40,7 @@ pub fn format_timestamp(time: SystemTime) -> String {
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
year, month, day, hour, minute, second
)
}
},
Err(_) => "2026-02-21T00:00:00Z".to_string(),
}
}
Expand Down Expand Up @@ -137,16 +137,25 @@ pub fn print_report(report: &ComplianceReport) {
pub fn print_json_report(report: &ComplianceReport) {
println!("{{");
println!(" \"version\": \"{}\",", VERSION);
println!(" \"repository\": \"{}\",", report.repository_path.display());
println!(" \"timestamp\": \"{}\",", format_timestamp(report.verified_at));
println!(
" \"repository\": \"{}\",",
report.repository_path.display()
);
println!(
" \"timestamp\": \"{}\",",
format_timestamp(report.verified_at)
);
println!(" \"checks\": [");

for (i, check) in report.checks.iter().enumerate() {
println!(" {{");
println!(" \"category\": \"{}\",", check.category);
println!(" \"item\": \"{}\",", check.item);
println!(" \"passed\": {}", check.passed);
println!(" }}{}", if i < report.checks.len() - 1 { "," } else { "" });
println!(
" }}{}",
if i < report.checks.len() - 1 { "," } else { "" }
);
}

println!(" ],");
Expand Down
Loading
Loading