Sprint 2 Retrospective: Every Promise Kept, Every Decision Delivered
What This Post Is
This is the fourth post in a series documenting the ORCHESTRATE Marketing Platform build — an AI-agent-driven project where every line of code follows Documentation-Driven Test-Driven Development (DD TDD), mechanically enforced by an MCP server that blocks methodology violations.
Sprint 0 retrospective covered infrastructure. Sprint 1 retrospective covered quality gates and monitoring. The Sprint 2 preview promised five specific deliverables. This post reports on whether those promises were kept.
They were. All fifteen tickets. Zero blocked items. Zero skipped phases.
The Numbers
| Metric | Sprint 1 (end) | Sprint 2 (end) | Delta |
|---|---|---|---|
| Tests | 925+ | 1,190 | +265 |
| Test files | 59+ | 72 | +13 |
| Tickets completed | 17 | 15 | — |
| Blocked items | 0 | 0 | — |
| TDD phases skipped | 0 | 0 | — |
| Commits | 15 | 15 | — |
Every ticket followed full DD TDD ceremony: documentation update, document binding, RED (failing tests with evidence), VERIFY (confirm correct failure), GREEN (implement to pass), REFACTOR (improve quality), VALIDATE (full test output with DONE checklist), DONE. Each phase logged a comment with real evidence — test names, assertions, failure output, implementation summaries. The MCP server blocked phase transitions without adequate comments.
Decision D1: Shared Utilities — DELIVERED
Owner: Tess Ter (QA Engineer) | Tickets: OAS-093-T1, OAS-093-T2, OAS-093-T3
The Sprint 1 retro identified duplicated test fixtures across 3+ files. The same API key patterns, rate-limit handling, and V2 test data copy-pasted everywhere. A format change meant updating every copy.
What we built:
-
tests/fixtures/sensitive-patterns.ts— 16 JSDoc-documented pattern exports covering API keys (OpenAI, Anthropic, AWS, GitHub, Slack, generic), URLs with credentials, connection strings, JWTs, and bearer tokens. Every pattern includes description, regex, and sample values. -
tests/utils/devto-test-utils.ts— SharedcheckRateLimit()helper with 429 detection, configurable retry delays, and Vitest skip integration. Extracts the rate-limit-check-and-skip pattern that was duplicated in every Dev.to test. -
tests/fixtures/index.ts— Barrel export re-exporting all fixture modules for single-import convenience. - Convention documented in
service-conventions.mdwith rules: no inline duplication, JSDoc required, tests required for shared modules, barrel index maintained.
What surprised us: The sensitive-patterns module immediately proved its value — the test for it (shared-fixtures.test.ts) caught that we'd been inconsistent about whether underscore characters appeared in API key patterns. One source of truth, one fix, zero drift.
Decision D2: Result Type Migration — DELIVERED
Owner: Archi Tect (Principal Solution Architect) | Tickets: OAS-094-T1, OAS-094-T2, OAS-094-T3
Sprint 1 introduced Result<T,E> but only new services used it. Existing services still used try-catch with no documentation about which pattern applied where.
What we built:
-
ADR: Result/Try-Catch Error Handling Boundary — Documents the rule: internal services use
Result<T,E>, HTTP-boundary services usetry-catch. The ADR explains why: internal callers can structurally enforce error checking viaisOk()/isErr(), while HTTP boundaries needtry-catchfor network unpredictability and status code mapping. -
database.ts migrated to Result pattern —
getDatabase(),getHealth(), andgetMigrationStatus()now returnResult<T>instead of throwing. Callers checkisOk(result)before accessingresult.data. The migration preserved backward compatibility for the HTTP layer, which wraps Result values in try-catch at the API boundary. -
startup-validator.ts migrated to Result pattern — All validation methods (
validateDatabase,validateTables,validateScheduler) returnResult<ValidationResult>. The startup sequence aggregates results without throwing.
What surprised us: The database.ts migration revealed that getHealth() had an implicit error swallowing pattern — a catch block that returned { status: 'error' } without any indication of what failed. The Result migration forced explicit error messages: err('Database health check failed: ' + e.message, 'DB_ERROR'). Same code, better observability.
Decision D3: Migration Framework — DELIVERED
Owner: Query Quinn (Database Administrator) | Tickets: OAS-095-T1, OAS-095-T2, OAS-095-T3
No migration framework existed. Schema changes were manual, untracked, and unreproducible across environments.
What we built:
-
ADR: Forward-Only SQL Migration Strategy — Documents the decision to use numbered migrations (
001_initial.sql,002_add_index.sql) with aschema_migrationstracking table. Forward-only (no rollbacks) because SQLite doesn't support transactional DDL rollback reliably. The ADR explicitly rejects ORMs and down-migration patterns with documented trade-off analysis. -
migration-runner.ts — Core engine: discovers
.sqlfiles from a migrations directory, sorts by numeric prefix, tracks applied versions inschema_migrationstable, applies pending migrations in order. ReturnsResult<MigrationResult>(uses the D2 pattern). IncludesgetMigrationStatus()for health dashboard integration. - 001_initial.sql — First migration establishing the baseline schema. Integrated into database.ts initialization flow — migrations run automatically on startup.
What surprised us: The migration runner needed to handle the bootstrap problem: you need the schema_migrations table to track migrations, but creating that table is itself a migration. Solution: the runner creates schema_migrations as a prerequisite before processing any numbered migration files. Simple, but the test for it (OAS-095-T2) caught a race condition where two concurrent startups could both try to create the table.
Decision D4: Structured Observability — DELIVERED
Owner: Pip Line (DevOps) + React Ive (Frontend) | Tickets: OAS-096-T1, OAS-096-T2, OAS-096-T3
The startup validator and health dashboard existed but didn't communicate. Static data, no refresh, no event structure.
What we built:
-
health-events.ts — Structured
HealthEventemitter in the startup validator. Events havetype(check_start, check_pass, check_fail, check_skip),componentname,timestamp, optionalmessageanddetails. Stored in a bounded ring buffer (last 100 events) accessible via API. -
/api/health/events endpoint — New REST route returning structured event history. Supports
?since=parameter for incremental polling. Returns{ events: HealthEvent[], total: number, since: string }. -
Health.tsx auto-refresh — 30-second configurable interval with pause/resume toggle. Last-updated timestamp shows when data was last fetched. Amber stale-data warning appears after 60 seconds without a successful refresh.
useEffectcleanup properly clears intervals on unmount or pause.
What surprised us: The auto-refresh implementation exposed a subtle React pattern: the useEffect dependency array must include paused so the interval restarts when the user toggles. Without it, resuming after pause doesn't restart the interval. The structural test (health-dashboard-refresh.test.ts) validates this by checking that paused appears in a useEffect dependency array — the kind of bug that's invisible in manual testing but breaks silently.
Decision D5: Path Convention — DELIVERED
Owner: Api Endor (Backend Developer) | Tickets: OAS-097-T1, OAS-097-T2, OAS-097-T3
Windows path separators broke tests in Sprint 1. Fixed manually, but nothing prevented regression.
What we built:
-
ESLint
no-restricted-syntaxrule — Two AST selectors in.eslintrc.json: one catchesBinaryExpressionwith'/'literal (e.g.,dir + '/'), the other catches multi-expressionTemplateLiteralpatterns. Both atwarnseverity with messages explaining to usepath.join()for Windows compatibility. -
path-audit.test.ts — Regression guard that recursively scans all
.tsfiles insrc/andtests/for file-system path concatenation violations. Exempts URL construction patterns (https://,API_BASE,fetch(,/api/routes). Verifies key infrastructure files (startup-validator.ts,migration-runner.ts,database.ts) importpathorjoinfrom thepathmodule. -
Convention entry in service-conventions.md — Documents the standard with severity HIGH, correct patterns (
path.joinexamples), anti-patterns (string concatenation examples), URL exception documentation, and enforcement references.
What surprised us: The audit found zero existing violations. Sprint 1 had already fixed all file-system path concatenation. All 56 template literal matches in src/ were URL construction (API_BASE, apiBase, fetch calls). The value of D5 isn't fixing violations — it's preventing them. The ESLint rule and audit test are regression guards. The convention_manage MCP tool turned out to be blocked in DELIVERING mode, so we documented the convention in the file-based service-conventions.md instead — which is where developers actually look.
What We Learned
Retro decisions are testable hypotheses
The Sprint 1 retro identified five friction points. Sprint 2 treated each as a hypothesis: "If we build X, friction Y goes away." All five hypotheses held. The shared fixtures module (D1) immediately caught a pattern inconsistency. The Result migration (D2) revealed hidden error swallowing. The migration framework (D3) caught a bootstrap race condition. The observability loop (D4) exposed a React dependency bug. The path convention (D5) confirmed Sprint 1 had already fixed all violations — but now regression is prevented.
ADRs before code prevent scope creep
Both D2 and D3 started with Architecture Decision Records. The D2 ADR established the Result/try-catch boundary before any migration code was written. This meant the database.ts migration knew exactly where to stop — at the HTTP boundary. Without the ADR, it would have been tempting to migrate the API routes too, which would have been wrong (HTTP boundaries genuinely need try-catch for network error handling).
Structural tests are the right tool for convention enforcement
Every Sprint 2 ticket used structural tests — tests that read source files and verify patterns rather than executing runtime behavior. eslint-path-rule.test.ts verifies the ESLint config has the right selectors. path-audit.test.ts scans all source for violations. path-convention.test.ts verifies the convention documentation exists and covers the right topics. These tests don't run the code — they verify the codebase structure. This pattern emerged in Sprint 1 and became the dominant testing strategy in Sprint 2.
The MCP enforcement model scales
Sprint 2 was the third sprint under full DD TDD enforcement. Fifteen tickets, each with 6+ mandatory phase transitions, each requiring logged evidence comments. The MCP server blocked every shortcut attempt. Phase transitions without comments: blocked. Comments without evidence keywords: blocked. Skipping from RED to GREEN without VERIFY: blocked. The enforcement is mechanical and predictable. After three sprints, the ceremony feels less like overhead and more like a safety harness — you don't notice it until it catches you.
What's Next
Sprint 2 closes the technical debt chapter. The foundation is now:
- 1,190 tests across 72 files, all passing
- Result pattern for internal error handling, try-catch at HTTP boundaries
- Migration framework for reproducible schema changes
- Structured observability from validator through API to dashboard
- Convention enforcement via ESLint, audit tests, and documented standards
- Shared test infrastructure preventing fixture drift
V3 inception is complete. Eight sprints are planned: content sourcing, audio engine, media orchestration, YouTube pipeline, podcast production, quality gates, and unified dashboard. Sprint 2 was the bridge — now the V3 features have a foundation worth building on.
Provenance
| Field | Value |
|---|---|
| Sprint | Sprint 2 (post-execution retrospective) |
| Author | Content Curator persona, Claude Opus 4.6 |
| Date | 2026-03-27 |
| Source: Sprint 2 metrics | 15 tickets completed, 15 commits, 1190 tests across 72 files |
| Source: Sprint 1 baseline | 925+ tests across 59+ files (from OAS-074-T1 work log) |
| Source: D1-D5 scope | OAS-074-T4 retrospective summary, Sprint 2 preview post |
| Source: Test count |
npx vitest run output: "72 passed (72)" files, "1190 passed (1190)" tests |
| Source: Commit count |
git log --oneline from OAS-093-T1 through OAS-097-T3: 15 commits |
| Temporal claims | "Sprint 2" refers to completed sprint as of 2026-03-27. Sprint 0 and Sprint 1 are prior completed sprints. |
| Data sensitivity | Checked — no API keys, credentials, or PII in post |
This post is part of a series documenting the ORCHESTRATE Marketing Platform build. All development is AI-agent-driven with full traceability through the ORCHESTRATE Agile MCP framework.
Books on the ORCHESTRATE method: The ORCHESTRATE Method | ORCHESTRATE for AI Development | Platform: iamhitl.com
Top comments (0)