propose_fix
Draft a fix PR grounded in a preceding diagnose run. Composes request_review under the hood, but adds a hard binding to a diagnosis_id and requires explicit MR-12 grounding for the proposed change. The verb fleets reach for when implementing fixes.
Upholds: MR-7 · MR-12
Side effects: Same as request_review: branch + PR. Plus: PR body includes the diagnosis_id and links to the diagnose run’s recent_findings entries.
Constraints
Section titled “Constraints”REPO_IN_REGISTRY— target_repo must appear in registry.yaml.DIAGNOSIS_EXISTS— diagnosis_id must reference a diagnose run completed within the last 24 hours against the same target_repo.DIAGNOSIS_REPO_MATCH— The diagnose run’s result.repo must equal envelope.target_repo.FILES_NONEMPTY— proposed_changes must contain ≥1 entry.TEST_PLAN_NONEMPTY— test_plan must list ≥1 verification step. Fixes without test plans are forbidden.GROUNDING_NONEMPTY— mr_grounding must cite at least one MR or one decision doc / spec section explaining why the change is correct.NO_PRIVILEGED_PATHS— Same privileged-path rejection as request_review.
Input shape
Section titled “Input shape”diagnosis_idstring(required)titlestring(required)rationalestring(required)proposed_changesarray(required)- items:
pathstring(required)operationstring(required) enum:create,modify,deletecontentsstringpatchstringedit_rationalestring— Per-file rationale; reviewer-readable.
- items:
test_planarray(required)mr_groundingarray(required)- items:
kindstring(required) enum:meta_rule,decision_doc,spec,north_star,findingrefstring(required)claimstring— What this grounding source claims that justifies the change.
- items:
merge_strategystringenum:request_review,request_merge_when_green— Whether to compose request_review (default, human merges) or request_merge_when_green (profile-permitting, auto-merge).
Output shape
Section titled “Output shape”diagnosis_idstringpr→PRRefdiff_preview→DiffPreviewcomposed_verbstringenum:request_review,request_merge_when_green
Example
Section titled “Example”Input:
{ "envelope": { "verb": "propose_fix", "target_repo": "kahn-hq", "idempotency_key": "2b7ffa28f53ee7791c25d4e8f9012b3c4d5e6f7a8b9c0d1e2f30415263748596", "dry_run": true, "actor": "fleet:kahn-implementer", "triggered_by": { "kind": "audit_fail", "ref": "diag-a3f29c4b1e8d7602" } }, "params": { "diagnosis_id": "diag-a3f29c4b1e8d7602", "title": "fix(scope): live-dot for sub-second runs (M7.2.1)", "rationale": "Diagnose surfaced live-dot regression on runs <1s; M7.2.1 acceptance criterion not met.", "proposed_changes": [ { "path": "frontend/src/components/live-dot.ts", "operation": "modify", "contents": "...", "edit_rationale": "Lower threshold from 1000ms to 250ms." } ], "test_plan": [ "vitest live-dot.spec.ts (existing + new sub-1s case)", "manual: scripts/seed-subsec-run.sh && curl /runs/<id> shows dot" ], "mr_grounding": [ { "kind": "meta_rule", "ref": "MR-2", "claim": "M6.5 carry-over rule: fix surfaced friction in next phase, not retrofit." }, { "kind": "decision_doc", "ref": "docs/decisions/2026-04-28-phase-6-friction-round.md", "claim": "Q1 surfaced live-dot subsec gap." } ] }}Output (output_dry_run):
{ "envelope": { "verb": "propose_fix", "status": "dry_run", "idempotency_key": "2b7ffa28f53ee7791c25d4e8f9012b3c4d5e6f7a8b9c0d1e2f30415263748596", "mr_citations": [ "MR-7", "MR-12" ] }, "result": { "diagnosis_id": "diag-a3f29c4b1e8d7602", "composed_verb": "request_review", "diff_preview": { "files": [ { "path": "frontend/src/components/live-dot.ts", "operation": "modify" } ], "branch": "petrova/propose-fix/2b7ffa28", "commit_message": "fix(scope): live-dot for sub-second runs (M7.2.1)" } }}Recipe
Section titled “Recipe”Schema: spec/verbs/propose_fix.schema.json
Upholds: MR-7, MR-12.
Composes: request_review (default) or request_merge_when_green.
Prerequisite
Section titled “Prerequisite”propose_fix requires a fresh diagnose run (≤24h) for the same
repo. The diagnosis cache lives at ~/.petrova/diagnoses.jsonl and
persists diagnosis_id values across sessions.
If you don’t have a recent diagnose, run one first:
petrova diagnose <slug># Capture the diagnosis_id from the output: diag-<16 hex>When to use
Section titled “When to use”Implementing a fix where the rationale grounds in something diagnose
surfaced — a recent finding, a milestone gap, a CI failure. The verb
encodes the diagnosis → fix link explicitly so reviewers can see the
chain.
Required params
Section titled “Required params”{ "diagnosis_id": "diag-<16 hex>", "title": "<conventional PR title>", "rationale": "<why; lands in PR body>", "proposed_changes": [ {"path": "<file>", "operation": "create|modify|delete", "contents": "<full contents>", "edit_rationale": "<per-file why>"} ], "test_plan": ["<verification step 1>", "<step 2>"], "mr_grounding": [ {"kind": "meta_rule|decision_doc|spec|north_star|finding", "ref": "<path or MR-N>", "claim": "<what this source claims that justifies the change>"} ]}Optional: merge_strategy — request_review (default, human merges)
or request_merge_when_green (profile-permitting, auto-merge).
Constraints
Section titled “Constraints”diagnosis_idmust exist in the local cache and be ≤24h old.- Diagnosis’s
repomust equal the target repo (no cross-repo binds). test_planmust contain ≥1 step. Fixes without test plans are forbidden.mr_groundingmust contain ≥1 entry. The “claim” field is what makes this MR-12-compliant — every fix names its source.- No privileged paths (
.github/workflows/,*.env, etc.).
Worked example
Section titled “Worked example”# 1. Diagnosepetrova diagnose kahn-hq --json | jq -r '.result.diagnosis_id'# → diag-a3f29c4b1e8d7602
# 2. Compose fixcat > /tmp/petrova-input.json <<'JSON'{ "diagnosis_id": "diag-a3f29c4b1e8d7602", "title": "fix(scope): live-dot for sub-second runs (M7.2.1)", "rationale": "Diagnose surfaced live-dot regression on runs <1s; M7.2.1 acceptance criterion not met.", "proposed_changes": [ {"path": "frontend/src/components/live-dot.ts", "operation": "modify", "contents": "...", "edit_rationale": "Lower threshold from 1000ms to 250ms."} ], "test_plan": [ "vitest live-dot.spec.ts (existing + new sub-1s case)", "manual: scripts/seed-subsec-run.sh && curl /runs/<id> shows dot" ], "mr_grounding": [ {"kind": "meta_rule", "ref": "MR-2", "claim": "M6.5 carry-over rule: fix surfaced friction in next phase, not retrofit."}, {"kind": "decision_doc", "ref": "docs/decisions/2026-04-28-phase-6-friction-round.md", "claim": "Q1 surfaced live-dot subsec gap."} ]}JSON
# 3. Dry-runpetrova propose_fix kahn-hq --input /tmp/petrova-input.json
# 4. Show diff to human, get go-ahead, then applypetrova propose_fix kahn-hq --input /tmp/petrova-input.json --applyFailure modes
Section titled “Failure modes”DIAGNOSIS_EXISTS— diagnose cache empty or older than 24h. Re-run.DIAGNOSIS_REPO_MATCH— diagnose was for a different repo. Run diagnose for the right repo first.NO_PRIVILEGED_PATHS— proposed change touches a privileged path. Refuse and route to a human.