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
86 changes: 81 additions & 5 deletions AUTONOMY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Where the dev workflow stops for a human today:
| Plan approval | write plan to the issue, proceed | write plan to the issue, proceed | present + wait |
| Ship PR sign-off | proceed through the autonomous-ship gate; end at PR-open | park (`awaiting-input`), question to the issue, continue elsewhere | present + wait |
| Heart YELLOW | park, unless the reason set was human-acknowledged at launch (see the autonomous-ship gate) | same as `safe` | present + wait |
| Heart RED | stop, report | stop, report | stop, report |
| Heart RED | stop, report | stop, report | stop, report — a human may separately invoke the corrective-PR exception (below), which is not an autonomy level |
| Merge / close | human, always | human, always | human, always |
| Version ask | n/a — release stays `human-required` (sole exception: the scheduled-nightly standing grant, dated below) | n/a | ask |
| Cleanup | proceed + log | proceed + log | confirm |
Expand Down Expand Up @@ -175,6 +175,74 @@ applicability rule so "n/a" is a stated fact, never an assumption:
A failed leg downgrades the run to a human checkpoint: state written to the
issue, nothing force-shipped, never modify code to make a leg pass.

## Corrective-PR exception for Heart RED (human-authorized)

Heart RED forbids commit, push and PR-open at every autonomy level (the levels
table). But Heart cannot clear a RED until the fixing source reaches `main`,
fresh wheels are built, and release-integration validation passes — so a source
fix that directly repairs the exact defect named by the RED reason cannot be
shipped, and recovery is impossible without violating policy. This section is
the **one** authorized way through that deadlock, and it is a **human act**, not
an autonomy level: it never fires under `--auto` (the hard invariant "Heart
YELLOW/RED is never acknowledged autonomously" stands verbatim — an unattended
`--auto` run on RED still stops and reports). A human invokes it, live, per
incident.

- **Trigger** — Heart is RED, and a source fix directly repairs a defect named
by a RED reason.
- **Authorization** — explicit, contemporaneous human authorization that
(a) quotes the exact RED reason string and (b) approves the specific
corrective issue. It is recorded as a human comment on that issue; a stored,
reused or "standing" authorization does not count — it must be for this RED,
now. **The agent provides the quote.** When the circumstance arises, the agent
surfaces the exact RED reason string(s) **verbatim from Heart's current
verdict** (`pyauto-heart readiness`) together with the specific corrective
request it is asking the human to approve — so the human authorizes what the
agent put in front of them, never a string reconstructed from memory. It is the
human's judgement, on the agent-surfaced reason, that authorizes.
- **Permitted, and nothing else** — commit, push, and opening **one**
pending-release feature PR whose issue, plan and diff all map to the named
reason.
- **Forbidden** — automatic merge, issue close, release, release rehearsal, and
any unrelated scope. Merge stays a separate human act; **every release stays
blocked while Heart is RED**.
- **Recorded in four sinks** — the authorization, the exact RED reason, the
causal mapping (reason → issue → plan → diff), the tests, and the validation
plan are written to: the **GitHub issue**; the **PR body**; the corrective
task's **`PyAutoMind/active.md`** entry (a `- corrective-red:` block naming
the reason and pointing at the authorization comment); and a
**`autonomy_log.md`** row whose outcome is tagged `corrective` (it is not an
`--auto` run, but the calibration log still records that the exception was
used).
- **Multiple RED reasons** — the authorization names exactly one. The PR body
states which reason the diff clears and that any sibling RED reasons remain,
so Heart stays RED and release stays blocked until every reason is cleared by
its own corrective PR. A corrective PR never claims to clear a reason it does
not address.

**Failure behaviour — park without shipping** (set the `active.md` entry to
`status: blocked` or `awaiting-input`, write why on the issue, open no PR):

- **Mixed-scope diff** — the diff touches anything beyond the named reason's
fix. The narrow permission covers only the causal fix; bundle nothing with it.
- **Stale or changed RED reason** — re-read Heart's verdict at ship time; if the
named reason string has changed, split, or cleared, the authorization no
longer matches the world. Park and re-authorize against the current verdict.
- **Missing evidence** — no causal mapping, no tests, or no validation plan.
- **Review finds the patch is not causal** — the review faculty (or a human
reviewer) judges the diff does not actually repair the named reason.

**Recovery sequence** — the exception opens a PR; it does not resume release
work. That resumes only along this path:

1. A **human merges** the corrective PR (a separate human act — the exception
never merges).
2. **Fresh post-merge wheels are built** and **release-integration validation**
is re-run on `main`.
3. Heart emits a **new verdict** over that fresh evidence.
4. Release work resumes only on that new verdict, and release stays
`human-required` throughout (the release cap is untouched by this section).

## Calibration log

`PyAutoMind/autonomy_log.md` — append-only. Every `--auto` run appends a row
Expand All @@ -184,9 +252,11 @@ at PR-open (or on parking):
| date | task | effective level | gates (tests/smoke/review/heart) | outcome |
```

Outcome ∈ `merged-unchanged` / `amended` / `rejected` / `parked`. This is the
evidence base for raising or lowering caps — autonomy grows by demonstrated
calibration, not by optimism.
Outcome ∈ `merged-unchanged` / `amended` / `rejected` / `parked` / `corrective`
(the last records a use of the human-authorized corrective-PR exception above —
not an `--auto` run, but logged so the exception's use is auditable alongside the
autonomy rows). This is the evidence base for raising or lowering caps — autonomy
grows by demonstrated calibration, not by optimism.

### Calibration review — 2026-07-09

Expand Down Expand Up @@ -223,6 +293,11 @@ tier), never by weakening leg 4.
- **Heart YELLOW/RED is never acknowledged autonomously.** A launch-time
human acknowledgement of a named reason set is a human acknowledgement — it
binds to that exact set, for that launch, and never extends to new reasons.
- **The corrective-PR exception for Heart RED is a contemporaneous human act**
(the section above), never reachable under `--auto`. It permits only commit,
push and opening one pending-release PR that repairs the named RED reason —
never merge, close, release or unrelated scope; every release stays blocked
while Heart is RED.
- **Never rewrite history** (`AGENTS.md` rules apply verbatim to autonomous
runs).
- The `Autonomy:` header is a model's own estimate. The caps, the explicit
Expand All @@ -234,7 +309,8 @@ tier), never by weakening leg 4.
- `start_dev` — `--auto` usage, effective-level computation, plan-to-issue
for `safe`, launch-acknowledgement recording (its "--auto mode" section).
- `ship_library` / `ship_workspace` — the four-leg gate at step 4, stop at
PR-open, validation checklist, calibration append.
PR-open, validation checklist, calibration append; the RED-handling step
points here for the human-authorized corrective-PR exception.
- `run_queue` — the generic queue loop: launching it is the batch's `--auto`
activation; per-entry effective-level dispatch, `PARKED` checkpointing, RED
stops the run.
Expand Down
8 changes: 7 additions & 1 deletion skills/WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,13 @@ pyauto-heart readiness --json # authoritative GREEN / YELLOW / RED verdict
- **YELLOW** → surface the warnings; proceed only with explicit user
acknowledgement (a human checkpoint at **every** autonomy level —
[`../AUTONOMY.md`](../AUTONOMY.md)).
- **RED** → stop; report what failed. Do not ship.
- **RED** → stop; report what failed. Do not ship — **unless** a human
authorizes the narrow corrective-PR exception naming the exact RED reason
([`../AUTONOMY.md`](../AUTONOMY.md) "Corrective-PR exception for Heart RED"):
commit/push/PR-open of one reason-scoped fix only, never merge or release.
When that exception is in play, surface the exact RED reason string(s)
verbatim from `pyauto-heart readiness` so the human authorizes the quote the
agent provided rather than hunting for the wording.

Tests/smoke runs that feed the verdict are Heart's domain — invoke them through
the vitals faculty rather than re-deriving pass/fail criteria in the skill.
Expand Down
8 changes: 7 additions & 1 deletion skills/ship_library/ship_library.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ pyauto-heart readiness --json # authoritative GREEN / YELLOW / RED
The library test suites are part of Heart's verdict — they run as the gate, not
as an ad-hoc step the skill re-judges. **GREEN** → proceed to step 4.
**YELLOW** → surface warnings, proceed only on explicit user acknowledgement.
**RED** → stop and report; do not ship. If `pyauto-brain`/`pyauto-heart` are
**RED** → stop and report; do not ship — **unless** a human authorizes the
narrow corrective-PR exception naming the exact RED reason (`AUTONOMY.md`
"Corrective-PR exception for Heart RED"), which permits commit/push/PR-open of
one reason-scoped fix only, never merge or release. When that exception is in
play, **surface the exact RED reason string(s) verbatim from `pyauto-heart
readiness`** and the specific corrective request, so the human authorizes the
quote the agent provided rather than hunting for the wording. If `pyauto-brain`/`pyauto-heart` are
unavailable, run the per-repo `pytest <test_dir>/ -x` inside the worktree as the
gate and treat any failure as RED (WORKFLOW.md). Under `--auto`, this step is
the four-leg **autonomous-ship gate** — tests (+ downstream dependents on
Expand Down
9 changes: 8 additions & 1 deletion skills/ship_workspace/ship_workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,14 @@ pyauto-heart readiness --json # GREEN / YELLOW / RED
```

Workspace **smoke tests** are part of Heart's verdict. **GREEN** → execute.
**YELLOW** → proceed only on explicit acknowledgement. **RED** → stop. If the
**YELLOW** → proceed only on explicit acknowledgement. **RED** → stop —
**unless** a human authorizes the narrow corrective-PR exception naming the
exact RED reason (`AUTONOMY.md` "Corrective-PR exception for Heart RED"), which
permits commit/push/PR-open of one reason-scoped fix only, never merge or
release. When that exception is in play, **surface the exact RED reason
string(s) verbatim from `pyauto-heart readiness`** and the specific corrective
request, so the human authorizes the quote the agent provided rather than
hunting for the wording. If the
organism CLIs are unavailable, run `/smoke_test` (with `activate.sh` sourced) as
the gate and treat any failure as RED. Under `--auto`, this step is the
four-leg **autonomous-ship gate** (`AUTONOMY.md` "The autonomous-ship gate");
Expand Down
135 changes: 135 additions & 0 deletions tests/test_corrective_red_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"""Doc-contract tests for the human-authorized corrective-PR exception to
Heart RED (PyAutoBrain#112).

The exception is doctrine, not code, so its guardrails are pinned against the
canonical text of AUTONOMY.md — a weakening edit (dropping a forbidden action,
a record sink, or the human-only clause) fails loudly here rather than silently
widening the one authorized path through a RED gate. The seam tests also pin
the "link, don't duplicate policy" contract: the ship skills point at
AUTONOMY.md and do not carry a second copy of the permitted/forbidden lists.
"""

from pathlib import Path

BRAIN_HOME = Path(__file__).resolve().parents[1]
AUTONOMY = (BRAIN_HOME / "AUTONOMY.md").read_text()
SHIP_LIBRARY = (BRAIN_HOME / "skills" / "ship_library" / "ship_library.md").read_text()
SHIP_WORKSPACE = (BRAIN_HOME / "skills" / "ship_workspace" / "ship_workspace.md").read_text()
WORKFLOW = (BRAIN_HOME / "skills" / "WORKFLOW.md").read_text()

SECTION_HEADER = "## Corrective-PR exception for Heart RED (human-authorized)"


def _norm(text):
"""Lowercase, strip markdown emphasis, and collapse whitespace so assertions
ignore ``**``/``` ` ``` and line wrapping."""
return " ".join(text.replace("*", "").replace("`", "").lower().split())


def _section():
"""The corrective-PR exception section body (header to the next ``## ``)."""
start = AUTONOMY.index(SECTION_HEADER)
rest = AUTONOMY[start + len(SECTION_HEADER):]
end = rest.find("\n## ")
return _norm(rest if end == -1 else rest[:end])


def test_section_exists():
assert SECTION_HEADER in AUTONOMY


def test_permitted_set_is_commit_push_pr_open_only():
sec = _section()
for verb in ("commit", "push", "opening one"):
assert verb in sec, verb
# the permission is explicitly bounded
assert "nothing else" in sec


def test_forbidden_set_named():
sec = _section()
for forbidden in ("merge", "issue close", "release", "release rehearsal",
"unrelated scope"):
assert forbidden in sec, forbidden
assert "every release stays blocked while heart is red" in sec


def test_four_record_sinks_named():
sec = _section()
for sink in ("github issue", "pr body", "pyautomind/active.md",
"autonomy_log.md", "- corrective-red:"):
assert _norm(sink) in sec, sink


def test_park_without_shipping_cases_named():
sec = _section()
assert "park without shipping" in sec
for case in ("mixed-scope diff", "stale or changed red reason",
"missing evidence", "not causal"):
assert case in sec, case


def test_multiple_reasons_names_exactly_one():
sec = _section()
assert "multiple red reasons" in sec
assert "names exactly one" in sec


def test_recovery_sequence_is_merge_then_fresh_validation_then_new_verdict():
sec = _section()
assert "recovery sequence" in sec
assert "human merges" in sec
assert "post-merge wheels" in sec
assert "release-integration validation" in sec
assert "new verdict" in sec
assert "human-required" in sec # release cap untouched


def test_agent_provides_the_quote():
# The human quotes the exact RED reason, so the agent must surface it
# verbatim from Heart's verdict — the human never reconstructs the wording.
sec = _section()
assert "the agent provides the quote" in sec
assert "verbatim from heart's current verdict" in sec
# and the ship gates instruct the agent to surface it at the RED step
for doc in (SHIP_LIBRARY, SHIP_WORKSPACE, WORKFLOW):
assert "verbatim from" in _norm(doc)


def test_exception_is_human_only_never_under_auto():
sec = _section()
assert "human act" in sec
assert "never fires under --auto" in sec


def test_never_acknowledged_autonomously_invariant_survives_verbatim():
# The exception must not weaken this hard invariant; it stays word-for-word.
assert (
"**Heart YELLOW/RED is never acknowledged autonomously.**" in AUTONOMY
)


def test_hard_invariant_bullet_scopes_the_exception():
inv = _norm(AUTONOMY)
assert "corrective-pr exception for heart red is a contemporaneous human act" in inv


def test_ship_skills_link_the_exception():
# Each ship gate points at the canonical section (link, not restatement).
marker = "Corrective-PR exception for Heart RED"
assert marker in SHIP_LIBRARY
assert marker in SHIP_WORKSPACE
assert marker in WORKFLOW


def test_ship_skills_do_not_duplicate_policy():
# The exception's permitted/forbidden lists, record sinks and failure cases
# live only in AUTONOMY.md; the ship skills reference the section by name and
# must not carry a second copy that could drift. (Tokens checked here are
# distinctive to the exception — not incidental words like "autonomy_log.md",
# which ship_library legitimately uses for the calibration append.)
for doc in (SHIP_LIBRARY, SHIP_WORKSPACE, WORKFLOW):
norm = _norm(doc)
assert "- corrective-red:" not in norm # the active.md block form
assert "park without shipping" not in norm # the failure-case list
assert "recovery sequence" not in norm # the resume-release steps