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
124 changes: 124 additions & 0 deletions contractiles/adjust/Adjustfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# SPDX-License-Identifier: PLMP-1.0-or-later
# Adjustfile — adaptive-maintenance contract for echidnabot
#
# Migration recipes for when upstream deps, toolchains, schemas, or
# GitHub-side APIs shift. Each adjustment has a probe to detect drift
# and either an `adjust` recipe to apply, or a `defer-if` escape hatch.
# Run with: just adjust-check
# Error-code namespace: A###

version: 1

metadata:
name: echidnabot-adjust-contract
spec: v0.1.0
description: "Adaptive maintenance for Rust deps, sqlx, octocrab, axum, tracing-OTel, GitHub Apps protocol."

# ── Toolchain-Adjust ───────────────────────────────────────────────────

toolchain:

- name: A001-rust-toolchain-drift
description: "rust-toolchain.toml pin behind current stable."
severity: low
probe: "rustc --version | grep -q $(cat rust-toolchain.toml 2>/dev/null | grep channel | cut -d'\"' -f2)"
adjust: "Bump rust-toolchain.toml to current stable; cargo update --workspace."
defer-if: "any pinned-feature crate breaks on bump."

- name: A002-cargo-edition-drift
description: "Cargo.toml edition < current default (2024)."
severity: low
probe: "grep -q '^edition = \"2024\"' Cargo.toml"
adjust: "Bump edition to 2024; address `let` chain / new-keyword breakage in src/."
defer-if: "downstream consumers pinned to older edition; flag in CHANGELOG."

# ── Dep-Adjust ─────────────────────────────────────────────────────────

deps:

- name: A010-cargo-deps-major-bump-available
description: "A tracked Cargo dep has a major bump available."
severity: low
probe: "cargo outdated --workspace --depth 1 2>/dev/null | grep -E '^\\w+\\s+\\S+\\s+\\S+\\s+\\d' | head -5"
adjust: "Review upstream CHANGELOG; bump via cargo update -p <crate> --precise <new>; run cargo test."
defer-if: "breaking-change requires call-site rewrites (nom 7→8 style)."

- name: A011-sqlx-major-bump
description: "sqlx has a major bump (e.g. 0.8 → 0.9)."
severity: high
probe: "cargo tree -p sqlx --depth 0 | head -1"
adjust: "Run sqlx migrate diff against new schema; update query! macros if changed."
defer-if: "migrations folder requires regeneration."

- name: A012-octocrab-major-bump
description: "octocrab has a major bump."
severity: high
probe: "cargo tree -p octocrab --depth 0 | head -1"
adjust: "Review GitHub API surface changes; update src/adapters/github.rs handlers."
defer-if: "echidna server contract on the bot side hasn't been updated to match (cross-repo seam)."

- name: A013-axum-major-bump
description: "axum has a major bump (e.g. 0.8 → 0.9)."
severity: high
probe: "cargo tree -p axum --depth 0 | head -1"
adjust: "Update Router builder + extractor signatures per axum CHANGELOG."
defer-if: "tower middleware compat lags."

- name: A014-tracing-otel-bump
description: "opentelemetry / tracing-opentelemetry have a bump."
severity: low
probe: "cargo tree -p opentelemetry --depth 0 | head -1"
adjust: "Bump the otel + tracing-opentelemetry pair together (they must match)."

- name: A015-dependabot-pr-backlog
description: "Open dependabot PRs accumulating."
severity: low
probe: "gh pr list --repo hyperpolymath/echidnabot --author 'app/dependabot' --state open --limit 50 --json number | jq 'length' | grep -q '^0$'"
adjust: "Triage; auto-merge safe patches; defer or close API-breaks."

# ── Schema-Adjust ──────────────────────────────────────────────────────

schemas:

- name: A020-sqlx-migration-pending
description: "migrations/ has a SQL file newer than the DB's last-applied."
severity: medium
probe: "test -d migrations && ls migrations/*.sql 2>/dev/null | wc -l | grep -qE '^[1-9]'"
adjust: "sqlx migrate run (forward) ; verify against echidnabot.example.toml schema docs."
defer-if: "rollback path not tested for that migration."

- name: A021-github-webhook-payload-drift
description: "GitHub webhook payload added a new field echidnabot doesn't decode."
severity: medium
probe: "Manual — review github webhook docs CHANGELOG; check serde Deserialize warnings in tests."
adjust: "Add field to CodebergPushPayload / GitHubPushPayload struct; bump CHANGELOG."

# ── Workflow-Adjust ────────────────────────────────────────────────────

workflows:

- name: A040-standards-reusable-sha-stale
description: "A standards-reusable workflow caller is behind current main."
severity: low
probe: "grep -rE 'uses: hyperpolymath/standards/.+@[0-9a-f]{40}' .github/workflows/ | head -3"
adjust: "Bump SHA pin; verify via gh api repos/hyperpolymath/standards/commits/<sha>."
defer-if: "recent reusable change introduces breaking input shape."

- name: A041-action-sha-pin-fake
description: "A 3rd-party action SHA pin is fake (does not exist upstream)."
severity: critical
probe: "test -x scripts/verify-action-shas.sh && bash scripts/verify-action-shas.sh 2>&1 | grep -q 'FAKE'"
adjust: "Replace with current real SHA via gh api repos/<owner>/<action>/commits/<branch>."
defer-if: "never — fake SHA is a supply-chain hazard."

- name: A042-governance-floating-ref
description: "A workflow file uses @main floating ref (not SHA-pinned)."
severity: medium
probe: "grep -rE 'uses: hyperpolymath/.+@main' .github/workflows/ | head -1"
adjust: "SHA-pin via gh api repos/hyperpolymath/<reusable>/commits/main."

adjust-philosophy:
- "Always check before adjust (every recipe has both)."
- "Never adjust without an explicit defer-if escape hatch."
- "Adjustments cascade: bump dep → cargo test → check container → check CI."
- "Prefer the narrowest change that resolves the drift."
164 changes: 164 additions & 0 deletions contractiles/bust/Bustfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# SPDX-License-Identifier: PLMP-1.0-or-later
# Bustfile — breakage / rollback contract for echidnabot
#
# When a build, test, container, dispatch, webhook, or migration goes
# wrong, this file declares the recovery sequence. Each scenario has
# an error code, a probe to detect it, and a recovery recipe.
# Run with: just bust-check
# Error-code namespace: B###

version: 1

metadata:
name: echidnabot-bust-contract
spec: v0.1.0
description: "Breakage rollback for Rust build, sqlx migrations, container, dispatch, observability, webhooks."

# ── Build-Bust ─────────────────────────────────────────────────────────

build:

- name: B001-cargo-build-broken
description: "cargo build fails after dep update or upstream API change."
severity: critical
probe: "! cargo build --offline 2>&1 | grep -q 'error\\['"
recovery: "just bust-cargo-cache && cargo build"
escalation: "git revert <last-dep-bump>; cargo update -p <crate> --precise <known-good>"

- name: B002-shared-context-path-broken
description: "Cargo build fails on missing ../../shared-context (echidnabot lives in gitbot-fleet)."
severity: high
probe: "cargo check --offline 2>&1 | grep -q 'failed to read .*shared-context'"
recovery: "Build from gitbot-fleet/bots/echidnabot/ where the relative path resolves."
escalation: "see [[feedback_echidnabot_src_abi_namespace_intentional]] for the dual-tree story."

- name: B003-edition-mismatch
description: "let-chain or other edition-2024 syntax in edition-2021 file."
severity: high
probe: "cargo build 2>&1 | grep -q 'experimental.*let.*chain'"
recovery: "Bump Cargo.toml `edition = \"2024\"` OR rewrite the syntax to edition-compatible form."

# ── Test-Bust ──────────────────────────────────────────────────────────

tests:

- name: B010-cargo-test-broken
description: "cargo test --lib regression after change."
severity: critical
probe: "! cargo test --lib --offline 2>&1 | grep -q 'test result: ok'"
recovery: "git bisect run cargo test --lib; revert offending commit."

- name: B011-property-test-shrink-too-slow
description: "tests/property_tests.rs proptest shrinking blows past timeout."
severity: low
probe: "cargo test --tests --offline 2>&1 | grep -q 'shrinking'"
recovery: "Lower PROPTEST_CASES env (e.g. 64); investigate the offending invariant offline."

- name: B012-webhook-e2e-broken
description: "tests/webhook_e2e_test.rs fails (signature verification / payload decode)."
severity: high
probe: "! cargo test --test webhook_e2e_test --offline 2>&1 | grep -q 'test result: ok'"
recovery: "Check signature header (X-Hub-Signature-256, X-Gitlab-Token, X-Gitea-Signature) routing in src/api/webhooks.rs."

# ── Container-Bust ─────────────────────────────────────────────────────

container:

- name: B020-containerfile-build-broken
description: "podman build -f Containerfile fails."
severity: warning
probe: "! podman build -f Containerfile -q . 2>&1 | tail -3 | grep -qE 'sha256:[0-9a-f]'"
recovery: "podman system prune -af; rebuild."
escalation: "check Chainguard base tag drift; stapeln.toml layer cache invalidation."

- name: B021-compose-stack-broken
description: "docker compose / podman-compose up fails on compose.yml."
severity: warning
probe: "podman-compose config 2>&1 | grep -q 'error'"
recovery: "podman-compose down -v; clear pgdata volume; up fresh."
escalation: "check echidnabot#69 compose.yml syntax against current Compose v2 spec."

# ── DB-Bust ────────────────────────────────────────────────────────────

database:

- name: B030-sqlx-migration-failure
description: "sqlx migrate run failed mid-transaction."
severity: critical
probe: "manual — sqlx logs"
recovery: "sqlx migrate revert; fix the offending migration locally; sqlx migrate run again."
escalation: "if DB state corrupt: restore from backup; never force-apply a broken migration."

- name: B031-postgres-conn-drop
description: "Postgres connection pool exhausted / dropped."
severity: medium
probe: "grep -q 'PoolTimedOut\\|pool exhausted' /var/log/echidnabot*.log 2>/dev/null"
recovery: "Increase pool size in src/store/postgres.rs; check for connection leak in long-running query."

- name: B032-sqlite-locked
description: "SQLite database is locked (concurrent writer)."
severity: medium
probe: "grep -q 'database is locked' /var/log/echidnabot*.log 2>/dev/null"
recovery: "Ensure single-writer convention; switch to WAL mode if not already."

# ── Dispatch-Bust ──────────────────────────────────────────────────────

dispatch:

- name: B040-echidna-server-unreachable
description: "Calls to echidna server fail (timeout / connection refused)."
severity: high
probe: "curl -sf --max-time 5 ${ECHIDNA_API_URL:-http://localhost:8081}/api/health >/dev/null"
recovery: "Check echidna server alive; check ECHIDNA_API_URL env; check network policy."
escalation: "fall back to graphql endpoint if REST down (echidna#188 added 3 new GraphQL ops)."

- name: B041-graphql-contract-mismatch
description: "GraphQL operations echidnabot client calls are absent from echidna server."
severity: high
probe: "echidna#180 surfaced this; current state: verifyProof + suggestTactics + proverStatus added in #188."
recovery: "Ensure echidna server is on commit >= 08771e64 (PR #188 merge)."

- name: B042-job-queue-stalled
description: "Job queue has > 100 pending items / scheduler not processing."
severity: high
probe: "src/scheduler/job_queue.rs depth metric"
recovery: "Increase scheduler concurrency; investigate backend slowness; check dispatch failure rate."

# ── Observability-Bust ─────────────────────────────────────────────────

observability:

- name: B050-otel-endpoint-unreachable
description: "OTLP exporter cannot reach OTEL_EXPORTER_OTLP_ENDPOINT."
severity: low
probe: "Check observability config; OTLP exporter errors in stderr."
recovery: "Unset OTEL_EXPORTER_OTLP_ENDPOINT to disable; spans still log via fmt layer."

- name: B051-graceful-shutdown-timeout
description: "Shutdown timeout fired with N in-flight jobs."
severity: low
probe: "grep -q 'graceful shutdown timeout' /var/log/echidnabot*.log"
recovery: "Increase ECHIDNABOT_SHUTDOWN_TIMEOUT_SECS env; investigate why jobs hang past 30s."

# ── Repo-Bust ──────────────────────────────────────────────────────────

repo:

- name: B060-bad-commit-on-main
description: "A known-bad commit is on origin/main."
severity: high
recovery: "git revert <SHA> on a fresh branch; PR; admin-merge."
escalation: "force-push to main is PROHIBITED without owner confirmation."

- name: B061-tag-points-at-wrong-sha
description: "A release tag was cut at the wrong commit."
severity: medium
recovery: "git tag -d <tag>; git push origin :refs/tags/<tag>; re-tag at correct SHA; push."
escalation: "notify any downstream consumers of the tag re-point."

bust-escalation-ladder:
- 1. revert a single commit (low blast radius)
- 2. reset local workspace (no remote impact)
- 3. invalidate a specific cache (just bust-* recipes)
- 4. force-push to main — PROHIBITED without owner confirmation
- 5. registry-level (delete release tag, archive package version) — human-only
Loading