コンテンツにスキップ

モジュール XX — マルチテナンシーと組織管理

モジュール XX はエンジンにぶら下がるサービスではない。それはエンジンそのものの 特性である。アタッチするための独立したテナンシーモジュールは存在しない。代わりに、 コアのデータモデルがあらゆるエンティティにテナント境界を持ち、ストアがすべての クエリの下でそれを強制する。このページは、その境界が今日保証していること、および 組織管理のうちまだ設計段階にある部分のリファレンスである。

マルチテナンシーは、プラットフォーム自身の API(モジュール XIX)と並んで Engine 層 (層 0)に存在する。なぜなら、稼働中のデータモデルに分離を後付けすることは、後から 安全に行えない類いの変更だからである。すべてのコアエンティティは tenant_id を 持ち、呼び出し側がそれを自由なパラメータとして渡すことは決してない。呼び出し側は テナントを一度固定し、そのリポジトリがすでにそれにバインドされたスコープを 受け取る。テナントをまたぐための語彙は API に存在しない — その不在こそが、 いかなるデータベース機構よりも前にある最初の分離障壁である。特権的なクロステナント スコープ(組織の作成、組織の一覧、テナントの削除)は、モジュールからではなく エンジン自身の起動処理からのみ到達できる。

テナントモデルは、モジュールごとのスキーマではなく、データモデルのコントラクトが 所有する。ルートエンティティは Org であり、これがテナントそのものである。 エンジンが組織をシードすると、その識別子がテナント識別子となり、組織自身の監査 チェーンが同じ瞬間に確立される。それ以外のすべてのコアエンティティ — エージェント、セッション、リソース、アイデンティティ、ポリシー、コスト記録、 検出結果、デプロイメント、access map、audit ledger — は、テナントスコープの内側で 作成され、書き込み時にそのテナントが刻印される。呼び出し側はそれを上書きできない。

分離はクエリ層で、デプロイメントごとに強制される:

  • PostgreSQL では、tenant_id を持つ各テーブルが FORCE ROW LEVEL SECURITY の下で動作し、トランザクションごとにバインドされる tenant_isolation ポリシーを持つ。テナントのバインドに失敗したトランザクションは、サイレントに ゼロ行を返すのではなくエラーを発生させる(フェイルクローズ)。また、 アプリケーションロールは非スーパーユーザーであり、BYPASSRLS を決して 持たない。そして FORCE はテーブルのオーナーに対してもポリシーを拘束する。 オーナーシップはデプロイメントの選択である: デフォルトのシングルロール インストールでは、アプリケーションロールがデータベースのオーナーのままとなる — RLS は依然としてそれを拘束するが、オーナーは自身のテーブルを変更できるため、 このポスチャは owner-proof ではなく**改竄検知可能(tamper-evident)**にとどまる。 強い権限境界 — アプリケーションロールが非オーナーでもあること — は、オーナーと アプリを分割したトポロジーから得られる。分離された owner ロールが プロビジョニングを行い、アプリケーションロールは必要な DML のみを受け取る。
  • SQLite(シングルノードのデプロイメント)には行レベルセキュリティがない。 同等の保証は次の 2 つの事実から得られる — データベースへの唯一の経路は、 常にテナント述語を付加するディスクリプタ生成 SQL であること、そしてトリップ ワイヤートリガーが、固定されたスコープと一致しないテナントへの書き込みを すべて中止することである。

起動時のセルフテストは、マイグレーション後にライブの分離ガードを問い合わせ、 tenant_id を持つテーブルのいずれかが保護されていない場合は、ストアを開くことを 拒否する — そのため、新しいテーブルのガードを忘れることは、サイレントな 漏洩ではなくブート失敗となる。

モジュール XX はイベントバスの表面を持たず、アクチュエーションも持たない。 edge.observed を消費せず、検出結果を発行せず、いかなるプロバイダも呼び出さない — それは他のモジュールが書き込みを通す基盤である。その唯一の観測可能な効果は構造的な ものである。どのモジュールが永続化するエンティティもすでにテナントスコープ化されて おり、監査対象エンティティへのすべての変更は、同一トランザクション内でそのテナントの ハッシュチェーン化された audit ledger に追記される。