name: ci
on:
push:
pull_request:
permissions:
contents: read
jobs:
text-pendants:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Obtain the pinned native text-share verifier
env:
QUIPU_SHA256: 1158b69c30c7eb83a4ad1c0c4d35c04f1dfea71dca889f3a8f0896dcc81a17f6
shell: bash
run: |
set -euo pipefail
archive=quipu-quipu-ai-v0.13.0-x86_64-unknown-linux-gnu.tar.gz
base=https://github.com/scbrown/quipu/releases/download/quipu-ai-v0.13.0
mkdir -p "$RUNNER_TEMP/pendant-verifier"
cd "$RUNNER_TEMP/pendant-verifier"
curl --fail --location "$base/$archive" --output "$archive"
curl --fail --location "$base/$archive.sha256" --output "$archive.sha256"
sha256sum --check "$archive.sha256"
echo "$QUIPU_SHA256 $archive" | sha256sum --check -
tar -xzf "$archive"
echo "QUIPU=$RUNNER_TEMP/pendant-verifier/${archive%.tar.gz}/quipu" >> "$GITHUB_ENV"
- name: Verify text pendants and failure preservation with the native CLI
run: python3 scripts/test-stack-pendants.py
self-update-macos:
strategy:
fail-fast: false
matrix:
os: [macos-15-intel, macos-14]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --locked --lib release_update::tests
- run: cargo test --locked --lib adapter::quipu_alias
- run: cargo test --locked --test cli published_release_can_update_the_installer_itself
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy,rustfmt
- uses: Swatinem/rust-cache@v2
- run: cargo fmt --check
- run: cargo clippy --all-targets --all-features -- -D warnings
- run: cargo test --all-features
- name: Validate documented release installer and metadata
run: scripts/test-release-install.sh
- name: Bootstrap legacy and renamed release controls
run: python3 scripts/test-install-bootstrap.py
- name: Registry visibility refusal controls
run: python3 scripts/test-crate-version.py
evaluation-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install pytest
- run: python -m pytest tests -q
clean-machine-install:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-go@v5
with:
go-version: '1.24.2'
- uses: Swatinem/rust-cache@v2
- name: Install from a clean checkout
run: cargo install --path . --locked --root "$RUNNER_TEMP/caboodle"
- name: Prove the installed binary starts
run: |
export PATH="$RUNNER_TEMP/caboodle/bin:$PATH"
caboodle --version
- name: Run guided interview and retain the reviewed plan
run: |
set -euo pipefail
export PATH="$RUNNER_TEMP/caboodle/bin:$PATH"
mkdir -p "$RUNNER_TEMP/caboodle-phase2"
cd "$RUNNER_TEMP/caboodle-phase2"
printf '%s\n' \
everything \
'index repositories and answer code ownership questions' \
0 \
1 \
'which fixture symbol is indexed?' \
'one code symbol' \
'the clean-machine fixture repository and its marker symbol' \
'SELECT ?s WHERE { ?s ?p ?o }' \
caboodle-verify-roundtrip \
| caboodle init --guided | tee guided-interview.log
test -s caboodle-plan.toml
python3 - <<'PY'
import tomllib
with open("caboodle-plan.toml", "rb") as plan_file:
plan = tomllib.load(plan_file)
assert plan["profile"] == "everything"
# Built-ins, then the members that join this profile (members/*.toml).
assert plan["tools"] == ["quipu", "camayoc", "bobbin", "yupana", "desire-path", "chaski", "seeds", "shuttle"]
assert plan["intent"]["anticipated_questions"][0]["expected"] == "caboodle-verify-roundtrip"
PY
- name: Install and functionally verify the expanded corpus
run: |
set -euo pipefail
export PATH="$RUNNER_TEMP/caboodle/bin:$HOME/.cargo/bin:$PATH"
cd "$RUNNER_TEMP/caboodle-phase2"
# A clean host has no issued credential; doctor must name that blocker.
if caboodle doctor > doctor.log 2>&1; then
echo 'clean host unexpectedly passed credential diagnostics' >&2
exit 1
fi
grep -q 'missing canonical Quipu credential' doctor.log
caboodle apply 2>&1 | tee first-apply.log
# A local CLI database roundtrip cannot prove a live client's auth.
# Issue a fresh fixture credential to an isolated authenticated server.
python3 "$GITHUB_WORKSPACE/scripts/start-credential-fixture.py" \
--home "$HOME" --root "$RUNNER_TEMP/caboodle-phase2/credential-fixture"
export QUIPU_SERVER=http://127.0.0.1:3030
echo "QUIPU_SERVER=$QUIPU_SERVER" >> "$GITHUB_ENV"
caboodle provision-quipu-token --from "$RUNNER_TEMP/caboodle-phase2/credential-fixture/issued-token" --server "$QUIPU_SERVER"
caboodle verify 2>&1 | tee first-install.log
jq -e '
.tools.quipu.verified == true and
.tools.camayoc.verified == true and
.tools.bobbin.verified == true and
.tools.yupana.verified == true and
.tools["desire-path"].verified == true and
.tools.seeds.verified == true and
.tools.shuttle.verified == true and
.tools.chaski.verified == true
' .caboodle/state.json
- name: Execute the interview's anticipated ontology question
run: |
set -euo pipefail
export PATH="$RUNNER_TEMP/caboodle/bin:$HOME/.cargo/bin:$PATH"
cd "$RUNNER_TEMP/caboodle-phase2"
if caboodle verify-questions --db empty-question.db > empty-question.log 2>&1; then
echo 'anticipated question unexpectedly passed without its seed fact' >&2
exit 1
fi
jq -n '{
name: "caboodle clean-machine question fixture",
episode_body: "seed fact for the reviewed anticipated question",
source: "caboodle clean-machine proof",
group_id: "caboodle-verification",
nodes: [{
name: "caboodle-verify-roundtrip",
type: "Verification",
description: "isolated anticipated-question fixture"
}],
edges: []
}' > question-episode.json
quipu episode question-episode.json --db question.db
caboodle verify-questions --db question.db | tee anticipated-question.log
grep -q 'question 1: verified' anticipated-question.log
- name: Prove an interrupted apply can resume idempotently
run: |
set -euo pipefail
export PATH="$RUNNER_TEMP/caboodle/bin:$HOME/.cargo/bin:$PATH"
cd "$RUNNER_TEMP/caboodle-phase2"
caboodle install 2>&1 | tee resumed-install.log
jq -e '[.tools[] | .applied and .verified] | all' .caboodle/state.json
jq '{
schema_version,
tools,
proof: {
guided_plan: true,
functional_install: true,
idempotent_resume: true
}
}' .caboodle/state.json > proof.json
- name: Render and validate reviewable observability artifacts
run: |
set -euo pipefail
export PATH="$RUNNER_TEMP/caboodle/bin:$HOME/.cargo/bin:$PATH"
cd "$RUNNER_TEMP/caboodle-phase2"
printf '%s\n' \
'[targets]' \
"quipu='127.0.0.1:8080'" \
"bobbin='127.0.0.1:3030'" \
> caboodle-observability.toml
caboodle render-observability
caboodle validate-observability
# One contract per selected tool; a member's says it declares no metrics.
jq -e '.contracts | length == 8' caboodle-observability/contracts.json
grep -q 'quipu_graph_facts' caboodle-observability/alerts.yml
grep -q 'bobbin_up' caboodle-observability/dashboard.json
- name: Prove lifecycle emission queue is redacted and idempotent
run: |
set -euo pipefail
export PATH="$RUNNER_TEMP/caboodle/bin:$HOME/.cargo/bin:$PATH"
cd "$RUNNER_TEMP/caboodle-phase2"
test "$(find .caboodle/episodes -maxdepth 1 -name '*.json' | wc -l)" -eq 16
printf '%s\n' '{"id":"demo-1","title":"token=fixture-secret","status":"closed","comments":[{"id":"c1","text":"done"}]}' > br-export.jsonl
caboodle queue-br br-export.jsonl
caboodle queue-br br-export.jsonl
test "$(find .caboodle/episodes -maxdepth 1 -name '*.json' | wc -l)" -eq 19
! grep -R -q 'fixture-secret' .caboodle/episodes
- name: Prove adversarial negative controls turn verification red
run: |
set -euo pipefail
cargo test camayoc_verification_refuses_broken_control_retrieval_and_replay \
-- --exact --nocapture 2>&1 | tee "$RUNNER_TEMP/caboodle-phase2/adversarial-controls.log"
cargo test expanded_adapter_negative_controls_turn_verification_red \
-- --exact --nocapture 2>&1 | tee -a "$RUNNER_TEMP/caboodle-phase2/adversarial-controls.log"
cargo test anticipated_questions_are_executable_and_answer_checked \
-- --exact --nocapture 2>&1 | tee -a "$RUNNER_TEMP/caboodle-phase2/adversarial-controls.log"
cargo test observability::tests::missing_or_malformed_contract_targets_fail \
-- --exact --nocapture 2>&1 | tee -a "$RUNNER_TEMP/caboodle-phase2/adversarial-controls.log"
cargo test observability::tests::uncovered_dashboard_metric_fails_validation \
-- --exact --nocapture 2>&1 | tee -a "$RUNNER_TEMP/caboodle-phase2/adversarial-controls.log"
cargo test emission::tests::offline_and_retry_keep_identical_queue_until_success \
-- --exact --nocapture 2>&1 | tee -a "$RUNNER_TEMP/caboodle-phase2/adversarial-controls.log"
- name: Assemble the reader-visible Phase 2 proof record
run: |
set -euo pipefail
cd "$RUNNER_TEMP/caboodle-phase2"
jq '.proof += {
anticipated_question_executed: true,
all_phase2_adapters_verified: true,
observability_validated: true,
lifecycle_queue_redacted_and_idempotent: true,
adversarial_controls: true
}' proof.json > proof.next.json
mv proof.next.json proof.json
jq -e '
.proof.guided_plan and
.proof.functional_install and
.proof.idempotent_resume and
.proof.anticipated_question_executed and
.proof.all_phase2_adapters_verified and
.proof.observability_validated and
.proof.lifecycle_queue_redacted_and_idempotent and
.proof.adversarial_controls
' proof.json
- name: Retain the reviewed plan and proof record
if: always()
uses: actions/upload-artifact@v4
with:
name: caboodle-phase2-proof
if-no-files-found: error
include-hidden-files: true
path: |
${{ runner.temp }}/caboodle-phase2/caboodle-plan.toml
${{ runner.temp }}/caboodle-phase2/.caboodle/state.json
${{ runner.temp }}/caboodle-phase2/proof.json
${{ runner.temp }}/caboodle-phase2/caboodle-observability/*
${{ runner.temp }}/caboodle-phase2/.caboodle/episodes/*.json
${{ runner.temp }}/caboodle-phase2/anticipated-question.log
${{ runner.temp }}/caboodle-phase2/empty-question.log
${{ runner.temp }}/caboodle-phase2/*.log