Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
## 概要 (Summary)
<!-- 変更の目的、背景、および実装内容を簡潔に記載してください -->
*

## 関連Issue / チケット (Related Issues)
* close #

## 変更タイプ (Type of Change)
- [ ] 🚀 新機能追加 (Feature)
- [ ] 🐛 バグ修正 (Bug Fix)
- [ ] 🔒 セキュリティ修正 (Security Fix)
- [ ] 🚫 機能のクローズ・非公開化・削除 (Feature Deprecation/Disable)
- [ ] ⚠️ 破壊的変更・データ移行を伴う修正 (Breaking Change / Migration)
- [ ] 📚 仕様書・マニュアル・APIリストの更新 (Documentation)

---

## 🤖 0. CI 自動チェック (API Inventory Drift)
<!-- .github/workflows/api-inventory-drift.yml が PR ごとに自動実行する。
手動で確認する必要はないが、FAIL したら下記に従って対処すること。
詳細な対処表: tools/api-inventory/ci/README.md -->

PR ごとに実機を起動し、url_map のダンプ・台帳との突き合わせ・変更行の到達可否測定を自動実行する。
結果は PR コメントと Actions の artifact (`api-inventory-drift`) に出る。

- [ ] **CI が PASS している**、または FAIL の各項目に対処済み

### API を追加・変更した場合(必須)

- [ ] **秘密側**の `api_snapshot.json` を更新し、対応する PR を出した
```bash
export WEKO_API_INVENTORY_DIR=/path/to/weko-secret
./install.sh
python3 tools/api-inventory/scripts/snapshot.py --out "$WEKO_API_INVENTORY_DIR/api_snapshot.json"
```
更新しないと CI が落ちる。**このリポジトリは public のため台帳もベースラインも
同梱していない。** 更新は秘密リポジトリ側の PR になる。
- [ ] **秘密側**の `weko3_api_list_full.tsv` に行を追加・更新し、
`build_checklist.py` で 24 列版を再生成した(未収載だと reconcile が FAIL する)
- [ ] 台帳・スナップショット・実測結果を**この公開リポジトリにコミットしていない**
(`git status` に `*.tsv` / `api_snapshot.json` が出ていないこと)

### FAIL したときの対処(要約)

| 検出 | 意味 | 対処 |
|---|---|---|
| G1 / G2 | 新規に認証デコレータが無い / 認証デコレータが削除された | 実装を直す。意図的な公開なら台帳に根拠を書いてベースライン更新 |
| G3 | 認証・認可のコメントアウトが増えた | 原則やり直し |
| G4 | `*_PERMISSION_FACTORY` 等が危険側に変わった | 原則やり直し |
| G5 | ModelView の `can_delete` / `can_export` を有効化 | 意図的なら台帳の `data_op` を更新 |
| G6 / G7 | 属性不明の経路が増えた / 依存更新で経路が増減 | 台帳に行を追加してレビューする |
| **G8** | **未認証で到達する書き込み系** | **原則やり直し** |
| **G9** | **台帳では遮断なのに実測で到達(認可の回帰)** | **原則やり直し** |
| reconcile A–D | 台帳と実機の不一致(未収載・メソッド・app 列) | 台帳を実機に合わせる |

実機に存在しないことが正当な行(プラグイン未登録等)は**秘密側**の `reconcile_allow.json` に
**理由付きで**登録する。理由なしの登録は不可。

> CI の出力は件数のみ。該当した経路名は Actions には出ないので、秘密側の完全版レポートで確認すること
> (このリポジトリは public で、ログ・artifact・PR コメントは誰でも読めるため)。

---

## 🔒 1. セキュリティ & API アクセス制御チェック (必須)

### 認証・認可 (Authentication & Authorization)
- [ ] 新規/変更された Blueprint・View・REST リソースに適切なデコレータ / Permission を設定している
- 例: `@login_required`, `@pass_record`, `need(...)`, Invenio Access Action
- `/api/*` では `Permission.require(http_exception=403)` を使うこと。
`@login_required` は API アプリに `security.login` が無いため 401 ではなく **500** になる
- <sub>CI: G1 / G2 が自動検出(デコレータの有無・削除)</sub>
- [ ] 状態変更・破壊的メソッド (POST / PUT / PATCH / DELETE) の権限が正しく制限されている
- <sub>CI: G8 が変更行を未認証で実測</sub>
- [ ] 未ログイン(Anonymous)状態でアクセスした際、意図しないデータ取得・変更が拒絶される
- <sub>CI: G8 / G9 が変更行を実測。ただし測定は変更行のみで、
ワークフロー系など未解決プレースホルダの行は skip される</sub>
- [ ] 認可を config の permission factory に委ねている場合、`None` で無効化していない
- <sub>CI: G4 が `*_PERMISSION_FACTORY` 等を監視</sub>

### 機能クローズ・非公開化の場合 (Feature Disable)
- [ ] UI(画面・ボタン)の非表示だけでなく、**バックエンド API(ルーティング・View)も完全に遮断**されている
- [ ] 無効化状態で直接 API を叩いた場合、`404 Not Found` または `403 Forbidden` が返ることを確認した

---

## 🧪 2. テストコード観点チェック (pytest / Invenio Test Suite)

### 権限・異常系テスト (Negative & Authorization Tests)
- [ ] **未認証アクセス (Anonymous)**: トークン/セッションなしのリクエストで `401 Unauthorized` または `403 Forbidden` / `404 Not Found` が返ることを検証するテストがある
- [ ] **権限不足ユーザー (Forbidden)**: 閲覧権限のみのユーザーが更新/削除 API を叩いた際に `403` になるテストがある
- [ ] **無効化/非公開機能の遮断テスト**: 対象機能が無効化されている場合、エンドポイントが `404` / `403` を返すテストがある

### 境界値・入力バリデーションテスト (Boundary & Validation)
- [ ] 不正なパラメータ(巨大ファイル、異常な MIME タイプ、無効な JSON/XML スキーマ、SQLi/XSS ペイロード等)で適切に `400 Bad Request` / バリデーションエラーが返るテストがある

### データ整合性・トランザクションテスト (Integrity & Rollback)
- [ ] ファイルストレージ(S3/ローカル)書き込み失敗時や DB エラー時に、中途半端なレコードやゴミファイルが残らずロールバックされるテストがある

---

## 🛡️ 3. データ保護 & 破壊的変更防止チェック (Data Safety)

- [ ] **物理削除・上書きの安全性**:
- ファイル・アイテム・メタデータの完全削除/置換処理に、意図しない一括削除や別レコードへの誤適用リスクがない
- 論理削除、バージョン管理、バックアップ等のロールバック機構が考慮されている
- [ ] **トランザクション整合性**:
- DB 更新とストレージ操作がアトミックに管理されている

---

## ⚙️ 4. マイグレーション & システム影響チェック (Invenio / WEKO3 Stack)

### データベース (DB / Alembic)
- [ ] `invenio alembic upgrade`(適用)および `downgrade`(ロールバック)スクリプトを作成・検証した
- [ ] 既存データに対する破壊的変更(カラム削除、型変更、NOT NULL 制約追加等)の移行スクリプト/データパッチを用意した

### 検索インデックス (Elasticsearch / OpenSearch)
- [ ] マッピング定義変更の有無を確認した
- [ ] インデックス再作成(Reindex)やエイリアス切り替え手順を準備・検証した

### 設定 & 非同期処理 (Config / Celery / Cache)
- [ ] `invenio.cfg` / 環境変数のデフォルト値を設定した
- [ ] Celery タスクのシグネチャ変更によるキュー滞留・不整合が発生しない
- [ ] キャッシュ(Redis/Memcached)のパージが必要か確認した

---

## 📚 5. ドキュメント・仕様書更新チェック (weko-document)
<!-- https://github.com/RCOSDP/weko-document への反映確認 -->

- [ ] **API インベントリ**: ツールは本リポジトリの `tools/api-inventory/`、
**台帳・調査記録は秘密の場所**(public リポジトリには置かない)。
§0 のチェック項目で対応済みなら、ここは確認のみ。
- エンドポイントの追加・変更・廃止、メソッド、認証・認可要件、リクエスト/レスポンス仕様を更新した
- 調査記録(`weko3_api_auth_findings.md`)も秘密側に置く。台帳は二重管理しない
- [ ] **WEKO3 機能仕様書**:
- 対象機能の仕様追加・変更・クローズ(非公開化)内容を反映した
- [ ] **各種マニュアル (管理者 / 利用者マニュアル)**:
- 画面導線・操作手順・権限仕様の変更を反映した
- [ ] *更新不要な場合(理由)*:

---

## 📋 6. 動作検証エビデンス (Verification Evidence)

### テスト実行結果
```bash
pytest tests/ -k <対象モジュール>
# -> PASS
```

### CI の成果物 (artifact: `api-inventory-drift`)
<!-- 手で貼る必要はない。レビュアが見る場所の案内 -->

| ファイル | 内容 |
|---|---|
| `drift.md` | ベースラインとの差分(**件数のみ**) |
| `reconcile.md` | 台帳と実機の突き合わせ(**件数のみ**) |

明細(該当した経路名・実測結果)は公開できないため artifact に含めていない。
秘密側で同じコマンドを `--summary-only` なしで実行して確認する。

### 手動で確認したこと
<!-- CI が測れない範囲(ワークフロー経由の操作、画面導線、外部連携など)を記載 -->
*
160 changes: 160 additions & 0 deletions .github/workflows/api-inventory-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# WEKO3 リポジトリ(RCOSDP/weko)の .github/workflows/ に配置する。
#
# 【重要】このリポジトリは public。Actions のログ・artifact・PR コメントは誰でも読める。
# したがって:
# - 台帳(所見・実証結果を含む)とベースラインは **このリポジトリに置かない**。
# 秘密のリポジトリから Secret 経由で取得する。
# - 出力は --summary-only で **件数のみ**。URI や endpoint 名は出さない。
# 明細は秘密側に置いたレポートで確認する。
#
# 必要な Secret:
# secrets.API_INVENTORY_REPO 取得元の private リポジトリ (RCOSDP/weko-secret)
# secrets.API_INVENTORY_SSH_KEY weko-secret に登録した read-only deploy key の秘密鍵
# deploy key を使うのは、対象が1リポジトリに構造的に限定され、読み取り専用で、
# 個人アカウントに紐づかないため(PAT より事故時の影響が小さい)。
# 未設定なら、このジョブは何もせずスキップする(fork からの PR でも安全)。
#
# 設置手順: tools/api-inventory/ci/README.md

name: API Inventory Drift

on:
pull_request:
branches: ['**']
workflow_dispatch:

jobs:
drift:
runs-on: ubuntu-latest
timeout-minutes: 60
# fork からの PR には Secret が渡らない。無駄に起動しない。
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # changed_rows.py が base..head の diff を取るため

- name: Check secrets
id: cfg
env:
REPO: ${{ secrets.API_INVENTORY_REPO }}
KEY: ${{ secrets.API_INVENTORY_SSH_KEY }}
run: |
if [ -n "$REPO" ] && [ -n "$KEY" ]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "::notice::API_INVENTORY_REPO / API_INVENTORY_SSH_KEY が未設定のためスキップします"
fi

# 台帳・ベースラインを秘密リポジトリから取得する。
# チェックアウト先は .api-inventory-data/(.gitignore 済み)。
- name: Checkout inventory data (private)
if: steps.cfg.outputs.enabled == 'true'
uses: actions/checkout@v4
with:
repository: ${{ secrets.API_INVENTORY_REPO }}
ssh-key: ${{ secrets.API_INVENTORY_SSH_KEY }}
path: .api-inventory-data
persist-credentials: false

- uses: actions/setup-python@v5
if: steps.cfg.outputs.enabled == 'true'
with:
python-version: '3.11'

- name: Start WEKO containers
if: steps.cfg.outputs.enabled == 'true'
run: |
chmod +x install.sh
./install.sh
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1

- name: Wait for web container
if: steps.cfg.outputs.enabled == 'true'
run: |
for i in $(seq 1 60); do
if docker compose -f docker-compose2.yml exec -T web \
bash -lc 'source ~/.virtualenvs/invenio/bin/activate; invenio --help' >/dev/null 2>&1; then
echo "ready"; exit 0
fi
sleep 10
done
docker compose -f docker-compose2.yml logs web | tail -100
exit 1

- name: Run drift checks
if: steps.cfg.outputs.enabled == 'true'
env:
WEKO_API_INVENTORY_DIR: ${{ github.workspace }}/.api-inventory-data
run: |
set -o pipefail
T=tools/api-inventory/scripts
# 実機 url_map からスナップショットを作る(生成物は公開領域に置かない)
python3 $T/snapshot.py --out /tmp/api_snapshot.new.json --profile default

# 以降はすべて --summary-only。件数だけを標準出力に出す。
python3 $T/diff_snapshot.py \
"$WEKO_API_INVENTORY_DIR/api_snapshot.json" /tmp/api_snapshot.new.json \
--summary-only --gate --out /tmp/drift.md

python3 $T/reconcile.py \
--snapshot /tmp/api_snapshot.new.json \
--summary-only --gate --out /tmp/reconcile.md

- name: Probe changed endpoints
if: always() && steps.cfg.outputs.enabled == 'true'
env:
WEKO_API_INVENTORY_DIR: ${{ github.workspace }}/.api-inventory-data
run: |
T=tools/api-inventory/scripts
# 変更が触れた台帳行を割り出す(no のみを出力。URIは出さない)
python3 $T/changed_rows.py \
"${{ github.event.pull_request.base.sha || github.event.before }}" "${{ github.sha }}" \
--out /tmp/rerun_nos.txt > /dev/null

# install.sh はレコードを作らないので最小コーパスを投入してから測る
python3 $T/fixtures.py --out /tmp/fixtures.json
python3 $T/probe_ci.py \
--fixtures /tmp/fixtures.json --only /tmp/rerun_nos.txt \
--allow-writes --summary-only --gate --out /tmp/probe.json

# artifact は件数のみのサマリに限定する(public なので誰でも取得できる)。
# drift.md / reconcile.md は --summary-only で生成済み。
# probe.json / api_snapshot.new.json は明細を含むため **上げない**。
- name: Upload summary (counts only)
if: always() && steps.cfg.outputs.enabled == 'true'
uses: actions/upload-artifact@v4
with:
name: api-inventory-summary
path: |
/tmp/drift.md
/tmp/reconcile.md

- name: Comment on PR (counts only)
if: always() && steps.cfg.outputs.enabled == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const read = (p, title) => {
try { return `\n\n### ${title}\n\n` + fs.readFileSync(p, 'utf8'); }
catch (e) { return `\n\n### ${title}\n\n(生成されませんでした)`; }
};
let body = '## API インベントリ差分(件数のみ)\n\n'
+ '> 明細は公開できないため件数のみ表示しています。'
+ '該当箇所は秘密側の台帳・レポートで確認してください。';
body += read('/tmp/drift.md', 'ベースラインとの差分');
body += read('/tmp/reconcile.md', '台帳との突き合わせ');
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body.slice(0, 60000),
});

- name: Teardown
if: always() && steps.cfg.outputs.enabled == 'true'
run: docker compose -f docker-compose2.yml down -v
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@ test/dummyfile/data
# inbox
inbox/
ui-tests/test-results/
/.api-inventory-data/
14 changes: 14 additions & 0 deletions tools/api-inventory/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# このリポジトリは public。台帳・スナップショット・実測結果は一切置かない。
# データは $WEKO_API_INVENTORY_DIR(秘密の場所)で管理する。
weko3_api_list*.tsv
weko3_api_list*_README.md
weko3_api_auth_findings.md
api_snapshot*.json
reconcile_allow.json
reconcile_report.md
probe*.json
drift*.md
# fixtures.py が生成する。OAuthアクセストークンと平文パスワードを含む。
fixtures.json
__pycache__/
*.pyc
Loading
Loading