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
12 changes: 12 additions & 0 deletions .ci-artifact-pins.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: MPL-2.0
# Pinned artifacts for the slm-real-inference CI job (SHA-256 verified at download time).
# llama.cpp nightly binary (ggml-org/llama.cpp release b11100, 2026-09-22):
LLAMA_CPP_TAG=b11100
LLAMA_CPP_UBUNTU_X64_URL=https://github.com/ggml-org/llama.cpp/releases/download/b11100/llama-b11100-bin-ubuntu-x64.tar.gz
LLAMA_CPP_UBUNTU_X64_SHA256=a836c913236ab4533ef9aaf49f0e1ad2955c8159869d7eec1a9072a92e13d61b
# Smoke model: Qwen2.5-0.5B-Instruct Q4_K_M (official Qwen repo, commit 9217f5db79a29953eb74d5343926648285ec7e67).
# Chosen over SmolLM2-135M-Instruct Q4_K_M (bartowski mirror) after on-runner evaluation:
# the 135M model loops `0.000000...` at temp 0 and never closes the verdict JSON within
# the token budget; Qwen2.5-0.5B emits a valid object on the first attempt.
GGUF_MODEL_URL=https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/main/qwen2.5-0.5b-instruct-q4_k_m.gguf
GGUF_MODEL_SHA256=74a4da8c9fdbcd15bd1f6d01d621410d31c6fc00986f5eb687824e7b93d7a9db
Empty file modified .clusterfuzzlite/build.sh
100755 → 100644
Empty file.
Empty file modified .github/hooks/validate-a2ml.sh
100755 → 100644
Empty file.
Empty file modified .github/hooks/validate-k9.sh
100755 → 100644
Empty file.
68 changes: 68 additions & 0 deletions .github/workflows/arbiter-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# SPDX-License-Identifier: MPL-2.0
name: Arbiter CI
on:
push:
branches: [main, master]
paths:
- "src/arbiter/**"
- "src/contract/src/arbiter.rs"
- ".github/workflows/arbiter-ci.yml"
pull_request:
branches: [main, master]
paths:
- "src/arbiter/**"
- ".github/workflows/arbiter-ci.yml"
permissions:
contents: read
jobs:
elixir:
name: OTP arbiter (format, deps, ExUnit, escript smoke)
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: read
defaults:
run:
working-directory: src/arbiter
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
with:
otp-version: "27"
elixir-version: "1.18"
- name: Install build tooling
run: mix local.hex --force && mix local.rebar --force
- name: Check formatting
run: mix format --check-formatted
# NOTE: no mix.lock is committed yet (cannot be generated without a
# local OTP toolchain). deps are resolved fresh; lockfile to be added
# by a maintainer — see docs/UPSTREAM-DELIVERY.adoc.
- name: Fetch dependencies
run: mix deps.get
- name: Run ExUnit suite
run: mix test
- name: Build escript
run: mix escript.build
- name: Protocol smoke — valid allow round-trip, exactly one audit record
run: |
set -euo pipefail
AUDIT_DIR="$(mktemp -d)"
export CONATIVE_AUDIT_PATH="$AUDIT_DIR/audit.jsonl"
RESPONSE="$(printf '%s\n' \
'{"protocol_version":1,"request_id":"smoke-1","llm":{"confidence":0.95},"slm":{"violation_confidence":0.05},"oracle":{"verdict":"allow"}}' \
| ./conative_arbiter)"
echo "response: $RESPONSE"
echo "$RESPONSE" | grep -q '"verdict":"allow"'
echo "$RESPONSE" | grep -q '"request_id":"smoke-1"'
echo "$RESPONSE" | grep -q '"audit_recorded":true'
test "$(wc -l < "$CONATIVE_AUDIT_PATH")" -eq 1
- name: Protocol smoke — malformed input fails closed (error, never a verdict)
run: |
set -euo pipefail
AUDIT_DIR="$(mktemp -d)"
export CONATIVE_AUDIT_PATH="$AUDIT_DIR/audit.jsonl"
RESPONSE="$(printf '%s\n' 'not json' | ./conative_arbiter || true)"
echo "response: $RESPONSE"
echo "$RESPONSE" | grep -q '"error"'
! echo "$RESPONSE" | grep -q '"verdict"'
test ! -s "$CONATIVE_AUDIT_PATH" || test "$(wc -l < "$CONATIVE_AUDIT_PATH")" -eq 0
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,22 @@ jobs:
with:
name: conative-cli
path: target/release/conative
nickel-native:
name: Native Nickel policy backend
# Dedicated job: nickel-lang-parser does not link-compile under ~2 GB
# (OOM-killed, reproduced), so this must run on a full-size hosted
# runner (16 GB). The exact command is part of the delivery contract —
# do not "optimise" it (single job, no debuginfo, warnings as errors,
# lib tests only, locked dependency set).
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
with:
key: nickel-native
- name: Test policy-oracle with native Nickel (pinned command)
run: CARGO_BUILD_JOBS=1 RUSTFLAGS="-C debuginfo=0 -Dwarnings" cargo test -p policy-oracle --features nickel --lib --locked
Empty file modified .github/workflows/pages.yml
100755 → 100644
Empty file.
116 changes: 116 additions & 0 deletions .github/workflows/slm-real-inference.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# SPDX-License-Identifier: MPL-2.0
name: SLM Real Inference
# Real-model smoke tests. Artifacts are pinned + SHA-256 verified and are
# never committed to the repository (see .ci-artifact-pins.txt).
#
# - `local-gguf`: pinned llama.cpp binary + pinned Qwen2.5-0.5B GGUF, runs the
# ignored llama-cli round-trip AND the HTTP adapter against a loopback
# llama-server. Safe on every PR: no secrets involved.
# - `remote-provider`: the live remote-provider smoke. Runs ONLY on
# workflow_dispatch or pushes to the protected main branch of the upstream
# repository, inside the `slm-remote-production` GitHub Environment (which
# holds CONATIVE_SLM_ENDPOINT / CONATIVE_SLM_MODEL_NAME / SLM_API_KEY and
# requires reviewer approval). Never on pull requests — fork PR code must
# never see these secrets.
on:
pull_request:
branches: [main, master]
push:
branches: [main, master]
workflow_dispatch:
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
local-gguf:
name: Local GGUF (pinned llama.cpp + pinned model)
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
env:
LLAMA_CPP_UBUNTU_X64_URL: https://github.com/ggml-org/llama.cpp/releases/download/b11100/llama-b11100-bin-ubuntu-x64.tar.gz
LLAMA_CPP_UBUNTU_X64_SHA256: a836c913236ab4533ef9aaf49f0e1ad2955c8159869d7eec1a9072a92e13d61b
GGUF_MODEL_URL: https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/main/qwen2.5-0.5b-instruct-q4_k_m.gguf
GGUF_MODEL_SHA256: 74a4da8c9fdbcd15bd1f6d01d621410d31c6fc00986f5eb687824e7b93d7a9db
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
with:
key: slm-real-inference
- name: Cache pinned artifacts
id: artifacts
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
${{ runner.temp }}/slm-artifacts
key: slm-artifacts-${{ env.LLAMA_CPP_UBUNTU_X64_SHA256 }}-${{ env.GGUF_MODEL_SHA256 }}
- name: Download + verify llama.cpp binary
if: steps.artifacts.outputs.cache-hit != 'true'
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/slm-artifacts"
cd "$RUNNER_TEMP/slm-artifacts"
curl -fsSL --retry 3 -o llama-cli.tar.gz "$LLAMA_CPP_UBUNTU_X64_URL"
echo "$LLAMA_CPP_UBUNTU_X64_SHA256 llama-cli.tar.gz" | sha256sum -c -
mkdir -p bin && tar xzf llama-cli.tar.gz -C bin --strip-components=1
- name: Download + verify GGUF model
if: steps.artifacts.outputs.cache-hit != 'true'
run: |
set -euo pipefail
cd "$RUNNER_TEMP/slm-artifacts"
curl -fsSL --retry 3 -o model.gguf "$GGUF_MODEL_URL"
echo "$GGUF_MODEL_SHA256 model.gguf" | sha256sum -c -
- name: llama-cli round-trip (real model, ignored-by-default test)
run: |
set -euo pipefail
export LD_LIBRARY_PATH="$RUNNER_TEMP/slm-artifacts/bin"
export CONATIVE_LLAMA_CLI="$RUNNER_TEMP/slm-artifacts/bin/llama-cli"
export CONATIVE_GGUF_MODEL="$RUNNER_TEMP/slm-artifacts/model.gguf"
cargo test -p slm-evaluator --test real_inference real_llama -- --ignored --nocapture
- name: HTTP adapter round-trip via loopback llama-server
run: |
set -euo pipefail
export LD_LIBRARY_PATH="$RUNNER_TEMP/slm-artifacts/bin"
"$RUNNER_TEMP/slm-artifacts/bin/llama-server" \
-m "$RUNNER_TEMP/slm-artifacts/model.gguf" \
--host 127.0.0.1 --port 18080 -t 4 -c 2048 --log-disable &
SERVER_PID=$!
trap 'kill $SERVER_PID 2>/dev/null || true' EXIT
for i in $(seq 1 120); do
curl -fsS http://127.0.0.1:18080/health >/dev/null 2>&1 && break
sleep 1
done
export CONATIVE_SLM_ENDPOINT=http://127.0.0.1:18080
cargo test -p slm-evaluator --features http --test real_inference real_http -- --ignored --nocapture
remote-provider:
name: Remote provider (protected environment, approval-gated)
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
environment: slm-remote-production
# Never on pull requests (fork code must never touch secrets); never on
# forks of the repository. Only dispatched runs or protected main pushes.
if: >-
github.repository == 'hyperpolymath/conative-gating' &&
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/main'))
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
with:
key: slm-remote-provider
- name: Remote provider round-trip (creds from Environment secrets)
env:
CONATIVE_SLM_ENDPOINT: ${{ secrets.CONATIVE_SLM_ENDPOINT }}
CONATIVE_SLM_MODEL_NAME: ${{ secrets.CONATIVE_SLM_MODEL_NAME }}
SLM_API_KEY: ${{ secrets.SLM_API_KEY }}
run: |
set -euo pipefail
: "${CONATIVE_SLM_ENDPOINT:?set in the slm-remote-production environment}"
: "${SLM_API_KEY:?set in the slm-remote-production environment}"
cargo test -p slm-evaluator --features http --test real_inference real_http -- --ignored --nocapture
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ Thumbs.db

# Dependencies
/node_modules/
/vendor/
# /vendor/ is ignored except the reviewed Bunsenite vendor fork used by the
# `nickel` feature (see vendor/bunsenite/VENDOR.adoc)
/vendor/*
!/vendor/bunsenite/
/deps/
/.elixir_ls/

Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ https://semver.org/spec/v2.0.0.html[Semantic Versioning].

==== Added

* feat(oracle): native Nickel policy backend via vendored Bunsenite
(`nickel` feature, default-features-off `nickel-lang-core 0.18.0`),
fail-closed `import` rejection, `Policy::from_policy_file` dispatch —
see `docs/NICKEL-POLICY.adoc`
* feat(slm): provider layer with verdict contract + correlation
preservation; `LlamaCppProvider` (pinned llama.cpp CLI, `--single-turn`
hardened) and feature-gated `HttpSlmProvider` (`http`, https-or-loopback,
`SLM_API_KEY` env-only) — see `docs/SLM_PROVIDERS.adoc`
* feat(contract): `ContractRunner::evaluate_with_provider` — terminal
oracle blocks, asymmetric Warn addend, threshold matrix, fail-closed
provider errors (`Sys902` Escalate)
* feat(arbiter): OTP consensus arbiter escript (protocol v1) with durable
JSONL audit sink (flush-before-ack, rotation, fail-closed, bounded
history, no proposal content) — see `docs/ARBITER_PROTOCOL.adoc`
* feat(contract): Rust arbiter client enforcing `audit_recorded: true`
* test(slm): env-gated real-inference smokes (local GGUF + HTTP adapter)
with pinned, SHA-256-verified CI artifacts
* test(contract): generative proptest suite (terminality, thresholds,
fail-closed, determinism, correlation, concurrency)
* ci: `nickel-native` job (exact pinned command), `arbiter-ci` workflow,
`slm-real-inference` workflow with approval-gated remote-provider job
* feat(crg): add crg-grade and crg-badge justfile recipes
* feat: add stapeln.toml container definition
* feat: deploy UX Manifesto infrastructure
Expand Down
Loading
Loading