コンテンツにスキップ

SALON BOARD クローン — 親SRS(System Requirements Specification)

SALON BOARD クローン — 親SRS(System Requirements Specification)

Section titled “SALON BOARD クローン — 親SRS(System Requirements Specification)”

Document ID: SRS-ROOT-001 Version: 0.3 Status: Draft Last Updated: 2026-04-25 Owner: yudai


本書は親SRS。実装前に各機能単位で作成する子SRSdocs/SRS/features/*.md)の上位規範として機能する。

子SRSは以下を定義する:

  • 個別機能のユースケース
  • 画面仕様・API仕様(zodスキーマレベル)
  • 状態遷移・業務ルール
  • 受け入れ基準(テストケース)

親SRSは子SRS全体で共通するルール・制約・契約を定義し、各子SRSでの再記述を不要にする。各子SRSの冒頭で「本書はSRS-ROOT-001に従う」と明記する。


  • 実装者(自分・将来の協力者)
  • レビュー時の自己チェック
  • LLMに実装を依頼する際のコンテキスト
  • バージョンはSemVer風:Major.Minor(Majorはアーキテクチャ変更、Minorは要件追加)
  • 変更は必ず理由を変更履歴に残す
  • 子SRS作成時、親SRSの更新が必要なら先に親を改訂してから子を書く
IDタイトル場所
DOC-ANALYSIS-001SALON BOARDクローン開発 基礎情報/outputs/SALON_BOARD_クローン開発_基礎情報.md
DOC-ERD-001ERD(Mermaid)/outputs/SALON_BOARD_ERD.md
DOC-DIR-001ディレクトリ構成/outputs/SALON_BOARD_DIRECTORY_STRUCTURE.md
DOC-SETUP-001セットアップガイド/outputs/SALON_BOARD_SETUP_GUIDE.md

用語定義
店舗(Store)本システムのマルチテナント単位。1オペレーターは複数店舗に所属しうる
オペレーター(Operator)管理画面を操作するユーザー。サロンオーナー・店長・スタッフを包含
顧客(Customer)来店者・予約者。来店履歴・予約・スマートペイ対象
スタッフ(Staff)施術担当者。オペレーターと一対一とは限らない(受付専業スタッフ等)
予約(Reservation)来店予定。11状態を持つ状態機械
メニュー(Menu)提供サービス。所要時間・料金・担当可能スタッフを持つ
レジ(Register)会計処理。現金・クレジット・電子マネー・スマートペイを束ねる
スマートペイ(Smart Pay)HPB連携の事前決済・オンライン決済
掲載(Listing)HPBに出す情報。サロン情報・メニュー・写真・クーポン等
LIFFLINE Front-end Framework。LINE内で動く顧客向けWebアプリ
MCPModel Context Protocol。LLMとツールサーバーの標準プロトコル
RLSRow-Level Security。Postgresの行単位アクセス制御

予約・顧客管理・会計・掲載を統合した美容サロン向けSaaS。SALON BOARDを参考にしながら、価格破壊・UX刷新・AIネイティブ・HPB非依存の4軸で差別化する。

  • SALON BOARD:HPB(ホットペッパービューティー)と一体提供、実質 ¥50,000/月水準
  • 本プロダクト:¥10,000〜¥15,000/月水準を目指す、HPB非依存の自社顧客チャネル(LIFF)を内蔵
  • AI差別化:MCPサーバー経由でClaude/ChatGPTから運営操作が可能

In-scope(Phase 1〜4)

  • 予約管理(作成・編集・キャンセル・状態管理・カレンダー)
  • 顧客管理(基本情報・来店履歴・タグ・メモ)
  • レジ・会計(現金・クレジット・電子マネー・スマートペイ)
  • メッセージング(メール・LINE・SMS)
  • スタッフ・設備・メニュー・営業時間管理
  • 売上集計・日報・月次レポート
  • HPB掲載管理(サロン・メニュー・写真・クーポン・ブログ・口コミ返信)
  • LIFF顧客アプリ(予約・会員証・履歴)
  • MCPサーバー(OAuth2.1経由)

Out-of-scope(本プロダクトで扱わない)

  • 物販EC(美容院のEC販売)— Phase 5以降の候補
  • 勤怠・給与計算 — 別SaaSと連携想定
  • ネイル・エステ業態特化機能 — 美容室・理容室にフォーカス
  • HPB”内部連携”(Recruit側API)— 一方向コンテンツ入稿のみ、予約同期はユーザー手動
  • ネイティブアプリ(iOS/Android)— PWA + LIFFで代替、判断は別途

4. ステークホルダー / ペルソナ

Section titled “4. ステークホルダー / ペルソナ”
ペルソナ特徴主な関心
サロンオーナー複数店舗経営、PC/スマホ両使い売上・会員・店舗横断の数値
店長1店舗を運営、シフト管理予約・顧客・スタッフ
スタイリスト自分の予約・顧客のみ閲覧自分のスケジュール、担当顧客
受付予約入力・会計業務カレンダー、レジ

権限モデルpermission-based + プリセット role(RBACファサード)。詳細は §7.7。

  • プリセット role(Phase 1 で UI公開):
    • owner:全店舗・全機能
    • manager:担当店舗の全機能
    • staff:担当店舗の予約・顧客読取+自分関連の書込
    • receptionist:担当店舗の予約・レジ書込
  • 内部は permission キー({scope}:{resource}:{action})で制御、プリセットは permission の束として表現
  • 個別 override(operator_permission_override)はスキーマに存在するが Phase 1 で UI非公開(feature flag off)
  • 20〜50代女性が中心(美容室の一般的な客層)
  • LINE利用率が高い(LIFFが効く前提)
  • 予約チャネルは HPB / 電話 / LINE / 店頭 の4系統

ERD(DOC-ERD-001)と同じ8分割を採用する。子SRSは原則いずれかのコンテキストに属する。

IDコンテキスト主要エンティティ
BC-TENテナンシー・認証Store, Operator, Session, Passkey, TOTP
BC-MSTマスター・リソースStaff, Equipment, Menu, BusinessHours, Shift
BC-CUS顧客・CRMCustomer, Note, Tag
BC-RES予約・会計Reservation, ReservationMenu, Event, Visit
BC-REGレジRegisterClosing, CashMovement
BC-MSGメッセージングMessage, AutoMessageRule, Coupon
BC-PAYスマートペイ・精算SmartPayTxn, PointLedger, Settlement
BC-LSTHPB掲載SalonListing, MenuListing, Photo, Blog, Review

  • API p95:通常操作 300ms以下、重い集計 1s以下
  • カレンダー初期表示:100予約程度の店舗で 1s以下
  • 同時接続:1店舗あたり 10オペレーター同時操作を想定、全体で 1,000オペレーター同時
  • ジョブキュー遅延:通常 5秒以内に処理開始、最大 30秒
  • 稼働率目標:Phase 3までは 99.5%、Phase 4以降 99.9%
  • メンテナンスウィンドウ:月1回、深夜3〜5時(事前告知)
  • 計画停止なしのデプロイ:rolling update / blue-green は Phase 3以降
  • 通信:HTTPS必須(TLS 1.2以上)
  • 認証:オペレーター=Passkey必須 + TOTPオプション、顧客=LINE Login
  • 認可:RLS + アプリ層RBAC二重ガード
  • 監査ログ:書込系操作は全て operator_action_log に記録、1年保持
  • パスワード:自前パスワード運用は原則しない(Passkey強制)、緊急復旧のみメール+TOTP
  • セッション:JWTは不使用、サーバーサイドセッション(better-auth標準)
  • CSRF:same-site cookie + カスタムヘッダー検証
  • 脆弱性対応bun pm audit(or npm audit)をCIで実行、Critical/High は24h以内に対応

6.4 プライバシー・コンプライアンス

Section titled “6.4 プライバシー・コンプライアンス”
  • 個人情報保護法:顧客データは店舗が管理者、本サービスは委託先として位置付け
  • GDPR:現時点は非対象(国内限定)、海外展開時再検討
  • 特定商取引法:決済画面に事業者情報表示必須(加盟店=店舗)
  • 景品表示法:クーポン・割引表記は店舗責任だがUI上で注意喚起を出す
  • データ削除:顧客削除要求は論理削除 + 90日後物理削除のジョブ
  • ログ:構造化JSON(pino)、stdout → docker logs → fluent-bit集約(Phase 3〜)
  • メトリクス:Phase 1は最小限(/healthz + DBコネクション数)、Phase 3でPrometheus
  • トレース:OpenTelemetry、Phase 2以降
  • エラートラッキング:Sentry(無料プラン)を Phase 1から
  • アラート:pager不要、Discord/Slack Webhookで通知
  • 言語:日本語のみ(Phase 1〜4)
  • タイムゾーンAsia/Tokyo 固定(DBは timestamptz、表示時に変換)
  • 通貨:JPY整数固定(小数なし、税抜・税込の両方を別列で保持)

7. 共通制約(すべての子SRSに適用)

Section titled “7. 共通制約(すべての子SRSに適用)”

子SRSはこの章に書かれたルールをすべて満たさなければならない。違反する場合は親SRSの改訂を伴う。

  1. 全テーブルに store_id 列を置く(例外なし、無関係テーブルは作らない)
  2. 外部キーは複合FK(store_id, target_id) → target(store_id, id)
  3. RLSはPhase 1の最初のマイグレーションから有効化(後入れではなく最初から):
    ALTER TABLE xxx ENABLE ROW LEVEL SECURITY;
    ALTER TABLE xxx FORCE ROW LEVEL SECURITY; -- ★ 所有者にも強制
    CREATE POLICY tenant_isolation ON xxx
    USING (store_id = current_setting('app.current_store_id'));
  4. DB接続を2本に分離
    • postgres(superuser):マイグレーション・管理用途のみ、アプリは絶対に使わない
    • app(非superuser):アプリ/ワーカーはこちらで接続、RLSが必ず適用される
  5. リクエスト毎に SET LOCAL app.current_store_id:Hono middlewareで統一実装
  6. リポジトリAPIは storeId を必須引数:型で強制
  7. ジョブペイロードに storeId を必ず含む:ワーカー側タスク起動時に SET LOCAL する
  8. テナント跨ぎクエリ禁止:集計等は専用SQL(superuser権限)で、アプリ層からは触れない
  9. DISABLE ROW LEVEL SECURITY 禁止(レビューでブロック、CIでgrep検出)

原則:UUIDv7(Postgres uuid 型、16バイト)を主キーの既定とする。一部の内部専用テーブルのみ bigserial を許容する。

  • URL・APIレスポンスに出る可能性がある
  • 件数・IDが業務KPI(売上・流量・顧客数・店舗数)を示唆する
  • テナント跨ぎ、マイグレーション、移管、復元の可能性がある
  • 対象例:store, operator, customer, reservation, visit, visit_line, register_closing, message, smart_pay_txn, coupon, menu, staff, equipment 等の業務実体全般

実装

  • Postgres uuid 型を使用(16バイト、native比較)
  • ID生成はアプリ層(uuidv7() ライブラリ)
  • DBの DEFAULT gen_random_uuid()使わない(v4になってしまうため)。ただしPostgres 18 native v7 gen入ったら再検討

以下の条件をすべて満たすテーブルに限り bigserial を認める:

  1. 外部(API/URL)に絶対に出ない内部専用
  2. 件数・ID値が漏れても業務影響がない
  3. 大量生成される(ログ系、中間テーブル)

対象例:

  • 監査系:operator_action_log, reservation_event, message_delivery_log
  • 中間テーブル(M:N):customer_tag_link, menu_eligibility
  • 純内部マスタ:business_hours, day_override, shift, cash_movement

bigserial列の運用ルール

  • APIレスポンスのトップレベル項目として出さない(レビューでブロック)
  • FK先がUUIDv7の場合、複合FKは (store_id, parent_uuid) を使い、bigserial自身はFK先にしない
  • 中間テーブルは可能なら複合主キー (a_id, b_id) で独立IDを持たせない最小構成を優先

7.2.3 表示用・共有用の別ID(必要に応じて追加)

Section titled “7.2.3 表示用・共有用の別ID(必要に応じて追加)”
  • customer_no:店舗内で採番される顧客番号(会員証・受付票用、4〜6桁)。主キーとは別列
  • coupon_code:人が入力・共有する短文字列(SUMMER2026 等、または8〜10文字のランダム)
  • salon_slug:公開URL用のスラッグ(hair-tokyo-shibuya 等、グローバル一意)。店舗自体を一意に指す識別子のため、システム全体で UNIQUE。冪等な seed・公開URL設計の安定キーとして機能する

customer_nocoupon_code は主キーではなく、店舗内unique制約で管理する。salon_slug のみグローバルUNIQUE。

UUIDv7側に倒す。「露出しないつもりだった列が後からAPIに露出する」事故は戻せないが、UUIDv7はoverkillでも害がない。

  • 整数 bigint(円単位)
  • 税抜 amount_excl_tax / 税込 amount_incl_tax / 消費税額 tax_amount を別列
  • 税率は tax_rate_pct(10 or 8)で保持
  • 端数処理は四捨五入を標準、設定で切捨て/切上げ切替可
  • DB:timestamptz(UTC保存)
  • アプリ:Luxon or Temporal(Temporal polyfill)
  • 表示:Asia/Tokyo
  • 予約の”時刻”は店舗タイムゾーン基準だが内部はUTCで処理
  • 物理削除はしないdeleted_at 列)、以下は例外
    • セッション・トークン類(物理削除OK)
    • ジョブ(graphile-worker管轄)
    • 一時ファイル
  • 論理削除された行はRLSで見えなくなる設計にしない(集計に影響)、アプリ層でフィルタ
  • zod-openapiで契約ファーストpackages/contracts にzodスキーマを定義、apps/api がルート結線
  • 型配布apps/apipackages/contracts/openapi.json を書き出し、scripts/codegen.sh(Docker経由)で openapi-typescript を実行、packages/contracts/src/generated/schema.ts を出力。FEは openapi-fetch + paths 型で参照
  • 生成物はgitignorepredev / prebuild フックで都度再生成
  • パスプレフィクス
    • /api/admin/* — オペレーター認証必須
    • /api/portal/* — 顧客(LINE)認証必須
    • /mcp — MCP (OAuth2.1)
    • /webhooks/* — 外部連携、HMAC検証
  • レスポンス形式:成功は { data: ... }、エラーは { error: { code, message, details? } }
  • ページング?limit=&cursor=(cursorベース、offset禁止)
    • 例外:店舗内で件数が小規模に閉じるマスタ(staff / menu / equipment 等、店舗あたり数十件程度)は、子SRSで明示した場合に限り max 200 件の全件返却を許容する。limit パラメータでクライアントが上限を絞ることはできるが cursor は要求しない。将来件数増大時に cursor 化する余地は残す
  • バージョニング:初期は不要、破壊的変更時にパス /v2 追加
  • 全エンドポイントはデフォルト要認証
  • 認可はミドルウェア + RLSの二重掛け
  • オペレーター→店舗のスコープは operator_store_link(operator_id, store_id, role_id) で管理

permission-based + プリセット role の二層構造。

  • permission(全テナント共通マスタ、bigserial): key, description
    • §7.1store_id 必須制約の例外:システム共通マスタとして全店舗で同一、店舗が編集できない
    • キー命名は {scope}:{resource}:{action} 形式
      • scope ∈ {admin, portal, mcp}
      • 例:admin:reservation:create, admin:reservation:override_business_hours, mcp:reservation:read
  • role(店舗スコープ、UUIDv7): store_id, key, name, is_preset
    • 店舗作成時にプリセット4種(owner/manager/staff/receptionist)を自動生成
  • role_permission(中間、bigserial
  • operator_permission_overridebigserial、Phase 1 で UI非公開): operator_id, store_id, permission_id, granted
    • 実効権限 = role の permission ∪ override(granted=true) − override(granted=false)

permission-based を将来 RBAC に戻せるよう、以下の規律を守る。

  1. packages/auth に閉じ込める:permission チェックのロジックはこのパッケージのみ。外部からは requirePermission(key) 経由でのみ呼ぶ
  2. permission キーは code-first な constpackages/authPERMISSIONS 定数で export。seed は起動時にこの定数から DB へ UPSERT(マイグレーションにハードコードしない)
  3. ドメイン層(packages/domain)は permission を知らない:権限チェックをドメインロジックに混ぜない
  4. override は feature flag で有効化:Phase 1 は flag=off、override テーブルを読まない分岐でリリース
  5. API 層は宣言的app.post(path, requirePermission(...), handler) のようにルート定義に貼る。handler 内部で個別チェックしない
  6. テストバイパス窓口auth.bypassForTest() を用意、permission 粒度変更でテストが全壊しないようにする
  • override UI は単品リリース禁止:必ず「実効権限ビューア」(オペレーターごとの effective permission 一覧)とセットでリリース
  • override は「プリセットから外れる例外」を意図し、恒久運用になったら該当 role を分岐させる(運用ガイドラインは Phase 3 で追記)

7.7.5 Permission 粒度変更のマイグレーション規約

Section titled “7.7.5 Permission 粒度変更のマイグレーション規約”

permission の粒度は初期設計で必ず外れる。以下の手順を守る。

  • split(1 permission を N に分割):新 permission 追加 → 旧 permission を持つ全 role/override に新 permission を付与するマイグレーション → 旧を deprecated(物理削除は 1 minor 後)
  • merge(N permission を 1 に統合):新 permission を追加 → 旧のいずれかを持つ role/override に新を付与 → 旧を deprecated
  • renamekey 変更は禁止。新 key を追加して旧を deprecated、seed で旧→新をマップ
  • 行ポリシーで解くべき制御(例:「自分担当の顧客だけ見える」)は permission で表現しない。RLS ポリシー(§7.1)側で解く
  • permission で解くべき制御は「リソースに対する操作の可否」に限る(属性ベースに踏み込まない)
  • ABAC(属性ベース認可)が必要になった時は、親SRS改訂を伴う判断とする

7.7.7 新 permission 追加時の preset role backfill 規約

Section titled “7.7.7 新 permission 追加時の preset role backfill 規約”

新規permissionキーを追加する子SRSは、その migration の責務として、既存店舗のプリセット role に対する role_permission の backfill を必ず実施しなければならない

理由:seed スクリプトは新規店舗の作成時にしか走らない。既存店舗に対しては migration が唯一の適用経路。これを忘れると「新店舗だけ permission を持つ/既存店舗は持たない」という不整合が永続化する。

手順

  1. 新 permission キーを packages/authPERMISSIONS 定数に追加
  2. PRESET_ROLE_PERMISSIONS の対象ロールに新 permission を追記
  3. migration で以下を実施:
    -- 例: admin:reservation:cancel を manager と receptionist に付与
    INSERT INTO permission (key, description) VALUES (...) ON CONFLICT (key) DO UPDATE SET description = EXCLUDED.description;
    INSERT INTO role_permission (store_id, role_id, permission_id)
    SELECT r.store_id, r.id, p.id
    FROM role r
    CROSS JOIN permission p
    WHERE r.is_preset = true
    AND r.key IN ('manager', 'receptionist')
    AND p.key = 'admin:reservation:cancel'
    ON CONFLICT DO NOTHING;
  4. テストで「全プリセット role が想定通りの permission 集合を持つ」ことを検証

§7.7.5 の split/merge/rename もこのbackfill規約に従う(実質同じ操作)。

  • 書込系は自動で operator_action_log に記録
  • 記録項目:actor(operator_id or ‘system’)、action(‘reservation.create’等)、targetdiffipuaat
  • 読取系の監査は明示的にsensitiveと指定したAPIのみ(顧客詳細閲覧等)

以下は operator_action_log への記録対象外とする。CLI ログ・migration 履歴で十分に追跡可能なため:

  1. seed スクリプトの実行bun run db:seed、初期店舗作成・permission UPSERT 等)
  2. bootstrap スクリプトの実行bun run scripts/db-bootstrap.ts、ロール作成・拡張・default privileges 設定)
  3. DBマイグレーションの実行drizzle-kit migrate、スキーマ変更・preset role backfill 等)

これらは postgres superuser ないし migrator 接続で実行され、current_store_id を持たないため actor の確定が困難で、かつテナントスコープを超えた基盤操作である。operator_action_log テーブル自体が作成されるのは SRS-TEN-002 以降であり、それ以前の seed/bootstrap は監査対象とできない(テーブル不在)という実務的制約も反映している。

  • 予期エラー:ドメイン層で DomainError を投げる、API層で4xxに変換
  • 予期しないエラー:5xx + Sentry通知
  • ユーザー向けメッセージは日本語・短く・次のアクションを示す
  • Unitpackages/domain は100%に近い(純粋関数なので容易)
  • Integrationapps/api は主要ユースケース網羅(Testcontainers + 実Postgres)
  • E2E:Phase 2からPlaywright、クリティカルフロー(予約作成〜会計)のみ
  • 契約テスト:zodスキーマをfixtureとしてFE/BE間で整合検証
  • Input validation は zod で全境界
  • SQLインジェクションはDrizzleで防止(生SQLを書く場合はprepared statement必須)
  • XSS:React標準のエスケープ + dangerouslySetInnerHTMLはレビュー必須
  • レート制限:ログイン系 10回/分、一般API 100回/分 を Phase 1から
  • レスポンシブ:デスクトップ優先、タブレット(iPad)で動くこと必須、スマホはPWA経由
  • アクセシビリティ:WAI-ARIA準拠、キーボード操作可能(業務利用で重要)
  • 言語:日本語のみ
  • ローディング:スケルトン表示、ブロッキングなグローバルローディング禁止
  • エラー表示:Toastで通知 + フォームはインライン
  • デザインシステム未整備:Phase 1〜2 の子SRS §4(UI仕様)は最低限の粒度に留める(画面の目的・主要要素の列挙・操作フロー・バリデーションまで)。ビジュアル・タイポグラフィ・コンポーネント選定には踏み込まない
  • デザインシステム導入は別SRSとして起票(仮ID:SRS-UI-001、Phase 2〜3)。導入以降のUIは一括でシステムに置換し、既存の子SRS §4 はその時点で総改訂する前提
  • store_settings(1 store = 1 行)で店舗ごとの設定を保持
  • Phase 1 に含む設定:customer_required_fields = ['name'](固定値、UI編集不可)
  • 動的フォーム(フィールド定義を設定で差し替える)は Phase 2 以降。設定項目がフォーム構造まで侵食する場合は DSL 化の判断を伴う
  • 設定の参照は packages/auth や domain ではなく、API 層で読んで明示的に渡す

7.14 リソース適格性マスタ(メニュー対応)

Section titled “7.14 リソース適格性マスタ(メニュー対応)”

メニュー × スタッフ × 設備の対応関係(「このメニューはどのスタッフが担当できるか」「このメニューにどの設備が必要か」)は単一テーブルでは表現しない。意味の異なる2軸を1表に同居させると NULL の多義(staff_id NULL の解釈/equipment_id NULL の解釈)が発生し、読みにくく壊れやすくなる。

  • スタッフ対応menu_staff_eligibility(store_id, menu_id, staff_id) — 行が存在 = そのスタッフが担当可
  • 設備要件menu_equipment_requirement(store_id, menu_id, equipment_id) — 行が存在 = その設備が必要
  • 両表とも複合主キーは (store_id, menu_id, <staff_id|equipment_id>)bigserial の独立IDは持たせない(§7.2.2 中間テーブル例外)

メニュー側に ホワイトリスト適用要否のフラグ(仮: restrict_staff_required boolean、命名・型・デフォルトは SRS-MST-002 で確定)を持つ。

restrict_staff_required意味予約作成時の挙動
false(既定)制限なし全スタッフが対応可。menu_staff_eligibility の行は無視
trueホワイトリストmenu_staff_eligibility の行に登録されたスタッフのみ対応可。0件のメニューは予約作成 API で MENU.NO_ELIGIBLE_STAFF を返してブロック

「行0件 = 全員可」と「行0件 = 全員不可」をフラグで切り替える。既定は事故を起こしにくい側(制限なし)に倒す。制限が必要なメニューだけ明示的に opt-in する。

  • menu_staff_eligibility / menu_equipment_requirement のスキーマ確定・CRUD・UI は SRS-MST-002(メニュー管理) が所有する
  • SRS-MST-001(スタッフ管理)は本節のテーブルを所有せず、staff マスタのみを扱う

7.15 業務実体テーブルの複合参照標準

Section titled “7.15 業務実体テーブルの複合参照標準”

§7.1 のマルチテナント規律で要求される「複合FK = (store_id, target_id) → target(store_id, id)」を Postgres の制約で実現するための標準形を固定する。

  1. 主キーは id 単独(UUIDv7、§7.2.1)
  2. 追加で UNIQUE(store_id, id) を必ず張る — 複合FKの参照先となるため Postgres は単独 PK 以外の UNIQUE 制約を要求する
  3. 外部キーは複合FK (store_id, foreign_id) REFERENCES other(store_id, id)
  4. PK を (store_id, id) 複合にする案は採用しない(UUIDv7 単独参照の利便性を優先)

bigserial を採るログ系・中間テーブル(§7.2.2)は本節の対象外。中間テーブルは §7.14.1 のように複合主キーで独立IDを持たせない最小構成を優先する。


要約のみ。詳細は DOC-DIR-001

採用
ランタイムBun 1.1+(Node互換モードで graphile-worker / Drizzle / better-auth 動作確認必須)
言語TypeScript(strict)
APIフレームワークHono
スキーマ/契約zod + @hono/zod-openapi
型共有OpenAPI → openapi-typescript + openapi-fetch(Dockerでcodegen、pre-dev/pre-buildフック)
ORMDrizzle ORM
DBPostgreSQL 16
認証better-auth(Passkey、TOTP、OAuth server)
ジョブキューgraphile-worker
リアルタイムSSE(Server-Sent Events)
フロントVite + React + TanStack Router + Tailwind + shadcn/ui
決済Stripe(国内はGMO-PG等も後で検討)
通知WebPush(VAPID)/ Resend / LINE Messaging API
監視Sentry、pino、Phase 3以降でOpenTelemetry
モノレポBun workspaces(タスクランナー未採用)
パッケージマネージャBun(bun install)
LintBiome
テストVitest + Testcontainers + Playwright(Bun上で実行)
本番Docker baseoven/bun:1-slim
実行環境Docker Compose 必須(開発・本番とも)。ホスト直実行は禁止

9. フェーズ計画と子SRSカタログ

Section titled “9. フェーズ計画と子SRSカタログ”
Child SRS IDタイトルBC
SRS-TEN-001店舗作成・初期設定BC-TEN
SRS-TEN-002オペレーター登録・Passkey登録BC-TEN
SRS-TEN-003ロールと店舗アサインBC-TEN
SRS-MST-001スタッフ管理BC-MST
SRS-MST-002メニュー管理BC-MST
SRS-MST-003営業時間・定休日・祝日BC-MST
SRS-MST-004シフト管理BC-MST
SRS-MST-005設備管理BC-MST
SRS-CUS-001顧客登録・検索BC-CUS
SRS-CUS-002顧客詳細・メモ・タグBC-CUS
SRS-RES-001予約カレンダー表示BC-RES
SRS-RES-002予約作成BC-RES
SRS-RES-003予約編集・移動BC-RES
SRS-RES-004予約状態遷移(11状態)BC-RES
SRS-RES-005二重予約判定BC-RES
SRS-REG-001レジ会計(現金・カード)BC-REG
SRS-REG-002日次締めBC-REG
Child SRS IDタイトルBC
SRS-MSG-001手動メッセージ送信(Email/LINE)BC-MSG
SRS-MSG-002予約リマインダー自動送信BC-MSG
SRS-MSG-003クーポン発行・適用BC-MSG
SRS-RES-006オンライン予約受付(顧客向けWeb)BC-RES
SRS-PAY-001スマートペイ決済連携BC-PAY
SRS-PAY-002キャンセル料自動課金BC-PAY
SRS-RES-007複数チャネル予約統合(電話/HPB/LIFF/直接)BC-RES

9.3 Phase 3:マーケ・分析・連携(2〜3ヶ月)

Section titled “9.3 Phase 3:マーケ・分析・連携(2〜3ヶ月)”
Child SRS IDタイトルBC
SRS-CUS-003顧客セグメント・一斉配信BC-CUS, BC-MSG
SRS-ANL-001売上日報・月次レポートBC-REG
SRS-ANL-002顧客分析(LTV、リピート率)BC-CUS
SRS-MCP-001MCPサーバー(OAuth2.1)cross
SRS-MCP-002MCPツールセット(予約・顧客・売上)cross
SRS-PWA-001PWA化(Web Push、ホーム追加)cross

9.4 Phase 4:LIFF・メディア(2〜3ヶ月)

Section titled “9.4 Phase 4:LIFF・メディア(2〜3ヶ月)”
Child SRS IDタイトルBC
SRS-LIFF-001LIFF基盤(LINE Login、顧客紐付け)BC-CUS
SRS-LIFF-002LIFF予約フローBC-RES
SRS-LIFF-003LIFF会員証・履歴BC-CUS
SRS-LST-001サロン基本情報掲載BC-LST
SRS-LST-002メニュー・クーポン掲載BC-LST
SRS-LST-003フォトギャラリーBC-LST
SRS-LST-004ブログ・特集BC-LST
SRS-LST-005口コミ返信BC-LST
SRS-LST-006スタッフ掲載情報BC-LST

各子SRSは以下の構造を最低限持つ。

# {Feature Name}
**Document ID**: SRS-{BC}-{NNN}
**Parent**: SRS-ROOT-001 v0.X
**Status**: Draft / Review / Approved / Implemented
**Depends on**: [SRS-XXX-YYY, ...]
## 1. 目的
## 2. ユーザーストーリー
As a {role}, I want to {action}, so that {benefit}.
## 3. ユースケース
3.1 主シナリオ
3.2 代替フロー
3.3 例外フロー
## 4. UI仕様
4.1 画面構成(スクリーンショット or ワイヤーフレーム)
4.2 要素と挙動
4.3 バリデーション
## 5. API仕様
5.1 エンドポイント一覧
5.2 zodスキーマ(リクエスト・レスポンス)
5.3 エラーコード
## 6. データモデル影響
6.1 スキーマ変更(あれば)
6.2 マイグレーション計画
## 7. 業務ルール
- 状態遷移、制約、計算式
## 8. 非機能要件(親SRS補足が必要な場合)
## 9. セキュリティ・認可
- 必要ロール、RLS影響
## 10. 受け入れ基準(Given-When-Then)
## 11. テスト計画
## 12. 関連ジョブ(graphile-worker)
## 13. Open questions
## 14. 変更履歴

1. 子SRS起票(Draft)
2. レビュー(親SRS適合チェック、Open questions解消)
3. Approved
4. 実装
4.1 スキーマ変更あれば db:generate
4.2 contracts/ に zodスキーマ追加
4.3 domain/ にビジネスロジック
4.4 apps/api にルート追加
4.5 apps/web にUI追加
4.6 ジョブ追加なら jobs/ に定義
5. テスト(unit + integration)
6. PRレビュー
7. マージ
8. 子SRS → Implemented、変更履歴更新

LLM連携時の手順:

  • Context7で関連ライブラリの最新ドキュメントを事前取得
  • 子SRSをプロンプトに貼り付ける
  • 実装と同時にテストも出させる
  • 人間は親SRS違反がないかをレビュー

12. Open Questions(親SRSレベル、未決)

Section titled “12. Open Questions(親SRSレベル、未決)”
#内容締切の目安
OQ-01顧客データの 店舗間共有を許すか(チェーン運用ニーズ)Phase 1後半
OQ-02スタッフ指名料の取り扱い(メニュー加算 or 別テーブル)SRS-MST-002作成時
OQ-03スマートペイの決済事業者:Stripe / GMO-PG / LINE Pay 比較Phase 2開始時
OQ-04HPB予約の取り込み方法:手動入力 / CSVインポート / スクレイピング禁止SRS-RES-007作成時
OQ-05MCPツールのwrite系をどこまで解禁するかSRS-MCP-002作成時
OQ-06本番デプロイ戦略:MiniPC継続 / VPS移行タイミングPhase 2完了時
OQ-07バックアップ戦略:pg_dump頻度・保存先・リストア手順Phase 1終了前
OQ-08消費税率変更への対応(歴史的税率保持)SRS-REG-001作成時
OQ-09Permission キーの Phase 1 初期リスト確定(何個で出荷するか、命名規約の当てはめ検証)SRS-TEN-002 作成時

VersionDateAuthorChange
0.12026-04-21yudai初版作成
0.22026-04-22yudai権限モデルを permission-based + プリセット role に拡充(§4.1, §7.7)。捨てやすさの設計原則・override 運用規律・粒度変更マイグレーション規約・RLS との境界を §7.7 に追記。店舗設定 store_settings を §7.13 に追加。子SRSのUI仕様を最低限粒度とし、デザインシステム導入SRS(仮 SRS-UI-001)で一括改訂する方針を §7.12 に追記。OQ-09 を追加
0.32026-04-25yudaiSRS-TEN-001 着手レビューと SRS-MST-001 起票準備の整合改訂を統合。§7.2.3: salon_slug を「グローバル一意」に修正(公開URL衝突回避と冪等seedキーの両立)。§7.6: ページング規則に小規模マスタ(staff / menu / equipment 等)の全件返却例外を追記。§7.7.7: 新 permission 追加時の preset role backfill migration 規約を新設(既存店舗への適用漏れ防止)。§7.8.1: seed/bootstrap/migration を監査記録の例外として明示。§7.14: 「リソース適格性マスタ」新設(menu_eligibilitymenu_staff_eligibility / menu_equipment_requirement に分離、メニュー側ホワイトリスト適用要否フラグで「行0件=全員可/全員不可」を切替、既定は制限なし)。§7.15: 「業務実体テーブルの複合参照標準」新設(PK単独+UNIQUE(store_id, id)+複合FK の標準形を固定)。§9.4: Phase 4 カタログに SRS-LST-006 スタッフ掲載情報 を追加。ERD(DOC-ERD-001)の §1 BC-TEN を operator_store_link 前提に整合、§2 BC-MST を menu_eligibility 分離に整合

Appendix A. 子SRS作成時のチェックリスト

Section titled “Appendix A. 子SRS作成時のチェックリスト”

起票時にコピペで使う。

  • Document IDを採番(SRS-{BC}-{NNN})
  • 親SRSのバージョンを明記
  • 依存する他の子SRSを列挙
  • ユーザーストーリーが1文で書けている
  • 主シナリオ・代替フロー・例外フローが揃っている
  • zodスキーマでリクエスト/レスポンス定義
  • 親SRS §7(共通制約)に違反していない
    • store_id 考慮済み(新規テーブルには必須)
    • RLS ENABLE + FORCE + POLICY をマイグレーションに含めた
    • app ユーザーに必要な権限を GRANT した
    • リクエスト/ジョブで SET LOCAL app.current_store_id が走る導線を確認
    • ID方式判断:原則UUIDv7、bigserial許容なら§7.2.2の3条件すべて満たすか確認
    • APIレスポンスにbigserial値が露出していない
    • 表示用ID(customer_no等)が必要な場合は別列で設計
    • 金額は整数・税抜/税込分離
    • 時刻は timestamptz
    • 論理削除(該当する場合)
    • zod-openapi契約
    • OpenAPI codegen の paths 型に反映(FEで確認)
    • 監査ログ対象か判定
  • 受け入れ基準がGiven-When-Thenで3件以上
  • 関連ジョブ(graphile-worker)の有無を記載
  • Open questionsは残っていない(残すなら親SRSに昇格検討)

Draft 起票直後、レビュー前
Review レビュー中
Approved 実装可能、着手前
In Progress 実装中
Implemented マージ済み、受け入れ基準クリア
Deprecated 廃止(後続SRSで置換)

ステータス変更は子SRSファイル先頭の Status: 行で管理、gitログで追跡。