このコミットが含まれているのは:
2026-10-31 13:50:10 +09:00
コミット dba409c3e8
33個のファイルの変更、1490行の追加、236行の削除
+45 -8
ファイルの表示
@@ -35,7 +35,9 @@ Web とワーカーは同一リリース、Rails secret、アプリケーショ
- POST `/counts/export/jobs`:要求検証・実行時点の固定・署名付きジョブトークンの発行。
- GET `/counts/export/jobs/:token`:状態、実際の処理済みレコード数、判明している総数。
- DELETE `/counts/export/jobs/:token`:取消を永続記録。
- GET `/counts/export/files/:token`:生成完了後の署名付きファイルトークンによる取得。
- POST `/counts/export/jobs/:token/download`:セッション所有者の開始確認要求を登録。
- GET `/counts/export/files/:token?job=...&attempt=...`:実 GET の認可・送出開始の確認。
- GET `/counts/export/files/:token`:既存クライアント向けの認可付きダウンロード。
ジョブ・ファイルのトークンは作成元のセッションに紐付く。
既存の BAN・CSRF 制御を使用する。フロントは約800ms間隔で状態を取得する。
@@ -80,16 +82,22 @@ processed は CSV では実際に生成したデータ行数、JSON のクロス
通常の Counts 応答では、使用していない全履歴の population_count は計算しない。
キーは null、population_count_computed は false とし、0に置き換えない。
現在条件の output_count は権限適用前、visible_output_count は権限適用後の
通常の集計行数(明細では明細行数)。小計・合計の物理行は含めない。
summary は非公開履歴を含む同じ母集団を集計する。非公開の軸はサーバーの CASE 式で
単一の予約値へまとめてから GROUP BY し、output_count / visible_output_count はその
安全化した通常行数を表す。excluded_count は返さない。details は従来どおり
output_count が権限適用前、visible_output_count が適用後の明細行数である。
小計・合計の物理行は行数に含めない。
CSV の物理行と JSON の pivot_cell には集計レコードを含むため、両者を混同しない。
除票境界・参照禁止集合は GROUP BY で識別キー単位にまとめる。
投稿の参照判定は、全除票履歴を JSON_CONTAINS / FIND_IN_SET で反復照合せず、
保存済みスナップショットから参照IDを JSON_TABLE で展開して照合する。
権限条件付きの母集団件数は同一 export インスタンス内で再利用する。
多段小計は、認可済み母集団の CTE を MySQL の無制限 LIMIT で実体化させ、
各枝が同じ認可判定をやり直すことを避ける。計数は COUNT(DISTINCT v.id) のまま。
多段小計は、全軸の JOIN へ実際に寄与するレコードを CTE に保持し、各枝で同じ
母集合を使う。changed_field には直前版がある実変更だけが寄与する。タグが行軸に
ない場合は DISTINCT 投稿版を数える。タグが行軸にある場合は投稿版×タグを数え、
全行軸の葉単位から小計・合計を求める。変更項目も行軸ならその寄与も保持する。
private のタグ・変更項目をまとめても、内部の寄与は捨てず、ラベルだけを保護する。
スキーマ上、以下の追加インデックスが候補となる。migration は作成・実行していない。
@@ -122,6 +130,35 @@ systemd の EnvironmentFile と Web の環境を照合し、directory の owner
アクセス制限、容量、期限切れ削除、worker heartbeat と restart を公開前に確認する。
この作業では systemctl・worker 起動・本番環境への適用を行っていない。
リリース保留:非公開グループと複数値・変更項目軸の交差集計、生成ファイルへの秘匿値の
非混入、実 GET の middleware/proxy 経由の開始通知は、回帰テストと実環境の確認が必要。
テスト・型チェック・build は未実行。
これらのコード対策と回帰テストを追加しているが、テスト・型チェック・build は未実行。
middleware/proxy 経由の転送は別途実環境で確認する。
## 開始確認の競合と権限
開始受付は pending、実 GET の単独取得は streaming、最初の Rack yield 後は started。
同じ job の重複受付は既存 attempt を返す。streaming への遷移はプロセス間ロックで
単独取得し、同一 attempt の二重 GET を拒否する。開始前の attempt は45秒で失効する。
Frontend は30秒の独立タイマーで確認を打ち切る。一時的なポーリング通信断は同じ
attempt で再取得するが、別の GET は始めない。取消失敗後の再試行は利用者の再操作で行う。
ファイル・ジョブは owner、署名、期限、現在の権限 revision 及び artifact_policy を
検証する。最初の送出前にも現ユーザー・BAN・ファイル権限を再確認する。旧ポリシーの
生成物は再利用しない。送出中は各チャンクで取消状態を確認し、例外時はログと failed
状態を記録して body を閉じる。コミット後に head で別レスポンスを書かない。
ResponseBody に to_path を提供せず、sendfile middleware による ACK の迂回を避ける。
Last-Modified と X-Accel-Buffering: no を設定するが、プロキシの動作まで保証しない。
サーバーの最初の Rack yield は、クライアントの受信開始の厳密な証明ではない。
非管理者の summary では、公開集計属性は操作日時・異動区分。その他の属性に対する
抽出条件・補助タグ検索は拒否する。measures / column_order はマスク後の式だけと
比較するため、推測した private ID は一致しない。予約値そのものの条件指定は拒否する。
private ID の正誤に応じて許可可否を変えないため、条件の一致探索を避けられる。
非公開列を含む列順変更は API の column_values_editable でも不可と通知する。
公開列の値を使うソートは引き続き利用できる。
明細には元の認可を適用し、抽出条件自体や取得済みの全言語値を表示設定で改変しない。
外部タグ履歴の linked_tags は名前しか記録していないため、非公開タグの記録名を含む
スナップショットを保守的に非公開とする。名前全体を空白区切りの境界で照合する。
区切りが曖昧な旧データでは過剰な秘匿の可能性はあるが、参照 ID がない旧形式から
名前の公開可否を楽観的に推定しない。
+148
ファイルの表示
@@ -0,0 +1,148 @@
# Counts リリース前確認
この作業では Git、テスト、lint、typecheck、build、migration、サーバー・worker・
systemd の起動、本番適用を行っていない。コードの静的確認とテストコードの編集のみ。
以下の実行と結果確認は利用者が行う。未実行の状態は GO としない。
## 対応箇所
- 全件集計と安全な軸:counts_axes.rb / counts_table_query.rb / counts_pivot_totals.rb。
非公開値は GROUP BY 前に統合。通常セル・ソート用集計・小計は同じ寄与母集合を使う。
- 出力:counts_table_export.rb / countsDisplay.ts / CountsSummaryResults.tsx。
CSV とコピーは伏字。JSON の非公開座標は予約値のみ。非公開セルはリンクを作らない。
- 配布:counts_exports_controller.rb / counts_export_jobs.rb / counts_export_download.rb。
owner/job/attempt、単独 GET、期限・現在権限・旧 artifact policy の拒否を確認する。
- 表示:summary 行番号を廃止し「行数」を表示。details の行番号・件数は維持。
非公開列座標のソート・並び替え可否はサーバーの能力フラグへ従う。
- テスト基盤:requestAnimationFrame の同期・時刻0固定を修正。JSDOM の Canvas は
計測非対応を返すテスト専用モックを置く。実ブラウザーの計測動作は変更していない。
## 利用者によるテスト
backend ディレクトリで提案する対象(未実行):
```sh
bundle exec rspec spec/requests/counts_table_spec.rb spec/requests/counts_download_spec.rb spec/services/counts_pivot_security_spec.rb spec/services/counts_export_spec.rb spec/models/history_access_spec.rb spec/requests/post_locale_filter_identity_spec.rb spec/requests/tag_description_wiki_spec.rb
```
frontend ディレクトリで提案する対象(未実行):
```sh
npm run test:run -- src/lib/counts.test.ts src/lib/api.test.ts src/pages/CountsResourcePage.test.tsx src/pages/help/HelpChangesPage.test.tsx src/components/counts/CountsControls.test.tsx src/components/counts/CountsSummaryResults.test.tsx src/components/counts/CountsExportProgress.test.tsx
```
SSR は frontend/server.ssr.test.mjs が dist/server/entry-server.js を読み込む。
ソースの PageHead はサイト名付きの共通タイトルを使用している。古い成果物のまま
SSR テストを実行しても現行ソースの検証にならない。利用者が最新成果物を用意してから
`npm run test:ssr` を実行する。Codex はビルド・SSR テストとも実行していない。
主要テストの後に既存の全テスト群でも回帰の有無を確認する。
## ブラウザーと本番切替前
- anonymous / guest / member / admin で同じ期間・公開条件を指定し、通常セル・総計の
数値を比較する。安全なグループ数は権限で異なり得る。details は秘匿版を出さない。
- 投稿 A,B / A / B,C のタグ行軸は5。タグを行軸から外した合計は distinct 投稿数。
changed_field、NULL、複数段小計、ページ境界、CSV / JSON でも値を比較する。
- 伏字セル、API 辞書、DOM、コピー、CSV / JSON に非公開の値・ID がないこと。
非公開セルのドリルダウンはなく、private 条件の summary は安全な403となること。
- 8軸・100列の専用エラー、SQL timeout、worker 不在、認証・権限、取消失敗を確認する。
- CSV / JSON とも ready / 100% だけでは閉じず、実 GET の matching attempt が
started となってから閉じること。通信遮断・取消・再描画・タイムアウトも確認する。
サーバー送出開始はブラウザーの受信完了・保存完了を保証しない。
- PC・スマホで独立/共通スクロール、D&D、列幅、セル選択・文字列選択・キャレット、
内表 JSON の折返し、ページタイトル、Counts の noindex・先読み禁止を確認する。
locale 切替後も対象 ID と集計値は同一で、全言語の登録値を保持すること。
- Web と worker の同一リリース・secret・DB・COUNTS_EXPORT_DIRECTORY、所有者、
flock / atomic rename、容量、期限切れ掃除、heartbeat、ログ、再起動を照合する。
- systemd unit の User / WorkingDirectory / EnvironmentFile / PATH を実環境と照合する。
設定適用・サービス起動・本番切替はこの作業では行っていない。
- ロール変更・logout・新しい discard 後に、生成済みファイルが再取得できないこと。
- ロールバック時も公開ダウンロードを止め、旧生成物を再配布しない。古いコードへ戻すと
artifact_policy やマスクの保護を失う可能性があるため、無条件に巻き戻さない。
P0 の回帰テストと利用者の動作確認が済むまで判定は NO-GO。
## 静的な対応状況
| 対象 | 状態 | 根拠 |
| --- | --- | --- |
| 非公開軸・参照・全件母集団 | 対応済み(静的確認のみ) | HistoryAccess / CountsAxes / CountsTableQuery と回帰コード |
| タグ・変更項目・小計 | 対応済み(静的確認のみ) | CountsPivotTotals と全セル・ソートの母集合の統一 |
| API / CSV / JSON / コピー / URL | 対応済み(静的確認のみ) | マスク後の値のみを表示・比較し、予約座標の条件化を拒否 |
| GET ACK・競合・取消・期限 | 対応済み(静的確認のみ) | サーバー状態遷移、単独 GET、独立タイマー、回帰コード |
| P1・既知テストのソース上の問題 | 対応済み(静的確認のみ) | 行番号、行数、エラー、RAF、fixture、title 契約 |
| テスト成功・実ブラウザー・本番稼働 | 判定不能 | 実行禁止のため未検証 |
## 今回の編集ファイル
すべて Counts 本体に必要な変更。共有箇所の例外は HistoryAccess の参照秘匿と、
api.ts の Abort/CSRF 待機・native download の機構であり、全域の安全性にも必要。
SQL の速度改善・索引追加は別 Issue とし、今回 migration は追加していない。
### Backend App
- backend/app/models/history_access.rb:外部タグの記録名参照と、投稿の記録済み参照版にも
同じ可視性判定を適用。旧 artifact policy の生成物を拒否。
- backend/app/services/counts_population.rb:軸条件の型・予約値の検証。
- backend/app/services/counts_axes.rb:マスク前の内部寄与を計数し、直前版が非公開の場合も
changed_field の名前を秘匿。
- backend/app/services/counts_table_query.rb:通常セル・ソートの寄与母集合、整数順、
条件表示名、公開列座標だけの操作、summary メタデータを整合。
- backend/app/services/counts_pivot_totals.rb:全セルに寄与する母集合と、タグ行軸の加算。
- backend/app/services/counts_table_export.rb:CSV の予約値を伏字化。
- backend/app/services/counts_export_jobs.rb:開始確認の排他・状態遷移・失効と安全なエラーコード。
- backend/app/services/counts_export_download.rb:64KiB の Rack body、開始確認、取消・close。
- backend/app/controllers/counts_exports_controller.rb:GET 直前・送出直前の再認可と専用エラー。
### Frontend App
- frontend/src/lib/api.ts:CSRF 待機中も中断可能にし、iframe の寿命・失敗通知を整理。
- frontend/src/lib/counts.ts:マスク予約値と列操作の API 型。
- frontend/src/lib/countsDisplay.ts:マスク表示と既知エラーの i18n 解決。
- frontend/src/components/counts/CountsExportProgress.tsx:一つのジョブを維持し、matching ACK、
独立期限、取消・再描画・通信断を処理。保存完了の成功表示はしない。
- frontend/src/components/counts/CountsSummaryResults.tsx:行番号コードの撤去、私的セルの
ドリルダウン抑止、公開列のソート維持。
- frontend/src/components/counts/CountsSortHeader.tsx:禁止座標のソート抑止と伏字の理由表示。
- frontend/src/pages/CountsResourcePage.tsx:専用エラーと、初回失敗後にも再実行できる状態。
- frontend/src/locales/ja/counts.json:承認されたエラー文言。
- frontend/src/locales/ain-Kana/counts.json:同じキーと未翻訳・日本語 fallback の管理。
### テスト・テスト専用基盤
- backend/spec/services/counts_pivot_security_spec.rb(新規):4権限、タグ寄与5、distinct、
changed_field、private group、NULL、整数順、ページ、座標探索、JSON/CSV。
- backend/spec/requests/counts_download_spec.rb(新規):CSV/JSON の実 GET、HEAD、二重取得、
別セッション、失効、取消、ファイル不在、新しい discard。
- backend/spec/services/counts_export_spec.rb:owner/job/attempt の ACK と Rack body の失敗。
- backend/spec/models/history_access_spec.rb:旧 policy、外部タグ参照、記録済みタグ参照版。
- backend/spec/requests/counts_table_spec.rb:専用軸数エラー。
- frontend/src/components/counts/CountsExportProgress.test.tsx(新規):matching ACK、StrictMode、
再描画、開始受付失敗、通信断、独立期限、取消の競合・再試行、unmount、iframe 失敗。
- frontend/src/components/counts/CountsSummaryResults.test.tsx(新規):伏字・理由・コピー・旧行番号。
- frontend/src/components/counts/CountsControls.test.tsx:summary 行番号廃止と表示件数選択。
- frontend/src/pages/CountsResourcePage.test.tsx:初期エラーからの再実行、サイト名付き title。
- frontend/src/lib/api.test.ts:共有 CSRF 待機中の中断、iframe の解放・重複失敗通知。
- frontend/src/lib/counts.test.ts:理由別エラーと任意のサーバー文字列を表示しないこと。
- frontend/src/test/setup.ts:時刻が進む非同期 RAF と Canvas のテスト専用 fallback。
- frontend/server.ssr.test.mjs:共通 title の既存 assertion を維持し canonical と秘匿を補足。
### 文書
- docs/counts-exports.md:実装と一致する計数・ACK・権限・運用上の限界。
- docs/counts-release-checklist.md:この一覧と利用者の検証・公開判定手順。
## 既知失敗の静的な確認結果
- counts_table_spec.rb:TagVersion.names の auto_generated、script_code、aliases があることを
確認。新しい投稿タグ fixture にも schema 必須の sections を設定。
- post_locale_filter_identity_spec.rb:検索文字列で基準集合を取得した後、tag_filters の
応答から references を定義して構造化 URL を作る修正を維持。
- tag_description_wiki_spec.rb:first / last とも対象 ID に限定した delete_all を維持。
- CountsResourcePage.test.tsx / HelpChangesPage.test.tsx:共有 RAF の同期・時刻0固定を修正。
Help の辞書応答は実 getHelpChanges が language を付ける契約も照合済み。
- SSR title:現行ソースはサイト名を含む。dist を使うテストの assertion は削除していない。
上記はソース照合の結果であり、過去の失敗件数が0になったという実行結果ではない。
既知の未修正 P0 を残して GO とする判断はしていない。現時点の阻害事項は、ユーザーに
よる MySQL・ブラウザー・worker/proxy を含む実検証がまだ行われていないことである。