設計原則: Frontend-Configurable First
すべての構成値は Settings 画面から編集できる、を原則とします。これにより新環境のセットアップ・本番テナントの設定変更・機密ローテーションが UI と監査ログだけで完結 します。
| ID | 原則 | 意味 |
| F1 | 起動時にしか効かない値であっても、Settings 画面に必ず項目を持つ | 背後で必要に応じてプロセス再起動 / ホットリロードする。 |
| F2 | .env / OS 環境変数はブートストラップ (= DB に到達するまでの最小値) だけに限定 | それ以外はすべて DB から動的に読む。 |
| F3 | 機密値もフロントから登録できる | ただし保存は KMS 暗号化、画面再表示時はマスク (sk-•••••abc)。 |
| F4 | 設定値の読み手は必ず Settings Service 経由 | 各サービスは getSetting(key, scope) で取得し、変更を SSE で受信して反映する。 |
| F5 | 設定変更はすべて監査ログに残る (who / when / old / new) | 機密値の old/new はハッシュのみ記録。 |
| F6 | 値には schema (型 + バリデーション) が必ず付随 | フロントは schema から自動的にフォームを生成する。 |
4 つのスコープと継承ルール
継承順は User → Project → Tenant → System で、下位が上位を上書きします。各キーには overridable_at プロパティで「どこまで下りられるか」を定義します (例: GCP プロジェクトは Tenant 止まり)。
| スコープ | 編集権限 | 例 |
| System | super-admin のみ | LLM プロバイダ、GCP プロジェクト、暗号鍵、デフォルトテナント設定 |
| Tenant | tenant-admin | BQ allowlist、cost cap、ストレージバケット、RBAC ポリシー |
| Project | project owner / scientist | 既定プリセット、Copilot モデル選択、レポート雛形 |
| User | 本人 | UI 言語、テーマ、通知、キーボードショートカット、Copilot 冗長度 |
解決ロジック
getSetting(key, ctx) の探索順:
User(user_id, key)
└ なければ Project(project_id, key)
└ なければ Tenant(tenant_id, key)
└ なければ System(*, key)
└ なければ DefaultRegistry[key].default 起動時ブートストラップ (純粋な OS 環境変数で残るもの)
これら だけ は OS env / .env から読みます (DB に到達できないため)。それ以外のすべての設定は Postgres settings テーブルに格納し、フロントから編集します。
| 環境変数 | 値の例 | 用途 |
SN_ENV | dev | staging | prod | 環境ラベル。LLM / Cookie domain 等の判定で使う |
SN_DB_URL | postgresql+asyncpg://... | Postgres 接続 (DB に到達するまでに必要) |
SN_REDIS_URL | redis://... | Redis 接続 (queue + pub/sub) |
SN_KMS_KEY_URI | gcp-kms://... または local fallback | 機密復号鍵 |
SN_SETTINGS_BOOT_TOKEN | ワンタイムトークン | 初回 super-admin 招待用 |
SN_LISTEN_HOST | 0.0.0.0 等 | API バインドアドレス |
SN_LISTEN_PORT | 8080 等 | API ポート |
SN_LOG_LEVEL | info | debug | warn | error | DB 不通時の fallback ログレベル |
SN_INTERNAL_TLS_CERT | PEM path (任意) | mTLS 用 |
SN_INTERNAL_TLS_KEY | PEM path (任意) | mTLS 用 |
例外: ブート env のキー (SN_DB_URL 等) もフロントの Settings 画面に 表示はされます が、編集すると「再起動が必要」バッジが立ち、変更内容は次回起動時に反映されます (DB に書きつつ、boot env を上書きするフォールバックは行わない)。
Settings UI
グローバルナビ右上アバター → Settings から到達。Studio / Workspace の両方から同じ Settings 画面に飛びます。URL: /settings/{scope}/{section} (例: /settings/tenant/bigquery)。
各項目に表示するメタデータ
- Key (例:
bq.cost_cap_usd.ui_preview) - 型 (string / int / float / bool / enum / json / secret)
- 既定値
- 現在のソース (User / Project / Tenant / System / Default)
- 影響範囲 (例: 「Studio Step4 dry-run」)
- 再起動要否 (Hot / Restart-required)
- 機密フラグ (secret はマスク表示 + 編集時のみ平文入力)
操作
- 編集: インライン or モーダル (schema に応じて自動 UI)
- テスト: Connection 系は "Test connection" ボタン (BQ ping / LLM ping / S3 list)
- エクスポート / インポート: スコープ単位で JSON エクスポート (機密は除外)
- 差分: 編集前後の diff プレビュー
- 履歴: 直近 50 件の変更を表示、ロールバック可能
永続化モデル
-- 非機密値
CREATE TABLE settings (
id uuid PK,
scope text CHECK (scope IN ('system','tenant','project','user')),
scope_id text,
key text NOT NULL,
value_json jsonb,
value_secret_id uuid, -- 機密値は KMS 経由で別表
schema_version int NOT NULL,
updated_by uuid NOT NULL,
updated_at timestamptz NOT NULL,
UNIQUE(scope, scope_id, key)
);
-- 機密値 (KMS 暗号化 blob)
CREATE TABLE settings_secrets (
id uuid PK,
ciphertext bytea NOT NULL, -- KMS encrypted
kms_key_uri text NOT NULL,
created_at timestamptz NOT NULL
);
-- 変更履歴 (監査)
CREATE TABLE settings_audit (
id bigserial PK,
scope text, scope_id text, key text,
old_hash text, new_hash text, -- 機密は old/new ハッシュのみ
old_value jsonb, new_value jsonb, -- 非機密は raw
changed_by text, changed_at timestamptz
); Hot reload (5 秒以内の配信)
変更時に Settings Service が Redis pub/sub に settings:changed イベントを発火します。各サービス (Studio API / Workspace API / Worker / Agent) は購読してメモリキャッシュを破棄。フロントは SSE でリスナ更新 (開いている Settings 画面の他者編集に即追従)。
Service A (FastAPI) Redis pub/sub Service B (Worker)
│ PUT /v1/settings/... │ │
│─────────────────────────────►│ │
│ settings_service.update() │ │
│ → DB write │ │
│ → publish "settings:changed" │ │
│─────────────────────────────►│ │
│ │── subscribe ──────────────►│
│ │ │ cache invalidate
│ │ │ → 次の getSetting で DB から再取得 Restart-required な値: storage.object_store_kind / bq.gcp_project_id / bq.location / bq.service_account_key など、起動時に確立するクライアントが関わる値は変更後にプロセス再起動が必要です。Settings 画面に「再起動が必要」バッジが立ちます。
カタログの読み方 (凡例)
- OV (overridable_at)
- どこまで下位スコープで上書きできるか。S = System / T = Tenant / P = Project / U = User
- HR (Hot reload)
- ● = Hot reload 対応 (5 秒以内に各サービスに反映) / ◯ = Restart required
- Sec (機密)
- ● = 機密値 (KMS 暗号化、UI マスク表示)
1. Connections (接続情報)
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
bq.gcp_project_id | string | — | T | Restart | | BigQuery プロジェクト ID |
bq.service_account_key | secret | — | T | Restart | Secret | サービスアカウント JSON (KMS 保管) |
bq.location | enum(US,EU,asia-northeast1) | US | T | Hot | | BQ ジョブのロケーション |
bq.workload_identity_audience | string | — | T | Restart | | WIF を使う場合の audience |
bq.source_allowlist | json(list) | ["xhro_01.*","xhro_02.*","xhro_03.*","xhro_04.*","xhro_view.*","xhro_04_modeling_v2.*"] | T | Hot | | 許可するデータセット/テーブル (glob)。**生 xhro は partition-filter 罠で既定除外** |
bq.source_blocklist | json(list) | ["xhro","xhro_backup","tmp","clns","dataflow_dev"] | T | Hot | | 明示的に隠すデータセット。allowlist より強い |
storage.object_store_kind | enum(gcs,s3,minio) | gcs | T | Restart | | Bundle 物理保管先種別 |
storage.bucket | string | — | T | Restart | | 既定バケット名 |
storage.signed_url_ttl_sec | int | 900 | T | Hot | | 署名付き URL の有効期間 |
auth.oidc_issuer | string | — | T | Restart | | OIDC issuer URL |
auth.oidc_audience | string | — | T | Restart | | OIDC audience |
auth.oidc_client_id | string | — | T | Restart | | クライアント ID |
auth.oidc_client_secret | secret | — | T | Restart | Secret | クライアントシークレット |
auth.session_ttl_min | int | 480 | T | Hot | | Web セッション TTL |
2. LLM / AI Copilot
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
llm.provider | enum(anthropic,openai,vertex) | anthropic | T | Hot | | LLM プロバイダ |
llm.api_base_url | string | (provider default) | T | Hot | | API エンドポイント上書き |
llm.api_key | secret | — | T | Hot | Secret | API キー |
llm.model.copilot_planner | string | claude-opus-4-7 | P | Hot | | 設計系 (hypothesis 提案) |
llm.model.copilot_codegen | string | claude-sonnet-4-6 | P | Hot | | コード生成 (denoise/feature 提案) |
llm.model.copilot_narrative | string | claude-sonnet-4-6 | P | Hot | | 所見ドラフト |
llm.model.studio_assistant | string | claude-sonnet-4-6 | P | Hot | | Studio 内 Window 提案 |
llm.max_tokens_per_turn | int | 4096 | P | Hot | | 1 ターン上限 |
llm.temperature | float | 0.2 | P | Hot | | サンプリング温度 |
llm.prompt_cache_enabled | bool | true | T | Hot | | Anthropic prompt cache 有効 |
llm.tool_call_timeout_sec | int | 30 | P | Hot | | 各 tool 呼び出しタイムアウト |
copilot.verbosity | enum(terse,normal,verbose) | normal | U | Hot | | Copilot 返答の詳しさ |
copilot.auto_dryrun_cells | bool | true | U | Hot | | 提案セルを Apply 前に sandbox dry-run |
copilot.show_diff_default | bool | true | U | Hot | | 提案を diff で見せる (false=full text) |
copilot.allowed_tools | json(list) | (全許可) | P | Hot | | Copilot が呼べるツール allowlist |
copilot.cost_alert_usd_per_day | float | 5.00 | U | Hot | | 1 日 LLM 課金しきい値 (超過で警告) |
3. Cost & Quota (BQ / sandbox / worker)
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
bq.cost_cap_usd.ui_preview | float | 0.05 | T | Hot | | UI プレビュー dry-run キャップ |
bq.cost_cap_usd.preview_window | float | 0.50 | T | Hot | | 単一 Window 取り出し |
bq.cost_cap_usd.analysis_dataset | float | 5.00 | T | Hot | | 解析データセット import |
bq.cost_cap_usd.export | float | 25.00 | T | Hot | | admin 承認エクスポート |
bq.scan_bytes_cap.preview_window | int(MB) | 512 | T | Hot | | スキャン MB 上限 |
bq.window_max_minutes.scientist | int | 30 | T | Hot | | scientist が 1 Window 取れる最大分数 |
bq.window_max_minutes.admin | int | 240 | T | Hot | | admin の最大分数 |
bq.windows_per_bundle_max | int | 12 | T | Hot | | 1 Bundle 内の Window 上限 |
bq.daily_user_cap_usd | float | 10.00 | T | Hot | | ユーザ 1 人 1 日の累計上限 |
bq.daily_tenant_cap_usd | float | 100.00 | T | Hot | | テナント 1 日の累計上限 |
sandbox.cpu_cores | int | 2 | T | Restart | | sandbox CPU |
sandbox.memory_mb | int | 2048 | T | Restart | | sandbox メモリ |
sandbox.wall_time_sec.feature | int | 15 | T | Hot | | feature 1 セル実行時間上限 |
sandbox.wall_time_sec.hypothesis | int | 120 | T | Hot | | hypothesis 実行上限 |
sandbox.allow_network | bool | false | T | Hot | | sandbox の外向き通信 |
worker.fanout_concurrency.feature | int | 8 | T | Hot | | feature 並列度 |
worker.fanout_concurrency.hypothesis | int | 4 | T | Hot | | hypothesis 並列度 |
worker.fanout_concurrency.bundle_build | int | 2 | T | Hot | | Bundle build 並列度 |
4. ECG Defaults (信号処理デフォルト)
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
ecg.preset_default | enum(default,noisy,xhro-multiday,holter-overnight,custom) | xhro-multiday | P | Hot | | Studio Step4 既定プリセット。XHRO 連続多日装着用 (holter-overnight は 12 誘導 Holter 想定で XHRO には合わない) |
ecg.sample_rate_hz | enum(250,500,1000) | 250 | P | Hot | | 既定サンプリング (Bundle build 時) |
ecg.notch_hz | enum(50,60) | 50 | T | Hot | | 既定 notch 周波数 |
ecg.highpass_hz | float | 0.5 | P | Hot | | 既定 HPF |
ecg.lowpass_hz | float | 40 | P | Hot | | 既定 LPF |
ecg.r_peak_detector | enum(pan_tompkins_v1,hamilton,custom) | pan_tompkins_v1 | P | Hot | | 既定 R ピーク検出器 |
ecg.rr_min_ms | int | 300 | P | Hot | | RR 異常値下限 |
ecg.rr_max_ms | int | 2000 | P | Hot | | RR 異常値上限 |
ecg.hrv_window_sec | int | 60 | P | Hot | | HRV ローリング窓 |
ecg.invalid_threshold.acc_g | float | 0.3 | P | Hot | | 体動 RMS しきい値 |
ecg.invalid_threshold.leadoff_ratio | float | 0.05 | P | Hot | | lead-off 比率 |
ecg.beat_label_set | enum(mit-bih,aha,xhro_ecg_annot,custom) | mit-bih | P | Hot | | 拍ラベル分類集合 |
ecg.leads_default | json(list) | ["xhro_ch1_minus_ch2"] | P | Hot | | 既定誘導 (XHRO 固有の差動 ID) |
viewer.timescale_sec | enum(1,5,10,30,60) | 10 | U | Hot | | Viewer 初期時間軸 |
viewer.pyramid_auto_level | bool | true | U | Hot | | ズーム自動レベル切替 |
viewer.show_r_peaks | bool | true | U | Hot | | R ピークオーバーレイ既定 ON |
viewer.show_invalid_intervals | bool | true | U | Hot | | 無効区間オーバーレイ |
5. Storage & Retention
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
bundle.retention_days | int | 365 | T | Hot | | Bundle 保管期間 |
bundle.archive_after_days | int | 90 | T | Hot | | コールドストレージ移行 |
bundle.max_size_gb | int | 10 | T | Hot | | 1 Bundle サイズ上限 |
evidence.retention_days | int | 1825 | T | Hot | | Evidence 保管 (= 5 年) |
audit.retention_days | int | 2555 | S | Hot | | 監査ログ (= 7 年) |
cache.feature_value_ttl_days | int | 30 | T | Hot | | feature 値キャッシュ TTL |
cache.preview_ttl_sec | int | 600 | T | Hot | | Studio プレビューキャッシュ |
cache.waveform_pyramid_ttl_days | int | 0 | T | Hot | | 0 = Bundle と同じ寿命 |
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
rbac.matrix_json | json | (組込既定) | T | Hot | | role × 操作の許可マトリクス |
rbac.default_role_for_new_users | enum(viewer,annotator,scientist) | viewer | T | Hot | | 新規ユーザ既定 |
rbac.scientist_can_pre_register | bool | true | T | Hot | | scientist の pre-reg 権限 |
rbac.who_can_verdict | enum(scientist,admin,project_owner) | project_owner | T | Hot | | verdict 付与者 |
rbac.who_can_edit_bundle_retention | enum(admin) | admin | T | Hot | | retention 編集 |
auth.mfa_required | bool | true | T | Hot | | MFA 強制 |
auth.allowed_ip_cidrs | json(list) | [] | T | Hot | | IP allowlist (空=制限なし) |
auth.session_idle_timeout_min | int | 30 | T | Hot | | アイドルタイムアウト |
7. Reports & Templates
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
report.brand_logo_url | string | — | T | Hot | | レポート左上ロゴ |
report.brand_color_primary | string(hex) | #1f2937 | T | Hot | | プライマリ色 |
report.template_session_pdf | string(template id) | default | P | Hot | | per-session PDF 雛形 |
report.template_hypothesis_pdf | string | default | P | Hot | | per-hypothesis PDF 雛形 |
report.template_pptx | string | default | P | Hot | | PPTX 雛形 |
report.include_code_snippets | bool | true | P | Hot | | コードを埋めるか |
report.include_evidence_table | bool | true | P | Hot | | evidence 表を埋めるか |
report.signature_block_text | string | (空) | P | Hot | | 末尾署名ブロック |
8. Notifications
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
notify.channel.email | bool | true | U | Hot | | メール通知 |
notify.channel.slack | bool | false | U | Hot | | Slack 通知 |
notify.slack_webhook_url | secret | — | T | Hot | Secret | Slack incoming webhook |
notify.events.bundle_ready | bool | true | U | Hot | | Bundle 完成通知 |
notify.events.hypothesis_done | bool | true | U | Hot | | 仮説実行完了 |
notify.events.stale_evidence | bool | true | U | Hot | | evidence stale 化 |
notify.events.cost_threshold | bool | true | U | Hot | | コスト警告 |
notify.daily_digest_time_local | string(HH:MM) | 09:00 | U | Hot | | 日次サマリ時刻 |
9. Locale & UI
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
ui.locale | enum(ja,en) | ja | U | Hot | | 表示言語 |
ui.theme | enum(light,dark,system) | system | U | Hot | | テーマ |
ui.density | enum(compact,comfortable) | comfortable | U | Hot | | UI 密度 |
ui.timezone | string(IANA) | Asia/Tokyo | U | Hot | | 表示タイムゾーン |
ui.number_format | enum(en-US,ja-JP) | ja-JP | U | Hot | | 数値表示 |
ui.units_voltage | enum(uV,mV) | mV | U | Hot | | ECG 表示単位 |
ui.units_time | enum(ms,s) | s | U | Hot | | 時間表示 |
ui.show_lineage_chips | bool | true | U | Hot | | Bundle lineage バッジ表示 |
10. Keyboard Shortcuts
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
keys.viewer.add_rpeak | string | + | U | Hot | | R ピーク追加 |
keys.viewer.remove_rpeak | string | - | U | Hot | | R ピーク削除 |
keys.viewer.mark_invalid | string | i | U | Hot | | 無効区間マーク |
keys.viewer.next_window | string | ] | U | Hot | | 次 Window |
keys.viewer.prev_window | string | [ | U | Hot | | 前 Window |
keys.notebook.run_cell | string | Shift+Enter | U | Hot | | セル実行 |
keys.notebook.apply_proposal | string | Cmd+. | U | Hot | | AI 提案 Apply |
keys.global.search | string | Cmd+K | U | Hot | | 検索パレット |
11. Feature Flags
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
flag.studio.ai_window_suggest | bool | true | T | Hot | | Studio AI Window 提案 |
flag.studio.derive_spo2_in_bundle | bool | false | T | Hot | | Bundle build 時の SpO2 派生 |
flag.workspace.beat_classifier | bool | false | T | Hot | | 拍分類モデルセル |
flag.workspace.live_collab | bool | false | T | Hot | | Notebook 共同編集 |
flag.workspace.outcome_projects | bool | true | T | Hot | | outcome 種別 Project |
flag.copilot.narrative_drafts | bool | true | P | Hot | | AI 所見ドラフト |
flag.export.async_large_jobs | bool | false | T | Hot | | 大規模エクスポートジョブ |
flag.annotation.continuous_learning | bool | false | T | Hot | | Annotation Continuous learning |
12. Audit & Diagnostics
| Key | 型 | 既定 | OV | HR | Sec | 説明 |
audit.export_destination | enum(gcs,s3,none) | gcs | T | Restart | | 監査ログの夜次エクスポート先 |
audit.export_bucket | string | — | T | Restart | | エクスポート bucket |
diag.otel_endpoint | string | — | T | Hot | | OpenTelemetry コレクタ |
diag.log_level | enum(debug,info,warn,error) | info | T | Hot | | 実行時ログレベル |
diag.trace_sample_rate | float | 0.1 | T | Hot | | トレースサンプリング率 |
diag.slow_query_threshold_ms | int | 1000 | T | Hot | | スロークエリしきい値 |
バリデーションとスキーマ (DefaultRegistry)
各キーには DefaultRegistry[key] として以下が紐づきます。このレジストリはコードに同梱 (型安全のため)、フロント Settings 画面はこの定義から フォームを自動生成 します。新しい設定キーの追加 = DefaultRegistry への 1 行追加で済みます。
{
key: "bq.cost_cap_usd.ui_preview",
type: "float",
min: 0,
max: 100,
default: 0.05,
scope_max: "tenant", // T までしか下りない
overridable_at: ["tenant"],
hot_reload: true,
secret: false,
description: "UI プレビュー dry-run 1 回あたりの USD 上限",
affects: ["Studio Step4", "BigQueryAccessService.dry_run"],
ui: { component: "money", currency: "USD" }
} 既存 suntory-nedo からの移行
旧リポの env-var で散らかっていた値は次の通り分類されます。9 割の env-var を消して Settings 画面に移し、残る OS env はブートストラップ 10 個前後だけです。
| 既存 env-var | v2 での行き先 |
DATABASE_URL | ブート env (SN_DB_URL) — そのまま残す |
REDIS_URL | ブート env (SN_REDIS_URL) |
BQ_PROJECT_ID | DB / Settings (bq.gcp_project_id) |
BQ_LOCATION | DB / Settings (bq.location) |
BQ_SA_KEY_FILE | DB / Settings (bq.service_account_key, KMS 暗号化済 secret) |
S3_BUCKET / S3_ACCESS_KEY / S3_SECRET_KEY / S3_ENDPOINT | DB / Settings (storage.*) |
AUTH_MODE / JWT_DEV_SECRET / JWT_ISSUER / JWT_AUDIENCE / JWT_ALG | DB / Settings (auth.*) |
ANTHROPIC_API_KEY | DB / Settings (llm.api_key) — KMS 暗号化 |
LOG_LEVEL | DB / Settings (diag.log_level) — boot fallback は env |
OTEL_EXPORTER_OTLP_ENDPOINT | DB / Settings (diag.otel_endpoint) |
NEXT_PUBLIC_API_BASE_URL | フロント側のビルド時 env (Studio Web のみ) |
受け入れ基準 (Settings)
- ✓新しいインスタンスを立てるとき、
.env にはブート env 10 個程度だけ書けばよい。 - ✓それ以外の全設定値は Settings 画面から編集できる (機密含む)。
- ✓Settings 画面のフォームは
DefaultRegistry から自動生成される (画面コードに値ごとの分岐がない)。 - ✓設定変更は監査ログに残り、機密は old/new がハッシュのみで記録される。
- ✓Hot reload 対象の値は変更 5 秒以内に各サービスに反映される。
- ✓機密値は再表示時にマスクされ、平文では返らない。
- ✓RBAC により、編集権限のないスコープは UI で disabled、API では 403。
- ✓テナント新規作成時に System の値を継承した雛形が自動で
settings に投入される。 - ✓JSON エクスポート / インポートで Tenant 設定をまるごと別環境に移植できる (機密は除外)。