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 "Incident Response and Escalation Policy". This layout runs, so there is no composition to rebuild — what it needs is content. ## What this layout does Set response targets, customer updates, and escalation steps for each incident priority. ## What you have to supply - Define each priority trigger, time target, update cadence, escalation owner, and action. ## The shape of the input - `title` — string — 方針の結論 - `subtitle` — string? — 適用範囲・前提・確認日 - `service` — string — 対象サービスまたは運用チーム - `tiers` — array (3–5) — 重大度ごとの条件・応答目標・連絡間隔・引き継ぎ段階(1–4) 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 incident-escalation-policy <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/incident-escalation-policy/source/template.tsx - https://design-vault.pages.dev/designs/incident-escalation-policy/source/schema.ts - https://design-vault.pages.dev/designs/incident-escalation-policy/source/sample-data.json - https://design-vault.pages.dev/designs/incident-escalation-policy/sample.html
Template, input schema, sample data and metadata. This document was generated from those four. The generated document
/designs/incident-escalation-policy/source/template.tsx
import type { Props } from "./schema";
const CSS = String.raw`
.asset-incident-escalation-policy{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-incident-escalation-policy *{box-sizing:border-box}.asset-incident-escalation-policy header{margin-bottom:2rem}.asset-incident-escalation-policy .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary);margin-bottom:.75rem}.asset-incident-escalation-policy h1{font-size:var(--text-h1);line-height:1.3;max-width:28em}.asset-incident-escalation-policy .subtitle{margin-top:.75rem;color:var(--color-secondary)}
.asset-incident-escalation-policy .policy{border-top:2px solid var(--color-primary)}.asset-incident-escalation-policy .tier{display:grid;grid-template-columns:minmax(9rem,1fr) minmax(18rem,2fr) minmax(12rem,1.15fr) minmax(16rem,1.6fr);gap:1rem;padding:1.2rem 0;border-bottom:1px solid var(--color-outline);align-items:start}.asset-incident-escalation-policy .tier>*{min-width:0}.asset-incident-escalation-policy .priority{display:flex;gap:.7rem;align-items:baseline}.asset-incident-escalation-policy .priority b{font-family:var(--font-mono);font-size:var(--text-h2);font-variant-numeric:tabular-nums}.asset-incident-escalation-policy .priority span{font-weight:650}.asset-incident-escalation-policy .trigger{margin-top:.45rem;color:var(--color-secondary);overflow-wrap:anywhere}.asset-incident-escalation-policy .target{display:grid;grid-template-columns:1fr 1fr;gap:.6rem}.asset-incident-escalation-policy .target div{border-left:2px solid var(--color-tertiary);padding-left:.65rem}.asset-incident-escalation-policy .target dt,.asset-incident-escalation-policy .small-label{font-size:var(--text-label);color:var(--color-secondary)}.asset-incident-escalation-policy .target dd{margin:0;font-family:var(--font-mono);font-size:var(--text-h3);font-variant-numeric:tabular-nums}.asset-incident-escalation-policy .updates{margin-top:.7rem;font-size:var(--text-label)}.asset-incident-escalation-policy .steps{display:flex;flex-direction:column;gap:.5rem;list-style:none;margin:0;padding:0}.asset-incident-escalation-policy .steps li{display:grid;grid-template-columns:3.5rem minmax(0,1fr);gap:.6rem;border-left:1px solid var(--color-outline);padding-left:.7rem}.asset-incident-escalation-policy .steps time{font-family:var(--font-mono);font-variant-numeric:tabular-nums;color:var(--color-tertiary);font-weight:650;white-space:nowrap}.asset-incident-escalation-policy .steps p{margin:0;overflow-wrap:anywhere}.asset-incident-escalation-policy .steps strong{font-weight:650}.asset-incident-escalation-policy .details{margin-top:1.25rem;border-top:1px solid var(--color-outline)}.asset-incident-escalation-policy .details summary{cursor:pointer;padding:.8rem 0;min-height:2.75rem;color:var(--color-secondary)}.asset-incident-escalation-policy .details summary:focus-visible{outline:2px solid var(--color-tertiary);outline-offset:2px}.asset-incident-escalation-policy .detail{padding:1rem 0;border-top:1px solid var(--color-outline)}.asset-incident-escalation-policy .detail h3{margin:0 0 .6rem;font-size:var(--text-body-md)}.asset-incident-escalation-policy .detail p{margin:.35rem 0;overflow-wrap:anywhere}.asset-incident-escalation-policy .detail ol{margin:.6rem 0;padding-left:1.4rem}.asset-incident-escalation-policy .detail li+li{margin-top:.4rem}.asset-incident-escalation-policy footer{margin-top:1.5rem;padding-top:.75rem;border-top:1px solid var(--color-outline);color:var(--color-secondary);font-size:var(--text-label)}
.asset-incident-escalation-policy .still-svg{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media(max-width:760px){.asset-incident-escalation-policy{padding:1.5rem 1rem}.asset-incident-escalation-policy h1{font-size:var(--text-h2)}.asset-incident-escalation-policy header{margin-bottom:1.25rem}.asset-incident-escalation-policy .tier{grid-template-columns:minmax(0,1fr);gap:.8rem;padding:1rem 0}.asset-incident-escalation-policy .target{max-width:26rem}.asset-incident-escalation-policy .updates{margin-top:.4rem}.asset-incident-escalation-policy .steps{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.65rem 1rem}.asset-incident-escalation-policy .steps li{grid-template-columns:1fr;gap:.1rem}.asset-incident-escalation-policy .steps time{font-size:var(--text-label)}}
@media print{.asset-incident-escalation-policy{max-width:none;padding:1rem}.asset-incident-escalation-policy .details>*{display:block}.asset-incident-escalation-policy .details summary{display:none}}
`;
const priorityNumber = (priority: string) => Number(priority.slice(1));
const compact = (value: string, max: number) => [...value].length > max ? `${[...value].slice(0, max - 1).join("")}…` : value;
function Still({ data }: { data: Props }) {
const sorted = [...data.tiers].sort((a, b) => priorityNumber(a.priority) - priorityNumber(b.priority));
const rowHeight = 112;
const firstY = 132;
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="50" fontSize="14" fontFamily="sans-serif" fill="var(--color-secondary)">{data.service}</text>
<text x="48" y="91" fontSize="28" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(data.title, 54)}</text>
<text x="48" y="119" fontSize="13" fontFamily="sans-serif" fill="var(--color-secondary)">初動目標・顧客連絡・段階的な引き継ぎ</text>
{sorted.map((tier, index) => {
const y = firstY + index * rowHeight;
return <g key={tier.id}>
<line x1="48" x2="1232" y1={y - 22} y2={y - 22} stroke="var(--color-outline)" />
<text x="52" y={y + 16} fontSize="25" fontWeight="700" fontFamily="sans-serif" fill="var(--color-tertiary)">{tier.priority}</text>
<text x="52" y={y + 39} fontSize="15" fontWeight="600" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(tier.label, 16)}</text>
<text x="52" y={y + 62} fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">{compact(tier.trigger, 28)}</text>
<text x="360" y={y + 4} fontSize="11" fontFamily="sans-serif" fill="var(--color-secondary)">一次応答</text>
<text x="360" y={y + 31} fontSize="22" fontFamily="sans-serif" fill="var(--color-on-surface)">{tier.acknowledgeMinutes}分</text>
<text x="480" y={y + 4} fontSize="11" fontFamily="sans-serif" fill="var(--color-secondary)">影響緩和</text>
<text x="480" y={y + 31} fontSize="22" fontFamily="sans-serif" fill="var(--color-on-surface)">{tier.mitigationTargetMinutes}分</text>
<text x="610" y={y + 4} fontSize="11" fontFamily="sans-serif" fill="var(--color-secondary)">顧客連絡</text>
<text x="610" y={y + 31} fontSize="22" fontFamily="sans-serif" fill="var(--color-on-surface)">{tier.customerUpdateEveryMinutes}分ごと</text>
{tier.steps.map((step, stepIndex) => {
const x = 770 + stepIndex * 112;
return <g key={step.id}>
<line x1={x - 14} x2={x + 92} y1={y + 20} y2={y + 20} stroke="var(--color-outline)" strokeWidth="2" />
<circle cx={x} cy={y + 20} r="5" fill="var(--color-tertiary)" />
<text x={x} y={y - 1} fontSize="11" fontFamily="sans-serif" fill="var(--color-tertiary)">{step.afterMinutes}分後</text>
<text x={x} y={y + 48} fontSize="12" fontWeight="600" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(step.owner, 11)}</text>
<text x={x} y={y + 68} fontSize="11" fontFamily="sans-serif" fill="var(--color-secondary)">{compact(step.id, 12)}</text>
</g>;
})}
</g>;
})}
<text x="48" y="704" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">適用前に当番・連絡先・時刻の起点を確認する</text>
</svg>;
}
export default function Template(data: Props) {
const tiers = [...data.tiers].sort((a, b) => priorityNumber(a.priority) - priorityNumber(b.priority));
return <section className="asset asset-incident-escalation-policy">
<style>{CSS}</style>
<header>
<p className="eyebrow">{data.service} の対応方針</p>
<h1>{data.title}</h1>
{data.subtitle && <p className="subtitle">{data.subtitle}</p>}
</header>
<div className="policy" aria-label="重大度ごとの初動とエスカレーション">
{tiers.map((tier) => <article className="tier" data-tier-id={tier.id} key={tier.id}>
<div>
<div className="priority"><b>{tier.priority}</b><span>{tier.label}</span></div>
<p className="trigger">{tier.trigger}</p>
</div>
<dl className="target">
<div><dt>一次応答まで</dt><dd>{tier.acknowledgeMinutes} 分</dd></div>
<div><dt>影響緩和の目標</dt><dd>{tier.mitigationTargetMinutes} 分</dd></div>
<div className="updates"><dt>顧客への状況連絡</dt><dd>{tier.customerUpdateEveryMinutes} 分ごと</dd></div>
</dl>
<div className="small-label">経過時間による引き継ぎ</div>
<ol className="steps">{tier.steps.map((step) => <li key={step.id}>
<time>{step.afterMinutes}分後</time>
<p><strong>{step.owner}</strong><br />{step.action}</p>
</li>)}</ol>
</article>)}
</div>
<details className="details">
<summary>対応条件をまとめて確認</summary>
{tiers.map((tier) => <section className="detail" key={tier.id}>
<h3>{tier.priority} · {tier.label}</h3>
<p><strong>適用条件:</strong> {tier.trigger}</p>
<p><strong>初動:</strong> {tier.acknowledgeMinutes}分以内に応答、{tier.mitigationTargetMinutes}分以内に影響を緩和。顧客へ{tier.customerUpdateEveryMinutes}分ごとに状況を連絡。</p>
<ol>{tier.steps.map((step) => <li key={step.id}>{step.afterMinutes}分後 — {step.owner}: {step.action}</li>)}</ol>
</section>)}
</details>
<footer>通知先と経過時間の起点を運用手順に明記し、定期的に見直す。</footer>
<Still data={data} />
</section>;
}
/designs/incident-escalation-policy/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 minutes = z.number().int().positive();
const escalationStepSchema = z.object({
id: short.describe("この段階の識別子"),
afterMinutes: minutes.describe("障害検知からの経過時間(分)"),
owner: short.describe("次に対応を引き継ぐ役割"),
action: text.describe("引き継ぎ時に行う確認・判断"),
}).strict();
export const incidentTierSchema = z.object({
id: short.describe("重大度行の一意な識別子"),
priority: z.enum(["P0", "P1", "P2", "P3", "P4"]).describe("P0が最優先"),
label: short.describe("重大度の短い名称"),
trigger: text.describe("この重大度を選ぶ状況"),
acknowledgeMinutes: minutes.describe("一次応答までの目標時間"),
mitigationTargetMinutes: minutes.describe("影響を抑えるまでの目標時間"),
customerUpdateEveryMinutes: minutes.describe("顧客への状況連絡間隔"),
steps: z.array(escalationStepSchema).min(1).max(4).describe("経過時間ごとの段階的な引き継ぎ"),
}).strict();
export const schema = z.object({
title: text.describe("対応方針の要点"),
subtitle: text.optional().describe("適用サービス・前提・確認日"),
service: short.describe("対象サービスまたは運用チーム"),
tiers: z.array(incidentTierSchema).min(3).max(5).describe("重大度ごとの初動とエスカレーション方針"),
}).strict().superRefine((data, ctx) => {
const ids = data.tiers.map((tier) => tier.id);
if (new Set(ids).size !== ids.length) {
ctx.addIssue({ code: "custom", path: ["tiers"], message: "重大度行のidは一意にすること" });
}
const priorities = data.tiers.map((tier) => tier.priority);
if (new Set(priorities).size !== priorities.length) {
ctx.addIssue({ code: "custom", path: ["tiers"], message: "重大度は重複できない" });
}
if (priorities.some((priority, index) => index > 0 && priority < priorities[index - 1])) {
ctx.addIssue({ code: "custom", path: ["tiers"], message: "重大度はP0から優先順に並べること" });
}
data.tiers.forEach((tier, tierIndex) => {
const stepIds = tier.steps.map((step) => step.id);
if (new Set(stepIds).size !== stepIds.length) {
ctx.addIssue({ code: "custom", path: ["tiers", tierIndex, "steps"], message: "引き継ぎ段階のidは一意にすること" });
}
if (tier.steps.some((step, index) => index > 0 && step.afterMinutes <= tier.steps[index - 1].afterMinutes)) {
ctx.addIssue({ code: "custom", path: ["tiers", tierIndex, "steps"], message: "引き継ぎ時刻は昇順で、一意にすること" });
}
});
});
export type Props = z.infer<typeof schema>;
/designs/incident-escalation-policy/source/meta.yaml
id: incident-escalation-policy
name:
ja: インシデント対応・エスカレーション方針
en: Incident Response and Escalation Policy
purpose:
ja: 重大度ごとの初動目標、顧客連絡、段階的な引き継ぎを事前に合意する。
en: Set response targets, customer updates, and escalation steps for each incident priority.
status: published
still: ./still.png
category: operations
catalogOrder: 790
jobs:
- plan-sequence
- assign-governance
useCases:
- process
layoutKinds:
- timeline
- table
tags:
- incident-response
- escalation
- operations
- offline
- responsive
whenToUse:
- ja: 障害の重大度と経過時間に応じた応答目標、連絡間隔、引き継ぎ先を合意するとき。
en: Use when agreeing on response targets, update cadence, and handoffs by incident priority.
avoidWhen:
- ja: 障害の原因や再発防止を記録する場合は、インシデントレビューを使う。
en: Use an incident review to record causes and prevention after an incident.
requiredInputs:
- ja: 重大度ごとの適用条件、時間目標、連絡間隔、引き継ぎ先と対応を定義する。
en: Define each priority trigger, time target, update cadence, escalation owner, and action.
relatedLayouts:
- incident-review
- release-readiness
dataSchema:
title: string — 方針の結論
subtitle: string? — 適用範囲・前提・確認日
service: string — 対象サービスまたは運用チーム
tiers: array (3–5) — 重大度ごとの条件・応答目標・連絡間隔・引き継ぎ段階(1–4)
sources: []
/designs/incident-escalation-policy/source/sample-data.json
{
"title": "重大度に応じた初動と引き継ぎを揃える",
"subtitle": "架空のSaaS運用方針|検知から経過した時間で引き継ぐ",
"service": "業務支援クラウド",
"tiers": [
{
"id": "tier-p0",
"priority": "P0",
"label": "全体停止",
"trigger": "全利用者が主要操作を行えず、回避策もない",
"acknowledgeMinutes": 5,
"mitigationTargetMinutes": 30,
"customerUpdateEveryMinutes": 15,
"steps": [
{
"id": "p0-step-1",
"afterMinutes": 10,
"owner": "当番責任者",
"action": "指揮役を決め、復旧担当を招集する"
},
{
"id": "p0-step-2",
"afterMinutes": 20,
"owner": "サービス責任者",
"action": "回避策と復旧見込みを判断する"
},
{
"id": "p0-step-3",
"afterMinutes": 40,
"owner": "事業責任者",
"action": "広域連絡と外部支援の要否を決める"
}
]
},
{
"id": "tier-p1",
"priority": "P1",
"label": "主要機能の障害",
"trigger": "一部の利用者で主要機能が使えず、影響が拡大している",
"acknowledgeMinutes": 15,
"mitigationTargetMinutes": 90,
"customerUpdateEveryMinutes": 30,
"steps": [
{
"id": "p1-step-1",
"afterMinutes": 30,
"owner": "当番責任者",
"action": "影響範囲と直近の変更を照合する"
},
{
"id": "p1-step-2",
"afterMinutes": 60,
"owner": "サービス責任者",
"action": "追加要員または機能停止を判断する"
}
]
},
{
"id": "tier-p2",
"priority": "P2",
"label": "機能劣化",
"trigger": "応答遅延または限定した機能不全が続く",
"acknowledgeMinutes": 60,
"mitigationTargetMinutes": 480,
"customerUpdateEveryMinutes": 120,
"steps": [
{
"id": "p2-step-1",
"afterMinutes": 120,
"owner": "担当チーム",
"action": "再現条件と影響を記録する"
},
{
"id": "p2-step-2",
"afterMinutes": 240,
"owner": "当番責任者",
"action": "当日中の修正可否を確認する"
}
]
},
{
"id": "tier-p3",
"priority": "P3",
"label": "軽微な不具合",
"trigger": "限定した利用者に影響し、回避策が利用できる",
"acknowledgeMinutes": 240,
"mitigationTargetMinutes": 2880,
"customerUpdateEveryMinutes": 1440,
"steps": [
{
"id": "p3-step-1",
"afterMinutes": 480,
"owner": "担当チーム",
"action": "通常の計画へ登録し対応時期を共有する"
},
{
"id": "p3-step-2",
"afterMinutes": 1440,
"owner": "プロダクト責任者",
"action": "未対応理由と次回確認日を合意する"
}
]
}
]
}