How we work · AI-Assisted SDLC
AI, embedded from idea to deployed code
Eight agents. Each builds on the last.
Eight agents. Each builds on the last.
Each agent outputs an artifact that becomes the input for the next.
One command installs the harness and activates all eight agents in their IDE.
Run once by the PO. Output committed to the repo; shared by the whole team.
Questions are code-grounded, asked one at a time.
## 1. Why This System Exists
A multi-tenant notification hub that unifies SMS, email, WhatsApp,
Slack, push, and Telegram delivery through a single API.
## 4. Value Creation Points
- Unified API across 6+ channels (integration overhead reduced)
- Template hydration (personalisation without code changes)
- Multi-provider per channel (vendor flexibility)
- Delivery analytics (PLANNED — drives platform stickiness)
## 7. Business Rules & Constraints
- Multi-tenant: organisation isolation on all data paths
- Provider credentials stored encrypted (KeyTools)
- SQS-based async delivery (Lambda consumer)
- Current statuses: SENT, ERRORED — no post-delivery tracking
## 9. Explicit Non-Goals
- Real-time analytics dashboard (future phase)
- Provider-side configuration management
10 sections. Generated from the codebase, not a template.
Run once by the tech lead. Documents architecture and the decisions behind it.
An 11-section architecture document, mapped from code.
We send via SendGrid, Twilio, and Firebase — but have no visibility into what happens after delivery. Are messages opened? Bouncing? We need engagement tracking.
A requirement arrives. Full path: @prd -> @hld -> @tech-analysis -> @plan-mode -> implement -> @review-mode -> commit.
A ticket, Slack message, or plain text becomes a reviewable specification. Business language only.
# PRD: Engagement Analytics
Epic ID: EN-ANALYTICS
## Goal
Capture post-delivery engagement events (open, click, bounce)
from SendGrid, Twilio, and Firebase via webhooks, normalized
into a common status model for cross-channel analytics.
## Success Metrics
| Metric | Current | Target |
|---------------------|----------------|---------------------|
| Delivery visibility | 0% (SENT only) | 100% tracked |
| Time to engagement | N/A | <30s from provider |
| Duplicate handling | N/A | Zero duplicate rows |
## Explicitly Out of Scope
- Real-time engagement dashboard UI (separate epic)
- Engagement-based routing decisions (future)
- Provider webhook retry configuration (use defaults)
Business language. Measurable metrics. Explicit scope boundaries.
Translates the PRD into technical architecture grounded in the existing codebase.
## Module Responsibility Matrix
| Module | New Responsibility |
|-----------------------|--------------------------------------------|
| server (Express) | + webhook endpoints (signature-verified) |
| NEW: WebhookProcessor | Parse, verify, normalize, persist |
## API Contracts
POST /webhooks/sendgrid — signature verified via header
POST /webhooks/twilio — Twilio request signing verified
Response: always 200 { received: true } (prevent retry storms)
## Schema Changes
notification_transactions:
+ engagementEvents: [{ status, timestamp, provider, rawEventId }]
+ currentEngagementStatus: DELIVERED|OPENED|CLICKED|BOUNCED
+ index on { notificationId, rawEventId } for idempotency
Module boundaries. API contracts with error cases. Mermaid flows. Story index with estimates.
Breaks the HLD into developer-ready stories, each carrying the contracts it needs.
# Story WH-1: Webhook Route Scaffolding + Signature Verification
Epic: EN-ANALYTICS | Service: server | Estimate: 3 SP
## HLD Reference (embedded — developer never re-reads the HLD)
POST /webhooks/sendgrid — X-Twilio-Email-Event-Webhook-Signature
POST /webhooks/twilio — X-Twilio-Signature
Response: 200 { received: true } always
## Acceptance Criteria
- [ ] Three webhook endpoints respond 200 for valid payloads
- [ ] Invalid signature → 200 + log warning + discard
- [ ] Endpoints bypass JWT auth (public, provider-initiated)
- [ ] Delegates to provider-specific parser via DI
- [ ] Request body limited to 1MB
## Code References
- Similar: src/server/controllers/NotificationController.ts (TSOA)
- Crypto: src/server/utils/crypto.ts (HMAC base)
- DI: src/server/container.ts (tsyringe)
4 stories, 11 SP total. No cross-module blockers. E2E + integration tests generated alongside.
For well-understood requirements. Skips PRD and HLD; goes straight to stories.
Every story starts with a phased plan. Phase 1 is always a characterization safety net, no production code changes.
## Phase 1: Characterization Safety Net (no production changes)
Files: src/server/middleware/authenticate.ts
Tests: tests/server/middleware/authenticate.test.ts (new)
Webhook routes will bypass JWT auth. Confirm existing
authenticated routes remain protected after adding
unauthenticated webhook routes.
Characterization coverage to lock in:
- /api/notifications without JWT → still returns 401
- /api/organisation without JWT → still returns 401
- Valid JWT → org context correctly attached
Phase 1 never touches production code. Run these tests green, then proceed.
## Phase 2: Webhook Endpoints + Signature Verification
Files: src/server/controllers/WebhookController.ts (new)
src/server/middleware/verifyWebhookSignature.ts (new)
Tests: tests/server/webhooks/signatureVerification.test.ts (new)
@Route("webhooks")
export class WebhookController extends Controller {
@Post("sendgrid")
@Middlewares(verifySendGridSignature)
async handleSendGrid(@Body() events: SendGridEvent[]) {
await this.processor.process('sendgrid', events)
return { received: true }
}
}
Test scenarios:
- Valid signature → reaches processor
- Invalid signature → 200, discarded
- Body >1MB → 413 rejected
Scans modified files in the commit. Issues ranked Critical to High to Medium. Engineer decides: refactor now, skip, elaborate, or backlog.
## Why This Matters
Provider webhooks don't retry indefinitely:
- SendGrid: 3 days, then gives up
- Twilio: 24 hours
- Firebase: no retries
If DB is down and you return 200, those events are gone.
## Before:
try { await processor.process(events) }
catch (err) { logger.error(err) }
return { received: true }
## After:
try { await processor.process(events) }
catch (err) {
await dlq.enqueue({ provider, events, error: err.message })
logger.error('Queued to DLQ', err)
}
return { received: true }
Engineer sees the business risk, then decides: Go Next or Re-visit same file.
AI accelerates repetitive engineering work while engineers retain control.
At every phase: approve, modify, reject, or refine. No agent proceeds without sign-off.
The harness turns using AI into an engineering process.