Task state plane split — semantic corpus, execution substrate
Status: open/accepted
Task frontmatter keeps only semantic state — what the work item is and how it ended. Execution state — who has it, what phase, what was verified for this run — becomes authoritative in the lease substrate. Four semantic transitions remain as commits on main; everything else is a lease write.
Summary
Section titled “Summary”- Task conflates two roles: the semantic work item (corpus) and
the execution record (runtime). The schema already declares the
doctrine —
status“is a workflow cache — the lease ref atrefs/sdlc/tasks/<id>is the authoritative claim” — but nothing enforces it: every run lands ~4 cache commits on main. - This decision finishes the split by storage plane, not by minting a second entity type. Frontmatter holds semantic state; the lease payload holds execution state.
- Four semantic transitions stay as commits on main: creation,
definition movement within
planning/*, promotion toopen/ready, and closure. After promotion, a task’s file changes on main exactly once more — at closure. readiness_verified_atis written once, ON the promotion commit — promotion IS the readiness claim. Per-run re-verification writes agatesobject in the lease payload and never commits to main.in-progress[/blocked]become lease phases only. The enum values stay tolerated-but-never-written through a deprecation window (schema v6 deprecates, v7 removes).- Migration is staged as seven tasks, expand → cutover → contract, each leaving the live orchestrator operable.
Decision
Section titled “Decision”The plane rule
Section titled “The plane rule”Corpus frontmatter holds facts about the work item that outlive any run. The lease substrate holds facts about the current run. Three tests separate the families; they agree on every field:
- Durability.
closed/doneis true forever.in-progressis only true while someone holds the work. - Crash behavior. If the executing agent dies,
closed/doneis still correct;in-progressbecomes a lie a human must notice and revert. A field that goes stale on crash is runtime state wearing corpus clothes. - Re-derivability. A gate stamp is recomputable by re-running
the gate. A
completion_noterecords external events nothing can re-derive. Caches belong to the substrate; unrecoverable history belongs to the corpus.
The four semantic transitions
Section titled “The four semantic transitions”The only status changes committed to main:
- Creation — the file appears at
planning/draft(orplanning/proposed). - Definition movement within
planning/*— draft → proposed → backlog, and the downshift toplanning/needs-definition+definition_gap. A readiness fail is semantic: it changes what work the document needs. - Promotion —
planning/*→open/ready. One commit carries both the status flip andreadiness_verified_at: promotion is the readiness claim. - Closure — →
closed/<reason>+completion_note+ the finalprs[]record.
Transitions 1–2 ride along with body edits. Transitions 3–4 are the only pure state-change commits in a task’s life. Today’s per-run traffic (verify stamp, start commit, record-PR commit, close commit) collapses to promotion and closure.
Field disposition
Section titled “Field disposition”| Field | Post-split home |
|---|---|
status majors (planning/*, open/ready, closed/*) | Frontmatter — semantic. |
in-progress[/blocked] minors | Lease phase (working / blocked). Never written to frontmatter again; frontmatter stays open/ready through the run. |
readiness_verified_at | Frontmatter, written once on the promotion commit. Per-run re-verification updates lease gates only. |
touchpoints_verified_at | Same as above. It has no TS writer today; lease gates is its first real execution-side home. |
prs | Frontmatter, closure-only historical record (close-commit’s idempotent append — the Obsidian affordance survives at closure). In-flight binding is lease pr_number. |
relevance_note | Frontmatter — a planning-plane triage artifact. Run-time relevance notes go to lease notes. |
definition_gap | Frontmatter — part of the needs-definition downshift, which stays semantic. |
last_reviewed | Frontmatter — review recency. The start commit’s last_reviewed → today write dies with the start commit. |
The two planes answer different questions, so neither shadows the
other: the frontmatter stamp answers “was this ready when
promoted” (dispatch reads this); lease gates answers “was this
verified for THIS run” (resume and the dashboard read this).
Enum policy
Section titled “Enum policy”in-progress and in-progress/blocked stay in the status enum
as tolerated-but-never-written values through a deprecation
window: schema v6 marks them deprecated in the doc-comment; v7
removes them once a corpus sweep confirms zero occurrences in
active tasks. Hard removal in v6 would make mid-flight task files
schema-invalid under the live orchestrator — every
validate-before-commit gate would refuse to commit on tasks
currently in flight.
Post-mortem stub
Section titled “Post-mortem stub”The ## Post-mortem body stub moves from the start commit to
close-out: close-commit appends it when absent (idempotent).
Interim run narrative lives in the lease’s handoff.md. The
start transition becomes write-free on main.
Context
Section titled “Context”The mirror machinery this decision retires is the highest-traffic write path in the system:
ensure_ready_mutate.tscommits the verify stamp to main (docs(tasks): verify <b> implementation-ready).start_task.tscommitsstatus: in-progress+last_reviewed+ the post-mortem stub to main, then transitions the leaseclaimed → working— the same fact written twice.append_pr_url.tscommits the PR URL toprs[]on the shared main checkout (the one writer not isolated in an ephemeral worktree), then transitions the lease toawaiting-review— again twice.close-commitwrites the genuinely semantic closure.
Meanwhile three surfaces classify “in flight” with three
different rules: task inflight (frontmatter + open PR),
probe-state (frontmatter + filesystem + open PR + a commit-
subject literal), and the dashboard (frontmatter + lease — the
only lease-aware reader). The lease payload already carries the
full execution state machine (claimed, working, blocked,
awaiting-review, responding, closing) and the PR binding
(pr_number). The split makes the substrate authoritative
instead of mirrored.
Consequences
Section titled “Consequences”- Task-state commit traffic on main collapses from ~4 per run to
the two semantic transitions. The
commitToMainViaWorktreestart/verify/record-PR paths andappend_pr_url.tsare retired; the shared-checkout write anomaly disappears with it. - The lease payload gains a
gatesobject (readiness_verified_at,touchpoints_verified_at). The payload schema is strict, so this is a version event: bump the payload version and fence old clients via the control plane’s minimum-client version rather than letting them fail on parse. probe-state,task inflight, andtask nextbecome lease-aware readers of the local ref mirror. Resume detection moves off themain_head_is_verify_stampcommit-subject literal (fragile — any later main commit broke it) to lease-phase inspection.task nextexcludes candidates with an active lease ref in the local mirror — network-free; a stale mirror degrades safely (under-dispatch self-heals on the next fetch; a stale-missing ref is caught bycas_createfailing at claim, which remains the hard backstop against double dispatch).- Ensure-ready’s fail carve-out becomes lease-aware: preserve
status when the task is
in-progress*OR holds an active lease. Post-split a leased task readsopen/ready, and the old carve-out would downshift live work. - Obsidian / static-viewer visibility of in-flight state is lost,
accepted: the dashboard (lease-mirror sync) is the run-state
surface, and
prs[]still lands at closure. A site-projection lease join is a non-blocking follow-up. - The
task-state-originlint narrows: its state-field set shrinks to the surviving semantic fields. lease task sweep’s frontmatter read ofclosed/*survives unchanged — closure stays a semantic commit.
Migration
Section titled “Migration”Seven stages, expand → cutover → contract, scaffolded as tasks
with depends_on enforcing order. Each stage leaves the system
operating under the live orchestrator.
- Lease payload
gates+ version handling (expand). No writer populatesgatesyet; old payloads parse under new code. probe-state+inflightgo lease-aware (dual-mode). Resume gate accepts lease-phase OR the legacy verify-stamp during the window. Pre-cutover, lease and frontmatter agree, so classifications are unchanged.task nextlease exclusion. Redundant pre-cutover (a leased task is alsoin-progress); the double-dispatch guard post-cutover. The orchestrate tick’s sync step gains therefs/sdlc/*refspec so the mirror is tick-fresh.- Ensure-ready plane rework. Unleased pass on
planning/*produces the single promotion commit; leased runs write leasegatesonly; the fail carve-out and the standalone--commitpath become lease-aware. Must land BEFORE stage 5 — otherwise a standalone fail against a leased task downshifts live work. - Cutover. Delete the start commit and
append_pr_url.ts; the PR is recorded via the lease transition’spr_numberonly; close-commit gains the post-mortem stub append. From this stage on,in-progressis never written. - Dashboard. Dual stale rule —
(in-progress ∧ no lease) ∨ (active lease ∧ expired ∧ phase ∉ {awaiting-review, blocked})— correct on both sides of the cutover; surface leasegates. - Contract. Schema v6 (deprecations), the v5→v6 migrate
transform (
in-progress+ active lease ⇒open/ready; no lease ⇒ flag the ambiguous tail), lint narrowing, retire the legacy resume arm and thestart/record-prcommit templates. Gated on an operational drain: no pre-cutover runs still open.
A run dispatched under the old prose mid-cutover still writes
in-progress harmlessly — readers keep the frontmatter fallback
until stage 7 sweeps it.
Open questions
Section titled “Open questions”- Re-verify of an already-
open/ready, unleased task: no-op, or a flag-gated stamp-refresh commit? Lean no-op. - v7 timing for hard enum removal — after the stage-7 sweep confirms zero active occurrences, but no consumer forces a date.
- Site-projection lease join (in-flight visibility for the static viewer) — spawned as a backlog-status follow-up task, not part of the seven stages.
References
Section titled “References”apps/sdlc/lib/model/entities/task/schema.ts— the “workflow cache” doctrine onstatus; the stamp-at-bottom convention.apps/sdlc/lib/services/lease/schemas.ts— the strict lease payload (phase enum,pr_number) thatgatesextends.apps/sdlc/skills/task-ensure-ready/ensure_ready_mutate.ts,apps/sdlc/skills/task-work/start_task.ts,apps/sdlc/skills/task-work/append_pr_url.ts,apps/sdlc/lib/model/entities/task/ops/close-commit.ts— the four mirror writers today.apps/sdlc/lib/model/entities/task/ops/probe-state.ts,ops/inflight.ts,ops/next.ts— the readers that become lease-aware.apps/sdlc/lib/model/entities/task/ops/lint-state-origin.ts— the state-field set that narrows at stage 7.- D-VSLI-distributed-work-runner-architecture — execution records are engine event-log territory, not entities; this decision is the interim step that stops mirroring them into the corpus.
- T-SIHV-task-state-frontmatter-commits-on-main-not-worktree-branch — the convention this decision supersedes in part (task-state commits land on main; after the split, only semantic ones do).