From dd2148fa72098890d51b7269c0a107d3d842db3a Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 03:32:28 +0100 Subject: [PATCH 1/5] Reject invalid Eclexia verdicts and expose unavailable native integration --- .github/workflows/actions.lock | 8 +- .github/workflows/ci.yml | 4 +- .github/workflows/governance.yml | 4 +- .github/workflows/hypatia-scan.yml | 6 +- .machine_readable/descriptiles/STATE.a2ml | 5 + crates/oikosbot-eclexia/Cargo.toml | 9 +- crates/oikosbot-eclexia/src/lib.rs | 130 ++++++++++------------ 7 files changed, 79 insertions(+), 87 deletions(-) diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index e141b7c..3e107d3 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -7,7 +7,7 @@ workflows: - 'actions/cache@v6.1.0' - 'actions/checkout@v7.0.1' - 'actions/configure-pages@v6.0.0' - - 'actions/deploy-pages@v5.0.0' + - 'actions/deploy-pages@v5.0.1' - 'actions/upload-pages-artifact@v5.0.0' - 'haskell-actions/setup@v2.12.0' '.github/workflows/ci.yml': @@ -84,9 +84,9 @@ dependencies: commit: 'sha1-45bfe0192ca1faeb007ade9deae92b16b8254a0d' owner_id: 44036562 repo_id: 513659658 - 'actions/deploy-pages@v5.0.0': - ref: 'v5.0.0' - commit: 'sha1-cd2ce8fcbc39b97be8ca5fce6e763baed58fa128' + 'actions/deploy-pages@v5.0.1': + ref: 'v5.0.1' + commit: 'sha1-368f82528645a54fb793d4d04e342629a3f51346' owner_id: 44036562 repo_id: 438112499 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f': diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c01f791..a2d0ef6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ -# SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 # SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell # # OikosBot - Continuous Integration @@ -68,6 +68,8 @@ jobs: run: cargo build --workspace --locked - name: Test (standalone workspace) run: cargo test --workspace --locked + - name: Eclexia adapter feature and rejection checks + run: cargo test --locked -p oikosbot-eclexia --all-features - name: Clippy (informational) run: cargo clippy --workspace continue-on-error: true diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index f47b7ef..7fce811 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,5 +1,5 @@ -# SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 name: Governance on: @@ -15,5 +15,5 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@4d104d325b96c348d685956c6678139640c15c4b + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@fcb566cfb8a86cea2d3666bf65a4f177a49b1313 secrets: inherit diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 436a2f3..698f33d 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,5 +1,5 @@ -# SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 name: Hypatia Security Scan on: @@ -18,4 +18,6 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@bd0df9ead7faf0cdfe0e13e7966d91e28d0101d4 + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@fcb566cfb8a86cea2d3666bf65a4f177a49b1313 + with: + block-on-high: true diff --git a/.machine_readable/descriptiles/STATE.a2ml b/.machine_readable/descriptiles/STATE.a2ml index 62f81ae..3901710 100644 --- a/.machine_readable/descriptiles/STATE.a2ml +++ b/.machine_readable/descriptiles/STATE.a2ml @@ -76,3 +76,8 @@ facts = [ last-run-utc = "2026-07-28T18:30:00Z" last-result = "pass" # unknown | pass | warn | fail notes = "main green across CI, CodeQL, Governance, Hypatia, Language Policy, Scorecard, Secret Scanner, Workflow Security Linter, Publish Image and GitHub Pages. Zero open PRs." + +[language-portfolio-audit-20260907] +policy-adapter = "Malformed binary verdicts now error; native feature explicitly unavailable; placeholder evaluation measurements removed" +validation = "Default and all-features adapter regression tests; CI feature check added" +remaining = "Real Eclexia input/result protocol, bound execution, source-policy evaluation and measured evaluation cost remain unimplemented" diff --git a/crates/oikosbot-eclexia/Cargo.toml b/crates/oikosbot-eclexia/Cargo.toml index 24d41ee..2aa55c1 100644 --- a/crates/oikosbot-eclexia/Cargo.toml +++ b/crates/oikosbot-eclexia/Cargo.toml @@ -12,11 +12,10 @@ description = "Eclexia policy engine integration for oikosbot" [features] default = [] -# Native Eclexia compiler integration. The upstream `eclexia` compiler crates -# (a separate hyperpolymath repo) were decoupled when OikosBot was extracted -# from gitbot-fleet so this crate builds standalone; the feature is retained as -# a no-op seam to be re-wired (path or git dep) when native evaluation is added. -# Until then the crate ships its lightweight built-in `.ecl` policy evaluator. +# Reserved native integration feature. Enabling it builds successfully but +# evaluation returns an explicit unavailable error until real dependencies, +# input binding, budgets, and the result protocol are integrated. +# The default builtin fallback does not parse or execute .ecl source. eclexia-native = [] [dependencies] diff --git a/crates/oikosbot-eclexia/src/lib.rs b/crates/oikosbot-eclexia/src/lib.rs index 8df04fc..0c3c32b 100644 --- a/crates/oikosbot-eclexia/src/lib.rs +++ b/crates/oikosbot-eclexia/src/lib.rs @@ -3,11 +3,10 @@ //! # OikosBot-Eclexia Integration //! -//! Policy engine integration with two backends: -//! - **Default**: shells out to `eclexia` binary (works without eclexia repo on disk) -//! - **Native** (`eclexia-native` feature): direct library integration via eclexia-interp -//! -//! Both backends implement the same `PolicyEngine` trait. +//! Experimental policy adapter. The binary protocol still requires upstream +//! integration; the builtin fallback uses hardcoded rules, not `.ecl` source. +//! The reserved `eclexia-native` feature reports an explicit unavailable error. +//! No backend currently provides measured policy-evaluation resource costs. #![forbid(unsafe_code)] use anyhow::{Context, Result}; @@ -58,8 +57,10 @@ pub fn evaluate_policies( let entries = std::fs::read_dir(policy_dir) .with_context(|| format!("Failed to read policy dir: {}", policy_dir.display()))?; - for entry in entries.flatten() { - let path = entry.path(); + for entry in entries { + let path = entry + .context("Failed to enumerate policy directory entry")? + .path(); if path.extension().and_then(|e| e.to_str()) == Some("ecl") { let policy_name = path .file_stem() @@ -145,7 +146,7 @@ fn evaluate_binary( if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); - let result: bool = serde_json::from_str(stdout.trim()).unwrap_or(false); + let result = parse_binary_verdict(stdout.trim())?; Ok(PolicyDecision { outcome: if result { @@ -155,7 +156,7 @@ fn evaluate_binary( }, message: format!("Policy '{}' evaluated via eclexia binary", policy_name), suggestion: None, - evaluation_cost: Some(placeholder_cost()), + evaluation_cost: None, policy_name: policy_name.to_string(), }) } else { @@ -170,63 +171,22 @@ fn evaluate_binary( } } -/// Native backend: direct eclexia-interp library integration +/// Decode only the documented boolean protocol. Empty output, diagnostics, +/// structured JSON, and trailing data are evaluation errors, never passes. +#[cfg(any(test, not(feature = "eclexia-native")))] +fn parse_binary_verdict(stdout: &str) -> Result { + serde_json::from_str(stdout).context("Invalid Eclexia policy output: expected a JSON boolean") +} + +/// Reserved feature: no native parser/interpreter dependencies are linked yet. +/// Failing explicitly prevents a successful build from claiming integration. #[cfg(feature = "eclexia-native")] fn evaluate_native( - policy_path: &Path, - results: &[AnalysisResult], - policy_name: &str, + _policy_path: &Path, + _results: &[AnalysisResult], + _policy_name: &str, ) -> Result { - let source = std::fs::read_to_string(policy_path) - .with_context(|| format!("Failed to read policy: {}", policy_path.display()))?; - - let (ast, errors) = eclexia_parser::parse(&source); - - if !errors.is_empty() { - let error_msgs: Vec = errors.iter().map(|e| format!("{:?}", e)).collect(); - anyhow::bail!( - "Policy parse errors in {}: {}", - policy_name, - error_msgs.join("; ") - ); - } - - // Run the policy through the interpreter - let mut interp = eclexia_interp::Interpreter::new(); - // Set a tight budget for policy evaluation itself (dogfooding!) - interp.set_energy_budget(1.0); // 1 Joule max for policy eval - interp.set_carbon_budget(0.001); // 0.001g CO2e max - - match eclexia_interp::run(&ast) { - Ok(value) => { - let should_warn = value.is_truthy(); - - Ok(PolicyDecision { - outcome: if should_warn { - PolicyOutcome::Warn - } else { - PolicyOutcome::Pass - }, - message: format!( - "Policy '{}' evaluated natively: result={:?}", - policy_name, value - ), - suggestion: None, - evaluation_cost: Some(placeholder_cost()), - policy_name: policy_name.to_string(), - }) - } - Err(e) => { - // Runtime error — treat as warning - Ok(PolicyDecision { - outcome: PolicyOutcome::Warn, - message: format!("Policy '{}' runtime error: {:?}", policy_name, e), - suggestion: Some("Check policy syntax and logic".to_string()), - evaluation_cost: None, - policy_name: policy_name.to_string(), - }) - } - } + anyhow::bail!("Eclexia native evaluation is unavailable: input binding, interpreter budgets, and result protocol are not integrated") } /// The builtin backend matches policies by FILE STEM and applies hardcoded @@ -338,7 +298,7 @@ fn evaluate_builtin(results: &[AnalysisResult], policy_name: &str) -> Result String { path.to_string() } -fn placeholder_cost() -> ResourceProfile { - ResourceProfile { - energy: oikosbot_metrics::Energy::joules(0.05), - duration: oikosbot_metrics::Duration::milliseconds(1.0), - carbon: oikosbot_metrics::Carbon::grams_co2e(0.000007), - memory: oikosbot_metrics::Memory::kilobytes(50), - } -} - /// Example policy in Eclexia (to be written to policies/ directory) pub const EXAMPLE_POLICY: &str = r#" // SPDX-License-Identifier: MPL-2.0 @@ -467,6 +418,39 @@ pub fn decisions_to_results(decisions: &[PolicyDecision]) -> Vec mod tests { use super::*; + #[test] + fn binary_protocol_rejects_invalid_output() { + for output in [ + "", + "null", + "0", + "{}", + "[]", + "\"false\"", + "false true", + "diagnostic\nfalse", + ] { + assert!(parse_binary_verdict(output).is_err(), "accepted {output:?}"); + } + assert!(!parse_binary_verdict("false").unwrap()); + assert!(parse_binary_verdict("true\n").unwrap()); + } + + #[cfg(feature = "eclexia-native")] + #[test] + fn native_feature_cannot_claim_policy_execution() { + let error = evaluate_native(Path::new("unused.ecl"), &[], "unused").unwrap_err(); + assert!(error.to_string().contains("unavailable")); + } + + #[test] + fn builtin_does_not_invent_evaluation_measurements() { + assert!(evaluate_builtin(&[], "energy_threshold") + .unwrap() + .evaluation_cost + .is_none()); + } + #[test] fn test_example_policy_syntax() { assert!(EXAMPLE_POLICY.contains("def evaluate_policy")); From 1195480aeaeec9902f1d1cfba7ac3e7bd9f5ff30 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 04:17:02 +0100 Subject: [PATCH 2/5] fix(ci): resolve audit blockers and reviewed toolchain gaps --- .github/workflows/governance.yml | 4 ++-- .github/workflows/hypatia-scan.yml | 4 ++-- .hypatia-ignore | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 .hypatia-ignore diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 7fce811..7e6b956 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,5 +1,5 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: Governance on: @@ -15,5 +15,5 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@fcb566cfb8a86cea2d3666bf65a4f177a49b1313 + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@3bc7de609f3fea6ddb5d584010e7568fba40686f secrets: inherit diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 698f33d..56264e6 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,5 +1,5 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: Hypatia Security Scan on: @@ -18,6 +18,6 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@fcb566cfb8a86cea2d3666bf65a4f177a49b1313 + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@3bc7de609f3fea6ddb5d584010e7568fba40686f with: block-on-high: true diff --git a/.hypatia-ignore b/.hypatia-ignore new file mode 100644 index 0000000..10b7033 --- /dev/null +++ b/.hypatia-ignore @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MPL-2.0 + +# The scanner only recognises JS/Elixir/ReScript test suffixes or a root test +# directory. Rust inline #[test] modules live under crates/*/src; CI executes +# cargo test --locked --workspace and the Eclexia all-features tests. +# Reviewed against the passing Rust Workspace job in PR #86 (2026-09-07). +honest_completion/no_tests:. From e906ee2b072ab1bed912f70912690db1d52109a3 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:35:18 +0100 Subject: [PATCH 3/5] Run adapter regression suite through an explicit test entry point --- .github/workflows/ci.yml | 4 ++-- .hypatia-ignore | 7 ------- tests/test-eclexia-adapter.sh | 7 +++++++ 3 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 .hypatia-ignore create mode 100644 tests/test-eclexia-adapter.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2d0ef6..647d70b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell # # OikosBot - Continuous Integration @@ -69,7 +69,7 @@ jobs: - name: Test (standalone workspace) run: cargo test --workspace --locked - name: Eclexia adapter feature and rejection checks - run: cargo test --locked -p oikosbot-eclexia --all-features + run: bash tests/test-eclexia-adapter.sh - name: Clippy (informational) run: cargo clippy --workspace continue-on-error: true diff --git a/.hypatia-ignore b/.hypatia-ignore deleted file mode 100644 index 10b7033..0000000 --- a/.hypatia-ignore +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 - -# The scanner only recognises JS/Elixir/ReScript test suffixes or a root test -# directory. Rust inline #[test] modules live under crates/*/src; CI executes -# cargo test --locked --workspace and the Eclexia all-features tests. -# Reviewed against the passing Rust Workspace job in PR #86 (2026-09-07). -honest_completion/no_tests:. diff --git a/tests/test-eclexia-adapter.sh b/tests/test-eclexia-adapter.sh new file mode 100644 index 0000000..087b0ae --- /dev/null +++ b/tests/test-eclexia-adapter.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# Run the real adapter suite, including malformed-output rejection and feature gates. +set -euo pipefail +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." +cargo test --locked -p oikosbot-eclexia --all-features From f3750d7b7e2dce06b51624061bc20457f22f8e54 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:58:47 +0100 Subject: [PATCH 4/5] Use the audited immutable scan helper and least-privilege governance --- .github/workflows/governance.yml | 5 +++-- .github/workflows/hypatia-scan.yml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 7e6b956..623190b 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -15,5 +15,6 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@3bc7de609f3fea6ddb5d584010e7568fba40686f - secrets: inherit + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@3c3acc19951b39c8ffe68b917aae4cc2e5b232e5 + secrets: + HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 56264e6..3570253 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -18,6 +18,6 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@3bc7de609f3fea6ddb5d584010e7568fba40686f + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@3c3acc19951b39c8ffe68b917aae4cc2e5b232e5 with: block-on-high: true From 5fa6c05be01eca9a37a5d832362cd7e07835cc54 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:41:25 +0100 Subject: [PATCH 5/5] Use the verified strict Hypatia baseline matcher --- .github/workflows/governance.yml | 2 +- .github/workflows/hypatia-scan.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 623190b..a05f4a5 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -15,6 +15,6 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@3c3acc19951b39c8ffe68b917aae4cc2e5b232e5 + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@469605210e767ee94d1c7a9c13cb6a1d0a78cad1 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 3570253..c139558 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -18,6 +18,6 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@3c3acc19951b39c8ffe68b917aae4cc2e5b232e5 + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@469605210e767ee94d1c7a9c13cb6a1d0a78cad1 with: block-on-high: true