Skip to content

SDLC Runbook — How to ship a change end-to-end

This is the operating manual for any change in aerospace-apps/* repos: bug fix, enhancement, new feature, tech debt, ops task. It uses the worked example of bug #67 in reservation-api-server (the is_business_admin outlet visibility fix) to make every step concrete.

Goal: at the end of the cycle, QA must be able to walk up to a single GitHub issue and find — without DM-ing anyone — the root cause, the diff, the deploy SHA, the test plan, the regression cases, and the rollback path.


Two flows are supported

Flow When to use First step
Issue-first Planned work, anything that needs design discussion or QA hand-off File an Issue from a template (auto-attaches to Project #1), then branch
Code-first Opportunistic small fixes, drive-by improvements, "I see it, I fix it" Just push code and open a PR. The pr-auto-tracking-issue workflow auto-creates a tracking issue, links it via Closes #M, applies the right type:* label, and (with PROJECT_TOKEN secret) attaches it to Project #1. You see a bot comment with the link.

Both flows converge at the same QA gate (Phases 8–9). Pick whichever fits the change. Don't let the runbook block a 5-line typo fix.

TL;DR — the 9 phases

# Phase Who Artifact created
1 Triage / file Reporter (anyone) GitHub Issue (auto-added to Project #1) — or skip and let auto-tracking create it from the PR
2 Refine & size Engineer + PM Labels, priority, sprint, story points
3 Decide artifact type Engineer Bug, Local Spec, or Trace Spec
4 Branch + code Engineer App repos: feature/TASK-{id}-… from develop
5 Open PR Engineer App repos: PR → develop; platform-context → main
6 Code review + CI Reviewer Approval, green checks
7 Merge Engineer or merge-bot Land on develop (apps). PROD = release cut — PROD-RUNBOOK / ADR-0007
8 Deploy comment + status:qa Engineer QA-ready comment with SHA + persona matrix
9 QA verify → close QA "QA: Pass" comment, label flips to status:done

The framework auto-handles: - Step 1 — template auto-adds to project; or pr-auto-tracking-issue creates a backfill issue if you went code-first - Step 6 — spec-discipline CI - Step 7 — post-merge spec status flip - Partially step 8 — post-merge CI emits the status flip; engineer adds the human-readable QA comment


Phase 1 — File the issue (or skip to Phase 4 and let auto-tracking handle it)

Code-first shortcut

If your change is small and obvious — typo, log message, dependency bump, one-line bug fix — you can skip Phase 1 entirely:

  1. Branch off develop (app repos) as fix/TASK-{id}-… (or let auto-tracking create the issue from the PR if you truly code-first).
  2. Push the fix.
  3. Open the PR into develop with a Conventional Commits title (e.g. fix(permissions): handle null user).
  4. The pr-auto-tracking-issue workflow runs within ~30 seconds:
  5. Detects no Closes #N in the PR body
  6. Creates a tracking issue with the PR title
  7. Applies a type:* label inferred from the title prefix (fix: → type:bug, feat: → type:enhancement, etc.)
  8. Inserts Closes #M into the PR body
  9. Comments on the PR confirming the auto-track
  10. From here, Phases 5–9 are identical.

You opt out by adding the skip-tracking-issue label to the PR (use sparingly — we lose the audit trail and Project #1 entry).

When to file an Issue first

Even with auto-tracking, file an Issue first when:

  • The change has design trade-offs that need discussion before code.
  • It will touch more than one repo (you need a Master spec).
  • You want PM / QA to weigh in before the diff exists.
  • There's a customer ticket / Slack thread to reference in the body.
  • You need a test plan written down before the PR is reviewable.

Decision: which template?

Symptom Template Why
Something is broken in production Bug No spec required. RCA goes in the issue body or a docs/FIX-*.md.
New behavior in one repo Local Spec Spec-driven. Single repo.
New behavior touching 2+ repos Trace Spec (per repo) + Master Spec (in bz-platform-context) Cross-repo coordination.
Refactor / tech debt with no user-visible behavior change Enhancement Light-weight. May or may not warrant a spec.
Ops task (rotate keys, scale up cluster, run migration) Operational No code change required; track for audit.

Rule of thumb: if a junior engineer would need a written explanation to understand "why are we doing this?", file a spec. If the code review can answer that question, a Bug or Enhancement template is enough.

File it from the GitHub UI (preferred)

https://github.com/aerospace-apps/<repo>/issues/new/choose → pick the template → fill in fields. The projects: ["aerospace-apps/1"] front matter on each template auto-adds the issue to Project #1 on submit.

⚠️ gh issue create --template ... does not auto-add to the project (the CLI bypasses template rendering). Use the web UI when possible. If you must use the CLI, manually add via gh project item-add 1 --owner aerospace-apps --url <issue-url> (requires project scope: gh auth refresh -s project,read:project).

What a good Bug issue looks like

See #67 for the canonical example. Sections:

  1. What happened — observed behavior, affected users, environment.
  2. Expected behavior — one paragraph.
  3. Steps to reproduce — numbered, copy-pasteable.
  4. Root cause analysis — 1–3 paragraphs. Link a docs/FIX-*.md if the analysis is long. Do not re-paste full diffs into the issue; link to the doc.
  5. Affected files — tells reviewers and QA the blast radius.
  6. Test plan (for QA) — numbered list of personas + expected results. These become the literal QA checklist.
  7. Notes — environment quirks, related fixes that must follow, irrelevant-but-confusing context.
  8. Severity — P0 (production down) → P3 (nice to have).

Phase 2 — Refine + size

Done in the weekly refinement meeting or ad-hoc on Slack for P0/P1.

Action items:

  1. Add labels: priority:p0|p1|p2|p3, type:* (already on from template), spec:* if applicable.
  2. Set Project #1 fields: Status = Ready, Repo, Sprint, Story Points (Fibonacci ½/⅗/8/13).
  3. If the issue spawns or depends on another, add a Blocked by task list in the body. The status:blocked label fires automatically on the blocked issue.

Phase 3 — Decide artifact type

A bug fix usually does not need a spec. The docs/FIX-*.md IS the spec for #67-class fixes — it carries the RCA, the diff plan, and the test plan in one file under the repo it changes.

If the fix is large/risky enough to warrant a permanent record:

mkdir -p reservation-api-server/specs/067-business-admin-outlets
# Use the local-spec template as a base
cp ../bz-platform-context/.github/ISSUE_TEMPLATE/local-spec.yml \
   reservation-api-server/specs/067-business-admin-outlets/spec.md
# Fill frontmatter: spec_id, status: Draft, owner, etc.

Otherwise, just keep the RCA in the issue body or in a docs/FIX-*.md.


Phase 4 — Branch + code

Branch naming

<type>/<issue-number>-<short-slug>

Type Example
fix fix/67-is-business-admin-outlet-visibility
feat feat/142-ghl-outlet-mapping
chore chore/89-bump-frappe-15-25
docs docs/runbook-and-ai-reviewers
ops ops/103-rotate-r365-sftp-creds

Commit message format

Conventional commits, with Closes #N in the body:

fix(permissions): is_business_admin managers see 0 outlets

<2-4 sentences explaining root cause and fix>

Closes #67

Closes #N (or Fixes #N) auto-closes the issue when the PR merges into the default branch. Without it, you'll have to close manually.

Release model (ADR-0007): App PRs merge to develop. Production ships only from release/x.y.z via manual deploy, then release/* → main + tag. See PROD-RUNBOOK.md. Until Epic F.1 disables push-triggered pipelines, treat unexpected environment deploys as incidents and prefer release-cut discipline.


Phase 5 — Open the PR

PR title format

<type>(<scope>): <short summary> (#<issue>)

Example: fix(permissions): is_business_admin managers see 0 outlets (#67)

PR body — required sections

## Summary
Closes #N. <1-3 sentences.>

## Changes
| # | File | Change |
|---|---|---|
| 1 | path/to/file.py | <what changed> |

## Risk
- Surface: <which code paths>
- Schema: <None | migration required>
- Hot-deploy: <Safe | Schedule a window>
- Rollback: <git revert | feature-flag toggle | DB restore>

## Test plan (QA)
1. ...
2. ...

## Deployment notes
<branch → env mapping, post-deploy steps>

## Spec / context
- Issue: #N
- Spec: specs/NNN-slug/spec.md (if applicable)
- Master spec: bz-platform-context/specs/NNN-slug/spec.md (if cross-repo)

What spec-discipline CI enforces on the PR

  • Branch is rebased off main (no merge commits from main).
  • PR body links an issue if added/changed lines exceed the threshold.
  • If specs/ was touched, frontmatter is valid (spec_id, status, owner, etc. present and well-formed).
  • Trace specs must point to a resolvable Master spec — either by relative path or by master_spec_id lookup in bz-platform-context.
  • bz-platform-context version pin in .platform-version is honored if present.

If your PR is a docs-only change and you don't want to file an issue, add the label bypass-spec-discipline to the PR (use sparingly).


Phase 6 — Code review + CI

CI runs:

Check What it does Where
discipline / spec-discipline Lint specs, validate PR body, check version pin reusable workflow in bz-platform-context
Repo-specific tests Unit tests, type-check, lint each repo's own CI

Reviewer checklist (humans):

  • Diff matches the PR description and the issue's RCA
  • Test plan in the PR is realistic and covers regressions
  • Risk section is honest about blast radius
  • No unrelated changes piggy-backing in the same PR
  • If touching permissions / payment / auth: at least 2 approvals

Phase 7 — Merge

App repos: prefer squash merge into develop.

Production ship is a separate release cut — do not treat merge-to-develop as PROD. Follow PROD-RUNBOOK.md / ADR-0007.

bz-platform-context: squash merge into main.

gh pr merge <PR> -R aerospace-apps/<repo> --squash

Don't delete the branch yet — keep it until QA passes, in case we need to push a follow-up commit before re-deploying. Once QA is green, delete:

gh api -X DELETE repos/aerospace-apps/<repo>/git/refs/heads/<branch>

After merge:

  • The Closes #N line auto-closes the issue.
  • The post-merge workflow runs (if specs/ was touched, it flips spec status from Draft → Published etc., commits back to main).
  • For Frappe apps: production auto-deploys.
  • For frontend apps: deploy is manual or via a separate workflow.

Phase 8 — Deploy comment + flip to status:qa

This is the most important step. Without it, QA has no idea what to test or against which build.

Reopen the issue

Closes #N auto-closes on merge. We re-open so the issue stays "live" through the QA gate:

gh issue reopen <N> -R aerospace-apps/<repo> \
  -c "Reopening for QA validation."
gh issue edit <N> -R aerospace-apps/<repo> --add-label "status:qa"

Move the Project #1 card to In QA.

Post the QA comment (template below)

## Deployed to production

|  |  |
|---|---|
| Fix commit | [`<SHA>`](https://github.com/aerospace-apps/<repo>/commit/<SHA>) |
| Main HEAD (live) | [`<HEAD_SHA>`](https://github.com/aerospace-apps/<repo>/commit/<HEAD_SHA>) |
| PR | #<PR> (merged, squashed) |
| Risk | Low / Medium / High — <why> |
| Rollback | `git revert <SHA> && git push origin main` |

## QA — please validate

### Primary test
1. ...

### Regression checks
| # | Persona | Expected |
|---|---|---|
| 1 | ... | ... |

## Where to find context
- RCA: `docs/FIX-*.md`
- PR + diff: #<PR>

## After QA passes
Comment **"QA: Pass"** with the persona you validated.
If a regression, comment **"QA: Fail"** + repro and I'll roll back.

See #67 deploy comment for a real example.


Phase 9 — QA verifies → close

QA process:

  1. Pull up the issue. Read the Deployed to production comment.
  2. Walk through the persona matrix on the live environment.
  3. Comment "QA: Pass — verified , " for each passing case. If any fail: "QA: Fail — shows " plus reproduction steps.
  4. Engineer closes the issue (or rolls back) once all personas pass.
  5. Move the card to Done in Project #1, swap label status:qa → status:done.
gh issue close <N> -R aerospace-apps/<repo> \
  -c "QA verified by @<qa-user>. Closing."
gh issue edit <N> -R aerospace-apps/<repo> \
  --remove-label "status:qa" --add-label "status:done"

Worked example — issue #67

The full execution of this runbook lives in this issue thread:

  • Issue: aerospace-apps/reservation-api-server#67
  • PR: aerospace-apps/reservation-api-server#68
  • Fix commit: 16eb5fd
  • Main HEAD at deploy: 8aba908
  • RCA doc: docs/FIX-is-business-admin-outlet-visibility.md

Use it as a template.


Edge cases

"I need to ship a hotfix at 2am"

  1. File a Bug issue with priority:p0 (still mandatory — audit trail).
  2. Skip the Project #1 sprint assignment; just label priority:p0.
  3. Branch + PR + merge in one shot. Self-review is acceptable for P0 if a second engineer is paged on Slack.
  4. Post the QA comment immediately after deploy. QA validates within business hours.
  5. The issue stays open as status:qa overnight.

"Two repos must change together"

  1. File a Master Spec issue in bz-platform-context (template).
  2. File a Trace Spec issue in each affected repo, each linking to the Master spec via master_spec_id.
  3. Branch in each repo. Open PRs.
  4. Coordinate the merges:
  5. Backend first (reservation-api-server) so the new contract is deployed before frontends try to call it.
  6. Pin the new contract version in bz-platform-context/contracts/.
  7. Bump the consuming frontend's .platform-version and ship the trace-spec PR.
  8. Each repo's QA comment links to its own deploy SHA, but all 3 issues reference the Master spec.

"QA finds a regression"

  1. Don't reopen the original issue — it's already documenting the first attempt. File a new Bug issue, link it to the original (Caused by #67).
  2. Decide: roll back vs. roll forward.
  3. If roll back: git revert <SHA> && git push origin main. Comment on the regression issue with the revert SHA.
  4. If roll forward: new branch, new PR, repeat phases 4–9.

Tools shortcut

# Find issues awaiting your QA
gh issue list -R aerospace-apps/<repo> --label "status:qa" --state open

# Find PRs in code review
gh pr list -R aerospace-apps/<repo> --state open --search "review:required"

# Find blocked issues
gh issue list -R aerospace-apps/<repo> --label "status:blocked" --state open

Why this is worth the friction

This runbook adds ~5 min of overhead per change vs. raw git push. In return, you get:

  • A QA hand-off that's self-service. No more "@engineer what should I test?" pings.
  • An audit log per change. When prod breaks 6 months later, the issue says exactly what changed and why.
  • A rollback path that's pre-decided. No 2am scramble.
  • A board (Project #1) that reflects reality. Sprint planning and retros stop being fiction.

If a step doesn't add value for your specific change, skip it and put a one-liner reason in the PR. The runbook is a default, not a religion.