Contributing¶
Branch naming¶
All feature and bug work happens on short-lived branches cut from origin/development (the integration base for ftl-backend and ftl-frontend; ftl-docs uses origin/main).
Rules:
- The slug must describe the work. No dates, no session IDs, no person names. A teammate reading
git branch -rshould understand the scope from the name alone. feat/— new user-visible behavior. Examples:feat/trade-window-gating,feat/reminder-bell.bug/— fixes to existing behavior. Examples:bug/squad-refresh-loss,bug/login-cookie-domain.- One branch = one scope. If a task expands into a separate concern, cut a new branch from
developmentfor the new concern. - Push to
originon your first meaningful commit. Work that only exists locally is invisible and unrecoverable. - Never commit directly to
main,release/staging, orrelease/prod. - Never force-push a shared branch without explicit confirmation from the team.
Pull requests¶
PR description must include Author: @<github-handle> — the GitHub account of the human operator running the session, not Claude. This applies even when Claude wrote every line of code.
Example footer:
Container names must be prefixed with the branch slug. When starting any Docker container for work on this branch, use the slug as a name prefix:
This keeps containers from parallel tasks isolated and makes ownership obvious in docker ps.
Pre-merge checklist:
- [ ]
make testpasses with the-raceflag (see Running tests). - [ ] If the PR changes behavior described in any Living Spec, the spec is updated in the same PR or a paired
ftl-docsPR. - [ ] If the PR introduces an architectural decision, a new ADR is added.
- [ ] PR description includes
Author: @<github-handle>. - [ ] Container names in any run instructions use the branch slug as a prefix.
Living Spec system¶
FTL uses three complementary doc types. Knowing when to write each prevents stale docs.
| Type | Path | Job |
|---|---|---|
| Living Spec | ftl-docs/architecture/specs/<domain>.md |
Describes one domain as it is today. Rewritten in place when behavior changes. Never versioned. |
| ADR (Architecture Decision Record) | ftl-docs/architecture/decisions/NNNN-<slug>.md |
Captures one decision — what was chosen, why, what was rejected. Append-only once accepted. |
| Per-commit walkthrough | ftl-docs/pr-reports/<branch>-<sha7>.html |
Auto-generated HTML after every Claude-authored commit. Captures the what of the commit. |
When to update a Living Spec¶
Update the affected spec whenever a code change alters behavior that the spec describes:
- Edit the relevant section of
ftl-docs/architecture/specs/<domain>.md. - Bump
last-updated:in the YAML frontmatter to today's date. - Verify the
code-refs:paths still exist and update line ranges if they shifted.
Do not pre-write spec sections for not-yet-shipped behavior. That content belongs in an ADR until the code lands.
When to add an ADR¶
Add an ADR whenever you make a decision that: - changes behavior, schema, or a public contract, or - a future engineer would be confused about why the change was made if they only saw the diff.
Skip ADRs for pure refactors, typos, or dead-code removal with no behavior change.
To create a new ADR:
# Find the next number
ls ftl-docs/architecture/decisions/ | grep -E '^[0-9]{4}-' | sort | tail -1
# Copy the template
cp ftl-docs/architecture/decisions/_TEMPLATE.md \
ftl-docs/architecture/decisions/NNNN-<slug>.md
Fill in every YAML field. affects-specs: and affects-code: are required. Add a row to ftl-docs/architecture/decisions/INDEX.md (newest first).
ADR lifecycle¶
ADRs carry two independent status fields:
| Field | Tracks | Values |
|---|---|---|
status: |
Decision lifecycle | proposed → accepted → superseded / rejected |
implementation-status: |
Code lifecycle | pending → in-progress → implemented |
Once status: accepted, only status, superseded-by, and implementation-status may change. The content of an accepted ADR is append-only.
Common flow — design first, code later:
- Open a docs PR with the ADR at
status: accepted, implementation-status: pending. The spec is not rewritten yet — it still describes the live system. The ADR may carry an implementation guide for the code author. - Add a row to
ftl-docs/PENDING.mdfor any ADR withimplementation-status: pending. - When the code lands, open a paired code PR that also: rewrites the affected spec section, flips the ADR to
implementation-status: implemented, and moves thePENDING.mdrow to the "Recently shipped" section.
Superseding an older ADR:
- Set
supersedes: NNNNon the new ADR. - Open the older ADR and set
status: supersededandsuperseded-by: <new number>. - Only supersede when the enacting code ships in the same PR. If the code hasn't landed yet, the older ADR still describes live behavior.
Per-commit HTML walkthrough¶
After every commit Claude makes in any ftl-* repo, Claude must also generate an HTML walkthrough and commit it separately.
Output path: ftl-docs/pr-reports/<branch-slug>-<sha7>.html
Audience: a 1st-year B.Tech IT intern in India — define every acronym on first use, lead with why the commit exists, and keep sentences under 18 words.
Required sections (in order):
1. Heading block (commit subject, SHA, branch, repo, author, date, files changed)
2. TL;DR for an intern
3. Background — what you need to know first
4. What changed, file by file (code commits only; skip for docs-only commits)
5. Documentation Changes (if any ftl-docs/ files were touched, excluding ftl-docs/pr-reports/)
6. How to verify locally (renamed "How to read this doc" for docs-only commits)
7. Glossary
8. Further reading
Follow-up commit: after generating the HTML, stage only ftl-docs/pr-reports/ and create a new commit:
Never amend the original commit.
Skip for: merge commits, no-op amends, and human-authored commits. Claude only documents its own commits.
See ftl-docs/PrInstructions.md for the full spec, including manifest.json schema, sidebar layout, and styling requirements.
Running tests¶
# Backend — from ftl-backend/
go test -race ./...
# Frontend E2E (Playwright) — requires the stack to be running
bash ftl.sh start
npx playwright test
Always run go test -race ./... before pushing a backend branch. The race detector catches concurrency bugs that are invisible under normal test runs.
Pre-launch security checklist (key items)¶
Work through the full checklist in CLAUDE.md before cutting over to production. The three highest-risk items:
-
Rotate
.envsecrets before the first production deploy. The Google OAuth client secret, Sportmonks API key, JWT keypair, andREG_TICKET_SECRETmay have been exposed during development. Regenerate JWT keys withftl-backend/scripts/gen-dev-keys.sh. -
Verify
DISABLE_RATE_LIMITis unset in the production Container App environment. The localftl.shorchestrator sets it to1for load testing. Leaving it on in production disables all rate limiting on the auth surface and referral endpoints. -
Confirm
activeRevisionsMode == "Single"for every Container App after any scale operation. Multiple-revision mode left 9 always-warm replicas running in staging, burning ₹1,234/day instead of the expected ₹150/day (seeftl-docs/reports/azure-cost-audit-2026-05-02.md). Check with: