Schema reference
Generated from the live contract module — always in sync with the editor and skill.
Overview
A SpecPad project lives in docs/specpad/ inside your git repository and
consists of up to five JSON files:
| File | Purpose |
|---|---|
[name].proj.json |
Project index — names the system and lists the SRS/VTP documents that belong to it. |
[name].srs.json |
Software Requirements Specification — ordered requirements and section headings. |
[name].vtp.json |
Verification Test Plan — ordered tests, each linking to the requirements it verifies. |
[name].releases.json (sidecar) |
Release manifest — one entry per git tag, with snapshot paths for change-tracking. |
[name].job.json (sidecar) |
Current-job marker — the active ticket or issue number attributed to in-flight spec changes. |
Snapshot files are cached under docs/specpad/.specpad/; that sub-folder is
regenerable from git history and is never the source of truth. Each release entry carries
a snapshot path (or null if not yet cached) pointing to the
corresponding raw document copy.
Schema versioning
Every file carries a schemaVersion field. Version "1.0" maps to
the pinned editor build at /v01/. When a new schema version ships, its
editor build is deployed at a new path (e.g. /v02/) and the old path remains
live forever — so existing documents always open in an editor that understands them.
What is and isn't stored
Git owns history. The files contain no modifiedBy,
modifiedDate, author, or change-list fields — git commits carry that context.
Nothing derived is stored. Test counts, pass/fail roll-ups, and change diffs
are computed on read from the raw JSON; storing them would create a drift hazard.
Field reference
Field descriptions are authoritative — they are read directly from the JSON Schema definitions at build time. A missing description is a build error.
project
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "project" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Short system name; also the filename stem ([name].proj.json). |
title |
string |
required | Human-readable project title shown in the editor. |
description |
string |
optional | Optional free-text summary of the system under specification. |
editorBaseUrl |
string |
optional | Optional base URL the generated launcher opens (e.g. "https://specpad.internal.corp" for a self-hosted server). Absent uses the public hosted editor; the version path is always derived from schemaVersion. |
editorProjectId |
string |
optional | Optional project id on a self-hosted server that hosts several projects, so this repository's launcher opens its own project. Absent opens the server's only project. |
safetyClass |
"A" | "B" | "C" |
optional | The software safety class (IEC 62304 4.3). Declared, not derived: authoring stays at maximum rigor whatever it says, so this records the judgement rather than switching content off. Material beyond the declared class is highlighted as advice, never dropped. |
safetyClassRationale |
string |
optional | Why that class — the injury the software could contribute to, and the reasoning that places it. 4.3 asks for the rationale as much as the classification. |
enforce |
array of string |
optional | Advisory governance rules this project has chosen to be held to. Naming a rule here moves its findings from advice into the blocking result, so a team adopts a practice when ready rather than on the day the rule ships. |
documents |
array of objects |
required | The SRS and VTP files that make up this project. |
documents[].type |
"srs" | "vtp" | "prd" | "sdd" | "risk" | "soup" | "threat" |
required | Which kind of document this entry points at: "srs", "vtp", "prd", "sdd", "risk", "soup", or "threat". |
documents[].path |
string |
required | Path of the document file, relative to the project index. |
documents[].title |
string |
required | Display title for the document. |
prd
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "prd" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Short system name; also the filename stem ([name].prd.json). |
title |
string |
required | Human-readable document title. |
items |
array of objects |
required | Ordered list of product requirements and section headings. |
items[].id |
string |
required | Stable machine identifier, generated once and never changed; SRS satisfies references target it. |
items[].code |
string |
optional | Human-facing label (e.g. "PROD-1"); freely renameable because references never use it. |
items[].text |
string |
required | The product requirement / user need statement. |
items[].heading |
boolean |
optional | True when this item is a section heading rather than a product requirement. |
items[].level |
integer |
optional | Indent depth for hierarchy; absent means 0. Headings form dotted section codes. |
items[].status |
"proposed" | "implemented" |
optional | Lifecycle: "implemented" (realized — must trace down to >=1 SRS requirement, enforced by prd-coverage) or "proposed" (approved intent not yet allocated; roadmap/vision, exempt from coverage). Absent is treated as not-yet-implemented (exempt). |
srs
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "srs" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Short system name; also the filename stem ([name].srs.json). |
title |
string |
required | Human-readable document title. |
items |
array of objects |
required | Ordered list of requirements and section headings. |
items[].id |
string |
required | Stable machine identifier, generated once and never changed; all cross-references target it. |
items[].code |
string |
optional | Human-facing label (e.g. "DOC-1"); freely renameable because references never use it. |
items[].text |
string |
required | The requirement statement. |
items[].heading |
boolean |
optional | True when this item is a section heading rather than a requirement/test. |
items[].level |
integer |
optional | Indent depth for hierarchy; absent means 0. Headings form dotted section codes. |
items[].satisfies |
array of string |
optional | Ids of the PRD product requirements this requirement satisfies — ids, never codes, so renames cannot break the upward trace. Empty/absent unless a PRD register is in use. |
items[].design |
array of string |
optional | Ids of the SDD sections that implement this requirement — the downward trace (IEC 62304 5.4; FDA SDS). Ids, never codes, so a section can be retitled or rewritten without breaking the link. Empty/absent unless an SDD is in use. |
items[].securityControl |
array of any |
optional | Which FDA security control categories this requirement implements (Cybersecurity in Medical Devices, February 2026, V.B.1 and Appendix 1). A list, because one requirement often serves several. Distinct from category: 5.2.2 e) says a requirement is a security requirement, this says which control it is. |
items[].draft |
boolean |
optional | Drafted by a tool and not yet ratified by a person. The baseline generator sets it on everything it writes; a reviewer clears it. Without it a scaffold is indistinguishable from a specification. |
items[].cites |
array of string |
optional | What this requirement rests on, so it can be checked rather than believed: a clause of a standard, a source construct, a test that pins it. Free text, because the sources a project cites are not SpecPad's to enumerate — but present, because a review pass verifying a citation needs something to look up. |
items[].category |
array of any |
optional | Which of IEC 62304 5.2.2 a)-l) this requirement is. A list, because A1:2015 NOTE 10 states that the requirements in a) through l) can overlap. Its worth is coverage: a category with no requirement is a question to answer once, not an omission to discover at review. |
items[].hazards |
array of string |
optional | Reserved hazard labels (legacy v1 field; the editor no longer surfaces it). |
vtp
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "vtp" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Short system name; also the filename stem ([name].vtp.json). |
title |
string |
required | Human-readable document title. |
items |
array of objects |
required | Ordered list of tests and section headings. |
items[].id |
string |
required | Stable machine identifier, generated once and never changed; all cross-references target it. |
items[].code |
string |
optional | Human-facing label (e.g. "DOC-1"); freely renameable because references never use it. |
items[].text |
string |
required | The test procedure: what to do to verify the linked requirements. |
items[].heading |
boolean |
optional | True when this item is a section heading rather than a requirement/test. |
items[].level |
integer |
optional | Indent depth for hierarchy; absent means 0. Headings form dotted section codes. |
items[].verifies |
array of string |
optional | Ids of the SRS requirements this test verifies — ids, never codes, so renames cannot break traceability. |
items[].expected |
string |
optional | The expected result that defines a pass. |
items[].result |
"" | "not_tested" | "passed" | "failed" |
optional | Latest recorded outcome for a MANUAL test: "", "not_tested", "passed", or "failed". For automated tests the outcome is derived from a captured run, not stored here. Roll-ups are computed on read. |
items[].kind |
"nominal" | "boundary" | "negative" | "stress" | "security" |
optional | What this test does to the system, as distinct from its level: the happy path, the boundaries, the refusals, sustained load, or testing in a security context. A requirement proven only by nominal tests has been shown to work when nothing goes wrong. |
items[].securityTest |
array of any |
optional | Which of FDA's recommended security testing types this is (Cybersecurity in Medical Devices, February 2026, V.C; vulnerability testing per ANSI/ISA 62443-4-1). Recorded so the set can be produced on request rather than searched for. |
items[].draft |
boolean |
optional | Drafted by a tool and not yet ratified by a person. The baseline generator sets it; a reviewer clears it. |
items[].verificationLevel |
"unit" | "integration" | "system" |
optional | Which verification activity this test belongs to: unit verification (IEC 62304 5.5), integration testing (5.6) or system testing (5.7). Without it a register can show requirements are covered by something, but not that each of the three activities was performed. |
items[].notes |
string |
optional | Evidence and context for the recorded result (free text; the machine link lives in automation). |
items[].automation |
array of objects |
optional | Framework-agnostic links to the automated test(s) that execute this verification. Absent/empty means the test is manual. The result for an automated test is derived from a captured run, never hand-set. |
items[].automation[].runner |
string |
required | Opaque test-runner id (e.g. "vitest", "playwright", "pytest"); interpreted by a runner adapter or CI, never by the SpecPad core. |
items[].automation[].file |
string |
required | Path to the test file (tracked in git), relative to the repo root. |
items[].automation[].selector |
string |
optional | Runner-interpreted identifier for the test or group of tests within the file (a test name, a group/describe name, "#15", …). A result answers a selector when its name equals it or begins with it at a word boundary, so naming a group matches every test beneath it. Absent matches the whole file. |
sdd
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "sdd" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Short system name; also the filename stem ([name].sdd.json). |
title |
string |
required | Human-readable document title. |
items |
array of objects |
required | Ordered list of detailed-design sections: one per software unit (IEC 62304 5.4.2), plus the cross-cutting design views a per-unit walk cannot express (IEEE 1016 viewpoints). |
items[].id |
string |
required | Stable machine identifier, generated once and never changed; SRS design references target it. This is what lets the section be retitled, reordered, or rewritten without breaking the trace. |
items[].code |
string |
optional | Human-facing label (e.g. "SDD-12"); freely renameable because references never use it. |
items[].title |
string |
required | Section heading — the software unit or design view this section describes. |
items[].heading |
boolean |
optional | True when this item groups sections rather than describing a design. |
items[].level |
integer |
optional | Indent depth for hierarchy; absent means 0. |
items[].kind |
"unit" | "view" |
optional | Whether this section describes a software unit (IEC 62304 5.4.2) or a cross-cutting design view (IEEE 1016 viewpoint). Absent means "unit". Only units may be named as the cause of a risk, and the unit list required by 5.4.1 is derived from this. |
items[].body |
string |
optional | The design, as markdown: what the unit hides, its algorithm and data, interface behaviour for valid and invalid input (5.4.3), and unit acceptance criteria (5.5.3). May embed images and diagrams like the architecture document. |
items[].source |
array of string |
optional | Repository paths this section describes, so the design can be checked against the code it claims to describe. |
items[].draft |
boolean |
optional | Drafted by a tool and not yet ratified by a person. |
items[].acceptance |
string |
optional | What "verified" means for this unit (IEC 62304 5.5.3; at Class C also 5.5.4 — event sequencing, resource use, fault handling, boundary values). A field rather than a line of prose, because 5.5.3 is asked per unit and buried in the body it cannot be rolled up or shown. |
items[].segregatedFrom |
array of string |
optional | Ids of other design sections this unit is segregated from, where the separation is essential to risk control (IEC 62304 5.3.5). Ids, never codes. |
items[].segregationRationale |
string |
optional | Why the segregation holds. A1:2015 asks how effectiveness is ensured, not merely that separation was intended. |
risk
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "risk" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Short system name; also the filename stem ([name].risk.json). |
title |
string |
required | Human-readable document title. |
items |
array of objects |
required | The software risk analysis (IEC 62304 clause 7): hazardous situations software can contribute to, and section headings. Hazards, harms, probability estimation and benefit-risk belong to the system risk management file, referenced by hazardRef. |
items[].id |
string |
required | Stable machine identifier, generated once and never changed. |
items[].code |
string |
optional | Human-facing label (e.g. "RISK-4"); freely renameable because references never use it. |
items[].text |
string |
required | The hazardous situation, stated in terms of what the software does or fails to do. |
items[].heading |
boolean |
optional | True when this item is a section heading rather than a risk. |
items[].level |
integer |
optional | Indent depth for hierarchy; absent means 0. |
items[].hazardRef |
string |
optional | Identifier of the hazard or hazardous situation in the system risk management file, which the quality system owns. SpecPad holds the software slice and references the rest rather than restating it. |
items[].severity |
"negligible" | "minor" | "serious" | "critical" | "catastrophic" |
optional | Severity of the resulting harm. There is deliberately no probability: for software you cannot argue probability down, so severity drives the analysis. ISO 14971 leaves the scale to the manufacturer; a project using a different one maps onto this. |
items[].sequence |
string |
optional | The sequence of events that turns the software failure into the hazardous situation (IEC 62304 7.1.5; 7.3.2 for a sequence a control introduces). The analysis rather than decoration: recording only the endpoint hides every step a control could break. |
items[].causes |
array of string |
optional | Ids of the software items that could cause this hazardous situation (IEC 62304 7.1): SDD sections of kind "unit", or SOUP components whose anomalies could contribute (7.1.2). Ids, never codes. |
items[].controls |
array of string |
optional | Ids of the SRS requirements implementing the risk control measures (IEC 62304 7.2; 5.2.2 requires a control implemented in software to be a software requirement). Their verifying tests are the evidence the control works (7.3), derived rather than restated. |
items[].justification |
string |
optional | Why no software control is needed, when there is none — for example a risk controlled in hardware, by labelling, or accepted at system level. |
items[].residual |
"acceptable" | "unacceptable" | "not_assessed" |
optional | The judgement recorded once the controls are in place. Absent is treated as not assessed. |
items[].notes |
string |
optional | Free-text analysis notes. |
soup
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "soup" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Short system name; also the filename stem ([name].soup.json). |
title |
string |
required | Human-readable document title. |
items |
array of objects |
required | The third-party software the product depends on, assessed (IEC 62304 SOUP; FDA off-the-shelf software). Not an SBOM: an SBOM is a recursive inventory of every dependency generated from the manifests, while this is the set that has been assessed. |
items[].id |
string |
required | Stable machine identifier, generated once and never changed; a risk naming this component as a cause targets it. |
items[].code |
string |
optional | Human-facing label (e.g. "SOUP-3"); freely renameable because references never use it. |
items[].name |
string |
required | The component's name, as its supplier calls it (IEC 62304 8.1.2). |
items[].heading |
boolean |
optional | True when this item is a section heading rather than a component. |
items[].level |
integer |
optional | Indent depth for hierarchy; absent means 0. |
items[].vendor |
string |
optional | Manufacturer, project or source of the component (IEC 62304 8.1.2; FDA "manufacturer"). |
items[].version |
string |
optional | The exact version in use, including patch level or upgrade designation. Exact rather than a range: an anomaly evaluation is only valid for the version it was performed against (IEC 62304 8.1.2; FDA "version level, patch number, upgrade designation"). |
items[].releaseDate |
string |
optional | Release date of that version (FDA). |
items[].license |
string |
optional | Licence the component is distributed under. |
items[].url |
string |
optional | Where the component and its documentation come from. |
items[].purpose |
string |
optional | What the component does in this product, and why it is appropriate for the job (FDA: functional role, purpose, and at Enhanced level the justification for selecting it). |
items[].requirements |
string |
optional | The functional and performance requirements placed on the component, necessary for its intended use (IEC 62304 5.3.3). Text rather than SRS entries: these are requirements on a supplier, not behaviour this product implements. |
items[].runtime |
string |
optional | Hardware and software the component itself needs in order to run (IEC 62304 5.3.4; FDA computer system specifications). |
items[].limitations |
string |
optional | Expected design limitations — what the component is known not to do (FDA). |
items[].endOfLife |
string |
optional | Date the supplier's support for this component ends, where it is known (FDA: end-of-life support plans and obsolescence). A date rather than prose, so a component already past support can be found rather than read for. |
items[].endOfLifeSource |
string |
optional | Where the end-of-life date came from — a URL or citation. An undated claim about a supplier's intentions is not evidence. |
items[].usedBy |
array of string |
optional | Ids of the SDD sections for the units that use this component. |
items[].tests |
array of string |
optional | Ids of the VTP items exercising this component, where its behaviour is verified directly (FDA testing). |
items[].maintenance |
string |
optional | The supplier's development and support practices, and the plan for when support ends — obsolescence contingency (FDA, Enhanced documentation level). |
items[].notes |
string |
optional | Free-text assessment notes. |
threat
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "threat" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Short system name; also the filename stem ([name].threat.json). |
title |
string |
required | Human-readable document title. |
items |
array of objects |
required | The threat model and security risk analysis, which are one register: assessing a threat and identifying it are the same act (FDA cybersecurity guidance; IEC 81001-5-1; AAMI SW96). |
items[].id |
string |
required | Stable machine identifier, generated once and never changed. |
items[].code |
string |
optional | Human-facing label (e.g. "THR-4"); freely renameable because references never use it. |
items[].text |
string |
required | The threat: what an attacker does, and what it gets them. |
items[].heading |
boolean |
optional | True when this item is a section heading rather than a threat. |
items[].level |
integer |
optional | Indent depth for hierarchy; absent means 0. |
items[].asset |
string |
optional | What is being attacked — the data, function or property at stake. |
items[].entryPoint |
string |
optional | Where the attack enters: the interface or trust boundary it crosses. |
items[].category |
"spoofing" | "tampering" | "repudiation" | "information_disclosure" | "denial_of_service" | "elevation_of_privilege" |
optional | STRIDE category, the classification threat modelling has settled on. Its value is coverage: an entry point with no threat in a category is a prompt to ask why. |
items[].exploitability |
"high" | "medium" | "low" |
optional | How readily the threat can be realised — the access required, the skill, and the opportunity. Exploitability rather than probability: an attacker chooses when to act, so a frequency estimate is meaningless, and a defence is worth what it costs to defeat. |
items[].impact |
"negligible" | "minor" | "serious" | "critical" | "catastrophic" |
optional | Severity of the consequence if the threat is realised, on the same scale as safety severity so the two analyses can be read together. |
items[].causes |
array of string |
optional | Ids of the design units or third-party components presenting this attack surface. Ids, never codes. |
items[].controls |
array of string |
optional | Ids of the SRS requirements implementing the security controls. A control is a requirement, so its verifying tests are the evidence it works — the same mechanism the safety risk register uses. |
items[].justification |
string |
optional | Why no software control is needed, when there is none — for example a threat accepted, or controlled by the deployment environment. |
items[].safetyRisk |
array of string |
optional | Ids of the risk items for the safety risk that exploiting this threat would create. This join is the point of AAMI SW96: a security finding with a patient consequence belongs in the safety risk file as well as here. |
items[].residual |
"acceptable" | "unacceptable" | "not_assessed" |
optional | The judgement recorded once the controls are in place. Absent is treated as not assessed. |
items[].notes |
string |
optional | Free-text analysis notes. |
releases
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "releases" |
required | Document discriminator; selects the schema this file is validated against. |
name |
string |
required | Project name this manifest belongs to. |
tagPattern |
string |
required | Git tag glob (e.g. "v*") that marks releases of the spec. |
baseline |
string | null |
required | Version whose snapshot the editor diffs the working copy against (the current redline base). |
releases |
array of objects |
required | One entry per matching git tag, oldest first. |
releases[].version |
string |
required | The release tag name. |
releases[].ref |
string |
required | Commit hash the tag points at. |
releases[].date |
string |
required | Commit date (ISO). |
releases[].author |
object |
required | Author of the tagged commit (release-granularity attribution). |
releases[].author.name |
string |
required | Author display name from git. |
releases[].author.email |
string |
required | Author email from git. |
releases[].snapshot |
string | null |
required | Path of the cached snapshot under docs/specpad/, or null if not yet cached. |
releases[].anomalies |
array of objects |
optional | Defects known to be present when this version shipped, each with the evaluation that made shipping acceptable (IEC 62304 5.8.2, 5.8.3). |
releases[].anomalies[].text |
string |
required | The defect, in terms of what a user would experience. |
releases[].anomalies[].evaluation |
string |
optional | Why shipping with it was acceptable — the 5.8.3 evaluation against safety. |
releases[].anomalies[].ref |
string |
optional | Where it is tracked, when it lives in an issue tracker. |
releases[].build |
string |
optional | How this version was built (5.8.5) and what makes that repeatable (5.8.8): toolchain and versions, environment, and where the build procedure lives. |
job
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
const "1.0" |
required | Contract version of this file; "1.0" documents open in the pinned editor build at /v01/. |
type |
const "job" |
required | Document discriminator; selects the schema this file is validated against. |
jobs |
array of string |
optional | The active work items current changes are attributed to — job-record ids (with a register) or tracker keys. One commit may carry several; the skill writes one Job: trailer per entry. |
job |
string |
optional | Legacy single active work item; readers normalize it into the jobs list via activeJobIds(). Prefer jobs. |
title |
string |
optional | Optional human-readable summary, meaningful only for a single external-tracker job with no register. |
Governance rules
JSON Schema validates structure only (required fields, types, enums). These policy rules
run in both the skill and the editor from one shared module
(src/shared/governance.ts) — they cannot drift.
traceability — Every requirement is verified
Every non-heading SRS requirement must be referenced by at least one VTP test.
referential-integrity — References resolve
Every VTP `verifies` entry must resolve to an existing SRS item id.
missing-expected — Tests declare an expected result
Every non-heading VTP test must have a non-empty `expected` value.
active-job-open — Active jobs are open
No active-job marker entry may point at a closed job record; reopen it or pick another before attaching more changes.
active-job-known — Active jobs exist in the register
When a jobs register exists, every active-job marker entry must resolve to a record in it (no dangling or mistyped ids).
prd-referential-integrity — PRD references resolve
When a PRD register is present, every SRS `satisfies` entry must resolve to an existing PRD item id.
prd-coverage — Every implemented product requirement is satisfied
When a PRD register is present, every non-heading PRD item marked `status: "implemented"` must be referenced by at least one SRS requirement via `satisfies`. Items that are `proposed` (or have no status) are roadmap/vision and exempt.
sdd-referential-integrity — Design references resolve
When an SDD is present, every SRS `design` entry must resolve to an existing SDD section id.
sdd-coverage — Every requirement reaches the design
When an SDD is present, every non-heading SRS requirement must reference at least one SDD section via `design` — the evidence that the design implements the requirements (IEC 62304 5.4; FDA SDS).
risk-referential-integrity — Risk references resolve
When a risk register is present, every `causes` entry must resolve to an SDD section of kind "unit" or to a component in the SOUP register, and every `controls` entry to an existing SRS requirement. A design view is rejected as a cause: it describes structure across units rather than something that can fail on its own.
risk-cause — Every risk names a contributing software item
When a risk register is present, every non-heading risk must name at least one software item that could cause it (IEC 62304 7.1). A hazardous situation with no software cause does not belong in the software risk analysis.
risk-controlled — Every risk is controlled or justified
When a risk register is present, every non-heading risk must reference at least one controlling requirement (IEC 62304 7.2), or record why no software control is needed — for example a risk controlled in hardware or by labelling.
soup-identity — Every component is exactly identified
When a SOUP register is present, every component must record a supplier and an exact version (IEC 62304 8.1.2). A version range is not an identity: an anomaly evaluation is only valid for the version it was performed against.
soup-requirements — Every component has requirements placed on it
When a SOUP register is present, every component must state the functional and performance requirements necessary for its intended use (IEC 62304 5.3.3).
soup-referential-integrity — Component references resolve
When a SOUP register is present, every `usedBy` entry must resolve to an SDD section and every `tests` entry to a VTP item.
threat-referential-integrity — Threat references resolve
When a threat model is present, every `causes` entry must resolve to a design unit or a component, every `controls` entry to a requirement, and every `safetyRisk` entry to a risk.
threat-assessed — Every threat is assessed
When a threat model is present, every non-heading threat must record an exploitability and an impact. A threat neither of which is stated has been identified but not analysed.
threat-controlled — Every threat is controlled or accepted
When a threat model is present, every non-heading threat must reference at least one controlling requirement, or record why none is needed — for example a threat accepted, or controlled by the deployment environment.
sdd-segregation — Segregation says why it holds
When an SDD is present, a section naming other sections it is segregated from must say why the segregation is effective, and each named section must resolve (IEC 62304 5.3.5, strengthened by A1:2015 — separation intended is not separation ensured).
vtp-negative-path — Requirements are attacked, not only demonstrated
A non-heading requirement whose tests are all `nominal` should also be tested on a boundary, a refusal, or under load. Advisory: a requirement proven only on the happy path has been shown to work when nothing goes wrong, which is the weaker half of the claim — and a near 1:1 register of requirements to tests is the shape that produces it.
sdd-unit-trace — Requirements reach a unit, not only a view
A requirement whose `design` references resolve only to sections of kind "view" should also reach a software unit. A design view describes structure across units and implements nothing on its own — the same reason `risk-cause` requires a unit rather than a view. Advisory rather than blocking, because a genuine model-level invariant can legitimately live only in a view; the point is that it be a decision rather than an accident.
sdd-acceptance — Units say what verified means
When an SDD is present, every section of kind "unit" should state its acceptance criteria (IEC 62304 5.5.3; at Class C also 5.5.4). Advisory: asked per unit, and answerable only by someone who knows the unit.
risk-sequence — Risks state the sequence of events
When a risk register is present, every non-heading risk should record the sequence of events from the software failure to the hazardous situation (IEC 62304 7.1.5). Advisory: it is the analysis rather than a field, and recording only the endpoint hides the steps a control could break.
srs-category — Requirements declare their content category
Every non-heading SRS requirement should declare which of IEC 62304 5.2.2 a)–l) it is — one or more, since A1:2015 NOTE 10 states the categories can overlap. Advisory: the worth of the list is coverage, and a category with no requirement is a question to answer once rather than an omission found at review.
srs-security-control — Security controls say which control they are
A requirement named as a control by a threat should declare which FDA security control categories it implements (Cybersecurity in Medical Devices, February 2026, V.B.1). Advisory: it is asked only of requirements the threat model already relies on, and an adequate-coverage argument is built from these categories rather than from the word "security".
srs-cites — Requirements say what they rest on
A non-heading requirement should record what it rests on (cites) — the construct that implements it, the clause that demands it, the decision it came from. Advisory: a requirement is not wrong for lacking one. But an audit can only check a claim it can look up, and without citations a review of the register is a re-read rather than a verification — which is the weakest check available.
vtp-verification-level — Tests declare their verification level
Every non-heading VTP test should declare whether it is unit verification (IEC 62304 5.5), integration testing (5.6) or system testing (5.7). Advisory: without it a register shows requirements are covered by something, but not that each activity was performed.
Annotated example
A real requirement from SpecPad's own SRS (DOC-1) and its verifying test
(TEST-1), showing how the key fields relate.
{
"id": "r_a101",
"code": "DOC-1",
"text": "The system shall store project documentation as JSON files under docs/specpad/: one project index, one SRS, and one VTP.",
"tags": ["schema"]
}
{
"id": "t_c001",
"code": "TEST-1",
"text": "Confirm docs/specpad/ holds the three core files and each parses as JSON.",
"verifies": ["r_a101"],
"expected": "All three files exist and parse without error.",
"result": "passed",
"notes": "dogfood.test.ts"
}
-
idr_a101/t_c001— stable machine identifiers generated once and never changed. Renamingcode(the human label) never touchesid. -
codeDOC-1/TEST-1— freely renameable display labels. The editor shows them; the skill increments them; traceability links ignore them entirely. -
verifies["r_a101"]— the test links to the requirement byid, never bycode, so renamingDOC-1toARCH-1cannot break the link. -
expected"All three files exist…"— a non-empty expected result is required on every non-heading test (governance rulemissing-expected).
Install & use the skill
The SpecPad Claude Code skill lets you create and maintain SRS/VTP documents directly from your AI assistant, with full validation and governance checks.
-
Download
specpad-skill.zipand unzip it into~/.claude/skills/so that~/.claude/skills/specpad/SKILL.mdexists. -
In any repo, tell Claude Code: "set up specpad" — it scaffolds
docs/specpad/with a project index, an empty SRS, and an empty VTP. -
Use natural language to manage your spec. Trigger phrases:
write a specformalize requirementsadd a requirementwrite tests forcheck traceability
- Before finishing any task the skill validates structure and runs all three governance rules — so the JSON files on disk are always editor-ready.
- Open the same files in the hosted visual editor at specpad.com/v01/ to review, edit, and record test results through a full UI. Git is the shared history layer; both halves read and write the same JSON.