Slice 5: graduation per §13

The §13.3 transactional sequence flips a super-draft to active —
five steps with paired undoes, an in-process orchestrator fed by
an asyncio.Queue, the §17 SSE endpoint streaming step transitions
to the dialog. Each step is a new bot primitive that logs an
`actions` row, bracketed by `graduate_start` / `graduate_complete`
for the linkable audit sequence. Rollback runs the undoes in
reverse from the last completed step; merge_pr has no undo by
design per §13.5.

The §9.8 precondition gate is enforced server-side at the top of
POST /graduate so the §13.3 rollback complexity does not grow.
The §13.4 chat migration is a database semantic no-op — the
(slug, branch_name='main') threads keep their identity, only the
interpretation changes. The §9.8 pre-graduation history surfaces
via a new _is_meta_target(rfc, branch) dispatch helper and lands
as pre_graduation_history on /main.

§13.1 claim flow landed alongside since it's the prerequisite for
non-admin graduation — bot.open_claim_pr plus broadening
api_prs._require_pr to accept meta_claim.

45/45 tests green; ten new integration tests cover the validator,
the §9.8 precondition refusal, happy path with audit verification,
mid-sequence rollback at steps 2 and 3, concurrent refusal,
chat-survives-without-data-movement, pre-graduation history, and
the §13.1 claim PR cycle.

SPEC.md §19.1 rewritten for Slice 6 (notifications); §19.2 grew
four candidates surfaced during the slice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-24 21:52:29 -07:00
parent 4565a6cb95
commit 1b0968a9a2
14 changed files with 2872 additions and 172 deletions
+118
View File
@@ -1021,3 +1021,121 @@
}
.pr-review-quote { font-size: 11px; color: #6b7280; margin-bottom: 6px; }
.pr-review-quote pre { background: #fff; padding: 4px 8px; border-radius: 4px; margin: 4px 0 0 0; }
/* ── Slice 5: §13 graduation dialog ──────────────────────────────────── */
.modal-wide { width: min(720px, 92vw); }
.modal-intro { margin: 0 0 16px 0; font-size: 13px; color: #4b5563; line-height: 1.55; }
.form-row { display: flex; flex-direction: column; gap: 4px; margin-bottom: 14px; }
.form-row label { font-weight: 600; font-size: 12px; color: #1a1a1a; }
.form-row input, .form-row textarea {
font: inherit; padding: 6px 8px; border: 1px solid #d1d5db; border-radius: 4px;
}
.field-help { font-size: 11px; color: #6b7280; margin: 2px 0 0 0; }
.field-error { font-size: 12px; color: #b91c1c; margin: 4px 0 0 0; }
.owner-list {
display: flex; flex-wrap: wrap; gap: 6px; min-height: 28px;
padding: 4px 0;
}
.owner-empty { font-size: 12px; color: #6b7280; font-style: italic; }
.owner-chip {
display: inline-flex; align-items: center; gap: 4px;
background: #eef2ff; color: #3730a3; padding: 2px 8px; border-radius: 99px;
font-size: 12px;
}
.owner-chip-x {
background: none; border: none; cursor: pointer; color: #6b7280;
font-size: 14px; line-height: 1; padding: 0 2px;
}
.owner-chip-x:hover { color: #b91c1c; }
.owner-picker { display: flex; gap: 6px; margin-top: 6px; }
.owner-picker input { flex: 1; }
.precondition-block { margin-top: 12px; padding: 10px; background: #fef2f2; border-radius: 6px; border: 1px solid #fecaca; }
.precondition-toggle {
background: none; border: none; cursor: pointer; color: #b91c1c; font-weight: 600;
font-size: 13px; padding: 0;
}
.precondition-popover { margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
.precondition-row {
display: flex; justify-content: space-between; align-items: center;
background: #fff; padding: 8px 10px; border-radius: 4px; border: 1px solid #f3f4f6;
}
.precondition-row-meta { font-size: 11px; color: #6b7280; }
.precondition-row-actions a { color: #5b5bd6; }
.precondition-help { font-size: 11px; color: #6b7280; margin: 4px 0 0 0; font-style: italic; }
.btn-graduate { margin-left: 6px; }
.step-stack { display: flex; flex-direction: column; gap: 6px; margin: 12px 0; }
.step-row {
display: grid; grid-template-columns: 24px 1fr auto; gap: 10px; align-items: center;
padding: 8px 10px; border-radius: 6px; background: #f9fafb; border: 1px solid #f3f4f6;
}
.step-row.step-running { background: #eff6ff; border-color: #bfdbfe; }
.step-row.step-done { background: #f0fdf4; border-color: #bbf7d0; }
.step-row.step-failed { background: #fef2f2; border-color: #fecaca; }
.step-row.step-not-reached { opacity: 0.45; }
.step-marker {
display: inline-block; width: 16px; height: 16px; border-radius: 50%;
background: #d1d5db;
}
.step-marker-pending { background: #d1d5db; }
.step-marker-running { background: #3b82f6; animation: pulse 1s ease-in-out infinite; }
.step-marker-done { background: #10b981; }
.step-marker-failed { background: #ef4444; }
.step-marker-not-reached { background: #e5e7eb; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.step-label { font-weight: 600; font-size: 13px; color: #1a1a1a; }
.step-detail { font-size: 11px; color: #6b7280; margin-top: 2px; }
.step-status-pill {
font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 3px;
text-transform: uppercase; letter-spacing: 0.04em;
}
.pill-pending { background: #e5e7eb; color: #4b5563; }
.pill-running { background: #3b82f6; color: #fff; }
.pill-done { background: #10b981; color: #fff; }
.pill-failed { background: #ef4444; color: #fff; }
.pill-not-reached { background: #e5e7eb; color: #9ca3af; }
.rollback-divider {
margin: 10px 0 6px 0; font-size: 11px; text-transform: uppercase;
letter-spacing: 0.06em; color: #b91c1c; font-weight: 700;
}
.what-happened {
margin-top: 14px; padding: 12px; background: #fef2f2;
border: 1px solid #fecaca; border-radius: 6px;
}
.what-happened h3 { margin: 0 0 6px 0; font-size: 14px; color: #991b1b; }
.what-happened p { margin: 0 0 6px 0; font-size: 13px; color: #4b5563; line-height: 1.55; }
.graduation-complete {
margin-top: 14px; padding: 12px; background: #f0fdf4;
border: 1px solid #bbf7d0; border-radius: 6px;
}
.graduation-complete h3 { margin: 0 0 6px 0; font-size: 14px; color: #166534; }
.graduation-complete p { margin: 0; font-size: 13px; color: #14532d; line-height: 1.55; }
.modal-progress-note { font-size: 12px; color: #6b7280; }
.modal-error {
padding: 8px 12px; background: #fef2f2; color: #991b1b;
border-top: 1px solid #fecaca; font-size: 12px;
}
.rfc-error-banner {
padding: 8px 12px; background: #fef2f2; color: #991b1b;
border-bottom: 1px solid #fecaca; font-size: 12px;
}
.branch-dropdown-section {
font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
color: #6b7280; padding: 8px 10px 4px 10px; font-weight: 700;
}
.branch-dropdown-item.pre-graduation {
font-style: italic; color: #4b5563;
}
.branch-dropdown-item.pre-graduation .branch-meta {
font-size: 10px; color: #9ca3af; margin-left: auto;
}