Skip to content

ADR-0112: Generalise bounded autonomy into an Autonomous Remediation Authority Standard

pitlab now actuates fixes autonomously in three places, each of which independently re-derived the same authority boundary, blast-radius cap, verify-before-close gate, and masking guard. This ADR ratifies extracting that shared contract into one Autonomous Remediation Authority Standard, so every future unattended actuator conforms to a single, checkable governance shape instead of reinventing it. The standard is the enforceable “how”; this ADR records why it was written and where its subjects agree and diverge.

  • Status: Accepted
  • Date: 2026-07-04
  • Epic: ADO #886 (vulnscan) → Issue #1474 (draft the standard)

Three autonomous actuators exist, built independently over ~two days:

  • ADR-0099 — autoheal: restarts opt-in containers whose healthcheck reports unhealthy. Low blast radius, idempotent-reversible action.
  • ADR-0101 — vulnscan: edits and deploys production to clear CVEs, fully unattended within an in-bounds class set. High blast radius, code-mutating.
  • ADR-0102 — test-plan gate auto-rollback: git reverts a deploy whose functional test plan regresses. High blast radius, code-mutating.

ADR-0101 explicitly flagged the gap in its own consequences: “Two auto-remediation capabilities exist, each re-deriving its own authority boundary, cap, and masking-guard from scratch. Per Prime Directive 9 this ‘solve it twice’ is the signal to write the standard once.” It raised the Autonomous Remediation Authority Standard as an ADO item rather than drafting it inline, because the pattern is cross-cutting across security (vulnscan) and observability (autoheal). Two subsequent Issues confirmed the pattern is still live and worth codifying: #1516 (the debounce needed a backoff — an instance of the blast-radius/loop-guard clause) and #1493 (the escalation must be a decision-ready artifact, not a stub — an instance of the escalation clause). The recurrence is the evidence: without a standard, each new actuator and each hardening re-reasons the same contract and drifts.

Adopt the Autonomous Remediation Authority Standard: one bounded-autonomy contract every unattended actuator conforms to. Its load-bearing clauses:

  1. Bounded authority envelope — an explicit in-bounds whitelist; everything else is out-of-bounds and escalated, never applied.
  2. Rollout control tiered to blast radius — a fail-safe-to-dryrun mode gate for code-mutating actuators; a conservative opt-in + grace for idempotent-reversible ones. One contract, tiered control — this is where the subjects legitimately diverge.
  3. Prove-it verify gate — a change is closed only after an acceptance check of the intended behaviour passes (PD12); “applied” is never “fixed”.
  4. Reversible on failure — auto-revert of a regressing mutation; a failed revert still fails + alerts.
  5. Blast-radius cap + loop guard — bound one run; a debounced destructive re-check backs off before re-sampling (#1516).
  6. Masking guard — a long-window recurrence alert tuned to cadence; attempted-but-reverted surfaced as a confidence signal.
  7. Decision-ready escalation — out-of-bounds work is handed over as a groomed Epic / options + recommendation, not a stub (#1493).
  8. The actuator is itself monitored — a freshness watchdog / liveness rule so a silent death is caught.
  9. Hard stops absolute — PD3 (UDM) and PD4 (HA) override any remediation demand; maintenance silences set before acting.
  10. Audited + notified by class — every action leaves a trail and notifies per ADR-0033 (ops → Pushover, report → email).

The existing three actuators are declared conformant as-built (the standard is extracted from them); the standard governs the next one and any future hardening of these.

  • Leave each actuator’s authority in its own ADR — rejected: this is exactly the “solve it twice (now thrice)” drift PD9 forbids; the fourth actuator would re-derive the same contract and inevitably omit a clause (a cap, a masking guard, a verify gate) that a shared standard would have made non-negotiable.
  • A single mandatory mode-gate for every actuator (force autoheal to carry a dryrun/live file too) — rejected: disproportionate for an idempotent, per-container-opt-in restart. Uniformity of mechanism over uniformity of guarantee would add ceremony without safety. The standard instead ties control strength to blast radius + reversibility, which is the real invariant.
  • A code framework / shared library every actuator imports — rejected as premature: the three actuators are in different languages/contexts (a compose sidecar, a Claude Code skill, a Python gate script). A documented contract + conformance checklist is the right altitude now; a shared library can follow if a fourth actuator in the same runtime justifies it.
  • Fold it into the Service Test Plan Standard — rejected: that standard supplies the verify gate (one clause) but says nothing about envelopes, mode gates, caps, masking guards, or escalation. Autonomy authority is a distinct concern that uses the test-plan standard, not a subset of it.
  • Bounded autonomy becomes a checkable contract, not a per-actuator habit. A new autonomous actuator is conformant only when it satisfies the checklist — the cap, the verify gate, the masking guard, and the escalation shape are non-negotiable from day one instead of remembered-or-forgotten.
  • The divergence is now principled, not accidental. “autoheal has no mode file but vulnscan does” is recorded as the blast-radius tier, so it reads as a deliberate design choice rather than an inconsistency a future reviewer might try to ‘fix’.
  • #1516 and #1493 are reframed as standard clauses, not one-off fixes — the debounce backoff is the loop-guard clause; the decision-ready briefing is the escalation clause. Future actuators inherit both without re-discovering them.
  • A conformance debt may surface. Auditing the three subjects against the checklist could reveal a missing clause (e.g. an actuator without an explicit audit trail). Any gap found is a follow-up Issue, not a silent pass — the standard is only worth writing if it is applied back to its own subjects.
  • The standard is itself docs-as-code — indexed in standards/index.md and this ADR in adr/index.md, rebuilt by pipeline 18, retrievable via pit-memory. The next actuator’s author finds the contract by search, not by reading three ADRs.
  • Extended for delegated authority (2026-07-05, ADR-0121). The original three subjects share one provenance — a standing unattended grant — so the standard assumed a narrow-whitelist envelope + mandatory blast-radius cap. The /justdoit skill introduced a second provenance (a per-invocation human grant scoped to one Epic), which conforms with a broad grant bounded by the hard-stop set and scope-binding in place of a cap. The standard now declares two authority-provenance tiers (standing vs delegated); this decision is unchanged, extended not reversed.
  • Delegated tier re-argued for fan-out (2026-08-14, ADR-0403). /justdoit was decommissioned and its authority withdrawn rather than renamed; /go holds the delegated tier now. The tier’s shape is unchanged, but ADR-0121’s finding that scope-binding replaces a blast-radius cap was reopened: once a delegated actuator sub-delegates to disposable workers, the human sees verdicts rather than the run, so a derived circuit breaker is mandatory alongside scope-binding, together with a no-self-authored-spec clause and independent re-assertion of every worker’s self-report.