From a69d0da22c41c29d8dd5b92a0d8e022f11a42a68 Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Tue, 18 Aug 2026 21:20:53 +1200 Subject: [PATCH] spec-driven-development: Spec Patterns That Pay Off (field-verified, Hatchmate SA- review) Claude-Session: https://claude.ai/code/session_01M2NgpY9AaKq6KsVmPXvG6L --- spec-driven-development.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec-driven-development.md b/spec-driven-development.md index f84b318..514b18e 100644 --- a/spec-driven-development.md +++ b/spec-driven-development.md @@ -206,6 +206,26 @@ Don't load everything — agents perform better with focused context than with a The spec→test→code workflow defines *when* to write tests. For *how* to write comprehensive tests — edge case discovery, property-based testing, mutation testing, AI agent testing patterns — see [Test-Driven Development](test-driven-development.md). +## Spec Patterns That Pay Off (Field-Verified) + +Patterns observed in specs that survived multi-wave agent implementation with near-zero rework (extracted from the Hatchmate `SA-` social-attribution spec review, 2026-08). Each turns an implicit convention into an explicit, testable artifact. + +1. **Per-operation authorization table when auth is enforced elsewhere.** When a module delegates permission checks to its caller (API layer), "the caller checks permissions" is not enough — include a table mapping *every* public function to the exact permission tuple the caller must enforce (resource_type, resource_id, noun, verb), including a row for deliberately unauthenticated operations. Without the table, each route author re-derives the mapping and they diverge. + +2. **Cross-spec error ownership: assert types, not messages.** When function A raises an error class owned by another spec, the requirement should instruct tests to import the class and assert `isinstance`, never the message string. One spec owns each message; everyone else asserts the type. Prevents cross-spec test breakage when the owning spec rewords a message. + +3. **Specify non-idempotency explicitly.** When repeated calls are *meant* to create new rows (e.g. one share row per promotion event), write a requirement saying so, with the rationale. Otherwise a reviewer or agent will "fix" it into idempotency and silently collapse distinct events. + +4. **Security filters live in the owning module, not the caller.** For a filter that is the sole gate on public exposure (e.g. `approved=true` on a public widget query), require it inside the query function itself and say why: *"removing it would require changing this module, not just the API layer."* Defense in depth expressed as a code-locality requirement. + +5. **Pin cross-module seams to exact signatures.** In Dependencies and in the requirements that cross a spec boundary, name the file path, function, and the argument list verbatim (`register_resource(resource_type=..., resource_id=..., actor=...)`). This is what makes the caller-side/callee-side consistency review mechanical instead of interpretive. + +6. **Projection requirements for public endpoints.** When an internal row is returned by a function but only some columns may appear in an unauthenticated HTTP response, state the allowed projection *and* the columns that must never appear (e.g. `customer_id must not appear in the public response`). The negative list is the test. + +7. **Every column must be reachable from a requirement.** A data-model column no requirement reads or writes (a dangling `is_active`) invites agents to invent behaviour. Either write the requirement, or label the column/table explicitly as a future-phase stub ("no Phase 1 logic touches this") — the stub label is itself testable. + +8. **Validate stored URLs at every ingress, not just the obvious one.** If a URL column is served on a public surface, *every* function that writes it applies the same scheme/host validation — including late setters like "mark delivered" flows, not only the create path. Audit: for each URL/HTML-adjacent column, list the functions that can write it and check each validates. + ## Post-Write Spec Audit After writing specs, audit them against best practices before implementation. Common gap categories: