Skip to content

Gates

Deterministic gate matrix for SDLC stage transitions.

LLM agents optimistically mark tasks complete. “I’ve implemented the feature” is a natural language claim, not proof. In a system where stage transitions trigger deploys, PR merges, and ticket closure, optimistic completion propagates errors downstream.

Gates convert “the agent said so” into “the filesystem and API state confirm it.”


Three enforcement surfaces, one contract:

.sdlc/sdlc.yaml gates: section ← declaration
.sdlc/bin/sdlc_gate_check.py ← runtime enforcement
.sdlc/doctor/runtime-alignment.ts ← structural validation (doctor)

If a gate exists in only one surface, it is a bug. ADR-012 requires declaration and code sync. Schema: .sdlc/schemas/gate-dsl.schema.json. Runner: .sdlc/bin/sdlc_gate_runner.py.


From sdlc.yaml:

gates:
integration:
- integration-report
- joint-e2e-pass
deploy:
- ci-target-aligned
- staging-smoke-or-url
post-deploy:
- evidence-manifest
- production-http-200
reflect:
- reflect-report-pass
workspace-rebind:
- rebind-report
- ci-target-aligned

Each orchestrator stage transition calls sdlc_gate_check.py with the relevant gate set. Exit code 1 blocks the transition.


Before a ticket moves to done:

FieldMeaning
subagent_dispatchedFeature-flow ran in subagent/worktree, not main session
feature_flow_completeAll feature-flow.yaml success_criteria satisfied
qa_verdictQA stage returned PASS
visual_gatePixel diff passed (clone mode)
evidence_dirPath to .sdlc/evidence/{TICKET}/ exists
pr_urlGitHub PR created
review_urlHuman or automated review recorded

Invalid ticket statuses (consolidated, merged_without_agent) fail at wave-complete and mark-done — these indicate protocol bypass.


From feature-flow.yaml:

success_criteria:
- planning.plan is defined
- implementation.typescript_check == "pass"
- implementation.build_check == "pass"
- review.review_status == "APPROVED"
- qa.qa_status == "PASS"
- pr_creation.pr_url is defined
- plane-in-review.ticket_state == "In Review"
- plane-closure.ticket_state == "Done"
- plane-closure.plane_comment_id is defined

sdlc_gate_check.py validates these before setting feature_flow_complete: true.


Stages must complete in order:

hydrate → classify → discovery → decomposition → plane-hierarchy
→ architecture → workspace-rebind → wave-planning → execution
→ integration → deploy → post-deploy → board-sync → reflect

Stages depending on work-items hierarchy (architecture through reflect) block until work-items-hierarchy (or plane-hierarchy in legacy manifests) completes.


The orchestrator may edit specific infra paths during deploy stage only:

Allowlisted patternRationale
.github/workflows/deploy.yamlPipeline target
.github/workflows/release.yamlVersion bump
.github/workflows/dependency-updates.yamlLockfile maintenance
.sdlc/integrations/cloudflare-pages/cloudflare-pages.yamlDeploy integration
.github/workflows/deploy.yamlCI deploy pipeline (wrangler pages deploy)
{target_root}/e2e/smoke.spec.tsDeploy smoke
{target_root}/CHANGELOG.mdRelease record
{target_root}/package.jsonVersion field only

Never allowlisted: {target_root}/src/**, feature E2E specs beyond smoke.

Implementation: sdlc_protocol_guard.py with _deploy_stage_allowlist(path).

Trade-off: deploy infra edits in main workspace are permitted; feature code edits are not. This avoids requiring a dedicated deploy worktree for every pipeline retarget while preventing orchestrator implementation bypass.


A run is not complete until:

GateCheck
handoff_recordedLATEST.md does not match stub pattern
sdlc_state_committedsdlc.yaml, manifest, INDEX delta committed or explicitly deferred with reason
doctor:operationalCI target alignment pass

Stub pattern: "STUB — Agent did not enrich".

Consequence: next session must hydrate from git + LATEST.md alone. If that fails, the run is incomplete regardless of deploy status.


When target_root changes:

Terminal window
python3 .sdlc/bin/sdlc_workspace_rebind.py --target packages/api --profile python-fastapi

Actions:

  1. Update sdlc.yaml workspace block
  2. Patch CI workflow working-directory fields
  3. Patch Cloudflare Pages integration root_directory
  4. Update architecture.md target_root row
  5. Run doctor structural gate workspace:ci-target-aligned

Stage workspace-rebind blocks until rebind report exists and doctor passes.


Domain playbook pipeline (/sdlc:learn) has separate gates in sdlc_playbook.py:

GatePhaseBlocks
research-gateF0 → F2Playbook materialization if research fails
contamination-checkF3Touch of agents, rules, skills, hooks, memories, sdlc.yaml, workflows without --wire, or {target_root}/

Macro gates (sdlc_gate_check.py) and micro-gates (sdlc_playbook.py) are intentionally separate — different lifecycles, different failure modes.


Manifest CLI must store booleans as YAML booleans, not strings:

Terminal window
# Correct
update-ticket --field plane_evidence_uploaded --value true true
# Wrong
update-ticket --field plane_evidence_uploaded --value true 'true'

String 'true' fails boolean gate checks silently until manifest inspection.


Gates add latency. Every stage transition runs Python checks. This is acceptable because the cost of a false “done” (bad deploy, missing evidence, unmerged PR marked complete) exceeds gate execution time.

Gates survive agent replacement. A new agent session cannot inherit a previous agent’s optimistic stage marks — it must pass the same checks.

Gates require maintenance. When a new success criterion is added to a workflow, sdlc_gate_check.py and sdlc.yaml gates: must update together. Doctor catches declaration drift.

Gates are not user-facing. They are infrastructure. Operators interact through manifest status and doctor reports, not raw gate script output.