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:
@@ -221,6 +221,54 @@ export async function editMetadata(slug, { title, tags, prDescription }) {
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// ── Slice 5: §13 graduation + §13.1 claim ────────────────────────────────
|
||||
|
||||
export async function claimOwnership(slug) {
|
||||
const res = await fetch(`/api/rfcs/${slug}/claim`, { method: 'POST' })
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function listBlockingPRs(slug) {
|
||||
return jsonOrThrow(await fetch(`/api/rfcs/${slug}/blocking-prs`))
|
||||
}
|
||||
|
||||
export async function graduateCheck(slug, { id, repo }) {
|
||||
const params = new URLSearchParams()
|
||||
if (id != null) params.set('id', id)
|
||||
if (repo != null) params.set('repo', repo)
|
||||
return jsonOrThrow(await fetch(`/api/rfcs/${slug}/graduate/check?${params}`))
|
||||
}
|
||||
|
||||
export async function startGraduation(slug, { rfcId, repoName, owners }) {
|
||||
const res = await fetch(`/api/rfcs/${slug}/graduate`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ rfc_id: rfcId, repo_name: repoName, owners }),
|
||||
})
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// Open an EventSource on the §13.3 progress stream. Returns the
|
||||
// EventSource so the caller can close() on dialog dismiss. Calls
|
||||
// onUpdate with the parsed state payload for every event.
|
||||
export function openGraduationProgress(slug, { onUpdate, onDone, onError }) {
|
||||
const es = new EventSource(`/api/rfcs/${slug}/graduate/progress`)
|
||||
const handle = (e) => {
|
||||
try {
|
||||
const payload = JSON.parse(e.data)
|
||||
onUpdate?.(payload, e.type)
|
||||
if (e.type === 'done' && payload?.finished) onDone?.(payload)
|
||||
} catch (err) {
|
||||
onError?.(err)
|
||||
}
|
||||
}
|
||||
for (const name of ['snapshot', 'step', 'rollback_step', 'completed', 'rolled_back', 'done']) {
|
||||
es.addEventListener(name, handle)
|
||||
}
|
||||
es.onerror = (e) => { onError?.(e); es.close() }
|
||||
return es
|
||||
}
|
||||
|
||||
// ── Slice 3: the §10 PR flow ─────────────────────────────────────────────
|
||||
|
||||
export async function draftPRText(slug, branch) {
|
||||
|
||||
Reference in New Issue
Block a user