# 非機能要件定義書

## 概要

本ドキュメントは、Decap CMS（旧 Netlify CMS）のソースコードから抽出した非機能要件を定義するものである。Decap CMS は、Git リポジトリをバックエンドとしたオープンソースのヘッドレス CMS であり、GitHub / GitLab / Bitbucket / Azure DevOps / Gitea など複数の Git プロバイダに対応するモノレポ構成のフロントエンドアプリケーションである。本プロジェクトは npm workspaces と Lerna によるモノレポで管理され、38 個のパッケージから構成される。

## 性能要件

### レスポンスタイム

| 処理種別 | 目標値 | 備考 |
| --- | --- | --- |
| API リクエスト全般 | 60秒以内 | `unsentRequest.js` にて `fetchWithTimeout` が60秒のタイムアウトを設定。AbortController による自動中断を実施 |
| 非同期ロック取得 | 15秒以内 | `asyncLock.ts` のデフォルトタイムアウト値。ロック取得に失敗した場合はセマフォをリセットして解放 |
| decap-server Mutex ロック | 3秒以内 | `decap-server/localGit/index.ts` にて `withTimeout(new Mutex(), 3000)` を設定。ローカル Git 操作の直列化に使用 |
| Redux Wait Action | 30秒以内 | `waitUntil.ts` の `waitUntilWithTimeout` がデフォルト30秒のタイムアウトで非同期アクション完了を待機 |
| Netlify Identity 初期化 | 2.5秒以内 | `git-gateway/implementation.ts` にて `Promise.race` で2500ms のタイムアウト後に手動初期化 |
| UI ローダー表示遅延 | 500ms | `Loader.js` にて表示前に500ms の遅延を設けユーザー体感を改善 |

### スループット

| 項目 | 目標値 | 備考 |
| --- | --- | --- |
| 同時 Git 操作数 | 1（直列実行） | decap-server にて Mutex により Git 操作を排他制御し、同時に1操作のみ許可 |
| GraphQL バッチクエリ（GitLab） | 最大90パス/バッチ | `gitlab/API.ts` の `batch` 関数にてパスを90件ずつバッチ分割 |
| GraphQL バッチクエリ（GitLab, 小規模） | 最大8パス/バッチ | `gitlab/API.ts` にて小規模処理では8件ずつバッチ分割 |
| Relation ウィジェットキャッシュ | 最大100件 | `RelationCache.js` にて Map 上限100件を管理し、超過時は FIFO で削除 |
| リクエストボディサイズ上限 | 50MB | `decap-server/common/index.ts` にて `express.json({ limit: '50mb' })` を設定 |

### リトライ・バックオフ

| 項目 | 設定値 | 備考 |
| --- | --- | --- |
| API リクエストリトライ上限 | 最大5回 | `lib-util/API.ts` の `requestWithBackoff` にて `attempt > 5` で打ち切り |
| リトライ間隔（バックオフ） | attempt^2 秒（指数関数的） | `requestWithBackoff` にて `attempt * attempt` 秒のバックオフを実施 |
| レートリミットリセット待機 | 最大3600秒（1時間） | `RateLimitError` にて `resetSeconds` を1秒～3600秒の範囲にクランプ |
| GitHub getDifferences リトライ | 最大10回（Open Authoring 時） | `github/API.ts` にてクロスリポジトリ比較が 404 を返す場合にリトライ。リトライ間隔は `i * 500ms` |
| GitLab リベースポーリング | 最大30回 | `gitlab/API.ts` にて `rebase_in_progress` の完了を1秒間隔で最大30回ポーリング |
| バックエンドステータスチェック | リトライ間隔 `i * 1000ms` | `backend.ts` にて `status()` 呼び出しの失敗時にインクリメンタルな遅延で再試行 |

## 可用性要件

| 項目 | 目標値 | 備考 |
| --- | --- | --- |
| 稼働率 | Git プロバイダ依存 | CMS はクライアントサイド SPA であり、ホスティング先とバックエンド Git プロバイダ（GitHub / GitLab 等）の稼働率に依存 |
| 計画停止時間 | 該当なし | 静的ビルド配信のため、サーバーサイドの計画停止は発生しない。Git プロバイダ側のメンテナンス時間に依存 |
| 障害復旧時間（RTO） | 該当なし（クライアントサイドアプリ） | SPA として動作し、ビルド済みアセットが配信されるため、RTO はホスティングプラットフォームに依存 |
| 目標復旧時点（RPO） | 0（データロスなし） | 全てのコンテンツは Git リポジトリに保持されるため、Git の履歴を通じてデータロスなしの復旧が可能 |
| Git Gateway ステータス監視 | Netlify Status API 連携 | `git-gateway/implementation.ts` にて `netlifystatus.com` の API を使用して Git Gateway の稼働状態を確認 |
| ローカルストレージ障害対応 | 自動検出 | `localForage.ts` にてブラウザの IndexedDB/localStorage のクォータ超過を検出し、エラーメッセージを表示 |

## セキュリティ要件

### 認証方式

| 方式 | 説明 | 根拠 |
| --- | --- | --- |
| OAuth 2.0 PKCE | `pkce-oauth.js` にて PKCE（Proof Key for Code Exchange）フローを実装。S256 チャレンジメソッドを使用し、`window.crypto.subtle.digest` で SHA-256 ハッシュを生成 | RFC 7636 準拠 |
| OAuth 2.0 Implicit Flow | `implicit-oauth.js` にて Implicit Grant フローを実装。アクセストークンを URL フラグメントから取得後、ブラウザ履歴から即座に削除 | レガシー対応 |
| Netlify Identity / GoTrue | `git-gateway/implementation.ts` にて GoTrue（Netlify Identity）による JWT ベースの認証を実装。JWT デコードによるトークン有効期限管理 | Netlify 統合 |
| OpenID Connect (OIDC) | `pkce-oauth.js` にて `.well-known/openid-configuration` エンドポイントからの自動ディスカバリをサポート | OIDC 準拠 |

### セキュリティ対策

| 対策 | 説明 | 根拠 |
| --- | --- | --- |
| HTTPS 強制 | `utils.js` の `isInsecureProtocol()` にて非 HTTPS 接続での認証を禁止（localhost / 127.0.0.1 を除く） | 通信経路暗号化の保証 |
| Nonce 検証 | `utils.js` にて UUID v4 ベースの nonce を生成し、sessionStorage に保存。認証コールバック時に nonce の一致を検証し、CSRF/リプレイ攻撃を防止 | CSRF 対策 |
| CORS 設定 | `decap-server/common/index.ts` にて `cors({ origin: process.env.ORIGIN \|\| '*' })` を設定。環境変数による制御が可能 | クロスオリジン制御 |
| トークンの安全な管理 | Implicit OAuth 完了後、URL フラグメントからトークンを即座に削除（`clearHash()`）。PKCE では code_verifier を sessionStorage に保存し、使用後に削除 | トークン漏洩防止 |
| パストラバーサル防止 | `decap-server/joi/customValidators` にて `pathTraversal` バリデータを使用し、ファイルパスの不正操作を防止 | サーバーサイドセキュリティ |
| 入力サニタイズ | `urlHelper.js` にて URI/スラッグのサニタイズを実施。Markdown ウィジェットにて `sanitize_preview` オプションによる HTML サニタイズをサポート | XSS 対策 |
| トークンリフレッシュ | Bitbucket バックエンドにて `refresh_token` を使用したトークンリフレッシュフローを実装。トークン期限切れの自動検出と再取得 | セッション維持 |
| リクエストボディサイズ制限 | decap-server にて `express.json({ limit: '50mb' })` でリクエストボディサイズを制限 | DoS 対策 |

## 拡張性要件

### プラグインアーキテクチャ

Decap CMS はグローバルレジストリパターンによる拡張ポイントを提供する。`registry.js` にて以下の拡張機能を登録・管理する。

| 拡張ポイント | 登録関数 | 説明 |
| --- | --- | --- |
| バックエンド | `registerBackend()` | 新規 Git プロバイダの追加 |
| ウィジェット | `registerWidget()` | カスタムエディタウィジェットの追加 |
| プレビューテンプレート | `registerPreviewTemplate()` | カスタムプレビューテンプレートの追加 |
| プレビュースタイル | `registerPreviewStyle()` | カスタムスタイルシートの追加 |
| メディアライブラリ | `registerMediaLibrary()` | 外部メディアライブラリ（Cloudinary / Uploadcare 等）の追加 |
| エディタコンポーネント | `registerEditorComponent()` | Markdown エディタ用カスタムコンポーネントの追加 |
| Remark プラグイン | `registerRemarkPlugin()` | Markdown 処理パイプラインへのプラグイン追加 |
| ロケール | `registerLocale()` | 多言語対応のロケール追加 |
| イベントリスナー | `registerEventListener()` | ライフサイクルイベント（preSave / postSave / prePublish / postPublish / preUnpublish / postUnpublish）のフック |
| ウィジェットシリアライザ | `registerWidgetValueSerializer()` | カスタムウィジェット値のシリアライズ処理追加 |
| フォーマット | `registerFormat()` | カスタムファイルフォーマットの追加 |

### モノレポ構成

| 項目 | 詳細 |
| --- | --- |
| パッケージ管理 | npm workspaces + Lerna（独立バージョニング） |
| パッケージ数 | 38パッケージ |
| ビルドツール | Webpack 5、Babel、TypeScript |
| タスクランナー | nx（並列ビルド対応） |
| パッケージ分類 | コア（decap-cms-core）、バックエンド（7種）、ウィジェット（15種）、UIコンポーネント、ライブラリ、メディアライブラリ |

### 対応ブラウザ

`package.json` の `browserslist` に基づく。

| ブラウザ | バージョン |
| --- | --- |
| Chrome | 直近2バージョン |
| Firefox | 直近2バージョン |
| Safari | 直近2バージョン |
| Edge | 直近2バージョン |
| Opera | 直近2バージョン |
| iOS Safari | 直近2バージョン |
| Chrome Android | 直近2バージョン |

## 保守性要件

### コード品質

| 項目 | ツール/設定 | 備考 |
| --- | --- | --- |
| 静的型検査 | TypeScript（strict モード） | `tsconfig.json` にて `strict: true`、`noUnusedLocals: true`、`noUnusedParameters: true` を設定 |
| リンター（JavaScript） | ESLint | `eslint-plugin-import`、`eslint-plugin-react`、`eslint-plugin-unicorn`、`@typescript-eslint` を使用 |
| リンター（CSS） | Stylelint | `stylelint-config-standard-scss`、`postcss-styled-syntax`（CSS-in-JS 対応）を使用 |
| コードフォーマッタ | Prettier | `.js`、`.jsx`、`.ts`、`.tsx`、`.css` を対象 |
| コミットメッセージ | Commitlint | `@commitlint/config-conventional` による Conventional Commits 規約の強制 |
| Git フック | Husky | コミット時にリンター・フォーマッタを自動実行 |
| 設定バリデーション | AJV（JSON Schema） | `configSchema.js` にて CMS 設定ファイルを JSON Schema で検証。`ajv-errors` と `ajv-keywords` を使用した詳細バリデーション |

### テスト

| テスト種別 | ツール | 備考 |
| --- | --- | --- |
| ユニットテスト | Jest 27 | `jest-environment-jsdom` 環境で実行。`@testing-library/react` と `@testing-library/jest-dom` を使用 |
| E2E テスト | Cypress 12 | Chrome ヘッドレスモードで実行。ビジュアルリグレッションテスト（`@simonsmith/cypress-image-snapshot`）対応 |
| モックサーバー | mockserver-node / nock | API テスト用のモックサーバーとHTTP リクエストインターセプタを使用 |
| スナップショットテスト | Emotion Jest Serializer | CSS-in-JS のスナップショットテストをサポート |

### バージョニング

| 項目 | 詳細 |
| --- | --- |
| バージョニング方式 | 独立バージョニング（Lerna `"version": "independent"`） |
| リリースブランチ | `main` および `release/*` |
| コミット規約 | Conventional Commits |
| 変更検出除外 | `__fixtures__`、`__tests__`、`*.md` |

## 運用・監視要件

### ロギング

| 項目 | 詳細 | 備考 |
| --- | --- | --- |
| サーバーサイドログ | Winston + Morgan（combined フォーマット） | `decap-server/common/index.ts` にて Morgan の出力を Winston logger の debug レベルへストリーム転送 |
| クライアントサイドログ | `console.log` / `console.warn` / `console.error` | レートリミットのバックオフ、非同期ロックの異常解放、設定エラーなどの重要イベントをブラウザコンソールへ出力 |
| 設定バリデーションエラー | `console.error` で詳細出力 | `configSchema.js` にてスキーマ検証失敗時にエラー詳細をコンソールへ出力 |

### レートリミット管理

| 項目 | 詳細 | 備考 |
| --- | --- | --- |
| GitHub API レートリミット | HTTP 403 + `API rate limit exceeded` を検出 | `X-RateLimit-Reset` ヘッダからリセット時刻を取得 |
| GitLab / Bitbucket API レートリミット | HTTP 429 を検出 | `requestWithBackoff` にてリトライを実施 |
| 自動バックオフ | asyncLock によるリクエスト一時停止 | レートリミット検出時に全リクエストを一時的にブロックし、リセット後に自動再開 |

### キャッシュ管理

| キャッシュ種別 | ストレージ | 詳細 |
| --- | --- | --- |
| ファイルコンテンツキャッシュ | localForage（IndexedDB） | `readFile` にて Git blob SHA をキーにしてファイル内容をキャッシュ。テキストとバイナリの両方に対応 |
| ファイルメタデータキャッシュ | localForage（IndexedDB） | `readFileMetadata` にて著者情報・更新日時をキャッシュ |
| ローカルツリーキャッシュ | localForage（IndexedDB） | `implementation.ts` にてブランチ・フォルダ単位でファイルツリーをキャッシュし、差分更新を実施 |
| GraphQL レスポンスキャッシュ | Apollo InMemoryCache | GitHub / GitLab バックエンドにてリポジトリ情報・ユーザー情報・blob SHA などの不変データに `cache-first` ポリシーを適用 |
| Relation ウィジェットキャッシュ | Map（メモリ） | 最大100件のクエリ結果をキャッシュ。重複リクエストの待ち合わせ機構付き |
| Algolia 検索キャッシュ | メモリ（entriesCache） | コレクション・ページ単位で検索結果をキャッシュ |
| API リクエストキャッシュ制御 | HTTP Cache ヘッダ | デフォルトで `cache: 'no-cache'` を設定。一部処理で `cache: 'no-store'`（アップロード・削除系）や `cache: 'force-cache'`（不変コンテンツ）を使い分け |
| ステガノグラフィエンコーディングキャッシュ | Map（メモリ） | `stega.ts` にてエンコード済み値をパスベースでキャッシュし、キーストローク中の再エンコードを防止 |

### エラーハンドリング

| 項目 | 詳細 | 備考 |
| --- | --- | --- |
| React ErrorBoundary | `ErrorBoundary.js` にてプレビューペインのレンダリングエラーをキャッチし、エラー詳細を表示 | ユーザー操作の継続性を保証 |
| API エラークラス | `APIError` クラスによる構造化エラー（メッセージ、HTTP ステータス、バックエンド名） | 全バックエンドで統一的なエラー表現 |
| 非同期ロック安全機構 | セマフォの `leave` 多重呼び出し時にリセットして復旧 | デッドロック防止 |

## 備考

- 本プロジェクトはクライアントサイド SPA を主体としたアーキテクチャであり、サーバーサイドコンポーネント（decap-server）はローカル開発用のプロキシサーバーとしてのみ動作する。
- 可用性に関する定量的目標値（稼働率・RTO 等）はコードベースからは明示的に定義されていない。実際の可用性はデプロイ先のホスティングプラットフォームおよびバックエンド Git プロバイダの SLA に依存する。
- セキュリティに関して、Content Security Policy（CSP）やサブリソース完全性（SRI）の設定はコードベースからは確認できなかった。デプロイ環境にて別途設定が必要な可能性がある。
- 性能要件の目標値はコード中のハードコーディングされた設定値から抽出したものであり、明示的な SLA 文書に基づくものではない。
