Architectural validation checklist for feature approval. Load when validating that features fit into the existing architecture.
Scanned 9/20/2026
Install to Claude Code
npx -y skills add woditschka/agentic-coding-reference --skill design-validation --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Design Validation?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/woditschka-design-validation-b95f71b1)More formats (shields.io, HTML) on the badges page.
---
name: design-validation
description: >-
Architectural validation checklist for feature approval.
Load when validating that features fit into the existing architecture.
compatibility:
- claude-code
- github-copilot
- opencode
reads:
- docs/architecture-principles.md
- docs/security-principles.md
- docs/system-design.md
- docs/prd.md
- docs/ubiquitous-language.md
metadata:
version: "1.0"
author: team
---
Read [`pipeline-contract.md`](pipeline-contract.md) first — the pipeline position (the two demand-driven modes plus the fix dispatch) and the input contract (the four dispatch shapes, the read discipline, the supersession rule). It ships beside this file from the harness core.
## Triage Mode
When dispatched on a `prd-entry`, your task is to decide one of six verdicts and append a `design-block` record. Read durable memory first, then judge.
### Read durable memory (every triage)
Always read, in this order:
1. `docs/system-design.md` — current architectural state, invariants, patterns, and § Scale and Load, the sizes and access patterns a slice is designed against.
2. `docs/adr/` — the decision log behind the design doc's lines, for humans first. Read an ADR by back-link when the slice touches a line that cites it, in `docs/system-design.md` or in the PRD's Non-Goals rows; the directory is never a routine read.
3. `docs/ubiquitous-language.md` — project vocabulary, terms to avoid.
4. The active `prd-entry` and any prior `design-block` records for the same `req_id` (the slice trail).
This prefix is stable across triage dispatches — caching it pays off. The variable part is the new slice's prd-entry.
### Five-signal foundational check
Before settling on a verdict, run a quick gate. If **any** signal trips **and** the current slice's concerns touch the gap, the verdict is `foundational`:
1. `docs/system-design.md` is empty, contains only template scaffolding, or carries only *derived* provenance marks with no confirmed statement (the `derive-briefs` skill's forms).
2. No ADR records the language/framework choice or the overall architecture shape (modulith, CLI, library, service).
3. `docs/ubiquitous-language.md` has no domain terms (only the header comment).
4. The slice touches a project-level concern (persistence, security, error-flow, configuration, logging) that has no project-level pattern recorded.
5. The slice introduces a new bounded context not reflected in current durable memory.
Foundation is demand-driven: do not commit foundation work for concerns the current slice does not touch. Other slices will surface those later.
### Verdict criteria
Every implementing verdict also places the slice's requirement id in the design doc's Contracts rows — the deterministic `contracts-sync` gate check reads that presence, and the implementer may not edit `docs/`.
| Verdict | When | What you write |
|---|---|---|
| `covered` | Existing durable memory handles the slice unchanged. | `design-block` with `architectural_fit` summarizing which sections cover it; `primary_paths` for the implementer. The only `docs/` edit is the requirement id joining its Contracts rows. |
| `minor` | Existing pattern with a small adjustment (a parameter, an extension point, a thin layer). | `design-block` with the adjustment described; possibly a small `system-design.md` edit. |
| `new` | Genuinely new design ground for this slice — new pattern, new module, new integration. | `design-block` plus `system-design.md` updates and (when the `adr-template` skill's § When to Create an ADR applies) an ADR. |
| `foundational` | Five-signal check tripped on a concern the slice touches. | Append a `consultation-request` targeting `human` with the unrecoverable foundational question(s); root interviews the user (`agentic-harness.md` § Conversations Stay in Root) and the response re-dispatches you. Then write `system-design.md`, possibly ADRs, possibly seed `docs/ubiquitous-language.md`. Settle the slice's own assessment (`new`/`minor`/`covered`) inside the record: `verdict` stays `"foundational"`, the assessment and pointers go in `architectural_fit`, the durable-memory writes in `notes`. A returned decision that contradicts existing durable memory instead surfaces as `verdict: "conflicting"`, never silently. |
| `conflicting` | The slice cannot be honored without contradicting current design or an ADR. | `design-block` with `verdict: "conflicting"` and an `escalations` array naming the contradiction. `route` blocks (`design-conflict`) and surfaces the escalations to the user; typical remediation is a non-goal ADR or a PRD revision. |
| `refactor-first` | An independently-meaningful refactor must land before this slice can be implemented (existing abstraction is wrong; forcing the slice through would ship a non-orthogonal extension or fold refactor + feature into one cycle). The refactor must have a one-sentence behavioural justification — not for incidental cleanup the implementer can fold into TDD Refactor steps. | `design-block` with `verdict: "refactor-first"` PLUS a sibling refactor `prd-entry` (new `req_id`, minted per the `prd-authoring` skill's ID rule, scoped to the refactor only). The refactor runs first (`route` escalates the ordering); `refactor-resume` re-triages the original via a new `design-block` with `supersedes_record_at` after the refactor completes. |
Match dialogue depth to verdict. `covered`/`minor` triggers no user dialogue. `new` may surface a single trade-off question. `foundational` is a multi-question interview about unrecoverable choices, run by root between your two dispatches.
A `covered` (or `minor`) verdict asserts that an existing symbol or pattern handles the slice — a resolution claim, not a text match. The `architectural_fit` that names the covering code cites the lookup that resolves it: {{FILL: the stack's semantic oracle call, where one is bound}}. Without an oracle, cite the grep and label it the weaker basis.
Pick the verdict by the question it answers, not by the row whose wording is closest. `covered`: does durable memory already handle this unchanged? `minor`: does one small adjustment suffice? `new`: is this fresh ground worth recording? `foundational`: is a project-level decision missing that the slice needs? `conflicting`: does honoring the slice contradict a committed decision? `refactor-first`: must the ground be reshaped before the slice can land cleanly? A slice that sits between two verdicts belongs to whichever question it truly answers — making that judgment is the point of having six verdicts instead of a checklist.
**Effort rating.** Every implementing verdict also carries `implementation_effort`: `routine` when the code work follows a pattern an adjacent seam already demonstrates, `involved` for novel or subtle work. The rating is orthogonal to the verdict — it rates the code work, the verdict rates the design delta. The rating's presence activates the effort ladder and its value records the judgment; initial implementations always run the base implementer, and only all-autofix fix rounds run the effort variant (reduced effort where the tool has an effort knob). An absent rating leaves the ladder inactive for the slice (`handoff.py tier` prints the derivation). The field is optional in the schema: rate every implementing verdict when authoring; a pre-ladder record lacks it and reads as `involved`.
### Foundational triage: vocabulary extraction on adoption
When the project being triaged has substantial existing docs and source code (i.e., it's being adopted by the harness rather than greenfield) and `docs/ubiquitous-language.md` is empty, extract a candidate vocabulary before appending the `consultation-request`:
1. Scan `docs/` for recurring domain terms.
2. Scan source code for domain types — value objects, aggregate roots, repositories — and the entity names they encode.
3. Identify variations and aliases (same concept named different ways across files).
4. Propose a candidate term list with one-line definitions and `Avoid:` lines for the alias variants you found.
5. Include the candidate list in the `consultation-request` with the foundational questions; root presents it for confirmation, refinement, and additions.
6. On the re-dispatch, write the confirmed set to `docs/ubiquitous-language.md` (this is the one path where you write to that file — usually owned by product-requirements-expert; the seeding case is the exception).
On a fresh project (no substantial code yet), the vocabulary seed is whatever the user names during root's interview — much shorter.
### Foundational resume
The `consultation-response` (author `human`) re-dispatches this agent with the decisions. Write them as durable memory, then run the slice's own triage in the populated context. Judge the returned decisions like any triage input: one that conflicts with durable memory surfaces as `conflicting`, never records silently. One that only restates the request text decides nothing: re-raise the questions as a fresh `consultation-request` instead of proceeding.
## Consultation Mode
When dispatched on a `consultation-request`, your task is to answer the specific question and append a `consultation-response`. The router returns control to the requester after your response.
### Process
1. Read the consultation-request (`question`, `context`, `stop_state`).
2. Read durable memory (same as triage — `system-design.md`, ubiquitous-language, and the ADRs the touched lines cite).
3. Locate the relevant pattern, decision, or constraint that answers the question. Most consultations are pointer-to-pattern, not new design.
4. If the question reveals genuine new design ground the slice's triage didn't anticipate, decide whether to crystallize it now or defer. Crystallize when:
- The decision affects more than this consultation (other slices will face it),
- The choice is hard-to-reverse,
- The pattern is non-obvious from existing memory.
5. A request asking to carry a PRD change into the design doc is answered by writing the mirrored rows and naming them in `memory_updates`. The change is a new edge case, a known defect, or a Done-when bullet; the rows are Known Defects, Contracts, or Invariants. A change wider than rows is a re-triage through a superseding `design-block` instead.
6. Append a `consultation-response` record with the answer, and any `memory_updates` describing durable writes that accompanied this consultation.
### What not to do in consultation mode
- Do not re-triage the entire slice — that's not what was asked.
- Do not produce a new `design-block` record; consultation is a substep, not a handoff.
- Do not over-write memory. If the answer points to existing patterns, `memory_updates` is empty.
- Do not exceed the question. Broad questions belong in triage, not consultation.
## Autofix Audit (Run First on Every Triage Dispatch)
Dispatches that end in a `design-block` — triage, re-triage, and fix — run this audit. Consultation-mode dispatches are exempt: consultation produces no `design-block`, so a finding would have no record to ride.
Before working on the active prd-entry, audit every `type: "design-doc-autofix"` record in `.scratch/handoff.jsonl` whose `ts` is later than your most recent `type: "design-block"` record (or any such record if you have not yet been dispatched for the active `req_id`). `handoff.py audit-autofix` (the `code-quality-gate` skill's autofix audit) has already re-checked the allowlist bounds mechanically; your job is the judgement check.
For each record, decide whether the change is legitimately mechanical:
- **Legitimate.** Writing-standards or structural fix that doesn't smuggle in a semantic shift. Common shape: sentence shortened, anchor added, code-fence language tag added.
- **Illegitimate.** The change reads as mechanical but moves architectural meaning — e.g. a "shortened sentence" drops a constraint, a "broken link fix" repoints to a different anchor that means something different, a "writing-standards" rewrite changes a definitional claim. These are substantive changes that escaped via mis-tagging.
For every illegitimate record:
1. Append a finding to your forthcoming `design-block` record's `notes` (or `risks` if you want it surfaced more loudly): `"autofix-rejected: <handoff.jsonl line N>: <reason>"`.
2. Recommend a corrective edit in the same `design-block` (you have write access to design docs; apply the correction yourself).
3. `handoff.py audit-autofix` re-checks bounds on every gate run; repeat offenders surface as `design-doc-autofix` audit failures and bounce back to system-design-expert for revert-or-redo.
If every audited record is legitimate, skip silently — no entry needed.
If `.scratch/handoff.jsonl` contains no `design-doc-autofix` records, skip silently.
## Output Contract
### Triage dispatch: append a `design-block` record
Schema: [`schemas/scratch/design-block.schema.json`](../../../schemas/scratch/design-block.schema.json).
**Required fields:**
| Field | Type | Notes |
|---|---|---|
| `type` | `"design-block"` | Discriminator. |
| `req_id` | string `^REQ-[A-Z]+-[0-9]{3}$` | Same as the prd-entry being implemented. |
| `ts` | ISO 8601 string | Stamped by `append`; never composed by the author. |
| `author` | `"system-design-expert"` | Pinned. |
| `verdict` | enum | `covered`, `minor`, `new`, `foundational`, `conflicting`, `refactor-first`. See the Verdict criteria table above. |
| `architectural_fit` | string | How the slice integrates with current durable memory. References `docs/system-design.md` sections when relevant. |
| `primary_paths` | array of paths | At least one. The starting target set for the implementer. With `supporting_paths`, also the record of every design-doc path this dispatch wrote: `append` refuses a block that leaves an uncommitted `docs/system-design.md` or `docs/adr/*` change uncovered. |
**Optional fields:** `implementation_effort` (`routine` | `involved` — see Effort rating above; absent reads as `involved`), `supporting_paths`, `integration_points`, `patterns` (each `{ref, description}`), `risks` (each `{risk, mitigation}`), `escalations` (required when `verdict == "conflicting"`), `supersedes_record_at` (line number of the prior design-block this revision supersedes, e.g. when revising after a build-failure; set after the first build-pass, it resets the review cycle: prior approvals and dissent are void and the full battery re-runs. Set before it with the same verdict and effort, it is a correction of record and triggers nothing), `notes`.
**Doubles per boundary.** For each boundary the slice's tests cross, an `integration_points` entry names the double. The boundaries are a repository, an external service, or the framework transport. The double is a real implementation, a hand-written double, or a framework stub with the reason neither fits. The brief's mocking policy is the ceiling. The implementer follows the entry and the test reviewer holds every new stub to it; a stub neither the block nor a later `consultation-response` names is a finding.
**Binding per entry point.** For each new entry point that binds a request, an `integration_points` entry names the binding target: a request-scoped object, or a persisted type behind an allow-list. A persisted type bound whole is never a design. The implementer follows the entry; the security reviewer holds the handler to the same law in `security-checks`, which needs no record.
**Field weight by verdict.** For `covered`, `architectural_fit` is a one-line pointer to existing sections and most optional fields are empty; `integration_points` still names a double for each boundary the tests cross and a binding target for each new entry point. `risks` still cites the § Scale and Load row for each path that scales with data. For `minor`, expect a short adjustment in `architectural_fit` and possibly a small `system-design.md` update. For `new` and `foundational`, expect full content — integration points, patterns, risks — plus accompanying writes to `docs/system-design.md` and possibly `docs/adr/`. For `conflicting`, `escalations` is required. For `refactor-first`, `architectural_fit` names the abstraction mismatch and the refactor's one-sentence behavioural justification, and the dispatch also appends a sibling refactor `prd-entry` record (the refactor runs first; the original slice resumes via a re-triage `design-block` with `supersedes_record_at` after the refactor completes).
**Risks cite principles.** A recorded risk, mitigation, or integration point is the implementer's briefing, derived from durable memory for this slice: it cites the principle-brief section or the `docs/system-design.md` row it instantiates. A risk that cites nothing is the signal that a principle is missing — record the principle in the brief that owns it, never the slice's detail in `docs/system-design.md`. Reviewers judge against the briefs, not this record (`review-workflow` § Reviewer Read-Set (Fresh Eyes)), so the briefs must carry every rule a reviewer needs. A `new` or `foundational` triage that establishes a principle writes it to the owning brief in the same dispatch; the rule bars slice detail, not the principle.
### Consultation dispatch: append a `consultation-response` record
Schema: [`schemas/scratch/consultation-response.schema.json`](../../../schemas/scratch/consultation-response.schema.json).
**Required fields:** `type`, `req_id`, `ts` (stamped by `append`; never composed by the author), `author`, `in_response_to` (line number of the matching consultation-request), `answer`.
**Optional fields:** `memory_updates` (array of `{path, summary}` describing durable-memory writes that accompanied this consultation; usually empty), `notes`.
### Append-only discipline (both dispatch types)
Append your record via `python3 scripts/handoff.py append <type>` — it validates against the schema and writes canonically (`handoff-append` skill). Never edit, reorder, or delete prior records — `supersedes_record_at` is how you correct a prior decision.
### Example Records
`design-block` for a `covered` verdict (most slices on a mature codebase):
```json
{"type":"design-block","req_id":"REQ-XX-099","author":"system-design-expert","verdict":"covered","architectural_fit":"Cache miss diagnostics fit the existing per-agent rate pattern in report/summary (§3.4 of system-design); no new module or pattern needed.","primary_paths":["report/summary","report/summary.test"]}
```
`design-block` for a `new` verdict (genuinely new design ground):
```json
{"type":"design-block","req_id":"REQ-XX-099","author":"system-design-expert","verdict":"new","architectural_fit":"Cache miss diagnostics live in the report layer alongside existing per-agent rates; new sub-module report/cachemiss/ introduced to encapsulate the calculation.","primary_paths":["report/cachemiss/cachemiss","report/cachemiss/cachemiss.test"],"supporting_paths":["cache/measure"],"integration_points":["summary report row gains a cache_miss_rate column derived from cache/measure"],"patterns":[{"ref":"report/summary:120","description":"existing per-agent rate computation pattern"}],"risks":[{"risk":"divisor zero when cache_eligible_token_count is 0","mitigation":"emit null with insufficient_data flag (architecture-principles § Domain Core)"}]}
```
`consultation-response`:
```json
{"type":"consultation-response","req_id":"REQ-XX-099","author":"system-design-expert","in_response_to":42,"answer":"Use the existing rate-computation pattern from report/summary:120. The cache_miss case is structurally identical to per-agent rates — same divisor-zero handling, same null-on-insufficient-data convention.","memory_updates":[]}
```
## Documentation Discipline
When updating `docs/system-design.md`, follow the state-vs-history split: the doc captures *current state* only; the *why* lives in ADRs.
| Pattern | Severity | Fix |
|---|---|---|
| "Why" prose in `docs/system-design.md` (paragraphs explaining a decision's rationale) | Critical | Move to a new ADR or extend an existing one; replace with a short rule + ADR back-link |
| Imperative line in `docs/system-design.md` (Do/Don't/Always/Never/Require) without an ADR back-link | High | Add the ADR link inline; if no ADR exists, write one before landing the rule |
| Trade-off discussion in `docs/system-design.md` | High | Move to the ADR's Decision + Consequences sections |
| Resolve domain terms against `docs/ubiquitous-language.md` | — | Use canonical ubiquitous-language terms in `architectural_fit` and `notes`; add new terms to the ubiquitous-language doc when introducing them |
The split is the kernel state-vs-history property: `docs/system-design.md` carries current state; `docs/adr/` carries the path to each decision. `document-writing` enforces the ADR back-link rule on every imperative line.
**Pre-handoff self-check.** Before appending the record, re-read every brief line the dispatch wrote or changed. Three checks: state statements only in `docs/system-design.md` — no causal clause; a rationale worth keeping becomes an ADR with a back-link (an ADR's own Rationale stays causal by design). Every sentence within the 30-word writing standard. Provenance marks preserved (`document-writing` § When Editing a Derived Brief). A violation caught here costs one edit; caught by the doc-reviewer it costs the slice a review round.
## Design Principles
Apply the principles in `docs/architecture-principles.md` § Design Principles when evaluating features. The brief is project-owned: enforce the project's principles as written, not a remembered list. If the brief contradicts itself or the codebase, raise a brief-defect finding instead of silently picking a side.
## Validation Checklist
Before approving a feature for implementation:
### Architectural Fit
- [ ] Feature aligns with project goals
- [ ] Feature not declined in Non-Goals or retired in Superseded
- [ ] Module placement follows the existing structure (the production roots declared in `scripts/layout.toml`)
- [ ] No reach into another module's internal packages
- [ ] Error handling follows the error-flow rule in `docs/architecture-principles.md`
- [ ] New types follow existing naming conventions
- [ ] No circular dependencies between modules
- [ ] Integration points identified
- [ ] New dependencies from approved sources (see `docs/system-design.md`); ADR required for exceptions
### DDD Alignment
The closed-kernel checks below hold in every project. Every other tactical choice conforms to `docs/architecture-principles.md` as written, not a remembered default — see that brief for the full pattern catalog.
- [ ] Value objects immutable, equal by value; invariants enforced at construction
- [ ] Aggregates are the consistency boundary: entered only through the root, referenced by identity
- [ ] Domain core free of infrastructure logic; business logic in the model, not orchestration
- [ ] Dependencies flow inward (infrastructure → service → domain)
- [ ] Anti-corruption guards every boundary the project does not control; an owned, closely-tracked model may be mapped directly
- [ ] All other tactical choices — mapping, persistence, ACL, annotation, aggregate granularity, naming — conform to `docs/architecture-principles.md` as written
### Security by Design
See `docs/security-principles.md` — the project's trust-boundary map (§ Trust Boundaries) and the stack's high-bar defaults (its Realization table). Validate the design against the brief, not a remembered list:
- [ ] Every trust boundary the slice introduces or crosses is identified, with validation placed at it
- [ ] Secrets stay out of logs, errors, URLs, and process arguments
- [ ] The design grants least privilege and fails closed on error
- [ ] The vulnerability classes the brief flags for this stack are addressed where the slice touches them
### Fit for the Workload
See `docs/system-design.md` § Scale and Load — the rows implementer and reviewer both select and judge against (`tdd-principles` § Fit for the Workload). Write the row, never a private estimate:
- [ ] Each path the slice adds that scales with data has a row: dominant operations, realistic size, access pattern, and the chosen form. A hot-path row also states the form's time and space bound with its kind: worst-case, average, or amortized. A new row is a `minor` verdict at least; `covered` cites an existing row only. A figure nobody knows is written as "unrecorded, treated as bounded", so the simplest correct form is the recorded design until the owner corrects the row
- [ ] A `risks` entry cites the row: `risk` names the path and its row, `mitigation` names the form; a bounded path's mitigation is "simplest readable form"
- [ ] A structure or algorithm the design hand-writes where a library offers one is a recorded exception: an ADR, and the row's Form column linking it. Security-sensitive code is never hand-written
### Reliability by Design
- [ ] Failure modes enumerated
- [ ] Timeouts specified for all blocking operations
- [ ] Resource limits defined (buffers, connections); the budgets themselves live in § Scale and Load
- [ ] Graceful shutdown / cancellation of long-running work specified
### Understandability
- [ ] Component can be understood in isolation
- [ ] State changes are explicit
- [ ] Interfaces are minimal and typed
- [ ] No implicit dependencies
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!