Seen as a design The generated document itself — its toggles and disclosures work here
Seen as a design The generated document itself — its toggles and disclosures work here
Copy and paste, or save a file and hand it over. For a machine with no network, one “Everything” file is enough.
Everything in this panel, including the contents behind each URL, in one Markdown file — Large, so it goes as a file (copy is for 32 KB and under)
Build an HTML document with "Security Control Exception Register". This layout runs, so there is no composition to rebuild — what it needs is content. ## What this layout does Tie temporary control deviations to scope, compensating controls, risk approvers, approval records, and expiry. ## What you have to supply - Define the control, scope, rationale, residual risk, compensating measure, owner, approver, review date, and expiry. ## The shape of the input - `title` — string — 例外統制の要点 - `subtitle` — string? — 適用組織・ポリシー版・基準日 - `program` — string — 対象セキュリティプログラム - `exceptions` — array (3–12) — 統制例外(理由、リスク、代替策、責任者、決裁者、承認参照、再審査日、終了日、状態) schema.ts holds the machine-checkable validation. Ask rather than guess when an input is missing. ## When not to use it - Use an incident review to record causes and prevention after an incident. ## How to build it Put the input in a JSON file and run: ```bash node scripts/render.mjs security-exception-register <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/security-exception-register/source/template.tsx - https://design-vault.pages.dev/designs/security-exception-register/source/schema.ts - https://design-vault.pages.dev/designs/security-exception-register/source/sample-data.json - https://design-vault.pages.dev/designs/security-exception-register/sample.html
Template, input schema, sample data and metadata. This document was generated from those four. The generated document
/designs/security-exception-register/source/template.tsx
import type { Props } from "./schema";
const CSS = String.raw`
.asset-security-exception-register{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.55;font-size:var(--text-body-sm)}
.asset-security-exception-register *{box-sizing:border-box}.asset-security-exception-register header{margin-bottom:1.25rem}.asset-security-exception-register .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary);margin:0 0 .6rem}.asset-security-exception-register h1{font-size:var(--text-h1);line-height:1.25;max-width:28em;margin:0}.asset-security-exception-register .subtitle{color:var(--color-secondary);margin:.65rem 0 0}.asset-security-exception-register .summary{display:flex;gap:1.5rem;flex-wrap:wrap;padding:.7rem 0;border-block:1px solid var(--color-outline);margin-bottom:1rem}.asset-security-exception-register .summary p{margin:0;font-size:var(--text-label);color:var(--color-secondary)}.asset-security-exception-register .summary strong{font-family:var(--font-mono);font-size:var(--text-h3);color:var(--color-primary);margin-right:.3rem}.asset-security-exception-register .exception{padding:.85rem 0;border-top:1px solid var(--color-outline)}.asset-security-exception-register .exception:last-child{border-bottom:1px solid var(--color-outline)}.asset-security-exception-register .head{display:flex;gap:.55rem;align-items:center;flex-wrap:wrap}.asset-security-exception-register code{font-family:var(--font-mono);font-size:var(--text-label);overflow-wrap:anywhere}.asset-security-exception-register .head h2{font-size:var(--text-body-md);margin:0;flex:1}.asset-security-exception-register .badge{font-size:var(--text-label);border:1px solid var(--color-outline);padding:.04rem .4rem;color:var(--color-secondary)}.asset-security-exception-register .detail{display:grid;grid-template-columns:1fr 1fr;gap:.55rem 1.4rem;margin-top:.55rem}.asset-security-exception-register .field{min-width:0}.asset-security-exception-register .label{display:block;color:var(--color-secondary);font-size:var(--text-label)}.asset-security-exception-register .field p{margin:.08rem 0 0;overflow-wrap:anywhere}.asset-security-exception-register .span{grid-column:1/3}.asset-security-exception-register .decision{display:flex;gap:.6rem;align-items:baseline;flex-wrap:wrap}.asset-security-exception-register .approval{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-secondary)}.asset-security-exception-register .still-svg{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media(max-width:680px){.asset-security-exception-register{padding:1.5rem 1rem}.asset-security-exception-register h1{font-size:var(--text-h2)}.asset-security-exception-register .detail{grid-template-columns:minmax(0,1fr);gap:.45rem}.asset-security-exception-register .span{grid-column:auto}.asset-security-exception-register .summary{gap:.8rem}}
@media print{.asset-security-exception-register{max-width:none;padding:1rem}.asset-security-exception-register .exception{break-inside:avoid}}
`;
const riskLabel = { low: "低", medium: "中", high: "高", critical: "重大" };
const statusLabel = { proposed: "審査中", approved: "承認済", denied: "却下", closed: "終了" };
const compact = (value: string, max: number) => [...value].length > max ? `${[...value].slice(0, max - 1).join("")}…` : value;
function Still({ data }: { data: Props }) {
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="44" fontSize="14" fontFamily="sans-serif" fill="var(--color-secondary)">{data.program}</text>
<text x="48" y="85" fontSize="26" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(data.title, 64)}</text>
<text x="48" y="120" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">対象統制 · 代替策 · リスク · 決裁者 · 再審査・終了日</text>
{data.exceptions.slice(0, 8).map((exception, index) => {
const y = 166 + index * 62;
return <g key={exception.id}>
<line x1="48" x2="1232" y1={y - 19} y2={y - 19} stroke="var(--color-outline)" />
<text x="52" y={y + 3} fontSize="11" fontFamily="monospace" fill="var(--color-tertiary)">{exception.id}</text>
<text x="170" y={y + 3} fontSize="12" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(exception.control, 28)}</text>
<text x="440" y={y + 3} fontSize="11" fontFamily="sans-serif" fill="var(--color-secondary)">{riskLabel[exception.risk]} · {compact(exception.owner, 29)}</text>
<text x="790" y={y + 3} fontSize="11" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(exception.compensatingControl, 44)}</text>
<text x="1110" y={y + 3} fontSize="11" fontFamily="sans-serif" fill="var(--color-secondary)">{exception.expiry}</text>
</g>;
})}
<text x="48" y="704" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">{data.exceptions.length}例外 · 期限前に再審査または解消</text>
</svg>;
}
export default function Template(data: Props) {
const approved = data.exceptions.filter((item) => item.status === "approved").length;
const critical = data.exceptions.filter((item) => item.risk === "critical").length;
return <section className="asset asset-security-exception-register">
<style>{CSS}</style>
<header>
<p className="eyebrow">{data.program} · 統制例外</p>
<h1>{data.title}</h1>
{data.subtitle && <p className="subtitle">{data.subtitle}</p>}
</header>
<div className="summary"><p><strong>{data.exceptions.length}</strong>例外</p><p><strong>{approved}</strong>承認済</p><p><strong>{critical}</strong>重大リスク</p></div>
{data.exceptions.map((exception) => <article className="exception" key={exception.id}>
<div className="head"><code>{exception.id}</code><h2>{exception.control}</h2><span className="badge">リスク {riskLabel[exception.risk]}</span><span className="badge" data-status={exception.status}>{statusLabel[exception.status]}</span></div>
<div className="detail">
<div className="field span"><span className="label">適用範囲</span><p>{exception.scope}</p></div>
<div className="field"><span className="label">標準統制を満たせない理由</span><p>{exception.justification}</p></div>
<div className="field"><span className="label">代替統制</span><p>{exception.compensatingControl}</p></div>
<div className="field"><span className="label">責任者</span><p>{exception.owner}</p></div>
<div className="field"><span className="label">決裁者・承認参照</span><p className="decision">{exception.approver}{exception.approvalRef && <code className="approval">{exception.approvalRef}</code>}</p></div>
<div className="field span"><span className="label">再審査日 → 例外終了日</span><p>{exception.reviewDate} → {exception.expiry}</p></div>
</div>
</article>)}
<Still data={data} />
</section>;
}
/designs/security-exception-register/source/schema.ts
import { z } from "zod";
const text = z.string().trim().min(1).max(160);
const short = z.string().trim().min(1).max(48);
const date = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "日付はYYYY-MM-DD形式にすること");
const exceptionSchema = z.object({
id: short.describe("一時例外の一意な識別子"),
control: short.describe("対象となるセキュリティ統制"),
scope: text.describe("例外が適用されるシステム・データ・利用者の範囲"),
justification: text.describe("標準統制を満たせない理由"),
risk: z.enum(["low", "medium", "high", "critical"]).describe("例外が残すリスク"),
compensatingControl: text.describe("リスクを抑える代替策"),
owner: short.describe("例外と代替策の実行責任者"),
approver: short.describe("リスク受容の決裁者"),
approvalRef: short.optional().describe("承認記録への参照"),
reviewDate: date.describe("次の再審査日"),
expiry: date.describe("例外の終了日"),
status: z.enum(["proposed", "approved", "denied", "closed"]).describe("例外の審査・運用状態"),
}).strict();
export const schema = z.object({
title: text.describe("例外統制の要点"),
subtitle: text.optional().describe("適用組織・ポリシー版・基準日"),
program: short.describe("対象セキュリティプログラム"),
exceptions: z.array(exceptionSchema).min(3).max(12).describe("期限と代替策が明確な統制例外"),
}).strict().superRefine((data, ctx) => {
const ids = data.exceptions.map(({ id }) => id);
if (new Set(ids).size !== ids.length) {
ctx.addIssue({ code: "custom", path: ["exceptions"], message: "例外IDは一意にすること" });
}
data.exceptions.forEach((exception, index) => {
if (exception.status === "approved" && !exception.approvalRef) {
ctx.addIssue({ code: "custom", path: ["exceptions", index, "approvalRef"], message: "承認済み例外には承認記録の参照が必要" });
}
if (exception.reviewDate > exception.expiry) {
ctx.addIssue({ code: "custom", path: ["exceptions", index, "reviewDate"], message: "再審査日は例外終了日以前にすること" });
}
});
});
export type Props = z.infer<typeof schema>;
/designs/security-exception-register/source/meta.yaml
id: security-exception-register
name:
ja: セキュリティ統制例外台帳
en: Security Control Exception Register
purpose:
ja: 一時的な統制逸脱に、範囲・代替策・リスク受容者・承認記録・終了期限を結びつける。
en: Tie temporary control deviations to scope, compensating controls, risk approvers, approval records, and expiry.
status: published
still: ./still.png
category: governance
catalogOrder: 850
jobs:
- prioritize-diagnose
- assign-governance
useCases:
- requirements
- status
layoutKinds:
- table
- stack
tags:
- security-exception
- risk-acceptance
- compensating-control
- expiry-review
- responsive
whenToUse:
- ja: 標準セキュリティ統制を一時的に満たせず、明示的なリスク受容と期限つき代替策が必要なとき。
en: Use when a security control cannot temporarily be met and the deviation needs explicit risk acceptance and expiring compensating measures.
avoidWhen:
- ja: 侵害や障害の発生後に原因と再発防止を記録する場合は、インシデントレビューを使う。
en: Use an incident review to record causes and prevention after an incident.
requiredInputs:
- ja: 対象統制、適用範囲、理由、残留リスク、代替策、責任者、決裁者、再審査日、終了日を定義する。
en: Define the control, scope, rationale, residual risk, compensating measure, owner, approver, review date, and expiry.
relatedLayouts:
- risk-matrix-register
- permission-matrix
dataSchema:
title: string — 例外統制の要点
subtitle: string? — 適用組織・ポリシー版・基準日
program: string — 対象セキュリティプログラム
exceptions: array (3–12) — 統制例外(理由、リスク、代替策、責任者、決裁者、承認参照、再審査日、終了日、状態)
sources: []
/designs/security-exception-register/source/sample-data.json
{
"title": "旧式監視ノードのTLS例外を短期承認し、隔離と更新期限を設定する",
"subtitle": "Security Standard 5.2 · 2026-09-20 review",
"program": "Northstar Security Assurance",
"exceptions": [
{ "id": "SEC-EX-041", "control": "TLS 1.2 minimum", "scope": "監視センサー ingest-east-2 と専用ゲートウェイ間", "justification": "ベンダー更新は次回保守窓まで適用できず、即時更新は計測停止を招く。", "risk": "medium", "compensatingControl": "送信元を専用VLANに限定し、受信側ACLと証明書期限アラートを有効化する。", "owner": "Observability Platform", "approver": "Security Duty Officer", "approvalRef": "GRC-APR-2841", "reviewDate": "2026-10-01", "expiry": "2026-10-30", "status": "approved" },
{ "id": "SEC-EX-044", "control": "MFA for service account", "scope": "夜間バッチ専用の移行アカウント svc-ledger-import", "justification": "対話ログインをせず、作業停止を避けるため段階的にワークロードIDへ切り替える。", "risk": "high", "compensatingControl": "鍵をVaultで短期ローテーションし、送信元IPと実行時刻を監視する。", "owner": "Finance Data Ops", "approver": "Identity Control Lead", "reviewDate": "2026-09-28", "expiry": "2026-10-15", "status": "proposed" },
{ "id": "SEC-EX-047", "control": "Encrypted backup retention", "scope": "地域規制データの隔離アーカイブ保管庫", "justification": "移管先の鍵管理検証が完了するまで旧保管形式を読み取り専用で保持する。", "risk": "low", "compensatingControl": "ネットワークを切断し、復号鍵を二者承認のHSMに保管する。", "owner": "Records Platform", "approver": "Data Protection Officer", "approvalRef": "GRC-APR-2858", "reviewDate": "2026-10-05", "expiry": "2026-11-01", "status": "approved" }
]
}