Skip to content
Back to skills

Design Validation

ASecurity

Architectural validation checklist for feature approval. Load when validating that features fit into the existing architecture.

  • 15 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 20, 2026
businesspythonrustgogitsecuritydocumentation

Works with

  • cli

Security analysis

A100/100

Scanned October 5, 2026

npx -y skills add woditschka/agentic-coding-reference --skill design-validation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Design Validation?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Design Validation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/woditschka-design-validation/badge)](https://www.skillsdirectory.com/skills/woditschka-design-validation)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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 tier ladder and its value decides the initial. A `routine` rating opens the first implementation on the checklist-tier variant; `involved` opens it on the base implementer. An all-autofix fix round runs the variant on either rating. A build failure, a mixed round after a routine initial, or any substantive dissent after a routine fix retires the variant for the slice. 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 placement question, where a new business rule lands, is answered from the component's Contracts row in `docs/system-design.md` and the catalog in `docs/architecture-principles.md`. A neighboring rule's home assigns nothing; a recorded deviation covers the rule it names alone. Record the home for the rule's kind on the component's Contracts row, and name the write in `memory_updates`. Skip the write only when one brief row assigns the home and no other row reads against it. When the home changes behavior on a route or flow the slice's bullets do not name, the answer says so. Reviewers judge the landing layer against the briefs, never against this response.
6. 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.
7. 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; a placement answer follows step 5 instead.
- 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 § Language 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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…