Templates
Pull Request Template
markdown
## What
<!-- One or two sentences. What does this change? -->
## Why
<!-- Why is this change needed? Link the Jira work item. End the description with: Closes <SUBTASK-KEY> -->
## How
<!-- Anything non-obvious about the implementation. -->
## Risk
<!-- What could go wrong? How will we know? Rollback plan? -->
## Test Notes
<!-- Which test scenarios (S1, S2, ...) this MR automates, and the test names. Anything that still needs a manual check on staging. -->
## Checklist
- [ ] Docs updated (if public API or behavior changed)
- [ ] Feature flag added (if rollout is risky)
- [ ] Migration is backwards-compatible (if DB change)
- [ ] Migrations re-stamped with `restamp.sh` after the final rebase (if DB change)Postmortem Template
markdown
# Postmortem: <Incident name>
**Date:** YYYY-MM-DD
**Severity:** SEV1 | SEV2 | SEV3
**Duration:** <start time> to <end time> (<total>)
**Author:** <name>
**Status:** Draft | Reviewed
## Summary
<!-- 2–3 sentences. What happened, who was affected, how we resolved it. -->
## Impact
<!-- Quantified: users affected, requests failed, revenue impact, etc. -->
## Timeline (UTC)
- HH:MM — <event>
- HH:MM — <event>
## Root cause
<!-- The actual underlying cause. Often more than one factor. -->
## What went well
<!-- Detection? Response? Communication? -->
## What went poorly
<!-- Where did the system make this hard to handle? -->
## Action items
| Item | Owner | Due | Type |
| ----------------- | ------ | ---------- | ---------- |
| Add alert for X | @alice | YYYY-MM-DD | Detection |
| Make Y idempotent | @bob | YYYY-MM-DD | Prevention |On-Call Handoff Template
markdown
# On-Call Handoff: <date range>
**Outgoing:** <name> → **Incoming:** <name>
## Open issues
<!-- Anything still on fire or smoldering. -->
## Notable events this week
<!-- Pages, near-misses, unusual traffic. -->
## Watch list
<!-- "Service X has been flaky after deploy Y; keep an eye on the error rate dashboard." -->
## Comp time taken
<!-- Hours of after-hours work; when you'll take it back. -->Onboarding Checklist (copy per new hire)
markdown
# Onboarding: <name>
**Start date:** YYYY-MM-DD
**Buddy:** <name>
## Pre-day-1 (lead)
- [ ] Workspace email created
- [ ] GitLab account created at `gitlab.internal`
- [ ] Jira account added to the team's projects
- [ ] Slack account created and added to team channels
- [ ] Fingerprint registered for door access
- [ ] Buddy assigned
- [ ] First PR ticket picked
## Day 1
- [ ] Welcome chat with lead
- [ ] Buddy walkthrough
- [ ] Dev env running locally
- [ ] Read playbook
## Week 1
- [ ] Ship starter PR
- [ ] 1:1 with each teammate
## Week 2
- [ ] Pick up real ticket (full task workflow)
- [ ] Pair with domain owner
## Month 1
- [ ] Lead a small project end-to-end
- [ ] Give first code review
## Month 2
- [ ] Shadow on-call
- [ ] Co-author or write an ADR
- [ ] Primary on-call (first shift)
## Month 3
- [ ] Domain ownership confirmed
- [ ] Onboarding retro with leadJira Work Item Examples
Each block is the description of one Jira work item as it looks when it reaches Ready. The first is a complete detailed spec; the others are shorter, but every test scenario still has an ID and an auto or manual tag. The subtasks under each are listed last; engineers create and estimate those themselves.
New Endpoint
Type: Story · Epic: User activity feed · Component: activity · Owner: BE dev
Title: Add GET /v1/users/:id/activity endpoint
Goal
The mobile app needs a user activity feed. Today the client stitches it from
three separate calls; one paginated endpoint replaces them.
Flow
1. The app calls GET /v1/users/:id/activity with the user's access token.
2. The API checks the token, then that the caller is :id or an admin.
3. It returns the newest events first, one page at a time.
Failures: no token -> 401; another user's id without admin -> 403;
:id not a UUID -> 400; unknown user -> 404; limit > 100 -> 400.
Endpoints
GET /v1/users/{id}/activity List a user's events, newest first.
Paging offset/limit (default 20, max 100). No filters yet.
Returns event type, time and metadata, plus the total count.
Rules
- Only the user themself or an admin can read the activity.
- Metadata never contains tokens or secrets.
Acceptance criteria
AC1. Given my own id, when I call the endpoint, then I get 200 and my events newest first.
AC2. Given offset=20&limit=10, then I get events 21-30 and total is the full count.
AC3. Given another user's id and no admin role, then I get 403.
AC4. Given no Authorization header, then I get 401.
AC5. Given up to 1 000 events, then p95 latency is under 200ms on staging.
Test scenarios
| ID | Scenario | Type | Test name |
| S1 | Own activity, 3 events, newest first | auto | TestActivity_S1_OwnEventsNewestFirst |
| S2 | offset=20&limit=10 returns the right slice | auto | TestActivity_S2_Pagination |
| S3 | No events -> 200, empty data, total=0 | auto | TestActivity_S3_Empty |
| S4 | Other user's id without admin -> 403 | auto | TestActivity_S4_Forbidden |
| S5 | :id not a UUID -> 400; limit=101 -> 400 | auto | TestActivity_S5_BadInput |
| S6 | Missing Authorization header -> 401 | auto | TestActivity_S6_NoToken |
| S7 | p95 < 200ms with 1 000 events on staging | manual | - |
Notes
Depends on the auth service (JWT validation), available. Reads activity_events, no
schema change. Out of scope: filtering by event type, real-time updates.
Open questions
(none)
Subtasks
- BE: endpoint, pagination, auth checks, tests S1-S6
- FE: activity feed screen against the new endpointNew Datasource / Data Layer
Type: Task (label infra) · Epic: In-app notifications · Component: notifications
Title: Add notifications table and repository layer
What
Create a notifications table and a repository that supports insert, mark-as-read,
and paginated fetch by user. This is the persistence layer for the upcoming
in-app notification feature.
Why
In-app notifications are the next feature on the roadmap. Standing up the data
layer first lets the API and worker tickets proceed independently.
Acceptance criteria
- notifications table exists with columns: id, user_id, type, payload (jsonb),
read_at (nullable), created_at
- Index on (user_id, created_at DESC) for the list query
- Repository exposes: Create, MarkRead, ListByUser(userID, limit, offset)
- Migration runs up and down cleanly on a fresh database
- No raw SQL string interpolation — parameterized queries only
Test scenarios
S1 [auto] Run migration up on empty DB — table and index created with no errors
S2 [auto] Run migration down — table dropped cleanly, no orphan objects
S3 [auto] Create a notification, then ListByUser — record appears in results
S4 [auto] MarkRead sets read_at; a second call is a no-op (idempotent)
S5 [auto] ListByUser with offset beyond total rows — returns empty slice, no error
S6 [auto] Simulate 10 000 rows for one user — query completes under 50ms (EXPLAIN ANALYZE attached)
Dependencies
- None (self-contained data layer; the API Story depends on this one)
Subtasks
- BE: migration (timestamped, re-stamped before merge) and repository with testsBug Fix
Type: Bug · Priority: High · Component: api
Title: Pagination returns duplicate items when records are inserted mid-page
What
When new records are inserted between paginated requests, offset-based pagination
returns the same item in two consecutive pages.
Why
Users see duplicate entries in the dashboard list view, causing confusion and
support tickets. Reported in #inc-20260508-dup-rows.
Root cause (to be confirmed)
Offset shifts when a new row is inserted before the current page window.
Switching to cursor-based pagination (by created_at + id) will make the
position stable regardless of inserts.
Acceptance criteria
- Fetching page 2 immediately after a new record is inserted on page 1 does not
return any item already seen on page 1
- Existing offset/limit query params remain accepted (backwards-compatible);
cursor param is additive
- All existing pagination tests pass
Test scenarios
S1 [auto] Fetch page 1 (limit=10), insert a new record, fetch page 2 — no duplicates
S2 [auto] Fetch all pages sequentially with no inserts — full dataset covered, no duplicates
S3 [auto] cursor param omitted — falls back to offset behaviour (existing callers unaffected)
S4 [auto] Invalid cursor value — returns 400 with a clear error message
Dependencies
- Affects: /v1/companies and /v1/users list endpoints
- No schema changes required
Subtasks
- BE: cursor pagination with offset fallback, regression test that fails before the fix
- FE: send the cursor from the list viewsPerformance Improvement
Type: Task (label tech-debt) · Component: reports
Title: Reduce p95 latency on GET /v1/reports from 1 400ms to under 400ms
What
The reports list endpoint is slow because it runs a COUNT(*) on a 2M-row table
on every request. Add a covering index and cache the count result for 60 seconds.
Why
p95 latency is at 1 400ms (baseline from Grafana, 2026-05-10). The PM dashboard
loads this endpoint on every page open; users are reporting the page feels broken.
Target: p95 < 400ms without changing the response contract.
Acceptance criteria
- p95 latency drops below 400ms under normal load (verified by load test)
- Response is identical to today's response — same fields, same pagination behaviour
- Cache miss (first request or TTL expired) still responds under 600ms
- No regression on related endpoints (GET /v1/reports/:id, POST /v1/reports)
Test scenarios
S1 [manual] Run k6 load test (100 VUs, 2 min) before and after — attach both result summaries to PR
S2 [auto] Cold cache (Redis flushed) — endpoint responds correctly and within 600ms
S3 [auto] Warm cache — response matches cold cache response byte-for-byte (minus timestamps)
S4 [auto] Cache TTL expires mid-test — no errors, stale count is refreshed automatically
S5 [manual] Run EXPLAIN ANALYZE on the query with and without the new index — attach both plans
Dependencies
- Redis (already available in the stack)
- Grafana dashboard for before/after screenshots
Subtasks
- BE: covering index, count cache, load test before and after