Back to index

Requirement Traceability Matrix

Table
Generates documents
Requirement Traceability Matrix

Seen as a design

Take it with you

Copy and paste, or save a file and hand it over. For a machine with no network, one “Everything” file is enough.

Everything

requirement-traceability-matrix-everything.md

Everything in this panel, including the contents behind each URL, in one Markdown file

Prompt

1.5 KB
Build an HTML document with "Requirement Traceability Matrix". This layout runs, so there is no composition to rebuild — what it needs is content.

## What this layout does
Link requirements to acceptance criteria, verification cases, and evidence to find untested scope.

## What you have to supply
- Provide requirement IDs, acceptance criteria, owners, test IDs, methods, results, and evidence.

## The shape of the input
- `title` — string — 追跡対象の結論
- `subtitle` — string? — 対象リリース・基準日・範囲
- `project` — string — 対象案件・リリース
- `requirements` — array (3–10) — 優先度、受け入れ条件、責任者、検証ケースID
- `tests` — array (2–16) — 検証方法、結果、証跡

schema.ts holds the machine-checkable validation. Ask rather than guess when an input is missing.

## When not to use it
- Use fit-gap-assessment when the task is only to evaluate implementation fit.

## How to build it
Put the input in a JSON file and run:

```bash
node scripts/render.mjs requirement-traceability-matrix <input.json> --theme ink -o out.html
```

The output is one self-contained file and fetches nothing at render time.

## The source
- https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/template.tsx
- https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/schema.ts
- https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/sample-data.json
- https://design-vault.pages.dev/designs/requirement-traceability-matrix/sample.html

Files

Template, input schema, sample data and metadata. This document was generated from those four. The generated document

template.tsx

9.3 KB

/designs/requirement-traceability-matrix/source/template.tsx

Show contents
import type { Props } from "./schema";

const CSS = String.raw`
.asset-requirement-traceability-matrix{font-family:var(--font-sans);color:var(--color-primary);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:3rem 2.5rem;line-height:1.6;font-size:var(--text-body-sm)}
.asset-requirement-traceability-matrix *{box-sizing:border-box}.asset-requirement-traceability-matrix header{margin-bottom:1.5rem}.asset-requirement-traceability-matrix .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary);margin-bottom:.75rem}.asset-requirement-traceability-matrix h1{font-size:var(--text-h1);line-height:1.3;max-width:28em}.asset-requirement-traceability-matrix .subtitle{margin-top:.75rem;color:var(--color-secondary)}
.asset-requirement-traceability-matrix .summary{display:flex;gap:2rem;flex-wrap:wrap;border-top:2px solid var(--color-primary);border-bottom:1px solid var(--color-outline);padding:.8rem 0;margin-bottom:1rem}.asset-requirement-traceability-matrix .summary p{margin:0;color:var(--color-secondary);font-size:var(--text-label)}.asset-requirement-traceability-matrix .summary strong{font-family:var(--font-mono);font-size:var(--text-h3);color:var(--color-primary);margin-right:.35rem}
.asset-requirement-traceability-matrix .requirements{border-top:1px solid var(--color-outline)}.asset-requirement-traceability-matrix .requirement{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.25fr);gap:1.4rem;padding:1.1rem 0;border-bottom:1px solid var(--color-outline);min-width:0}.asset-requirement-traceability-matrix .requirement>*{min-width:0}.asset-requirement-traceability-matrix .req-head{display:flex;gap:.65rem;align-items:baseline;flex-wrap:wrap}.asset-requirement-traceability-matrix .req-head code{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary)}.asset-requirement-traceability-matrix .req-head h2{font-size:var(--text-body-md);margin:0}.asset-requirement-traceability-matrix .priority{font-size:var(--text-label);color:var(--color-secondary);border:1px solid var(--color-outline);padding:.05rem .4rem}.asset-requirement-traceability-matrix .statement{margin:.5rem 0;font-weight:600;overflow-wrap:anywhere}.asset-requirement-traceability-matrix .acceptance{margin:0;color:var(--color-secondary);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .owner{margin-top:.45rem;font-size:var(--text-label);color:var(--color-secondary)}.asset-requirement-traceability-matrix .test-list{display:flex;flex-direction:column;gap:.45rem;margin:0;padding:0;list-style:none}.asset-requirement-traceability-matrix .test{display:grid;grid-template-columns:minmax(5.5rem,.7fr) minmax(0,1.5fr) auto;gap:.7rem;align-items:start;padding:.45rem .6rem;border-left:2px solid var(--color-tertiary);background:var(--color-surface-subtle);min-width:0}.asset-requirement-traceability-matrix .test-id{font-family:var(--font-mono);font-size:var(--text-label);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .test-name{font-weight:600;overflow-wrap:anywhere}.asset-requirement-traceability-matrix .test-meta{font-size:var(--text-label);color:var(--color-secondary);text-align:right;white-space:nowrap}.asset-requirement-traceability-matrix .evidence{grid-column:2/4;color:var(--color-secondary);font-size:var(--text-label);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .empty-evidence{color:var(--color-tertiary);font-weight:600}.asset-requirement-traceability-matrix footer{margin-top:1.2rem;padding-top:.75rem;border-top:1px solid var(--color-outline);color:var(--color-secondary);font-size:var(--text-label)}.asset-requirement-traceability-matrix .still-svg{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media(max-width:680px){.asset-requirement-traceability-matrix{padding:1.5rem 1rem}.asset-requirement-traceability-matrix h1{font-size:var(--text-h2)}.asset-requirement-traceability-matrix .requirement{grid-template-columns:minmax(0,1fr);gap:.7rem;padding:1rem 0}.asset-requirement-traceability-matrix .test{grid-template-columns:minmax(0,1fr) auto;gap:.2rem .6rem}.asset-requirement-traceability-matrix .test-id{grid-column:1}.asset-requirement-traceability-matrix .test-name{grid-column:1}.asset-requirement-traceability-matrix .test-meta{grid-column:2;grid-row:1/3}.asset-requirement-traceability-matrix .evidence{grid-column:1/3}.asset-requirement-traceability-matrix .summary{gap:1rem}}
@media print{.asset-requirement-traceability-matrix{max-width:none;padding:1rem}.asset-requirement-traceability-matrix .requirement{break-inside:avoid}}
`;

const methods = { unit: "単体", integration: "結合", acceptance: "受入", manual: "手動" };
const results = { passed: "確認済み", failed: "不合格", pending: "未実施", blocked: "保留" };
const compact = (value: string, max: number) => [...value].length > max ? `${[...value].slice(0, max - 1).join("")}…` : value;

function Still({ data }: { data: Props }) {
  const byId = new Map(data.tests.map((testCase) => [testCase.id, testCase]));
  const rows = data.requirements.slice(0, 10);
  const rowHeight = 51;
  return <svg className="still-svg" data-layout-still="true" width="1280" height="720" viewBox="0 0 1280 720" aria-hidden="true">
    <rect width="1280" height="720" fill="var(--color-surface)" />
    <text x="48" y="48" fontSize="14" fontFamily="sans-serif" fill="var(--color-secondary)">{data.project}</text>
    <text x="48" y="88" fontSize="28" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(data.title, 56)}</text>
    <text x="48" y="117" fontSize="13" fontFamily="sans-serif" fill="var(--color-secondary)">要求から受け入れ検証までの対応を追跡</text>
    <text x="48" y="145" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">要求</text>
    <text x="560" y="145" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">紐づく検証ケース</text>
    {rows.map((requirement, index) => {
      const y = 174 + index * rowHeight;
      return <g key={requirement.id}>
        <line x1="48" x2="1232" y1={y - 16} y2={y - 16} stroke="var(--color-outline)" />
        <text x="52" y={y + 5} fontSize="11" fontFamily="sans-serif" fill="var(--color-tertiary)">{requirement.priority}</text>
        <text x="102" y={y + 5} fontSize="12" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{requirement.id}</text>
        <text x="190" y={y + 5} fontSize="12" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(requirement.statement, 48)}</text>
        {requirement.testIds.slice(0, 4).map((id, testIndex) => {
          const testCase = byId.get(id);
          return <g key={id}>
            <line x1={560 + testIndex * 164} x2={578 + testIndex * 164} y1={y} y2={y} stroke="var(--color-tertiary)" strokeWidth="2" />
            <circle cx={560 + testIndex * 164} cy={y} r="4" fill="var(--color-tertiary)" />
            <text x={584 + testIndex * 164} y={y + 4} fontSize="11" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(testCase?.id ?? id, 16)}</text>
          </g>;
        })}
      </g>;
    })}
    <text x="48" y="704" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">{data.requirements.length}要件 · {data.tests.length}検証ケース</text>
  </svg>;
}

export default function Template(data: Props) {
  const byId = new Map(data.tests.map((testCase) => [testCase.id, testCase]));
  const linkCount = data.requirements.reduce((count, requirement) => count + requirement.testIds.length, 0);
  return <section className="asset asset-requirement-traceability-matrix">
    <style>{CSS}</style>
    <header>
      <p className="eyebrow">{data.project} · 要件の検証状況</p>
      <h1>{data.title}</h1>
      {data.subtitle && <p className="subtitle">{data.subtitle}</p>}
    </header>
    <div className="summary" aria-label="追跡件数">
      <p><strong>{data.requirements.length}</strong>要件</p>
      <p><strong>{data.tests.length}</strong>検証ケース</p>
      <p><strong>{linkCount}</strong>対応づけ</p>
    </div>
    <div className="requirements">
      {data.requirements.map((requirement) => <article className="requirement" key={requirement.id}>
        <div>
          <div className="req-head"><code>{requirement.id}</code><span className="priority">{requirement.priority}</span><h2>{requirement.owner}</h2></div>
          <p className="statement">{requirement.statement}</p>
          <p className="acceptance"><strong>受け入れ条件:</strong> {requirement.acceptance}</p>
        </div>
        <ul className="test-list" aria-label={`${requirement.id}を検証するケース`}>
          {requirement.testIds.map((id) => {
            const testCase = byId.get(id)!;
            return <li className="test" key={id}>
              <code className="test-id">{testCase.id}</code>
              <span className="test-name">{testCase.name}</span>
              <span className="test-meta">{methods[testCase.method]} · {results[testCase.result]}</span>
              <span className={testCase.evidence ? "evidence" : "evidence empty-evidence"}>{testCase.evidence ?? "証跡を記録"}</span>
            </li>;
          })}
        </ul>
      </article>)}
    </div>
    <footer>要求の完了は、対応づいた検証ケースと証跡を確認して判断する。</footer>
    <Still data={data} />
  </section>;
}

schema.ts

2.8 KB

/designs/requirement-traceability-matrix/source/schema.ts

Show contents
import { z } from "zod";

const text = z.string().trim().min(1).max(160);
const short = z.string().trim().min(1).max(48);

export const requirementSchema = z.object({
  id: short.describe("要求の一意な識別子"),
  priority: z.enum(["P0", "P1", "P2", "P3"]).describe("要求の優先度"),
  statement: text.describe("実現する要求"),
  acceptance: text.describe("満たしたと判断できる条件"),
  owner: short.describe("要求の確認責任者"),
  testIds: z.array(short).min(1).max(4).describe("要求を検証するケースID"),
}).strict();

export const verificationSchema = z.object({
  id: short.describe("検証ケースの一意な識別子"),
  name: short.describe("検証ケースの名称"),
  method: z.enum(["unit", "integration", "acceptance", "manual"]).describe("検証方法"),
  result: z.enum(["passed", "failed", "pending", "blocked"]).describe("実行結果"),
  evidence: text.optional().describe("実行記録・証跡・未実施理由"),
}).strict();

export const schema = z.object({
  title: text.describe("追跡対象の結論"),
  subtitle: text.optional().describe("対象リリース・基準日・範囲"),
  project: short.describe("対象案件・リリース"),
  requirements: z.array(requirementSchema).min(3).max(10).describe("要求から検証ケースへの対応"),
  tests: z.array(verificationSchema).min(2).max(16).describe("検証ケースと結果"),
}).strict().superRefine((data, ctx) => {
  const requirementIds = data.requirements.map((item) => item.id);
  const testIds = data.tests.map((item) => item.id);
  if (new Set(requirementIds).size !== requirementIds.length) {
    ctx.addIssue({ code: "custom", path: ["requirements"], message: "要求IDは一意にすること" });
  }
  if (new Set(testIds).size !== testIds.length) {
    ctx.addIssue({ code: "custom", path: ["tests"], message: "検証ケースIDは一意にすること" });
  }
  const knownTests = new Set(testIds);
  const usedTests = new Set<string>();
  data.requirements.forEach((requirement, index) => {
    if (new Set(requirement.testIds).size !== requirement.testIds.length) {
      ctx.addIssue({ code: "custom", path: ["requirements", index, "testIds"], message: "同じ検証ケースを重複指定できない" });
    }
    requirement.testIds.forEach((id) => {
      if (!knownTests.has(id)) {
        ctx.addIssue({ code: "custom", path: ["requirements", index, "testIds"], message: `未登録の検証ケース: ${id}` });
      }
      usedTests.add(id);
    });
  });
  data.tests.forEach((testCase, index) => {
    if (!usedTests.has(testCase.id)) {
      ctx.addIssue({ code: "custom", path: ["tests", index, "id"], message: `要求から参照されていない検証ケース: ${testCase.id}` });
    }
  });
});

export type Props = z.infer<typeof schema>;

meta.yaml

1.6 KB

/designs/requirement-traceability-matrix/source/meta.yaml

Show contents
id: requirement-traceability-matrix
name:
  ja: 要件トレーサビリティ表
  en: Requirement Traceability Matrix
purpose:
  ja: 要求・受け入れ条件・検証ケース・証跡を結び、未検証の範囲を追跡する。
  en: Link requirements to acceptance criteria, verification cases, and evidence to find untested scope.
status: published
still: ./still.png
category: governance
catalogOrder: 800
jobs:
  - trace-flow
  - assign-governance
useCases:
  - requirements
layoutKinds:
  - table
tags:
  - requirements
  - verification
  - traceability
  - offline
  - responsive
whenToUse:
  - ja: 要求ごとに受け入れ条件と検証ケースを結び、実施結果と証跡を追うとき。
    en: Use to connect each requirement to its acceptance criteria, verification cases, and evidence.
avoidWhen:
  - ja: 実装可否や適合状況だけを評価する場合は、fit-gap-assessmentを使う。
    en: Use fit-gap-assessment when the task is only to evaluate implementation fit.
requiredInputs:
  - ja: 要件ID、受け入れ条件、責任者、検証ケースID・方法・結果・証跡を定義する。
    en: Provide requirement IDs, acceptance criteria, owners, test IDs, methods, results, and evidence.
relatedLayouts:
  - fit-gap-assessment
  - release-readiness
dataSchema:
  title: string — 追跡対象の結論
  subtitle: string? — 対象リリース・基準日・範囲
  project: string — 対象案件・リリース
  requirements: array (3–10) — 優先度、受け入れ条件、責任者、検証ケースID
  tests: array (2–16) — 検証方法、結果、証跡
sources: []

sample-data.json

2.6 KB

/designs/requirement-traceability-matrix/source/sample-data.json

Show contents
{
  "title": "受け入れ条件から実施証跡までを追跡する",
  "subtitle": "架空の顧客管理刷新|2026年度 第2リリース",
  "project": "顧客管理刷新",
  "requirements": [
    {
      "id": "REQ-01",
      "priority": "P0",
      "statement": "利用者は登録済み顧客を検索できる",
      "acceptance": "氏名または顧客番号で検索し、該当する顧客だけを表示する",
      "owner": "プロダクト担当",
      "testIds": [
        "TEST-01",
        "TEST-02"
      ]
    },
    {
      "id": "REQ-02",
      "priority": "P0",
      "statement": "変更履歴を顧客詳細から確認できる",
      "acceptance": "変更日時・変更者・変更項目が新しい順で表示される",
      "owner": "業務担当",
      "testIds": [
        "TEST-03"
      ]
    },
    {
      "id": "REQ-03",
      "priority": "P1",
      "statement": "権限のない利用者は顧客情報を変更できない",
      "acceptance": "閲覧権限だけの利用者による更新要求を拒否し、記録する",
      "owner": "セキュリティ担当",
      "testIds": [
        "TEST-04",
        "TEST-05"
      ]
    },
    {
      "id": "REQ-04",
      "priority": "P2",
      "statement": "顧客一覧をCSV形式で出力できる",
      "acceptance": "画面で指定した絞り込みと列順を出力へ反映する",
      "owner": "運用担当",
      "testIds": [
        "TEST-06"
      ]
    }
  ],
  "tests": [
    {
      "id": "TEST-01",
      "name": "顧客番号で検索",
      "method": "acceptance",
      "result": "passed",
      "evidence": "受入記録 AC-14。完全一致と該当なしを確認"
    },
    {
      "id": "TEST-02",
      "name": "氏名の一部で検索",
      "method": "integration",
      "result": "passed",
      "evidence": "結合試験 INT-22。表記ゆれを含む3件を確認"
    },
    {
      "id": "TEST-03",
      "name": "変更履歴の並び順",
      "method": "acceptance",
      "result": "pending",
      "evidence": "リリース候補で担当者レビューを予定"
    },
    {
      "id": "TEST-04",
      "name": "閲覧者の更新拒否",
      "method": "integration",
      "result": "passed",
      "evidence": "権限試験 ACL-08。更新と監査記録を確認"
    },
    {
      "id": "TEST-05",
      "name": "監査ログの記録",
      "method": "unit",
      "result": "passed",
      "evidence": "テスト結果 UT-91"
    },
    {
      "id": "TEST-06",
      "name": "絞り込みCSV出力",
      "method": "manual",
      "result": "blocked",
      "evidence": "文字コード確認用の実データを準備中"
    }
  ]
}