# RAIOPS-13 RBAC/Cost Guardrail 実装レポート

## 概要

RAIOPS-13 のvalidation sliceとして、企業IAM/FinOps本実装ではなく、AIアーキテクトとして破ってはいけないRBAC/LLM-safe/cost境界をコード、trace metadata、SQL setup memo、docs、pytestで証跡化した。

## 変更ファイル

- `retail_ai_ops/models.py`
  - `store_manager`, `regional_manager`, `analyst`, `admin` のrole boundary contractを追加
  - LLM-safe fields allowlistとraw/personal field hintsを追加
  - `QuestionPlan` に `role_name`, `llm_safe_fields` を追加
- `retail_ai_ops/config.py`
  - `role_name`, `role_region`, `role_nation`
  - `max_result_rows`, `query_timeout_seconds`, `query_tag`
  - `llm_safe_fields`
  - LLM-safe fields overrideをcanonical subsetへ制限し、raw/personal fieldや非正整数guardrail値をfail-closed
- `retail_ai_ops/planner.py`
  - sensitive/raw/personal request keywordsを拡張
  - raw/personal field hintsも `safe_refusal` へ統合
  - 未対応roleとscope外regionを `unsupported_question` / `safe_refusal` で停止
  - `store_manager` はfixture上の `nation_name`、`regional_manager` は `region_name` をSQL filterへ反映
  - SQLの `limit` を `Settings.max_result_rows` から生成
- `retail_ai_ops/trace.py`
  - `warehouse_name`, `query_tag`, `max_result_rows`, `query_timeout_seconds`, `role_name`, `llm_safe_fields`, `raw_payload_logged`, `result_cache_hit` をtraceへ追加
  - `sensitive_or_raw_data_request` のrequest本文を `[redacted_sensitive_or_raw_request]` にredact
- `retail_ai_ops/clients.py`
  - Snowflake connector `session_parameters` へ `QUERY_TAG` と `STATEMENT_TIMEOUT_IN_SECONDS` を渡す
- `retail_ai_ops/copilot.py`
  - `TraceLogger(settings.trace_path)` のmetadataなし通常呼び出しでも、`build_trace_metadata(settings)` を補完してsettings由来のguardrail metadataをtraceへ残す
- `tests/test_planner_and_trace.py`
  - raw/personal/sensitive safe refusal
  - raw/personal field hint網羅のnegative test
  - LLM-safe fields overrideとcost guardrail整数のfail-closed test
  - fake Snowflake connectorによるsession parameter test
  - metadataなしTraceLoggerでSQL limitとtrace guardrail metadataが一致する回帰test
  - 明示TraceLogger metadataがsettings metadataで上書きされない回帰test
  - role boundary contract
  - store/regional scope
  - query limit
  - cost/RBAC trace metadata
  - sensitive safe-stop trace redaction
- `sql/00_setup_from_snowflake_sample.sql`
  - XS warehouse, auto suspend, statement timeout, query tagを明記
  - resource monitorは設計メモに留める
  - `OPS.RBAC_ROLE_BOUNDARIES`, `OPS.COST_GUARDRAIL_SETTINGS`, trace log metadata columnsを追加
- `docs/obsidian/06_セキュリティ・コスト境界.md`
  - role matrix、LLM-safe allowlist、safe-stop関係、cost trace fields、evidence boundaryを追加

## 受入基準対応

| 受入基準 | 対応 |
| --- | --- |
| role boundaryとLLM-safe fieldsが文書化されている | `ROLE_BOUNDARIES`, `LLM_SAFE_FIELDS`, Obsidian doc, setup SQL |
| 顧客個人情報やraw row要求がunsupported/safe-stopになる | planner keywordsとpytestで `safe_refusal` を確認 |
| traceに秘密情報を残さない方針がある | sensitive/raw request本文をredactし、raw payloadをtraceに保持しないpytestを追加 |
| cost-relevant fieldsが定義されている | trace metadataとdocに `warehouse_name`, `query_tag`, `max_result_rows`, `query_timeout_seconds`, `role_name`, `llm_safe_fields`, `result_row_count`, `live_external_executed` を追加 |
| Snowflake live pathでコスト暴走を避ける基本前提が書かれている | setup SQLでXS/auto suspend/timeout/query tag、resource monitor設計メモ |
| 深掘りしない範囲が明確 | Obsidian docにIAM/FinOps/RLS等の非目標を維持し、reportでもlive未実行を分離 |

## Review Repair Mapping

| Finding | Repair |
| --- | --- |
| F1 High: raw/personal field hintsがplanner sensitive判定に入っていない | `SENSITIVE_OR_RAW_KEYWORDS` に `RAW_OR_PERSONAL_FIELD_HINTS` を統合し、全hintのnegative pytestと代表hintのtrace redaction pytestを追加 |
| F2 High: `RETAIL_AI_OPS_LLM_SAFE_FIELDS` がunsafe fieldもoverride可能 | `Settings.__post_init__()` でcanonical `LLM_SAFE_FIELDS` subsetのみ許可。raw/personalまたは未知fieldは `ValueError` でfail-closed。env override負例pytestを追加 |
| F3 Medium-High: `query_tag` / `query_timeout_seconds` がlive client未接続 | `SnowflakeKpiClient` のconnector `session_parameters` に `QUERY_TAG` と `STATEMENT_TIMEOUT_IN_SECONDS` を渡す。fake connector testで固定。live実行は未実施 |
| F4 Medium: 非正のcost guardrail値がtraceに残りうる | `Settings.__post_init__()` で `max_result_rows` / `query_timeout_seconds` を正の整数に限定。非正値のdirect/env負例pytestを追加 |

## Trace Metadata Repair Follow-up

残blocking finding:

- `answer_question(..., trace_logger=TraceLogger(settings.trace_path))` のようなmetadataなし通常呼び出しでは、SQLは `Settings(max_result_rows=7)` により `limit 7` になる一方、trace metadataはdefault値を記録しうる。

Repair:

- `answer_question()` が `build_trace_metadata(settings)` を `TraceLogger.append()` へ渡すようにした。
- `TraceLogger(metadata=...)` が既に持つ明示metadataは、settings由来metadataより優先する。
- pytestで `max_result_rows=7`, `query_timeout_seconds=12`, custom `query_tag` の通常TraceLogger呼び出しを固定し、traceのSQL `limit 7` とmetadata値一致を確認した。

## 実行コマンド結果

```text
$ pytest -q tests/test_planner_and_trace.py
......................................................................   [100%]
70 passed in 0.03s
```

```text
$ pytest -q
........................................................................ [ 62%]
............................................                             [100%]
116 passed, 2 skipped in 2.67s
```

```text
$ python3 tools/sync_obsidian_docs.py --direction check
obsidian_sync=ok
```

```text
$ git diff --check
```

結果: exit code 0。出力なし。

## Live / External 実行境界

- Snowflake live query: `NOT_EXECUTED_NOT_PROVEN`
- Resource monitor作成: `NOT_EXECUTED_NOT_PROVEN`
- Snowflake上の `OPS.COPILOT_TRACE_LOG` insert: `NOT_EXECUTED_NOT_PROVEN`
- 外部送信: 未実施

理由: RAIOPS-13 briefのnon-goalsに従い、実Snowflake live実行、実customer data、外部送信、精密FinOps dashboard、実resource monitor作成は行っていない。local pytestとsetup SQLはvalidation sliceの証跡であり、live成功証跡ではない。

## L6 Public Surface 反映

状態:

- `state=reported`
- `pages_status=pending-review`
- `final_artifact_status=pending-review`

L6 public-surface laneでは、mainが確認済みとして渡したRAIOPS-13証跡だけをPages向けの人間可読面へ反映した。常駐監視や追加のlive検証は行っていない。

反映先:

- `docs/architecture/retail-ai-ops-copilot-architecture.html`
  - RBAC / アクセス制御、Cost Guardrails、Planner、Agent Router、Tool Adapter、Trace Logger、Trace StoreのreadinessとtooltipへRAIOPS-13のlocal contract evidenceを追加
- `docs/project-management/test-patterns.html`
  - RAIOPS-13をRBAC / LLM-safe / cost trace boundaryのcontract-flow E2E / fake connector unit patternとして追加
- `docs/project-management/code-review-findings.html`
  - unsafe env override、raw-personal field detection gap、Snowflake client session params未適用、trace metadata driftをソースコード改修実例として追加
- `docs/index.html`
  - RAIOPS-13レポート、テストパターン、改修実例への導線を追加

この反映はlocal docs更新であり、pushは禁止のため新しいPages deploymentはまだ存在しない。前提として渡されたPages build `28584336044` successはsource main commit `45c867d41656ceca609e78a2ba7ff3368470dc30` の証跡であり、このL6ローカル更新後の公開HTTP 200を証明しない。

## Lane-Local Objective Review

- reviewer_type: main-context self-review
- real_subagent_status: unavailable/not-run
- evidence: tool searchでは汎用sub-agent起動ツールが露出せず、GitHub系ツールのみ確認
- verdict: PASS
- blocking findings: なし
- main sessionの独立review、branch/worktree確認、promotion decisionが必要

レビュー観点:

- artifact defect: blockingなし
- evidence overclaim: live未実行を `NOT_EXECUTED_NOT_PROVEN` としてreport/docsへ明記
- stale docs: Obsidian mirror checkは `obsidian_sync=ok`
- branch/integration risk: local dirty diffあり。main sessionが共有ファイル衝突を確認する必要あり
- reviewer-process gap: 実sub-agent reviewerは未使用。brief上は「可能なら」なのでblockingではない

## Feedback Reflection Boundary

このrepair laneはartifact repairのみ担当。feedback reflectionはmain/orchestrator担当のため、このlaneでは `.agent-feedback/**` を編集していない。

## 次工程

- main sessionがdiffとreportを独立reviewする
- 他RAIOPS laneとの共有ファイル衝突を確認する
- promotionする場合はmain側でintegration branchへ取り込み、必要に応じてL6/public surface更新を別laneへevent-driven dispatchする
