From a452c4ca37dd4a2336726eb7008b89d884ddd9fb Mon Sep 17 00:00:00 2001 From: Masaharu Hayashi Date: Fri, 28 Aug 2026 04:19:14 +0000 Subject: [PATCH] =?UTF-8?q?ci(api-inventory):=20=E5=8F=B0=E5=B8=B3?= =?UTF-8?q?=E3=82=92=E3=83=96=E3=83=A9=E3=83=B3=E3=83=81=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E3=81=A7=E5=8F=96=E5=BE=97=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 台帳とベースラインはプライベートリポジトリ(RCOSDP/weko-secret)から取るが、 checkout に ref を渡していなかったため、常に既定ブランチ(main)を見ていた。 develop_v2.0.4 への PR でも main の台帳と突き合わせるので、ブランチ間の 経路差がそのまま reconcile の A/B/C/D/E として出続ける。件数が常に非ゼロに なれば誰も読まなくなる。 Resolve inventory ref ステップを足し、ls-remote で存在を確かめてから head → base → 既定 の順で同名ブランチを選ぶ。head を先に見るのは、 公開リポジトリのコード変更と台帳更新が別 PR になるため。base 同士だけで 対応させると、weko の CI が回る時点で台帳更新はまだ作業ブランチにあり、 「プライベート側を先にマージしないと公開側が通らない」直列の制約ができる。 採用したブランチ名は PR コメントの冒頭に出す。どの台帳と比べた件数かが 分からなければ、レビュアは数字を判断できない。対応ブランチが無い場合は 既定ブランチと比較したうえで警告を出す。FAIL にしないのは、対応ブランチの 無いリリースラインで全 PR が止まるのを避けるため。 ci/README.md の「PR ブランチのベースラインを比べる」は実装と食い違って いたので、対応の決め方(§3a)を書いて訂正した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CQXqdhUzMHyxhPJbHKw6Zr --- .github/pull_request_template.md | 14 +++++ .github/workflows/api-inventory-drift.yml | 62 ++++++++++++++++++- tools/api-inventory/ci/README.md | 57 ++++++++++++++++- .../api-inventory/ci/api-inventory-drift.yml | 62 ++++++++++++++++++- 4 files changed, 189 insertions(+), 6 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6e76051a43..86dd734892 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -28,12 +28,23 @@ PR ごとに WEKO3 コンテナを起動し、`url_map` のダンプ・台帳と 実データは**プライベートリポジトリ `RCOSDP/weko-secret`** にあり、CI は Secret 経由で取得する。 以降この文書では、そこを単に**プライベートリポジトリ**と呼ぶ。 +台帳はブランチごとに内容が違うため、CI は **weko 側と同名のブランチ**を +プライベートリポジトリから探して使う(head → base → 既定ブランチ の順)。 +採用されたブランチ名は PR コメントの冒頭に出るので、**件数を読む前にそこを見ること**。 +対応ブランチが無い場合は既定ブランチと比較され、コメント冒頭に警告が出る。 +その件数は当てにならないので、PASS でも「確認済み」と読まないこと。 +詳細: `tools/api-inventory/ci/README.md` §3a + - [ ] **CI が PASS している**、または FAIL の各項目に対処済み Secret (`API_INVENTORY_REPO` / `API_INVENTORY_SSH_KEY`) が未設定のリポジトリ、 および fork からの PR では、このジョブは何もせずスキップされる。 ### API を追加・変更した場合(必須) +- [ ] **プライベートリポジトリ側の作業ブランチを、この PR のブランチと同名で切った** + この PR が `fix/issue62569` → `develop_v2.0.4` なら、プライベート側も + `fix/issue62569` → `develop_v2.0.4`。同名にしておけば台帳 PR が未マージでも + CI がそれを見るので、2つの PR のマージ順を気にしなくてよい。 - [ ] **プライベートリポジトリ**の `api_snapshot.json` を更新し、対応する PR を出した ```bash export WEKO_API_INVENTORY_DIR=/path/to/weko-secret @@ -48,6 +59,9 @@ PR ごとに WEKO3 コンテナを起動し、`url_map` のダンプ・台帳と ### FAIL したときの対処(要約) +まず PR コメント冒頭の**台帳ブランチ**を見る。警告が出ていれば、件数を追う前に +プライベートリポジトリ側の対応ブランチを用意すること(比較相手が違うので件数に意味がない)。 + ジョブが落ちる条件は 3 つある。PR コメントのどのセクションに件数が出ているかで切り分ける。 | 落ちた場所 | 落ちる条件 | diff --git a/.github/workflows/api-inventory-drift.yml b/.github/workflows/api-inventory-drift.yml index 06fb3ab097..f21290378a 100644 --- a/.github/workflows/api-inventory-drift.yml +++ b/.github/workflows/api-inventory-drift.yml @@ -4,6 +4,9 @@ # したがって: # - 台帳(所見・実証結果を含む)とベースラインは **このリポジトリに置かない**。 # プライベートリポジトリ(RCOSDP/weko-secret)から Secret 経由で取得する。 +# - 台帳は WEKO3 のブランチごとに内容が違う。既定ブランチを固定で見ると、ブランチ間の +# 経路差がそのまま差分として出続けて形骸化する。PR の head → base → 既定 の順で +# プライベートリポジトリの**同名ブランチ**を探して使う(Resolve inventory ref)。 # - 出力は --summary-only で **件数のみ**。URI や endpoint 名は出さない。 # 明細はプライベートリポジトリ側に置いたレポートで確認する。 # @@ -47,13 +50,53 @@ jobs: echo "::notice::API_INVENTORY_REPO / API_INVENTORY_SSH_KEY が未設定のためスキップします" fi + # 使う台帳ブランチを決める。存在しない ref を checkout に渡すと落ちるので、 + # 先に ls-remote で候補を絞る。 + # 1. PR の head ブランチ名 台帳更新 PR が**未マージでも**拾える(順序制約を作らない) + # 2. PR の base ブランチ名 台帳を触らない PR、および両方マージ後の定常状態 + # 3. 既定ブランチ 警告を出して続行(件数は当てにならない) + - name: Resolve inventory ref + id: ref + if: steps.cfg.outputs.enabled == 'true' + env: + REPO: ${{ secrets.API_INVENTORY_REPO }} + KEY: ${{ secrets.API_INVENTORY_SSH_KEY }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} + BASE_REF: ${{ github.event.pull_request.base.ref || github.ref_name }} + run: | + set -eu + d=$(mktemp -d); trap 'rm -rf "$d"' EXIT + printf '%s\n' "$KEY" > "$d/key"; chmod 600 "$d/key" + export GIT_SSH_COMMAND="ssh -i $d/key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" + # 出るのはブランチ名だけ。鍵も台帳の中身も出ない。 + # 認証に失敗したらここで落とす(黙って既定ブランチに落ちるのを防ぐ)。 + heads=$(git ls-remote --heads "git@github.com:$REPO.git") + have() { printf '%s\n' "$heads" | awk -v r="refs/heads/$1" '$2==r{f=1} END{exit !f}'; } + pick='' + if [ -n "${HEAD_REF:-}" ] && have "$HEAD_REF"; then + pick="$HEAD_REF" + elif [ -n "${BASE_REF:-}" ] && have "$BASE_REF"; then + pick="$BASE_REF" + fi + if [ -n "$pick" ]; then + echo "ref=$pick" >> "$GITHUB_OUTPUT" + echo "fallback=false" >> "$GITHUB_OUTPUT" + echo "::notice::台帳ブランチ: $pick" + else + echo "ref=" >> "$GITHUB_OUTPUT" + echo "fallback=true" >> "$GITHUB_OUTPUT" + echo "::warning::プライベートリポジトリに ${HEAD_REF:-} / ${BASE_REF:-} のいずれもありません。既定ブランチの台帳と比較します。ブランチ間の経路差がそのまま差分に出るため件数は当てになりません。同名ブランチを作ってください。" + fi + # 台帳・ベースラインをプライベートリポジトリから取得する。 # チェックアウト先は .api-inventory-data/(.gitignore 済み)。 + # ref が空文字なら actions/checkout は既定ブランチを取る(上記フォールバック)。 - name: Checkout inventory data (private) if: steps.cfg.outputs.enabled == 'true' uses: actions/checkout@v4 with: repository: ${{ secrets.API_INVENTORY_REPO }} + ref: ${{ steps.ref.outputs.ref }} ssh-key: ${{ secrets.API_INVENTORY_SSH_KEY }} path: .api-inventory-data persist-credentials: false @@ -136,6 +179,9 @@ jobs: - name: Comment on PR (counts only) if: always() && steps.cfg.outputs.enabled == 'true' && github.event_name == 'pull_request' uses: actions/github-script@v7 + env: + INVENTORY_REF: ${{ steps.ref.outputs.ref }} + INVENTORY_FALLBACK: ${{ steps.ref.outputs.fallback }} with: script: | const fs = require('fs'); @@ -143,8 +189,20 @@ jobs: try { return `\n\n### ${title}\n\n` + fs.readFileSync(p, 'utf8'); } catch (e) { return `\n\n### ${title}\n\n(生成されませんでした)`; } }; - let body = '## API インベントリ差分(件数のみ)\n\n' - + '> 明細は公開できないため件数のみ表示しています。' + // どの台帳と比べた件数なのかが分からないと、レビュアは数字を判断できない。 + const ref = process.env.INVENTORY_REF; + const fallback = process.env.INVENTORY_FALLBACK; + let body = '## API インベントリ差分(件数のみ)\n\n'; + if (fallback === 'true') { + body += '> ⚠️ **プライベートリポジトリに対応ブランチが無いため、既定ブランチの台帳と比較しています。**\n' + + '> ブランチ間の経路差がそのまま差分として出るので、以下の件数は当てになりません。\n' + + '> この PR のブランチと同名のブランチをプライベートリポジトリに作ってください。\n\n'; + } else if (ref) { + body += `> 台帳ブランチ: \`${ref}\`\n\n`; + } else { + body += '> ⚠️ 台帳ブランチを解決できませんでした(Resolve inventory ref が失敗しています)。\n\n'; + } + body += '> 明細は公開できないため件数のみ表示しています。' + '該当箇所はプライベートリポジトリ側の台帳・レポートで確認してください。'; body += read('/tmp/drift.md', 'ベースラインとの差分'); body += read('/tmp/reconcile.md', '台帳との突き合わせ'); diff --git a/tools/api-inventory/ci/README.md b/tools/api-inventory/ci/README.md index 04ee35b865..c6c512d602 100644 --- a/tools/api-inventory/ci/README.md +++ b/tools/api-inventory/ci/README.md @@ -115,6 +115,8 @@ git add -A && git commit -m "chore(ci): API インベントリ差分検知を追 **公開リポジトリにはデータを一切 commit しないこと。** `git status` で `tools/api-inventory/` 配下に `*.tsv` や `api_snapshot.json` が現れたら、置き場所を間違えている。 +また、プライベートリポジトリ側にも **weko と同名のブランチ**を作っておくこと(§3a)。 + 3 のベースラインは**そのブランチのリビジョンで取り直す**こと。 weko-document に置いてあるものは `d2fdc0e3b`(v2.0.3) 時点なので、 導入先のブランチが進んでいれば差分が出る。 @@ -133,18 +135,67 @@ weko-document に置いてあるものは `d2fdc0e3b`(v2.0.3) 時点なので、 python3 tools/api-inventory/scripts/snapshot.py \ --out "$WEKO_API_INVENTORY_DIR/api_snapshot.json" # → プライベートリポジトリ側で commit / PR を作る +# ブランチ名は weko 側の作業ブランチと**同名**にする(§3a) ``` 差分は**プライベートリポジトリの `git diff`** に出る。「どの経路が増えたか・認証がどう変わったか」を レビュアの目に入れる仕組みは維持されるが、見る場所がプライベートリポジトリ側になる。 公開リポジトリの CI は件数だけを報告し、詳細は出さない。 -なお PR の CI は「PR ブランチの実機」と「PR ブランチのベースライン」を比べるため、 -ベースラインを更新すると差分は 0 になる。**変更の妥当性を見るのは CI ではなくレビュア**で、 +なお PR の CI は「PR ブランチの実機」と「**対応するブランチの**ベースライン」を比べるため +(対応の決め方は §3a)、ベースラインを更新すると差分は 0 になる。 +**変更の妥当性を見るのは CI ではなくレビュア**で、 CI の役割は「ベースラインを更新せずに API を変えること」を防ぐことにある。 --- +## 3a. ブランチの対応(weko ⇔ プライベートリポジトリ) + +台帳とベースラインは **WEKO3 のブランチごとに内容が違う**。`develop_v2.0.4` のコードを +`main` の台帳と突き合わせれば、ブランチ間の経路差がそのまま A/B/C/D/E として出る。 +件数が常に非ゼロになれば、そこから先は誰も読まなくなる。 + +**規則: プライベートリポジトリには、weko 側と同名のブランチを作る。** +`develop_v2.0.4` には `develop_v2.0.4`、`fix/issue62569` には `fix/issue62569`。 + +CI の `Resolve inventory ref` ステップが、次の順で使うブランチを決める。 + +| 順 | 見るブランチ | 使う場面 | +|---|---|---| +| 1 | PR の **head** ブランチ名 | 台帳更新 PR がまだマージされていない段階。作業ブランチ上の台帳を見る | +| 2 | PR の **base** ブランチ名 | 台帳を触らない PR。および両方がマージされた後の定常状態 | +| 3 | 既定ブランチ | 1・2 のどちらも無い場合。`::warning::` と PR コメント冒頭の警告を出して続行する | + +採用したブランチ名は **PR コメントの冒頭に出る**。件数を読む前にそこを見ること。 + +### なぜ head を先に見るのか + +公開リポジトリのコード変更と台帳更新は別 PR になる(§3)。base 同士だけで対応させると、 +weko の PR の CI が回る時点で台帳更新はまだプライベートリポジトリの作業ブランチにあり、 +`develop_v2.0.4` には入っていない。「プライベート側を先にマージしないと公開側の CI が +通らない」という直列の制約ができてしまう。head を先に見れば、2つの PR を並行して +レビューでき、マージ順にも依存しない。 + +``` +RCOSDP/weko fix/issue62569 ──PR──> develop_v2.0.4 + │ 同名で対応する +RCOSDP/weko-secret fix/issue62569 ──PR──> develop_v2.0.4 +``` + +作業ブランチは**両方のリポジトリで同じ名前**にする。台帳を触らない変更なら +プライベート側にブランチを作らなくてよい(2 の base 解決に落ちる)。 + +### 対応ブランチが無いとき + +ジョブは止まらないが、出る件数は当てにならない。新しいリリースラインを切ったら、 +プライベートリポジトリ側にも同名ブランチを作り、そのリビジョンでベースラインを +取り直すこと(§2 の 3、§3b)。 + +FAIL にせず警告に留めているのは、対応ブランチの無いリリースラインで一律にすべての PR が +止まるのを避けるため。**警告が出ている PR の件数を「PASS だった」と読まないこと。** + +--- + ## 3b. ベースラインは CI と同じ環境で作る `api_snapshot.json` は `meta.packages` にインストール済みパッケージの版を持ち、 @@ -184,10 +235,12 @@ git push origin v2.0.3 ``` タグ名は **WEKO3 側のタグと同じ**にする(`v2.0.3` なら `v2.0.3`)。 +ブランチを同名で対応させる(§3a)のと同じ理由で、タグも同名で対応させる。 メッセージには対象コミットの完全な SHA と、その時点の台帳規模・突き合わせ結果を残す。 ### バージョンアップ時の流れ +0. プライベートリポジトリに **WEKO3 と同名のブランチ**を作る(§3a) 1. WEKO3 の新バージョンで `install.sh` → `snapshot.py` でベースラインを作り直す 2. `reconcile.py` の差分を 0 にする(新規経路を台帳に追加、消えた経路を整理) 3. `changed_rows.py` が出す行を Phase 2-3 で再確認する diff --git a/tools/api-inventory/ci/api-inventory-drift.yml b/tools/api-inventory/ci/api-inventory-drift.yml index 06fb3ab097..f21290378a 100644 --- a/tools/api-inventory/ci/api-inventory-drift.yml +++ b/tools/api-inventory/ci/api-inventory-drift.yml @@ -4,6 +4,9 @@ # したがって: # - 台帳(所見・実証結果を含む)とベースラインは **このリポジトリに置かない**。 # プライベートリポジトリ(RCOSDP/weko-secret)から Secret 経由で取得する。 +# - 台帳は WEKO3 のブランチごとに内容が違う。既定ブランチを固定で見ると、ブランチ間の +# 経路差がそのまま差分として出続けて形骸化する。PR の head → base → 既定 の順で +# プライベートリポジトリの**同名ブランチ**を探して使う(Resolve inventory ref)。 # - 出力は --summary-only で **件数のみ**。URI や endpoint 名は出さない。 # 明細はプライベートリポジトリ側に置いたレポートで確認する。 # @@ -47,13 +50,53 @@ jobs: echo "::notice::API_INVENTORY_REPO / API_INVENTORY_SSH_KEY が未設定のためスキップします" fi + # 使う台帳ブランチを決める。存在しない ref を checkout に渡すと落ちるので、 + # 先に ls-remote で候補を絞る。 + # 1. PR の head ブランチ名 台帳更新 PR が**未マージでも**拾える(順序制約を作らない) + # 2. PR の base ブランチ名 台帳を触らない PR、および両方マージ後の定常状態 + # 3. 既定ブランチ 警告を出して続行(件数は当てにならない) + - name: Resolve inventory ref + id: ref + if: steps.cfg.outputs.enabled == 'true' + env: + REPO: ${{ secrets.API_INVENTORY_REPO }} + KEY: ${{ secrets.API_INVENTORY_SSH_KEY }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} + BASE_REF: ${{ github.event.pull_request.base.ref || github.ref_name }} + run: | + set -eu + d=$(mktemp -d); trap 'rm -rf "$d"' EXIT + printf '%s\n' "$KEY" > "$d/key"; chmod 600 "$d/key" + export GIT_SSH_COMMAND="ssh -i $d/key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" + # 出るのはブランチ名だけ。鍵も台帳の中身も出ない。 + # 認証に失敗したらここで落とす(黙って既定ブランチに落ちるのを防ぐ)。 + heads=$(git ls-remote --heads "git@github.com:$REPO.git") + have() { printf '%s\n' "$heads" | awk -v r="refs/heads/$1" '$2==r{f=1} END{exit !f}'; } + pick='' + if [ -n "${HEAD_REF:-}" ] && have "$HEAD_REF"; then + pick="$HEAD_REF" + elif [ -n "${BASE_REF:-}" ] && have "$BASE_REF"; then + pick="$BASE_REF" + fi + if [ -n "$pick" ]; then + echo "ref=$pick" >> "$GITHUB_OUTPUT" + echo "fallback=false" >> "$GITHUB_OUTPUT" + echo "::notice::台帳ブランチ: $pick" + else + echo "ref=" >> "$GITHUB_OUTPUT" + echo "fallback=true" >> "$GITHUB_OUTPUT" + echo "::warning::プライベートリポジトリに ${HEAD_REF:-} / ${BASE_REF:-} のいずれもありません。既定ブランチの台帳と比較します。ブランチ間の経路差がそのまま差分に出るため件数は当てになりません。同名ブランチを作ってください。" + fi + # 台帳・ベースラインをプライベートリポジトリから取得する。 # チェックアウト先は .api-inventory-data/(.gitignore 済み)。 + # ref が空文字なら actions/checkout は既定ブランチを取る(上記フォールバック)。 - name: Checkout inventory data (private) if: steps.cfg.outputs.enabled == 'true' uses: actions/checkout@v4 with: repository: ${{ secrets.API_INVENTORY_REPO }} + ref: ${{ steps.ref.outputs.ref }} ssh-key: ${{ secrets.API_INVENTORY_SSH_KEY }} path: .api-inventory-data persist-credentials: false @@ -136,6 +179,9 @@ jobs: - name: Comment on PR (counts only) if: always() && steps.cfg.outputs.enabled == 'true' && github.event_name == 'pull_request' uses: actions/github-script@v7 + env: + INVENTORY_REF: ${{ steps.ref.outputs.ref }} + INVENTORY_FALLBACK: ${{ steps.ref.outputs.fallback }} with: script: | const fs = require('fs'); @@ -143,8 +189,20 @@ jobs: try { return `\n\n### ${title}\n\n` + fs.readFileSync(p, 'utf8'); } catch (e) { return `\n\n### ${title}\n\n(生成されませんでした)`; } }; - let body = '## API インベントリ差分(件数のみ)\n\n' - + '> 明細は公開できないため件数のみ表示しています。' + // どの台帳と比べた件数なのかが分からないと、レビュアは数字を判断できない。 + const ref = process.env.INVENTORY_REF; + const fallback = process.env.INVENTORY_FALLBACK; + let body = '## API インベントリ差分(件数のみ)\n\n'; + if (fallback === 'true') { + body += '> ⚠️ **プライベートリポジトリに対応ブランチが無いため、既定ブランチの台帳と比較しています。**\n' + + '> ブランチ間の経路差がそのまま差分として出るので、以下の件数は当てになりません。\n' + + '> この PR のブランチと同名のブランチをプライベートリポジトリに作ってください。\n\n'; + } else if (ref) { + body += `> 台帳ブランチ: \`${ref}\`\n\n`; + } else { + body += '> ⚠️ 台帳ブランチを解決できませんでした(Resolve inventory ref が失敗しています)。\n\n'; + } + body += '> 明細は公開できないため件数のみ表示しています。' + '該当箇所はプライベートリポジトリ側の台帳・レポートで確認してください。'; body += read('/tmp/drift.md', 'ベースラインとの差分'); body += read('/tmp/reconcile.md', '台帳との突き合わせ');