Migrated from williacj/cowrite#937 (opened 2026-07-31).
Moved 2026-08-03 as harness-product work under epic williacj/cowrite#173 (Rigger extraction).
Recreated rather than transferred: GitHub forbids issue transfer from a private repo to a public one.
Labels not carried (consumer-taste vocabulary per setup-labels.mjs HX-7 / spec §7.2.1, not engine contract): type:feature, area:harness, conflict:harness-engine-tests, conflict:harness-run-loop, conflict:review-marker-contract, conflict:harness-provider-rotation.
Parent epic in cowrite was #650 — relink to its Rigger counterpart.
Goal
Complete #650 by driving every Reviewer and re-review through the latched opposite provider, binding both providers into the exact-SHA review proof, enforcing that proof in the engine and server-side merge gate, and cutting Cowrite over only after the full path is fail-closed.
Decision trace
- Parent epic: #650
- Ratified mechanism: DH-35 amendment,
docs/proposals/cross-provider-card-rotation.md
- Builds on #935 (ROT-1)'s board/config contract and #936 (ROT-2)'s lease-bound provider-pair latch.
- Strengthens DH-8's coder≠reviewer and exact-SHA gates; it does not replace or loosen either.
- Extends DH-28's server-side merge enforcement.
- Requires the coordination-journal reliability fix in #881 / PR #904.
Scope
harness/engine.mjs
- Reviewer/spike-reviewer dispatch and publication prompts
.claude/hooks/review-marker.cjs
.claude/hooks/review-gate.cjs
- Focused engine and hook tests
harness/board-config.json for the final atomic cutover
docs/test-matrix.md
Acceptance
- Every ordinary Reviewer, spike Reviewer, and re-review dispatch for a rotated card uses the journal's latched
reviewerProvider, mechanically opposite engineerProvider.
- The exact PR head recorded in the journal is bound to that provider pair.
- A rotated card's SHA-bound marker carries
engineerProvider and reviewerProvider.
- Both the engine and the server-side
gh pr merge gate reject a rotated marker that:
- omits either provider;
- names equal providers;
- disagrees with the journal's pair; or
- is not bound to the exact current PR head.
- Legacy/non-rotated markers retain their existing schema behavior.
- Opposite providers never substitute for distinct dispatch identity; the existing coder≠reviewer gate remains independently enforced.
- Reviewer/provider failure waits or escalates through DH-18 against the assigned provider; no same-provider fallback can produce merge authority.
- Missing adapters, unreadable journal state, board/latch mismatch, and provenance mismatch all fail closed before merge.
- After the board field exists and the complete test suite is green, Cowrite's committed configuration enables
coordination.enabled: true and provider.rotation: ["claude", "codex"] together. Startup validation succeeds on the resulting live configuration.
- Rollback remains one configuration change: disabling rotation affects only unclaimed cards, while claimed cards finish under their latch.
- TDD: focused Node and hook tests land first, then implementation.
npm run test:harness passes.
Test-matrix row
Add NI-provider-rotation-review-gate covering at least:
- ordinary/spike Reviewer and re-review dispatches use the latched opposite provider;
- rotated marker publication records both distinct providers for the exact SHA;
- engine classification rejects missing, equal, stale-SHA, or pair-mismatched provenance;
- the server-side merge gate independently rejects every malformed rotated proof;
- legacy marker fixtures retain current behavior;
- assigned-provider outage never falls back; and
- the committed Cowrite configuration validates with coordination and the
claude/codex rotation enabled.
Dependencies
- Native
blocked_by: #881 and #936 (ROT-2).
Non-goals
- Do not weaken Critical or recorded-decision escalation.
- Do not infer control state from telemetry.
- Do not add rotation to single-role senior/intake dispatches.
Goal
Complete #650 by driving every Reviewer and re-review through the latched opposite provider, binding both providers into the exact-SHA review proof, enforcing that proof in the engine and server-side merge gate, and cutting Cowrite over only after the full path is fail-closed.
Decision trace
docs/proposals/cross-provider-card-rotation.mdScope
harness/engine.mjs.claude/hooks/review-marker.cjs.claude/hooks/review-gate.cjsharness/board-config.jsonfor the final atomic cutoverdocs/test-matrix.mdAcceptance
reviewerProvider, mechanically oppositeengineerProvider.engineerProviderandreviewerProvider.gh pr mergegate reject a rotated marker that:coordination.enabled: trueandprovider.rotation: ["claude", "codex"]together. Startup validation succeeds on the resulting live configuration.npm run test:harnesspasses.Test-matrix row
Add
NI-provider-rotation-review-gatecovering at least:claude/codexrotation enabled.Dependencies
blocked_by: #881 and #936 (ROT-2).Non-goals