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
84 changes: 62 additions & 22 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,91 @@ permissions:

env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
# IMPORTANT: do NOT set RUSTFLAGS=-Dwarnings at workflow-env level. That
# promotes warnings in *dependency* code to errors, which breaks the build
# non-deterministically as upstream crates emit deprecations. Use
# `cargo clippy -- -D warnings` below, which is scoped to this workspace.

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1

- name: Install Rust toolchain
# Install via official rustup script directly. This avoids any
# dependency on third-party setup actions whose SHA pins may
# drift; rustup.rs is the canonical install path.
run: |
set -eux
curl --proto '=https' --tlsv1.2 -sSfL https://sh.rustup.rs \
| sh -s -- -y --default-toolchain stable --profile minimal \
--component rustfmt,clippy
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"

- name: Print toolchain
run: |
rustc --version
cargo --version
rustfmt --version
cargo clippy --version

- name: Check formatting
run: cargo fmt --all -- --check

- name: Clippy lints
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Clippy lints (workspace, errors only on our code)
run: cargo clippy --workspace --all-targets --all-features -- -D warnings

- name: Run tests
run: cargo test --all-features
run: cargo test --workspace --all-features --no-fail-fast

- name: Build release
run: cargo build --release
run: cargo build --workspace --release

security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Security audit

- name: Install Rust toolchain
run: |
set -eux
curl --proto '=https' --tlsv1.2 -sSfL https://sh.rustup.rs \
| sh -s -- -y --default-toolchain stable --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"

- name: Install cargo-audit (locked)
run: cargo install --locked cargo-audit

- name: Security audit (advisories)
# cargo audit fails on any RUSTSEC advisory. Yanked-crate notes are
# informational warnings and do not fail without `--deny warnings`.
run: cargo audit
- name: Check for outdated deps
run: cargo install cargo-outdated && cargo outdated --exit-code 1 || true

coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable
- name: Install tarpaulin
run: cargo install cargo-tarpaulin
- name: Generate coverage
run: cargo tarpaulin --out Xml
- uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0

- name: Install Rust toolchain (with llvm-tools-preview)
run: |
set -eux
curl --proto '=https' --tlsv1.2 -sSfL https://sh.rustup.rs \
| sh -s -- -y --default-toolchain stable --profile minimal \
--component llvm-tools-preview
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"

- name: Install cargo-llvm-cov (locked)
# cargo-llvm-cov uses Rust's native coverage instrumentation —
# faster and more reliable than tarpaulin (which depends on a
# kernel module that breaks under Ubuntu kernel updates).
run: cargo install --locked cargo-llvm-cov

- name: Generate coverage (lcov)
run: cargo llvm-cov --workspace --all-features --lcov --output-path lcov.info

- name: Upload to codecov (best-effort)
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: cobertura.xml
files: lcov.info
continue-on-error: true
10 changes: 8 additions & 2 deletions .github/workflows/workflow-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,17 @@ jobs:
- name: Check SHA-Pinned Actions
run: |
echo "=== Checking Action Pinning ==="
# Find any uses: lines that don't have @SHA format
# Find any uses: lines that don't have @SHA format.
# Pattern: uses: owner/repo@<40-char-hex>
# Allowed exceptions:
# - local actions (./)
# - docker actions (docker://)
# - actions/github-script (inline scripting context, pinned via GITHUB_TOKEN)
# - hyperpolymath/* org-internal actions tracked on @main
unpinned=$(grep -rnE "^[[:space:]]+uses:" .github/workflows/ | \
grep -v "@[a-f0-9]\{40\}" | \
grep -v "uses: \./\|uses: docker://\|uses: actions/github-script" || true)
grep -v "uses: \./\|uses: docker://\|uses: actions/github-script" | \
grep -v "uses: hyperpolymath/" || true)

if [ -n "$unpinned" ]; then
echo "ERROR: Found unpinned actions:"
Expand Down
16 changes: 11 additions & 5 deletions crates/bridge/benches/bridge_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
//! Benches for the bridge crate.

use bridge::{Bridge, BridgeConfig};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use ndarray::Array1;
use std::hint::black_box;

fn bench_encode_sizes(c: &mut Criterion) {
let mut g = c.benchmark_group("bridge_encode");
for &(n_lsm, n_esn) in &[(100usize, 50usize), (512, 300), (2048, 1024)] {
let lsm = Array1::from_elem(n_lsm, 0.4);
let esn = Array1::from_elem(n_esn, 0.4);
g.bench_with_input(BenchmarkId::from_parameter(format!("lsm{n_lsm}_esn{n_esn}")),
&(lsm, esn), |b, (lsm, esn)| {
g.bench_with_input(
BenchmarkId::from_parameter(format!("lsm{n_lsm}_esn{n_esn}")),
&(lsm, esn),
|b, (lsm, esn)| {
let mut br = Bridge::new(BridgeConfig::default()).unwrap();
b.iter(|| black_box(br.encode(lsm.view(), esn.view())))
});
},
);
}
g.finish();
}
Expand All @@ -28,7 +32,9 @@ fn bench_encode_with_dynamics(c: &mut Criterion) {
let esn = Array1::from_elem(300, 0.4);
b.iter(|| {
t = t.wrapping_add(1);
for v in lsm.iter_mut() { *v = ((t % 100) as f32) * 0.01; }
for v in lsm.iter_mut() {
*v = ((t % 100) as f32) * 0.01;
}
black_box(br.encode(lsm.view(), esn.view()));
})
});
Expand Down
95 changes: 74 additions & 21 deletions crates/bridge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,20 @@ pub struct BridgeConfig {
}

impl Default for BridgeConfig {
fn default() -> Self { Self { activation_threshold: 0.3, lsm_weight: 0.5 } }
fn default() -> Self {
Self {
activation_threshold: 0.3,
lsm_weight: 0.5,
}
}
}

impl BridgeConfig {
pub fn validate(&self) -> Result<(), BridgeError> {
if !(0.0..=1.0).contains(&self.activation_threshold) {
return Err(BridgeError::InvalidConfig("activation_threshold ∉ [0,1]".into()));
return Err(BridgeError::InvalidConfig(
"activation_threshold ∉ [0,1]".into(),
));
}
if !(0.0..=1.0).contains(&self.lsm_weight) {
return Err(BridgeError::InvalidConfig("lsm_weight ∉ [0,1]".into()));
Expand Down Expand Up @@ -69,20 +76,31 @@ pub struct Bridge {
impl Bridge {
pub fn new(config: BridgeConfig) -> Result<Self, BridgeError> {
config.validate()?;
Ok(Self { config, last_lsm: None })
Ok(Self {
config,
last_lsm: None,
})
}

pub fn config(&self) -> &BridgeConfig { &self.config }
pub fn config(&self) -> &BridgeConfig {
&self.config
}

fn count_active(view: ArrayView1<f32>, thresh: f32) -> usize {
view.iter().filter(|v| v.abs() >= thresh).count()
}

fn rms_delta(prev: &Array1<f32>, curr: ArrayView1<f32>) -> f32 {
if prev.len() != curr.len() { return 0.0; }
if prev.len() != curr.len() {
return 0.0;
}
let n = prev.len() as f32;
if n == 0.0 { return 0.0; }
let sum_sq: f32 = prev.iter().zip(curr.iter())
if n == 0.0 {
return 0.0;
}
let sum_sq: f32 = prev
.iter()
.zip(curr.iter())
.map(|(a, b)| (a - b).powi(2))
.sum();
(sum_sq / n).sqrt().min(1.0)
Expand All @@ -93,8 +111,16 @@ impl Bridge {
let t = self.config.activation_threshold;
let lsm_active = Self::count_active(lsm, t);
let esn_active = Self::count_active(esn, t);
let lsm_frac = if lsm.len() == 0 { 0.0 } else { lsm_active as f32 / lsm.len() as f32 };
let esn_frac = if esn.len() == 0 { 0.0 } else { esn_active as f32 / esn.len() as f32 };
let lsm_frac = if lsm.is_empty() {
0.0
} else {
lsm_active as f32 / lsm.len() as f32
};
let esn_frac = if esn.is_empty() {
0.0
} else {
esn_active as f32 / esn.len() as f32
};
let w = self.config.lsm_weight;
let salience = (w * lsm_frac + (1.0 - w) * esn_frac).clamp(0.0, 1.0);

Expand All @@ -106,10 +132,18 @@ impl Bridge {
let description = describe(salience, urgency, lsm_active, esn_active);
self.last_lsm = Some(lsm.to_owned());

NeuralContext { salience, urgency, lsm_active, esn_active, description }
NeuralContext {
salience,
urgency,
lsm_active,
esn_active,
description,
}
}

pub fn reset(&mut self) { self.last_lsm = None; }
pub fn reset(&mut self) {
self.last_lsm = None;
}
}

fn describe(salience: f32, urgency: f32, lsm_active: usize, esn_active: usize) -> String {
Expand All @@ -129,20 +163,34 @@ fn describe(salience: f32, urgency: f32, lsm_active: usize, esn_active: usize) -
)
}

pub fn hello() -> &'static str { "bridge" }
pub fn hello() -> &'static str {
"bridge"
}

#[cfg(test)]
mod tests {
use super::*;
use ndarray::Array1;

#[test] fn config_validates() {
assert!(BridgeConfig { activation_threshold: -0.1, lsm_weight: 0.5 }.validate().is_err());
assert!(BridgeConfig { activation_threshold: 0.5, lsm_weight: 1.5 }.validate().is_err());
#[test]
fn config_validates() {
assert!(BridgeConfig {
activation_threshold: -0.1,
lsm_weight: 0.5
}
.validate()
.is_err());
assert!(BridgeConfig {
activation_threshold: 0.5,
lsm_weight: 1.5
}
.validate()
.is_err());
assert!(BridgeConfig::default().validate().is_ok());
}

#[test] fn quiet_state_low_salience() {
#[test]
fn quiet_state_low_salience() {
let mut b = Bridge::new(BridgeConfig::default()).unwrap();
let lsm = Array1::zeros(100);
let esn = Array1::zeros(50);
Expand All @@ -153,7 +201,8 @@ mod tests {
assert!(ctx.description.contains("quiet"));
}

#[test] fn high_state_high_salience() {
#[test]
fn high_state_high_salience() {
let mut b = Bridge::new(BridgeConfig::default()).unwrap();
let lsm = Array1::from_elem(100, 1.0);
let esn = Array1::from_elem(50, 1.0);
Expand All @@ -162,29 +211,33 @@ mod tests {
assert!(ctx.description.contains("high"));
}

#[test] fn urgency_zero_on_first_call() {
#[test]
fn urgency_zero_on_first_call() {
let mut b = Bridge::new(BridgeConfig::default()).unwrap();
let lsm = Array1::from_elem(10, 0.5);
let ctx = b.encode(lsm.view(), Array1::zeros(10).view());
assert!(ctx.urgency.abs() < 1e-6);
}

#[test] fn urgency_rises_with_change() {
#[test]
fn urgency_rises_with_change() {
let mut b = Bridge::new(BridgeConfig::default()).unwrap();
let _ = b.encode(Array1::zeros(10).view(), Array1::zeros(10).view());
let ctx = b.encode(Array1::from_elem(10, 1.0).view(), Array1::zeros(10).view());
assert!(ctx.urgency > 0.5);
}

#[test] fn reset_clears_history() {
#[test]
fn reset_clears_history() {
let mut b = Bridge::new(BridgeConfig::default()).unwrap();
let _ = b.encode(Array1::from_elem(10, 1.0).view(), Array1::zeros(10).view());
b.reset();
let ctx = b.encode(Array1::zeros(10).view(), Array1::zeros(10).view());
assert!(ctx.urgency.abs() < 1e-6);
}

#[test] fn description_contains_markers() {
#[test]
fn description_contains_markers() {
let mut b = Bridge::new(BridgeConfig::default()).unwrap();
let ctx = b.encode(Array1::zeros(10).view(), Array1::zeros(10).view());
assert!(ctx.description.starts_with("[NEURAL_STATE]"));
Expand Down
10 changes: 8 additions & 2 deletions crates/bridge/tests/aspect_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ use ndarray::Array1;

#[test]
fn aspect_invalid_threshold_rejected() {
let cfg = BridgeConfig { activation_threshold: 1.5, lsm_weight: 0.5 };
let cfg = BridgeConfig {
activation_threshold: 1.5,
lsm_weight: 0.5,
};
assert!(Bridge::new(cfg).is_err());
}

#[test]
fn aspect_invalid_weight_rejected() {
let cfg = BridgeConfig { activation_threshold: 0.5, lsm_weight: -0.1 };
let cfg = BridgeConfig {
activation_threshold: 0.5,
lsm_weight: -0.1,
};
assert!(Bridge::new(cfg).is_err());
}

Expand Down
Loading
Loading