diff --git a/contractiles/adjust/Adjustfile b/contractiles/adjust/Adjustfile new file mode 100644 index 0000000..8b61694 --- /dev/null +++ b/contractiles/adjust/Adjustfile @@ -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 --precise ; 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/." + 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///commits/." + 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//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." diff --git a/contractiles/bust/Bustfile b/contractiles/bust/Bustfile new file mode 100644 index 0000000..a28e440 --- /dev/null +++ b/contractiles/bust/Bustfile @@ -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 ; cargo update -p --precise " + + - 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 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 ; git push origin :refs/tags/; 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