Back to index

Incident Response and Escalation Policy

Timeline
Generates documents
Incident Response and Escalation Policy

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

incident-escalation-policy-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 "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

Files

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

template.tsx

10.1 KB

/designs/incident-escalation-policy/source/template.tsx

Show contents
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>;
}

schema.ts

2.7 KB

/designs/incident-escalation-policy/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);
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>;

meta.yaml

1.6 KB

/designs/incident-escalation-policy/source/meta.yaml

Show contents
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: []

sample-data.json

3.1 KB

/designs/incident-escalation-policy/source/sample-data.json

Show contents
{
  "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": "未対応理由と次回確認日を合意する"
        }
      ]
    }
  ]
}