Populate the Decision Bridge
This is the core of the capture. Map every planned decision to its outcome:
a. Decisions to Make → Decisions Made: For EACH checkbox from intent.md, record:
- What was decided
- Why (the rationale — not just the choice, but the reasoning)
- Any constraints that influenced the decision
b. Questions to Resolve → Answers: For EACH question from design.md, record:
- The answer that emerged during implementation
- How it was determined
c. Capture Surprises: Identify decisions made during implementation that were NOT in the original plan:
- "What did we decide that we didn't plan to decide?"
- "What changed from the original design?"
- "What unexpected requirements emerged?"
If a planned decision was NOT made during implementation, note it as unresolved and ask the user.
## Decisions Made
Rate limit storage → Redis
Chose Redis over in-memory. The app runs on 3 instances behind an ALB.
In-memory rate limiting would let users bypass limits by hitting different
instances. Redis adds ~2ms latency per check, but our p99 is already 180ms
so the overhead is negligible. Reused the existing ioredis client at
src/lib/redis.ts rather than adding a new dependency.
Limit granularity → Both IP and token
IP-only would block shared offices (NAT). Token-only would let unauthenticated
abuse through. Implemented tiered: 100/15min per IP for unauthenticated,
1000/15min per token for authenticated. The token tier uses the JWT sub claim.
429 response → Standard with Retry-After
Went with standard 429 + Retry-After header. Custom error body would require
updating all API clients. The Retry-After header is sufficient for automated
retry logic and doesn't break existing integrations.
Why good: Each decision has the WHAT (choice), WHY (rationale), and HOW
(specific implementation detail). References actual code and numbers.
## Decisions Made
- Used Redis for rate limiting
- Implemented per-IP and per-token limits
- Returns 429 status code
Why bad: Just restates WHAT was done. No WHY. No trade-off reasoning.
A future developer learns nothing about why these choices were made.
## Surprises (not in original plan)
Added X-Request-ID middleware — During implementation, discovered that
429 responses were impossible to debug without a request ID. Added
X-Request-ID header generation as a prerequisite in src/middleware/requestId.ts.
This wasn't in the plan but is essential for production debugging.
Follow-up: Should be extracted into its own WhySpec change if we add more observability.
Changed Redis key schema — Plan assumed simple key-value, but discovered
the sliding window algorithm needs sorted sets. Changed from ratelimit:{ip}
string keys to ratelimit:{ip} sorted sets with timestamp scores.
This affects the Redis memory profile — noted in Risks.
Why good: Documents unplanned decisions with full context. Notes follow-up items.
(No surprises section)
Why bad: Every implementation has surprises. If you didn't document any,
you weren't paying attention.
Generate ctx_.md in SaaS XML format
Write to <path>/ctx_<id>.md using the GitWhy SaaS format:
<context>
<title>Short title describing what was built and why</title>
<story>
Phase-organized engineering journal. First-person, chronological.
Capture the FULL reasoning — not a summary.
Phase 1 — [Setup/Context]:
What the user asked for, initial understanding, preparation work.
Phase 2 — [Implementation]:
What was built, key decision points encountered, problems solved.
Reference specific files and approaches.
Phase 3 — [Verification]:
How the work was verified, test results, manual checks.
</story>
<reasoning>
Why this approach was chosen over alternatives.
<decisions>
- [Planned decision] — [chosen option] — [rationale]
</decisions>
<rejected>
- [Alternative not chosen] — [why it was rejected]
</rejected>
<tradeoffs>
- [Trade-off accepted] — [what was gained vs lost]
</tradeoffs>
Surprises (decisions not in the original plan):
- [Unexpected decision] — [why it was needed]
</reasoning>
<files>
path/to/file.ts — new — Brief description
path/to/other.ts — modified — Brief description
</files>
<agent>claude-code (model-name)</agent>
<tags>comma, separated, domain, keywords</tags>
<verification>Test results and build status</verification>
<risks>Open questions, follow-up items, known limitations</risks>
</context>