Skip to content

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.

LocationRole today
apps/sdlc/lib/services/lease/schemas.tsTaskLifecycleLeaseSchema — strict payload (phase, owner, pr_number, …); no gates.
apps/sdlc/lib/services/lease/runtime.tsPayload build/parse, CAS helpers (acquireLease, transitionLease, reacquireLease).
apps/sdlc/lib/services/lease/ops/task/transition.tsPhase transitions; already carries --pr-number.
apps/sdlc/lib/services/lease/ops/inspect.tsRenders a payload for humans.
apps/sdlc/lib/services/lease/ops/list.tsEnumerates leases.

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.

  1. Add gates to TaskLifecycleLeaseSchema and bump the payload version constant; v1 payloads parse with gates absent.
  2. Extend the transition op (or add a sibling lease task update-gates) with a CAS gates write.
  3. Render gates in inspect and list output.
  4. Tests: v1 payload parses under v2 code; gates round-trip through a CAS write; strictness still rejects unknown keys.
LocationKindChange
apps/sdlc/lib/services/lease/schemas.tsmodifyAdd optional gates object; version bump.
apps/sdlc/lib/services/lease/runtime.tsmodifyv1 tolerance on parse; gates in payload build.
apps/sdlc/lib/services/lease/ops/task/transition.tsmodifyCAS gates write surface.
apps/sdlc/lib/services/lease/ops/inspect.tsmodifyRender gates.
apps/sdlc/lib/services/lease/ops/list.tsmodifyRender gates.
  • 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 inspect shows 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).
  • 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.
  • none

Captured by /sdlc:task-work on 2026-08-03. PR: pending.

  • 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 through updateLeaseGates.
  • 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/lease 171/171, plus cli/tests/lease_dispatch.test.ts and skills/task-work/tests/lease_integration.test.ts 36/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.
  • The expand-stage framing held: gates is .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.
  • 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) adds readiness_verified_at / touchpoints_verified_at as FLAT .default(null) payload fields plus --readiness-verified-at flags on transition, against this task’s nested gates object plus sibling op. Two decisions claim one migration and they collide on schemas.ts and transition.ts — readiness gating is per-task and saw neither. A cross-task touchpoint-overlap check at pickup (same ## Files to touch rows 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 between D-S30G and D-S30G-task-state-plane-split, not a third task).
  • git stash is repo-global across worktrees, so splitting commits with stash push --keep-index pulled in unrelated files from other sessions’ worktrees and had to be unwound by hand — the task-work commit guidance should mandate path-scoped git 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 carry gates until stage 6 — a sanctioned sdlc verb 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/sdlc runs across parallel sessions cross-talk: the dashboard suite’s dashboard list picked 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

← Back to Tasks