Development & Deployment Workflow¶
Updated April 2026 — we simplified from feature → staging → main to single-branch. All PRs now target main directly. The staging branch still exists for internal validation deploys but is no longer part of the contribution flow.
The flow¶
┌──────────────┐ ┌──────────────┐
│ feature/* │────►│ main │
│ (develop) │ PR │ (prod) │
└──────────────┘ └──────┬───────┘
│
auto-deploy
│
┌──────▼───────┐
│ Prod VPS │
│147.93.168.216 │
│ editor │
│.astradial.com│
└──────────────┘
Maintainers test PRs locally or on open.astradial.com before approving. Main is protected — only approved PRs merge.
Engineering flow — MANDATORY for every feature, issue, and bug¶
The git flow above is how code ships. This is how work gets done — no exceptions, whether it's a new feature, an issue, or a one-line bug fix. Do not skip a step because a change "looks small"; small changes cause most incidents.
1. Requirements → gather until you have >95% clarity. Ask questions; never
start on assumptions. Restate the requirement and the
acceptance criteria back before moving on.
2. Plan → produce a written plan (files to touch, approach, risks,
rollback). Use a Fable-5 agent to PLAN BEFORE CODE.
3. Implement → build the smallest correct change that satisfies the plan.
4. Audit code → an independent code audit/review of the diff (correctness,
security, edge cases). Use a Fable-5 agent to AUDIT/REVIEW —
the author does not self-certify.
5. Create TCs → write the test cases (happy path + edge/negative) that prove
the acceptance criteria from step 1.
6. Run scenarios → execute every test case (unit + real-device/staging where
the behaviour lives — never the emulator for call audio).
7. All TCs pass → do not call it done, merge, or promote until EVERY test case
passes. A failing/looks-better-but-unverified TC = not done.
Fable-5 for Plan + Audit is required, not optional. Planning before code and an independent audit are the two steps humans and single-pass coding skip most — delegate them to a Fable-5 agent so they actually happen. Capture the plan in the PR description and the audit findings + TC results in the PR/commit.
This composes with the two disciplines below: RCA before fixing (step 1–2 for bugs) and rule out the test environment (step 6). See also the platform repo's CLAUDE.md (prod-change guardrails) — the engineering flow does not override the "confirm before any prod change / back up first" rules; it runs inside them.
Step-by-step¶
1. Branch from main¶
Use conventional prefixes: feat/, fix/, docs/, refactor/, chore/.
2. Develop and test locally¶
cd editor
npm install
npm run dev # local dev server (port 3001)
npm run build # verify build
npx tsc --noEmit # verify TypeScript
For backend services: docker compose up mariadb redis asterisk api workflow-engine.
3. Push and open PR to main¶
git push origin feat/add-whatsapp-templates
# GitHub → New PR → base: main → compare: feat/add-whatsapp-templates
Fill out the PR template. Link the issue (Closes #<number>). Tick the shadcn-only and no-secrets boxes.
4. Review + merge¶
Maintainer reviews. On merge → auto-deploys to production via self-hosted runner.
5. Verify production¶
Open https://editor.astradial.com — confirm live.
Why the flow changed¶
The old feature → staging → main flow added a mandatory extra PR and slowed contributions without catching issues that local testing + reviewer validation didn't already catch. Staging VPS is still running for longer-lived validation of platform-level changes (schema migrations, NUC routing, etc.) — but we promote to it manually from main rather than gating every PR through it.
When to still use staging (manually):
- Schema migrations that need real data shape validation before prod.
- Platform changes touching NUC / Asterisk / WireGuard routing.
- Any change a maintainer flags as "test on staging first" during review.
For those, cherry-pick to the staging branch after the PR lands on main, validate on stageeditor.astradial.com, then deploy to prod.
Repositories¶
As of the April 17 2026 monorepo cutover, all four apps live in astradial/astradial:
| Path | What it is |
|---|---|
editor/ | Next.js dashboard + editor UI |
api/ | Node.js PBX API server (AstraPBX) |
workflow-engine/ | Bull job scheduler |
pipecat-flow/ | Python AI voice bot gateway |
One repo, one PR, one auto-deploy pipeline. See docs/development/cicd.md for runner details.
Branch retention policy¶
Branches are never auto-deleted after merge across all Astradial repositories. This is enforced via the GitHub repo setting ("Automatically delete head branches" = off).
Why: merged branches serve as an audit trail — they let you trace exactly what code shipped in a given PR, check out the state at any point, and diff against main without reconstructing history from commits.
If you want to clean up your local working copies after a PR merges, that's fine:
Never run git push origin --delete feat/my-feature on a merged branch.
Keeping staging in sync with prod hotfixes¶
When production bugs are fixed directly on the prod VPS (147.93.168.216) and not yet in git, those fixes must be manually applied to staging (94.136.188.221) before they diverge further. Do this immediately after the prod fix is confirmed working.
Checklist for a prod-to-staging sync:
- DB schema — run the same
ALTER TABLE/ migration on staging MariaDB - nginx — apply the same config changes in
/etc/nginx/sites-enabled/stageeditor.astradial.com, thennginx -t && nginx -s reload - server.js — apply the same endpoint additions/fixes; restart astrapbx:
pm2 restart astrapbx - Editor source — apply the same file edits under
/opt/pipecat-flow-editor/; rebuild and restart:npm run build && pm2 restart editor - Verify — check
pm2 statuson staging, confirmunstable restarts = 0
The goal is that staging always has a superset of what prod has, so that staging tests don't fail on bugs already fixed in prod.
Debugging discipline — always do RCA before fixing¶
A bug report is a symptom. Never patch the first plausible guess — prove the root cause with evidence, then fix it at the root.
- Reproduce and observe first. Get the actual error / log / packet capture / DB row / trace. Rule out the environment before blaming the code.
- Name the cause with evidence (file + line, a log line, a captured packet), not a hunch. If you can't point at it, you haven't found it yet.
- Fix at the root, where all callers route through — one guard in the shared function beats a patch in every caller. Patching the one path a ticket names leaves every sibling caller still broken.
- State the RCA in the PR/commit: symptom → root cause → why this fix addresses it. "Tried X, seems better" is not an RCA.
Rule out the test environment before assuming a code bug¶
The one that keeps biting: Android emulators have no real microphone. Call audio on an emulator is unreliable — one-way audio, or the far side hearing only a buzz / synthetic tone (the emulator's fake mic), is an emulator artifact, not a call-engine bug. Always reproduce call-audio issues on two real devices before touching audio / RTP / codec code. Same idea applies broadly: the iOS Simulator has no CallKit/PushKit fidelity, emulator networking differs from a phone, etc. (Real case, 2026-07: hours nearly spent chasing "one-way audio" that was the emulator with no mic — see Lessons Learned.)
What NOT to do¶
- Never push directly to
main— always open a PR. - Never commit
.env,.env.local,firebase-sa-key.json, or any secret. - Never use custom colors — only shadcn/ui default tokens.
- Never deploy via manual
scp(use the PR flow). - Never run
pm2 restarton prod — alwayspm2 reload(graceful, no downtime). - Never target a PR at
stagingunless a maintainer explicitly asked for it. - Never delete a merged branch — branches are kept for audit purposes (see above).