T-5G81-lease-payload-gates
Status: closed/done · Impact: high · Complexity: medium
Stage 1 of the D-S30G-task-state-plane-split migration (pure
expand). The lease payload gains a gates object so per-run
verification stamps can live in the substrate instead of task
frontmatter. No writer populates it yet; this task only makes the
shape exist, versioned and tolerated by every reader.
| Location | Role today |
|---|---|
apps/sdlc/lib/services/lease/schemas.ts | TaskLifecycleLeaseSchema — strict payload (phase, owner, pr_number, …); no gates. |
apps/sdlc/lib/services/lease/runtime.ts | Payload build/parse, CAS helpers (acquireLease, transitionLease, reacquireLease). |
apps/sdlc/lib/services/lease/ops/task/transition.ts | Phase transitions; already carries --pr-number. |
apps/sdlc/lib/services/lease/ops/inspect.ts | Renders a payload for humans. |
apps/sdlc/lib/services/lease/ops/list.ts | Enumerates leases. |
Proposed
Section titled “Proposed”Payload v2: optional gates { readiness_verified_at, touchpoints_verified_at } (nullable timestamps). The schema is
.strict(), so this is a version event: bump the payload version,
tolerate v1 payloads on read (absent gates), and expose a CAS write
surface for gates. inspect / list render gates when present.
Behavior of every existing flow is unchanged.
Approach
Section titled “Approach”- Add
gatestoTaskLifecycleLeaseSchemaand bump the payload version constant; v1 payloads parse with gates absent. - Extend the transition op (or add a sibling
lease task update-gates) with a CAS gates write. - Render gates in
inspectandlistoutput. - Tests: v1 payload parses under v2 code; gates round-trip through a CAS write; strictness still rejects unknown keys.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
apps/sdlc/lib/services/lease/schemas.ts | modify | Add optional gates object; version bump. |
apps/sdlc/lib/services/lease/runtime.ts | modify | v1 tolerance on parse; gates in payload build. |
apps/sdlc/lib/services/lease/ops/task/transition.ts | modify | CAS gates write surface. |
apps/sdlc/lib/services/lease/ops/inspect.ts | modify | Render gates. |
apps/sdlc/lib/services/lease/ops/list.ts | modify | Render gates. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A v1 (gates-less) payload parses under the new schema; a gates-carrying payload round-trips through a CAS write.
- AC-2:
sdlc lease inspectshows gates when present. - AC-3: The five existing lease ops — acquire, transition, heartbeat, reacquire, archive — pass their tests unchanged.
- AC-4: Unknown payload keys are still rejected (strictness preserved).
Out of scope
Section titled “Out of scope”- Writing gates from ensure-ready — that is T-S27R-ensure-ready-plane-rework, which also bumps the control-plane minimum client version as the first gates writer.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”- Scaffolded from D-S30G-task-state-plane-split (migration stage 1 of 7).
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-08-03. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
schemas.test.ts“payload v2 gates” cases cover the v1 (gates-less) parse;tests/gates.test.ts“stamps a gate on a v1 lease and it survives the CAS round-trip” covers the round-trip throughupdateLeaseGates. - AC-2: auto —
tests/gates.test.ts“inspect omits the gate rows for a v1 lease” / “inspect shows both gate rows once gates exist” spawn the real CLI and assert both directions. - AC-3: auto — no existing test was edited (new cases appended only);
bun test apps/sdlc/lib/services/lease171/171, pluscli/tests/lease_dispatch.test.tsandskills/task-work/tests/lease_integration.test.ts36/36. - AC-4: auto — “rejects an unknown gate name” (nested) and “top-level strictness survives the addition”; the pre-existing “rejects extra field (strict)” still passes.
What worked
Section titled “What worked”- The expand-stage framing held:
gatesis.optional()with no.default(), so a v1 payload parses AND re-serializes without gaining the key — the property that keeps the first CAS-REPLACE over an existing lease from silently rewriting it as v2. - The baseline-gated quality run did its job: the only finding it surfaced was cross-session noise, and the 139 pre-existing findings stayed out of the way.
Friction and automation gaps
Section titled “Friction and automation gaps”- A conflicting in-flight task specifies the same two stamps in a
contradictory shape — T-HTN8-lease-gate-memo-fields (from
D-S30G,open/ready,autonomy: autonomous/pr) addsreadiness_verified_at/touchpoints_verified_atas FLAT.default(null)payload fields plus--readiness-verified-atflags ontransition, against this task’s nestedgatesobject plus sibling op. Two decisions claim one migration and they collide onschemas.tsandtransition.ts— readiness gating is per-task and saw neither. A cross-task touchpoint-overlap check at pickup (same## Files to touchrows on another open/in-progress task) would have caught it before implementation. → T-HTN8-lease-gate-memo-fields (linked; the collision needs a human ruling betweenD-S30Gand D-S30G-task-state-plane-split, not a third task). git stashis repo-global across worktrees, so splitting commits withstash push --keep-indexpulled in unrelated files from other sessions’ worktrees and had to be unwound by hand — the task-work commit guidance should mandate path-scopedgit commit -F <msg> -- <paths>alongside its existing “never stage broadly” rule. → T-ZVR9-path-scoped-commits-over-stash- Splitting into per-AC commits left an intermediate commit that did not typecheck (a barrel re-export landed a commit before its definition); caught by inspection, not by tooling — a per-commit typecheck over the branch before push would catch it mechanically. → T-KDJ6-per-commit-typecheck-before-push
- The desktop parity fixture
(
apps/sdlc/desktop/scripts/fixtures/parity/task-lifecycle-lease.schema.json) is a hand-maintained JSON projection of the zod schema, and regenerating it here would FAIL the gate since the Rust read model does not carrygatesuntil stage 6 — a sanctionedsdlcverb that dumps a service schema as JSON would retire the hand-copy. → T-5WOP-parity-fixture-derived-from-zod (already in flight from T-HTN8-lease-gate-memo-fields’s post-mortem, PR #1153 — no new task spawned). - Parallel task-work sessions collide in the shared session
scratchpad: two runs both wrote
gate.log, and the other session’s truncation destroyed this run’s quality-gate output after it had been written — the skill should namespace scratch filenames by task basename. → T-ZKTV-namespace-task-work-scratch-files - Concurrent
bun test apps/sdlcruns across parallel sessions cross-talk: the dashboard suite’sdashboard listpicked up the other session’s live dashboard and reported it as new drift (an un-normalized PID in the table row). The dashboard test needs isolation from concurrently-running sibling suites, or its row needs the same<PID>normalization the surrounding lines get. → T-B75W-dashboard-test-isolates-live-dashboards
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-ZVR9-path-scoped-commits-over-stash
(https://github.com/sksizer/dev/pull/1156) [open/ready] — task-work
commit guidance mandates path-scoped commits over repo-global
git stash; spawned. - T-KDJ6-per-commit-typecheck-before-push (https://github.com/sksizer/dev/pull/1159) [open/ready] — task-work typechecks every branch commit before push; spawned.
- T-ZKTV-namespace-task-work-scratch-files (https://github.com/sksizer/dev/pull/1154) [open/ready] — task-work namespaces scratch filenames by task basename; spawned.
- T-B75W-dashboard-test-isolates-live-dashboards (https://github.com/sksizer/dev/pull/1158) [open/ready] — dashboard suite isolates from concurrently-running dashboards; spawned.
- T-HTN8-lease-gate-memo-fields [in-progress] — the contradictory
gate-stamp spec; linked, not spawned. Resolving it is a human ruling
between
D-S30Gand D-S30G-task-state-plane-split. The bullet’s secondary automation ask — a cross-task touchpoint-overlap check at pickup — was deliberately not spawned; raise it separately if wanted. - T-5WOP-parity-fixture-derived-from-zod [open/ready] — the parity fixture derived from the zod schema; already in flight on PR #1153 from T-HTN8-lease-gate-memo-fields’s post-mortem, so this run spawned nothing for that bullet.