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:
- Branch off
develop(app repos) asfix/TASK-{id}-…(or let auto-tracking create the issue from the PR if you truly code-first). - Push the fix.
- Open the PR into
developwith a Conventional Commits title (e.g.fix(permissions): handle null user). - The
pr-auto-tracking-issueworkflow runs within ~30 seconds: - Detects no
Closes #Nin the PR body - Creates a tracking issue with the PR title
- Applies a
type:*label inferred from the title prefix (fix:→type:bug,feat:→type:enhancement, etc.) - Inserts
Closes #Minto the PR body - Comments on the PR confirming the auto-track
- 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 viagh project item-add 1 --owner aerospace-apps --url <issue-url>(requiresprojectscope:gh auth refresh -s project,read:project).
What a good Bug issue looks like¶
See #67 for the canonical example. Sections:
- What happened — observed behavior, affected users, environment.
- Expected behavior — one paragraph.
- Steps to reproduce — numbered, copy-pasteable.
- Root cause analysis — 1–3 paragraphs. Link a
docs/FIX-*.mdif the analysis is long. Do not re-paste full diffs into the issue; link to the doc. - Affected files — tells reviewers and QA the blast radius.
- Test plan (for QA) — numbered list of personas + expected results. These become the literal QA checklist.
- Notes — environment quirks, related fixes that must follow, irrelevant-but-confusing context.
- 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:
- Add labels:
priority:p0|p1|p2|p3,type:*(already on from template),spec:*if applicable. - Set Project #1 fields: Status = Ready, Repo, Sprint, Story Points (Fibonacci ½/⅗/8/13).
- If the issue spawns or depends on another, add a
Blocked bytask list in the body. Thestatus:blockedlabel 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 fromrelease/x.y.zvia manual deploy, thenrelease/*→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 frommain). - 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_idlookup inbz-platform-context. bz-platform-contextversion pin in.platform-versionis 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.
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:
After merge:
- The
Closes #Nline auto-closes the issue. - The
post-mergeworkflow runs (ifspecs/was touched, it flips spec status fromDraft→Publishedetc., commits back tomain). - 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:
- Pull up the issue. Read the Deployed to production comment.
- Walk through the persona matrix on the live environment.
- Comment "QA: Pass — verified
, for each passing case. If any fail: "QA: Fail —" shows plus reproduction steps." - Engineer closes the issue (or rolls back) once all personas pass.
- 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"¶
- File a
Bugissue withpriority:p0(still mandatory — audit trail). - Skip the Project #1 sprint assignment; just label
priority:p0. - Branch + PR + merge in one shot. Self-review is acceptable for
P0if a second engineer is paged on Slack. - Post the QA comment immediately after deploy. QA validates within business hours.
- The issue stays open as
status:qaovernight.
"Two repos must change together"¶
- File a Master Spec issue in
bz-platform-context(template). - File a Trace Spec issue in each affected repo, each linking to
the Master spec via
master_spec_id. - Branch in each repo. Open PRs.
- Coordinate the merges:
- Backend first (
reservation-api-server) so the new contract is deployed before frontends try to call it. - Pin the new contract version in
bz-platform-context/contracts/. - Bump the consuming frontend's
.platform-versionand ship the trace-spec PR. - Each repo's QA comment links to its own deploy SHA, but all 3 issues reference the Master spec.
"QA finds a regression"¶
- 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). - Decide: roll back vs. roll forward.
- If roll back:
git revert <SHA> && git push origin main. Comment on the regression issue with the revert SHA. - 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.