tambo 0.5.0

Install and prove the whole AI knowledge-tool kit
Documentation
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:
    # The Text2KGBench harness is Python and its guards (provider drift, gold
    # leakage, manifest universe, billing-path preflight) had no CI step at all,
    # so they only ever ran when someone remembered to. A guard that does not run
    # is not a guard.
    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"
          # An explicitly selected remote still requires an issued credential.
          # The zero-credential local default is proved by readme-first-success.
          if QUIPU_SERVER=http://127.0.0.1:3030 caboodle doctor > doctor.log 2>&1; then
            echo 'remote mode 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