Files
rfc-app/docs/DEV.md
T
Ben Stull 1b0968a9a2 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>
2026-05-24 21:52:29 -07:00

27 KiB
Raw Blame History

Build notes

The slicing plan for the v1 build, the current state of the codebase, and the next slice's brief.

The slicing plan

Eight slices carry §§115 of SPEC.md end-to-end. The build does not extend the spec; spec corrections during the build are rare and surgical and live in the appropriate numbered section per §19.3's working agreement.

  1. Repository scaffolding + propose-to-super-draft vertical. The chokepoint that every Git operation flows through (§1 bot wrapper), the §4 cache machinery (webhook + reconciler), the §5 schema, Gitea OAuth + user provisioning, the minimal §7 catalog, and one end-to-end vertical: propose → idea PR → merge → super-draft view.
  2. The active-RFC view per §8 in full. Editor, branch creation, per-branch chat with AI participation (the §18 <change> protocol), the change-card panel, accept/decline/edit, manual-edit flushes, sub-threads, flags, DiffView.
  3. The PR flow per §10. Open, review surface (diff + compressed chat), the §10.3 seen-cursor, §10.4 review threads, merge, post-merge, §10.9 conflict resolution.
  4. Super-draft body editing per §9.5 + §9.6. Meta-repo edit branches as the unit of work; everything from §8 inherits.
  5. Graduation per §13. The dialog, the five-step transactional sequence, rollback, the pre-graduation history affordance.
  6. Notifications per §15. Last, because every other surface produces signals the inbox receives — notification correctness depends on the producers being in place first.
  7. The §14 chrome. Landing page polish, the /philosophy route, the persistent About link.
  8. Hardening. End-to-end tests, dev/prod deployment shape, the §12 30/90 branch-hygiene timers.

State of the codebase

Slice 1 — shipped

The repository scaffolding (backend/, frontend/, scripts/, docs/), the §5 schema as numbered migrations under backend/migrations/, the §1 bot wrapper (app/bot.py) that is the single chokepoint every Git write flows through, Gitea OAuth and the §6.1 user-provisioning row in users, the §4.1 webhook receiver and the §4.1 periodic reconciler (both writing to the cache; user actions never do), the §7 left pane (catalog list, search, sort, state-filter chips, pending-ideas disclosure), and one end-to-end vertical: propose → idea PR opens → owner merges → super-draft appears in the catalog → super-draft view renders the body.

Slice 2 — shipped

The §8 active-RFC view in full. The bot wrapper grew per-RFC-repo write operations — branch cut from main, accept-change commit with the structured original/proposed/reason body and trailers, manual-edit flush, and a ensure_rfc_repo_seed seam Slice 5's graduation will eventually replace. The §4 cache now mirrors per-RFC repos via a new refresh_rfc_repo path; the webhook receiver dispatches on repository.full_name so per-RFC events refresh just that repo, and the reconciler sweeps every active entry. The §18 carryovers landed as backend/app/providers.py (the multi-provider abstraction, unchanged from the prototype) and backend/app/chat.py (an adapter that runs the provider's streaming interface against thread_messages rows, parses <change> blocks, and materializes changes rows per §8.14). The §17 endpoints owned by Slice 2 — the branches/<branch>/* and threads/<thread_id>/* families — live in backend/app/api_branches.py, mounted alongside Slice 1's routes via api.make_router. On the frontend, RFCView.jsx was rebuilt as the §8 three-column surface; Editor.jsx, ChatPanel.jsx, ChangePanel.jsx, PromptBar.jsx, SelectionTooltip.jsx, DiffView.jsx, ModelPicker.jsx, and modelStyles.js were lifted from the prototype and adapted to the canonical threads / thread_messages / changes shape rather than the prototype's global session_id. The §18 carryovers explicitly preserved: SSE streaming with base64-encoded chunks, Tiptap + ProseMirror plugin for the paragraph-margin gutter accent, the prompt-bar selection-quote machinery, the model picker.

The §17 endpoints exercised so far:

Method Path §
GET /api/auth/me §6
GET /api/rfcs §7, §17
GET /api/rfcs/{slug} §17
GET /api/proposals §17
GET /api/proposals/{pr_number} §17
POST /api/rfcs/propose §9.1
POST /api/proposals/{pr_number}/merge §9.3
POST /api/proposals/{pr_number}/decline §9.3
POST /api/proposals/{pr_number}/withdraw §9.3
POST /api/webhooks/gitea §4.1
GET /auth/login / /auth/callback / /auth/logout §18
GET /api/models §18
GET /api/rfcs/{slug}/main §8.1, §8.2, §17
GET /api/rfcs/{slug}/branches/{branch} §8.4, §17
POST /api/rfcs/{slug}/branches/main/promote-to-branch §8.14, §17
POST /api/rfcs/{slug}/branches/{branch}/changes/{id}/accept §8.9, §17
POST /api/rfcs/{slug}/branches/{branch}/changes/{id}/decline §8.9, §17
POST /api/rfcs/{slug}/branches/{branch}/changes/{id}/reask §8.11, §17
POST /api/rfcs/{slug}/branches/{branch}/manual-flush §8.11, §17
POST /api/rfcs/{slug}/branches/{branch}/visibility §11.1, §17
POST /api/rfcs/{slug}/branches/{branch}/grants §6.4, §17
DELETE /api/rfcs/{slug}/branches/{branch}/grants/{login} §6.4
GET /api/rfcs/{slug}/branches/{branch}/threads §8.12, §17
POST /api/rfcs/{slug}/branches/{branch}/threads §8.12, §8.13
GET /api/rfcs/{slug}/branches/{branch}/threads/{id}/messages §8.12
POST /api/rfcs/{slug}/branches/{branch}/threads/{id}/messages §8.12
POST /api/rfcs/{slug}/branches/{branch}/threads/{id}/resolve §8.12
POST /api/rfcs/{slug}/branches/{branch}/threads/{id}/chat §18

Slice 2 ships covered by backend/tests/test_rfc_view_vertical.py — the FakeGitea simulator from Slice 1 grew per-RFC-repo support (PUT contents, POST orgs/{org}/repos, seed_rfc_repo), and a new test file walks the §8 vertical end-to-end: main-view read, promote-to- branch, accept (with and without edit-before-accept), decline, manual flush + system message, flag creation, visibility flip, anonymous read-but-no-contribute, stale-change refusal, and the chat-streaming path with a fake provider injected.

Slice 3 — shipped

The §10 PR flow in full. The bot wrapper grew per-RFC-repo PR operations — open_branch_pr (with the §10.9 Supersedes: trailer hook), merge_branch_pr (no-fast-forward via Gitea's style='merge', the On-behalf-of: trailer carrying the merging user per §6.5), withdraw_branch_pr, cut_resolution_branch, and commit_replay_change for the §10.9 per-accept replay onto fresh main. The §4 cache learned about per-RFC PRs via the existing refresh_rfc_repo sweep, plus a _parse_supersedes pass that bumps an original PR's state to closed and records the supersession the moment the resolution PR's merge arrives — whether via webhook or the reconciler. The §17 endpoints owned by Slice 3 — the branches/<branch>/{pr-draft,open-pr} and the prs/<n>/* family — live in backend/app/api_prs.py, mounted alongside Slices 1 and 2's routes via api.make_router. The migration in 007_pr_flow.sql adds superseded_by_pr_number and merge_commit_sha columns to cached_prs plus the pr_resolution_branches join table that records resolution-branch parentage so the cache can supersede the original on the resolution PR's merge.

On the frontend, the Open PR affordance landed on RFCView.jsx's branch view (gated on the branch having commits ahead of main and no already-open PR), opening a new PRModal.jsx that fetches the AI draft via /pr-draft, lets the contributor edit, and surfaces the §11.3 universal-public confirmation inline when the source branch is private. The PRView.jsx sibling to RFCView.jsx is mounted at /rfc/:slug/pr/:prNumber and renders the §10.3 three-column shape: catalog left (App chrome), a unified/split diff in the center computed from main and branch RFC.md bodies, and a compressed conversation surface on the right that interleaves chat / flag / review threads with visual distinction per §10.4. The per-user seen-cursor advances on every visit; new commits and new messages since the cursor surface with an accent. The merge button is arbiter-gated per §6.3; withdraw is contributor-or-arbiter per §10.8; the §10.9 Start resolution branch affordance fires from the conflict banner when the live Gitea pull reports the PR as unmergeable, and the new resolution branch opens in the §8 editor for the contributor to re-anchor stale changes before opening the resolution PR.

The §17 endpoints exercised in Slice 3:

Method Path §
POST /api/rfcs/{slug}/branches/{branch}/pr-draft §10.2
POST /api/rfcs/{slug}/branches/{branch}/open-pr §10.1
GET /api/rfcs/{slug}/prs/{n} §10.3
POST /api/rfcs/{slug}/prs/{n}/seen §10.3
POST /api/rfcs/{slug}/prs/{n}/review §10.4
POST /api/rfcs/{slug}/prs/{n}/merge §10.5
POST /api/rfcs/{slug}/prs/{n}/withdraw §10.8
POST /api/rfcs/{slug}/prs/{n}/description §10.2
POST /api/rfcs/{slug}/prs/{n}/resolution-branch §10.9

Slice 3 ships covered by backend/tests/test_pr_flow_vertical.py — nine integration tests against an extended FakeGitea that grew PR mergeability via base-snapshot tracking, no-fast-forward merge behavior, and a mergeable field on PR responses. The tests cover opening (with the §11.3 visibility flip and the §10.9 one-PR-per- branch refusal), the AI draft, the three-column payload shape, seen-cursor advance with stale-tab protection, review-thread posting, arbiter-only merge, contributor withdraw with the withdrawn state distinct from generic closed, anonymous read of a public PR, and the full §10.9 conflict-replay path including the auto-close of the original PR on the resolution PR's merge.

Slice 5 — shipped

Graduation per §13 in full. The §13.3 five-step transactional sequence flips a super-draft to active: create the per-RFC repo, seed RFC.md / README.md / .rfc/metadata.yaml, open a meta-repo PR that strips the entry's body and fills the graduation frontmatter (state: active, id: RFC-NNNN, repo, graduated_at, graduated_by), auto-merge that PR with the admin as merge actor, refresh the cache so the catalog row and the new RFC view reflect active immediately. Each step goes through a new bot primitive — create_rfc_repo_for_graduation, seed_graduated_rfc, open_graduation_pr, merge_graduation_pr — that records its own row in actions, bracketed by graduate_start and graduate_complete for the linkable sequence the §13.3 audit shape calls for. The orchestrator in backend/app/api_graduation.py runs the sequence as an asyncio task fed by an in-memory queue; the §17 SSE endpoint subscribes to that queue and emits one event per step transition, plus the trailing rollback step's events if any earlier step fails.

Rollback is per-step and runs in reverse. Each forward step has a paired undo registered in _UNDO_BY_STEP: create_repo → delete the repo, seed_files → folded into the repo deletion (the seed commits live inside the same repo), open_pr → close the graduation PR. There is no merge_pr undo by design — once the meta-repo merge has landed, graduation is irreversible per §13.5; the path forward is withdraw via §3. The rollback also records graduate_rollback in actions with the failed-at step name, the error, and the list of undone steps, so the failure surface in the dialog and the actions log carry the same record.

The §9.8 precondition gate — open body-edit PRs against rfcs/<slug>.md would attempt to re-introduce a body to a frontmatter-only entry after step 3 — is enforced before the bot starts the sequence, so the §13.3 rollback complexity does not grow. The check runs both client-side as the dialog probes GET /api/rfcs/<slug>/blocking-prs and server-side at the top of POST .../graduate as an atomic re-check.

§13.4 chat migration is a database semantic no-op. The whole-doc main thread on the super-draft (rfc_slug=<slug>, branch_name='main') is the same row interpreted as the super-draft's canonical-body thread before graduation and as the new RFC's main thread after — the slug is the canonical key per §2.3, the branch_name 'main' now points at the per-RFC repo's main, no data movement is needed. Range and paragraph sub-threads on the canonical-body view migrate the same way per §9.8. Edit-branch chats stay attached to their original branch_name on the meta repo per §9.8's "no data movement" framing; the §9.8 pre-graduation history affordance on the new RFC view surfaces them as a distinct disclosure in the breadcrumb dropdown.

The §13.1 claim flow landed alongside graduation since claiming is the prerequisite for non-admin graduation. The bot grew open_claim_pr; the existing api_prs merge endpoint broadened to accept pr_kind='meta_claim' so the merge surface inherits structurally from §10. Until §13.1's claim runs, the dialog refuses the start when owners=[] and the popover surfaces "Claim ownership yourself" as a remediation affordance — admins are contributors per §6.1 and can claim solo if they intend to graduate without further ceremony.

The five §17 routes Slice 5 added:

Method Path §
POST /api/rfcs/{slug}/claim §13.1
GET /api/rfcs/{slug}/blocking-prs §13.2
GET /api/rfcs/{slug}/graduate/check §13.2
POST /api/rfcs/{slug}/graduate §13.3
GET /api/rfcs/{slug}/graduate/progress §13.3

On the frontend, RFCView.jsx's breadcrumb actions grew a Graduate to RFC repo button (admins/owners and entry owners) and a Claim ownership button (signed-in non-owners). GraduateDialog.jsx owns the three-field surface with debounced /check polling, the precondition popover backed by /blocking-prs, and the live step stack fed by an EventSource on the progress SSE. The BranchDropdown gains a Pre-graduation history (N) disclosure that surfaces edit-branch threads on the new RFC view per §9.8.

Slice 5 ships covered by backend/tests/test_graduation_vertical.py — ten integration tests against the FakeGitea (extended with DELETE /repos/{owner}/{repo} for the rollback inverse). The tests cover the dialog validator's per-field checks, the no-owners refusal, the §9.8 open-body-edit-PR precondition refusing the start, the §13.3 happy path end-to-end (with audit-log verification), mid-sequence rollback at step 2 (seed) and step 3 (PR open), the concurrent-graduation refusal, §13.4's chat-row-survives-without- data-movement contract, the §9.8 pre-graduation history surface, and the §13.1 claim PR cycle. The full Slices 15 test suite is 45/45 green.

The orchestrator's ?_sync=1 test seam on POST .../graduate awaits the sequence inline so integration tests can assert post-conditions without driving the SSE. Production clients use the spec-described shape — POST returns immediately and the client subscribes to the progress SSE.

Slice 4 — shipped

Super-draft body editing per §9.5 + §9.6 + §9.7. The §17 routing-collapse rule landed in backend/app/api_branches.py and backend/app/api_prs.py — every branches/<branch>/... and prs/<n>/... route now dispatches on the entry's state to pick the right Gitea repo, and the body extracted from the entry's frontmatter envelope is what the editor and the diff see. The bot wrapper grew open_metadata_pr; the rest of the bot's methods already accepted owner/repo arguments and worked against the meta repo without change. The §4 cache learned about meta-repo edit branches via a new refresh_meta_branches pass that mirrors edit-<slug>-<6hex> branches into cached_branches and synthesizes a per-slug main row so the §10.1 has-commits-ahead check works uniformly across active and super-draft surfaces. The §5 schema needed no migration — the super-draft scoping note already settled that the existing tables carry both cases.

The two §17 routes Slice 4 added:

Method Path §
POST /api/rfcs/{slug}/start-edit-branch §9.5
POST /api/rfcs/{slug}/metadata §9.5

Everything else from the §8 vertical (chat, accept, decline, manual flush, threads, flags, visibility, grants, the SSE chat stream) and the §10 PR flow (open, draft, review, merge, withdraw, conflict-replay) reaches super-drafts through the same routes Slices 2 and 3 shipped — no per-state forks at the API surface.

The branch-naming choice: §9.5 names the structural shape edit/<slug>/<auto-name>, but FastAPI's default {branch} path matcher refuses slashes (the §19.2 path-routing candidate). Slice 4 picked edit-<slug>-<6hex> — same dash-separated shape Slice 2 used for <login>-draft-<6hex>. Metadata-pane PRs use the parallel metadata-<slug>-<6hex> form. The cache parsers in app/cache.py recognize both the dashed and slashed prefixes so a future routing-fix slice can flip back without a data migration.

On the frontend, RFCView.jsx's super-draft placeholder was replaced by the full editor surface — same component, dispatched on entry.state. The BranchDropdown renders canonical body as the first position when the entry is a super-draft, per §9.4. A new MetadataPaneModal opens from the breadcrumb actions when the viewer holds super-draft edit authority per §9.5 (until §13.1's claim runs, that's app admins/owners only).

Slice 4 ships covered by backend/tests/test_super_draft_vertical.py — ten integration tests against the FakeGitea, covering main-view read, start-edit-branch, body extraction from the envelope on read, accept preserving the frontmatter on write, manual flush through the envelope, the body-edit PR's pr_kind='meta_body_edit' shape, the full cut-accept-open-merge loop with the §9.5 unclaimed-merge gate (admin/owner only), the metadata pane PR cycle, the canonical-body branch (main for super-drafts) being read-only, and the metadata pane permission gate.

What's deferred from Slice 2

These were in the §8 spec but lean on infrastructure later slices build, so they were scoped out of this slice without altering the spec:

  • Super-draft body editing on the meta repo (§9.5). The branches/<branch> machinery is structurally general enough that meta-repo edit branches fall out of it once Slice 4 wires the super-draft view's "Start Contributing" gesture to cut against the meta repo. The Slice 2 RFCView renders a placeholder for super-draft entries pointing at Slice 4.
  • The §10.4 review threads on PRs. thread_kind='review' is in the schema and the threads endpoints honor it generically, but the PR-page surface where review threads anchor to diff hunks lands with Slice 3.
  • DiffView's full reconstruction from changes history. Slice 2 renders the editor's current HTML (which carries the session-local tracked-change markup from the accepts that happened in this session) into DiffView; rebuilding the full accepted-change markup from changes for a returning contributor needs a render pipeline DiffView doesn't yet own. The current behavior matches §8.10's "session-local" framing exactly; the §19.2 "persistent accepted-change markup" topic is the durable extension when evidence demands it.
  • The §10.6 PR-side commit / chat reconciliation. Manual-edit flushes drop a system-author message into branch chat per §10.6 in Slice 2, but the PR-side seen-cursor that uses the marker ships with Slice 3.
  • Branch-name path conversion for slashes. The auto-generated branch name in Slice 2 is <login>-draft-<hex> (no slash) so the FastAPI {branch} path segment matches without {branch:path}. Users can still rename to a slashed name, but the routes will 404 on read; the proper fix is {branch:path} everywhere, which lands cleanly when Slice 3 makes the same change to the PR routes (PR numbers don't have this problem, but resolving the routing shape once across both surfaces is the right hop).

Environment notes

  • Python 3.13. Earlier 3.11+ should also work; 3.13 is what the build session ran on.
  • Node 20+ for the frontend.
  • Local Gitea on port 3000. Anything that exposes the Gitea v1 REST API works. If you tunnel Gitea elsewhere (e.g. a container, a Codespace), re-run scripts/seed_meta_repo.py so the webhook re-registers against the right APP_URL.

Conventions

  • Bot writes only via app/bot.py. If a module wants to call app/gitea.py's write methods directly, the spec is right and the module is wrong — the wrapper is the chokepoint that makes the §6.5 On-behalf-of: trailer and the §6 authorization both consistent.
  • Cache writes only from app/cache.py. User actions trigger Git operations via the bot; the cache learns about them when the webhook arrives (or the next reconciler sweep), and never before. This invariant is what makes §4's "Git is truth" claim hold operationally.
  • Spec corrections during the build are rare and surgical. When running code reveals the spec was wrong at a structural level (per §19.3's working agreement), the correction lands in the appropriate numbered section with a brief note explaining what running code revealed. Spec extensions during the build are not in scope — they accumulate in §19.2.
  • §16 stays deferred. Body full-text search, per-RFC model picker, funder role, persistent accepted-change markup, slug renames — these are not shipped in any slice. They earn their own topic sessions when use surfaces evidence they matter.

Next slice

Slice 6: notifications per §15.

Every other vertical now produces signals: propose, claim, merge, graduate, body edits, manual flushes, PR open/withdraw/merge, review threads, conflict-replay, super-draft chat. Slice 6 builds the inbox, the fan-out, the digest, and the email loop that turn those signals into a contributor's surface. The §5 schema already carries the notifications, watches, branch_chat_seen, notification_user_mutes, and notification_digests tables; Topic 13's session settled the producer-side rules per §15.1 (the signal-surface stack), the §15.2 inbox grouping, §15.3 badges and toasts, §15.4 email categories, §15.5 digest cadence, §15.6 watch/subscription, §15.7 unread mechanism, §15.8 do-not-disturb, and §15.9 attribution.

Slices 15 left this clean: every user gesture goes through the bot wrapper and lands an actions row with the underlying actor. The producer-side hook is "after a write succeeds, evaluate watches and fan-out notification rows." The consumer-side hook is the header badge, the inbox panel, the toast surface, and the per-row read-state machinery.

What Slice 6 owns specifically:

  • The producer fan-out. Every actions row whose event maps to a §15 signal produces zero-or-more notifications rows by joining against watches and applying the §15.1 priority rules. The fan-out lives as a small module that the bot wrapper invokes inline after each write — same chokepoint shape Slice 1's _log uses.
  • The §15.2 inbox. GET /api/notifications with the unread / rfc_slug / category / bundled filter chips, POST /api/notifications/<id>/read for per-row marking, POST /api/notifications/read for the bulk filter mark, and the SSE GET /api/notifications/stream that backs the live badge.
  • The §15.3 surface. The header badge counter (live via the SSE), the toast on personal-direct events while the user is active, and the ambient signal — a colored dot per row on the §7 catalog pointing at watched RFCs with unseen activity.
  • The §15.4 email loop. Per-category opt-in/out preferences on the users table (already in the schema), the /api/users/me/notification-preferences endpoints, the email-send adapter that routes a notification's category through the user's category toggle, and the POST /api/webhooks/email-bounce receiver that sets the global opt-out. Plus the GET /api/email/unsubscribe signed-URL one-click flow.
  • The §15.5 digest. A scheduled-job that runs daily and weekly to roll up unseen notifications into a single email, with the notification_digests table tracking what was included so the next digest skips what already shipped.
  • The §15.6 watch model. Auto-watch on first interaction with an RFC, the per-row state column (watching / following / muted), the 90-day auto-decay for unset rows, and the explicit POST /api/rfcs/<slug>/watch overrides.
  • The §15.7 unread mechanism. Advance the branch_chat_seen cursor on every branch read, reconcile inbox notifications to read when their underlying surface is consumed.
  • The §15.8 do-not-disturb. Quiet-hours config on the user, the per-user notification mute list, the orthogonality vs §6.2's app-wide write-mute.

What Slice 6 does NOT own:

  • The §14 chrome polish (still Slice 7).
  • The §12 30/90 branch-hygiene timers (still Slice 8).
  • The §16 deferred items.

The carryovers Slice 6 inherits — the existing actions audit log (every signal traces back to a row there per §15.9), the SSE machinery from Slices 2 and 5 (chat-stream and graduate-progress respectively), and the §5 schema's notification tables (already in place from Topic 13).

The §15 surface depends on the producers being in place; with Slice 5 landing the last structural producer (graduation events, specifically graduate_complete as a personal-direct event for the proposer per §15.4), every signal a contributor needs to see is now in the audit log waiting to be fanned out.

The next build session should read SPEC.md, README.md, docs/DEV.md, and SPEC.md's §19.1 and pick up Slice 6 cleanly without re-briefing. The working agreement in §19.3 continues to apply: implement the slice, correct the spec only where running code reveals it was wrong at a structural level, accumulate new candidate topics in §19.2, do not extend the spec beyond what the slice requires.