Appearance
KIZAMI Cloud(hosted mode)
対象: ロードマップ「SaaS トラック」(app.kizami.dev)。2026-08-31 設計、未着手。 要件は 要件定義書 §7(マルチテナント)と マルチテナントとテナント分離 を前提とする。
方針 — セルフホストと同じものが動く
KIZAMI Cloud は本リポジトリのコードそのものを運用者がホストして提供する。 SaaS 専用のコードは「登録・課金・テナント運用」の薄い制御レイヤに限定し、 勤怠機能そのものには分岐を作らない。
- 信頼: 勤怠データは人事情報そのもの。「中身は公開リポジトリで全部読める」ことが 最大のセキュリティ説明になる。
- 保守: 単独運用で2系統のコードベースは負債。SaaS がセルフホスト構成の動作保証を兼ねる。
- ライセンス: AGPL-3.0 は著作権者自身のホスト提供を制約しない。SaaS レイヤも本体と 同じ AGPL でこのリポジトリに置く。
コードの置き場(判断点)
別リポジトリの制御プレーンに切り出す案も検討したが、本体側に管理 API の口を開ける 必要が生じて結局本体が汚れるうえ、単独運用で2リポジトリは負債なので採らない。 モノレポ内・フラグゲートとする(Cal.com 等の先例に同じ):
- 既定値はすべて「SaaS 機能 OFF」。
SIGNUP_MODEや Stripe 系の環境変数が無ければ 完全に眠り、セルフホスト体験を一切変えない。 - 副産物として、セルフホスト派も「招待制ではなく自由登録制」の運用に同じコードを使える。
実行基盤 — Phase 1 は Kubernetes + PostgreSQL
| 案 | 判定 | 理由 |
|---|---|---|
| k8s + PostgreSQL | 採用 | PG ダイアレクトは実装・CI 済み。DATABASE_URL=postgres://… を渡すだけで既存イメージが PG で起動し、マイグレーションも自動適用される(packages/db/src/migrate.ts の URL ディスパッチ)。 |
| Cloudflare Workers + D1 | 見送り | D1 が BEGIN を拒否するため db.transaction() を使う全書込系が未対応(Workers + D1 対応)。解消は独立した工事であり、SaaS の立ち上げをそれに賭けない。 |
| 新規 VPS | 見送り | 既存クラスタに対して費用増のみで利点が無い。 |
テナントが増えた時点で Workers + D1 への移行を再評価する。migrate-data (SQLite→PG)の設計は逆方向にも流用できる。
構成要素:
- 専用 namespace(自分用本番・デモとは完全分離)。PG は単一 StatefulSet + PVC で開始 (HA 構成は現段階では過剰)。
- 暗号化鍵(
KIZAMI_ENCRYPTION_KEY)は hosted 専用に新規発行し、他環境と共有しない。 - バックアップ: 日次
pg_dump -Fc→ オブジェクトストレージ、30日保持+月初世代。 復旧手順を文書化し、実際に restore を一度通してから公開する。 - 可用性: 単一ノードである間は SLA を掲げず、規約に「ベストエフォート」を明記する。
サインアップ
- 登録フォーム(メール+パスワード+組織名)。CAPTCHA(Turnstile 等)必須、 既存のレート制限基盤に signup 用バケットを追加。
- メール確認: セッションと同じトークン作法(乱数+SHA-256 保存)。送信は通知基盤の SMTP 経路を流用。
- テナント作成:
create-tenantCLI の中身(tenant-bootstrap)を API 化して呼ぶ。 システムプリセット・既定 work policy・初期管理者まで一括。slug は自動採番とし ユーザーに選ばせない(衝突・商標問題の回避)。 - 初回ログイン後は既存のオンボーディングツアーがそのまま動く。
Closed Beta の間は招待コード制(コードが無ければ signup は 403)。公開時にコード要求を 外すだけの作りにしておく。
課金 — Stripe Checkout + Customer Portal + Webhook
カード情報は一切保持しない。アプリ内に決済フォームは作らず、加入は Stripe Checkout、 変更・解約・カード更新は Customer Portal に委ねる。自前実装は Webhook 受信と 状態機械のみ。
- シート計測: 「有効(deactivate されていない)ユーザー数」。metered ではなく licensed quantity を日次ジョブ+メンバー増減イベントで同期する(単純・請求が予測可能)。
- プラン構造: 機能制限プランは作らない。法定機能(36協定アラート等)を上位プランに 閉じ込めるのは「労働者に不利な実装をしない」という本製品の方針と衝突する。 差別化は無料枠の人数・サポート水準・専有インスタンスで行う。具体の価格は運用者の 事業判断でありこの文書の範囲外。
- テーブル:
billing_customers/billing_subscriptions(tenant_id、Stripe ID 群、 plan、seats、status、猶予期限)。Webhook は event id を記録して冪等化。
執行(enforcement)— 打刻は止めない
勤怠は業務クリティカルで、支払いトラブルの巻き添えで労働時間の記録が欠けるのは 「1分を刻む」という製品思想に反する。制限は管理機能側に寄せる。
| 状態 | 従業員(打刻・本人閲覧・申請) | 管理者(承認・締め・設定・招待) |
|---|---|---|
| 正常 / トライアル中 | ○ | ○ |
| 支払い失敗(猶予期間) | ○ | ○ + 全画面バナー |
| 猶予超過 / 無料枠超過 | ○(記録は守る) | ロック(閲覧のみ)。招待不可 |
| 解約後 | エクスポート案内 → 一定期間後にテナント削除 | エクスポートのみ可 |
法務・運用の要件
- 必須ページ: 利用規約 / プライバシーポリシー / 特定商取引法に基づく表記 / セキュリティ説明(暗号化・分離・バックアップ・監査ログ)。
- 個人情報の整理: テナント企業が個人情報取扱事業者、運用者は委託先相当。
@kizami/privacy-template(テナント→従業員向け雛形)の対になる 「運用者→テナント」文書が要る。 - データポータビリティ: CSV / API / 給与ソフト向けエクスポートは実装済み。
- 監視は既存の 可観測性 の構成に hosted 環境を追加する。 テナント数・シート数もメトリクス化する。
- 運用者コンソールはまず CLI(テナント一覧・suspend・プラン上書き・退会処理)。 管理 UI は必要になってから。
フェーズ計画
- Closed Beta(課金なし・招待コード制) — PG デプロイ+バックアップ/復旧手順、 self-serve signup、監視配線。出口条件: 運用者以外のテナントが実勤怠を1ヶ月回して 締めまで通ること。
- Billing —
packages/billing(Checkout / Portal / Webhook / シート同期 / 執行)、 法務ページ、トライアル・無料枠。 - Public Launch — テナント退会(下記ギャップ1)、招待コード撤廃、 テナント別クォータ。
公開前に塞ぐべき既知のギャップ
- テナント一括削除(退会)が未実装 —
member.eraseは個人単位のみ。 全データエクスポート→物理削除の退会フローは公開ローンチのブロッカー (退職者データの保持と消去 の対になるテナント版)。 - 人ごとの所定労働時間(時短勤務) — 現状、work policy は kind(flex/fixed)ごとに テナント高々1本の共有で、「一般は所定8時間・育児時短の人だけ所定6時間」が 固定時間制・フレックスでは表現できない(
apps/api/src/routes/members.tsの 設計コメントで将来課題として明示済み。シフト制は所定が日単位なので対応可能)。 育児・介護休業法23条の短時間勤務措置は中小でも普通に発生するため、 不特定多数に提供する前に対応が要る。DB(user_policy_assignmentsはwork_policy_id参照)とエンジン(standardDayMinutesは入力)は複数ポリシーを 既に許容しており、制約は API の get-or-create 設計と UI のみ。 - 時短フレックス(清算期間の契約枠が法定枠より短い) — エンジンに「契約枠」の 概念追加が必要で工事が大きい。需要が見えてから。
- テナント別クォータ — 既存レート制限は IP/メール軸のみ。API キー数・通知送信数 などテナント軸の上限を Public Launch までに追加。