Identity Hub(縁と絆)
要件定義書+設計書(最小実装版)
作成日: 2026-02-24
作成者: akira tada
対象: NEEXT-ERP 基盤(村A/村B/村C)
目次
※Wordの自動目次(フィールド)ではなく、読みやすさ優先で章立てを固定しています。
本ドキュメントは、自治体メタファ(県/市/村)と、「縁(出会い・起点)」「絆(結合・信頼)」「統合(merge)」「履歴(lineage)」を中核に、認証基盤(Keycloak等)に依存しない形で、サービス分配を可能にする最小実装の要件・設計を定義する。
| 縁(Origin) | 最初の登録起点。村・招待・窓口など。原則不変。 |
| 絆(Bond) | 現在の結合状態(信頼/契約/内部化まで含む)。上下評価ではなく関係の質。 |
| 所属(Current) | 現在のレルム/村。移動し得る。 |
| 統合(Merge) | 二重登録を削除せず、正(canonical)に束ね、複数Originを参照可能にする。 |
| 履歴(Lineage) | 移動・昇格・統合などのイベント履歴。監査と運用改善の根拠。 |
サービスが増えても、identity_hub と service_binding の構造は変えない。追加は「登録するだけ」。
| 村A(名無し) | Drupal, LINE Bot など(低結合) |
| 村B(顧客) | Salesforce, 見積, 請求など(契約・取引) |
| 村C(社員) | Nextcloud, Vaultwarden, Rocket.Chat, BookStack…(内部) |
| 共通 | service_binding に追加するだけでサービス増設が成立(SSO連携は別工程) |
最小機能は以下。運用しながら拡張する。
・起点(Origin)を永続保持し、更新禁止(不変)を保証する。
・現在所属(Current)と絆(Bond)を更新できる。
・外部ID(Google sub/LINE ID/メール等)により同一性を検知し、二重登録を抑止/統合できる。
・正(canonical)解決: merged を辿って一意の参照先を返す。
・サービス分配: principal_id をキーにサービスを紐付け、村ごとに異なるサービス構成を許容する。
・履歴(Lineage)を残し、監査/トラブル解析/改善に使える。
| セキュリティ境界 | internal(社内)と external(外部)を物理分離(別DB/別スキーマ/別テーブル)。 |
| 権限最小化 | 同期やn8nは SELECT/INSERT を必要最小限。DELETE/UPDATEを原則禁止(運用設計で)。 |
| 監査性 | merge・bond変更・realm移動は必ず記録(lineage_event)。 |
| 可用性 | まずは単一PostgreSQLで開始。将来はレプリカ/分割へ。 |
| 拡張性 | 認証基盤・サービスは交換可能。Hubは“憲法”として維持。 |
・Keycloakは認証実行装置。Hubは「正体(canonical)」と「縁(origin)」と「絆(bond)」と「履歴」を握る。
・Drupalは入口UI。招待コード/窓口で起点を確定し、Hubに記録する。
・n8nは血流。検知、同期、サービス追加、通知、運用バッチを担う。
1) 招待コード/窓口で登録(origin確定)
2) identity_external_link(google/line/email)で同一性チェック
- 既存なら canonical解決して紐付け
- 新規なら identity_hub に作成(origin不変)
3) bond更新(linked -> verified -> contracted -> internal 等)
4) current_realm 変更(引越し/所属変更)
5) サービス追加:service_binding に追加(SSOは別工程)
以下は“最小実装”の推奨DDL。internal/externalは同形で物理分離する。
-- identity_hub_{internal|external}(同形)主なカラム
principal_id
status (active/inactive/merged/blocked)
-- ORIGIN(不変)
origin_municipality_id
origin_realm_key
origin_issuer
origin_ref
origin_at
-- CURRENT(可変)
current_municipality_id
current_realm_key
-- BOND(可変・関係の質)
bond_code
bond_at
-- MERGE(統合)
merged_into_id
merged_at
外部IDの一意性と二重登録検知は identity_external_link で担保する。
-- identity_external_link
(provider, external_id) UNIQUE -- google sub / line userId / email normalized 等
履歴は lineage_event にイベントとして保存する(Hubを肥大化させない)。
-- lineage_event
event_type: origin | bond_change | realm_move | merge | link_add ...
payload: JSONB
-- BEFORE UPDATE triggerで origin_* を変更禁止
-- (アプリ側だけで守ると事故るためDBで強制)
削除しない。片方を正(canonical)に決め、もう片方を merged として束ねる。
canonical決定は bond優先(強い結合が正)を推奨。
Merge手順(概略)
1) canonical と merged を決める(bond優先、同値はcreated_at古い方)
2) merged側: status='merged', merged_into_id=canonical, merged_at=now()
3) identity_external_link を canonical 側へ移す(UNIQUEで衝突があればルールで解決)
4) lineage_event に merge を記録
principal_id(=人の核)をキーに、村ごとのサービスを増減させる。
例)村C(社員)にGitea追加
service_binding: principal_id=h42, service='gitea', service_uid='h42', status='active'
→ 認証は hub の principal_id を前提にSSOで連携
→ サービスが増えてもHub構造は不変
実装はFastAPI等を想定。最小の読み取りAPI。
GET /hub/resolve?provider=google&external_id=...
レスポンス:
- canonical(現在の正)
- origins[](mergeで辿れる起点の一覧)
- links[](google/line/emailなど)
- lineage[](主要イベント)
まずは“会社村(村C)”から開始し、Keycloak名寄せを起点に全体へ展開する。
| Phase 0(器) | .60 portal-postgres に identity スキーマ/テーブル作成 |
| Phase 1(usermaster) | .113 desknet's NEO から tm_user を抽出(90名) |
| Phase 2(投入) | identity_hub_internal に投入(bond_code='internal'相当) |
| Phase 3(名寄せ) | Keycloak へ一括登録、SSO連携を順次追加(service_binding) |
| Phase 4(拡張) | 村A/村B(外部)へ展開、merge/lineage強化、NEEXT-ERPへ統合 |
| Origin(縁) | 最初の登録起点。不変。 |
| Bond(絆) | 現在の結合の質。信頼/契約/内部化など。 |
| Current | 現在所属(realm/municipality)。 |
| Canonical | 統合後の正。参照は常にcanonicalへ解決。 |
| Merge | 二重登録の統合。削除しない。 |
| Lineage | イベント履歴。 |
| Village(村) | 独立した運用単位(サービスセット+データ境界)。 |