T-1VF3-lease-token-rotation-full-policy
Status: closed/superseded · Impact: medium · Complexity: medium
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to open/ready before picking up.
Slice 1’s CLI implements a deliberately approximate token-rotation
rule (“transitions to claimed or working rotate; other phases
preserve”) that handles single-host operation but not the ADR’s full
owner-rotation matrix — in particular, explicit rotation on
cross-host successor takeover. Slice 2’s /sdlc:task-work
integration will exercise the cross-host path (different runner =
different owner), so the approximation needs to be replaced with the
full matrix before slice 2 lands. Cite
T-QC31-add-sdlc-lease-cli-commands’s post-mortem for the
slice-1 limitation note.
| Location | Role today |
|---|---|
plugin/scripts/lease_cli/transition.py (best guess) | Implements the directional rule: rotate token on transition to claimed or working, preserve otherwise. README documents this as a slice-1 limitation. |
plugin/lib/lease/primitives.py (best guess) | The library’s cas_replace primitive accepts a new token but doesn’t itself enforce a rotation policy — the policy lives in the caller (the CLI today). |
docs/planning/decisions/github-ref-leases/protocol.md | The ADR’s authoritative owner-rotation matrix lives here (Token rotation section). |
Proposed
Section titled “Proposed”Move the rotation policy out of the CLI and into the library so all
callers (CLI today, /sdlc:task-work tomorrow, headless workers
later) get the same behavior:
- A
should_rotate_token(old_payload, new_payload) -> boolhelper in the lease library that consults the ADR’s matrix on (old.phase, new.phase, old.owner, new.owner). - Cover the full matrix: claim/working rotations as today, plus cross-host successor takeover (different owner.kind or owner.id = rotate), plus explicit “release-and-reclaim” sequences if the ADR calls them out.
- The CLI’s transition handler stops embedding the rule and just calls the helper.
The matrix details come from the ADR section — implementer reads it during planning and translates each row into a test case.
Approach
Section titled “Approach”- Re-read the ADR’s Token rotation section (or equivalent) and enumerate every row of the matrix as a (old.phase, new.phase, old.owner, new.owner) → rotate? tuple.
- Implement
should_rotate_token(...)in the library; one parametrized test per matrix row. - Replace the CLI’s inline rotation rule with a call to the helper.
- Update the CLI README to describe the rule as “consults the
library’s
should_rotate_tokenhelper” and drop the slice-1 limitation note. - Re-run the existing CLI rotation tests
(
test_task_transition_rotates_token_to_working,…_keeps_token_on_awaiting_review) — they should still pass unchanged because the matrix’s single-host rows match the directional rule.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/lease/primitives.py | modify | Add should_rotate_token(old, new) -> bool implementing the ADR’s full matrix. |
plugin/lib/lease/tests/test_token_rotation.py | new | Parametrized test, one case per matrix row. |
plugin/scripts/lease_cli/transition.py | modify | Replace inline rotation rule with a call to the library helper. |
plugin/scripts/lease_cli/README.md | modify | Drop the “slice-1 limitation” note from the token-rotation section. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
should_rotate_tokenis callable from the lease library and its return value matches the ADR’s matrix for every row. - AC-2: A cross-host successor takeover (transition where
new.owner.id != old.owner.idandnew.owner.kindmatches a successor scenario from the ADR) rotates the token, asserted by a parametrized test. - AC-3: All existing CLI rotation tests (the single-host claim / working / awaiting-review cases from slice 1) continue to pass without modification.
- AC-4:
grep -n "rotate" plugin/scripts/lease_cli/transition.pyshows zero policy logic — the CLI delegates entirely to the library helper. - AC-5: The lease-CLI README no longer describes the rotation rule as a slice-1 limitation.
Out of scope
Section titled “Out of scope”- Wiring the helper into
/sdlc:task-work’s phase transitions. That’s slice 2. - Heartbeat / release token semantics. This task is only about transition-driven rotation.
Dependencies
Section titled “Dependencies”- T-S0PK-add-lease-protocol-library-and-schemas — the library this task extends.
- T-QC31-add-sdlc-lease-cli-commands — the CLI this task refactors.
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of T-QC31-add-sdlc-lease-cli-commands on 2026-05-23.
Dedup search (spawn-from-post-mortem)
Section titled “Dedup search (spawn-from-post-mortem)”Bullet: Token-rotation policy is approximate. Implemented the directional rule ‘transitions to claimed or working rotate; other phases preserve’ which matches AC-3’s stated intent. The ADR’s full owner-rotation matrix is richer (e.g., explicit rotation on cross-host successor takeover). The CLI’s rule is correct for single-host operation and gets the slice-1 cases right; slice 2’s task-work integration will need the fuller policy. Documented as a slice-1 limitation in the README. Keywords searched: token-rotation, owner-rotation, approximate, implemented, directional, transitions, single-host, integration Excluded: 2026-05-23-add-sdlc-lease-cli-commands Top candidates (score / status / headline):
- 6 / closed/done / 2026-05-19-add-epic-entity-task-depends-on-dependencies — Add epic entity + task depends_on dependencies
- 6 / closed/done / 2026-05-19-implement-entities-migrate — Implement /sdlc:entities-migrate to apply mechanical schema-drift fixes
- 4 / in-progress / 2026-05-20-orchestrator-tick-race-fixture — Integration fixture for concurrent orchestrator tick race on task-close-out
- 2 / closed/done / 2026-05-19-add-update-skill-doc-skill — Add /update-skill-doc skill that refreshes a skill’s markdown doc from its SKILL.md
- 2 / closed/done / 2026-05-19-extract-task-close-out-skill — Extract task-work Step 11 into /sdlc:task-close-out skill Decision: SPAWNED