Newer
Older
client_dwatch / docs / handoff / PR説明_develop_to_main_骨子.md

PR説明の骨子 — develop → main

★★これは「骨子」です。確定版ではありません。★★

  • 本書の数字は ★すべて develop の 992a456(2026-09-16)時点での実測★ です。 ★develop は並行作業で進むため、PRを出す直前にもう一度実測し直してください★(→ 付録のコマンド)。
  • ★★§6(マージ方式)は「ユーザー判断待ち」です。★★ 本書では両案の帰結を実データで併記するだけにしてあり、 ★どちらかに決めていません★。決めた結果は docs/decisions.md に決定として記録してください。
  • ★★PRを出す前に develop を push する必要があります。★★ 実測で ★origin/develop は develop より 71コミット遅れています★(git rev-list --count origin/develop..develop → ★71★)。 ★GitHub の PR は origin/develop を見るので、push しないと本書の数字とPRの中身が一致しません★。

更新日: 2026-09-16(初版 2026-09-11)/ 対象: develop(★992a456★)→ main(★1f214cc★, PR #5 マージ, ★2026-08-27★) ※ 実測: git log -1 main --format='%H %ad %s' --date=iso → 1f214cc… 2026-08-27 00:44:18 +0900 Merge pull request #5 …


0. このPRの位置づけ

  • ★main は 2026-08-27 の PR #5 で止まっており、以降 ★約3週間★ の作業がすべて develop に載っています。★ 範囲内の最初のコミットは ★2026-08-30★、最後は ★2026-09-16★(実測 git log --date=short main..develop)。
  • main は develop の祖先です(git merge-base --is-ancestor main develop が真)。 ★競合はありません。★ 範囲内にマージコミットは ★0本★ で、履歴は一直線です。
  • 過去のPR #1〜#5 は「1マイルストーン=1PR」でしたが、★本PRは #1〜#5 の全体量を大きく超える1本★になります。 そのため本説明では「何が入るか」より★「どこを見ればレビューが成立するか」に重心を置きます★(§2)。

1. 何が入るのか(機能単位)

コミット順ではなく成果物で整理しています。時系列は docs/handoff.md の追記1〜52、 判断の根拠は docs/decisions.md の ★D-050〜D-141★ を参照してください。 ★入口としては docs/handoff/セッション引き継ぎ_20260916.md(347行)が最短です。★

1-1. 実DB接続 ★このPRの主題★

PR #5 時点ではアプリは lib/fixtures/ のサンプルデータだけで動いていました。本PRで ★「実データの入ったテストDBから読んで画面が出る」ところまで★到達しています。

内容 主なファイル 根拠
Prisma + MariaDB アダプタでの接続、USE_FIXTURES による fixtures / Prisma 切替 lib/db/index.ts, lib/db/prisma.ts, lib/db/use-fixtures.ts D-044 / D-045 / D-086
schema.prisma を実DDL(訂正版0911)に追従。率13列+面積10列を double 化 prisma/schema.prisma(★+936 / -6★) D-056 / D-059 / D-094 / D-096 / D-113(指示書E・Q・X)
ローカル検証用DB dwatch_local(MySQL 8.4)と冪等なシード scripts/seed-local-db.ts D-095(指示書M)
誤接続ガード(dwatch_local 以外への書き込みを構造的に止める) lib/db/local-guard.ts:23 assertLocalDwatchDatabaseUrl() 指示書M 段階2-4
DB行→APIモデルの変換を純粋関数に分離 lib/db/clean-log-mapping.ts, report-mapping.ts, precheck-mapping.ts, ds-status.ts 指示書R / S / T
★DATETIME を「日本時間の壁時計値」として読む★ lib/format/date.ts:67 fromDbJst() / :95 jstIsoFromDb() ★D-114 / D-115(指示書Y)★
★絞り込み・並べ替え・期間を SQL に降ろした(全件読み込みをやめた)★ lib/db/index.ts(DataSource 全メソッドが取得条件を必須引数で受ける) ★D-131(指示書AC)★
★接続情報を部品変数から組み立てる(パスワードの @ 問題・リモートDBのタイムアウト対策)★ lib/db/prisma.ts mariadbAdapterConfig() 追記48〜52 の申し送り

★prismaSource の実装状況★

  • ★未実装(notImplemented())は ★2本★ だけです — inspections / inspectionDetail (実測: lib/db/index.ts:1705 / :1708)。→ §3 #1
  • ★初版で未実装としていた continuousProblems は実装済み★(lib/db/index.ts:2302。D-116)。

1-2. 画面ごとの実装

PR #5 時点の実装済み画面は F-01 / F-02 中心でした。本PRで★画面が一通り揃います★ (実測: components/**/*.tsx ★26本★、API ルート app/api/**/route.ts ★22本★、 app/(dashboard)/ 配下 ★11ディレクトリ★)。

画面 場所 主な内容
F-01 ダッシュボード app/(dashboard)/dashboard/, components/features/dashboard/ 実DB由来の集計に切替。★要レスキュー店舗を詳細ログの上に追加★(D-135 / D-137)
F-02 清掃詳細ログ .../detail-log/, app/api/clean-logs/ 実DB化。CSVエクスポート、行展開アドバイス。★期間上限を2年(731日)に拡大★(D-136)。★エリア別完了率モーダル★(D-119)
F-03 アラート(事前チェック) .../alerts/, app/api/prechecks/ 実DB化(指示書S / D-105)。★precheck は「分数そのもの」として読む★(D-112 / D-113)
F-04 レポート .../reports/, app/api/reports/* ★新規★。KPI / 施設別パフォーマンス / エラー種別内訳 / 連続問題施設(指示書F・T・V・W)。★確定期間KPIは対象顧客が1社に定まるときだけ出す★(D-138 / D-140)
★清掃面積★ .../clean-area/, app/api/clean-area/, components/features/clean-area/ ★★新規(モニタリングの4つ目 📐)★★。ホールディングス/屋号/店舗の粒度で清掃面積・対象面積・完了率。★サマリを読まず clean_status_histories から集計★(D-141)
F-06 D-Secログ .../ds-logs/, app/api/ds-devices/ 2状態化+備考入力(指示書G / D-076)、実DB化(指示書P)
F-07 D-Secアラート .../ds-alerts/, app/api/ds-alerts/ detail 列削除(D-088)、実DB化
F-08 施設ロボットマスタ / F-09 スケジュール / F-10 システム管理 .../facilities/, .../schedules/, .../system/ ★新規3画面★(指示書L / D-084)。システム管理から★連続日数の表示上限を編集可能★(D-130)
ログイン app/login/, app/api/auth/login, .../logout ★新規★(指示書I / D-101)
点検記録 app/(dashboard)/inspections/page.tsx ★プレースホルダのみ。メニュー非表示(components/layout/AppShell.tsx:22 でコメントアウト)★ → §3 #1

横断で入ったもの:

  • ★一覧6画面のページ送り★(components/ui/Pagination.tsx / lib/pagination/page-items.ts。★モックに無いUIを例外として新設★。D-120)
  • ★分類(ホールディングス / 屋号 / 店舗)の3階層絞り込みを復活★ — GET /groups /customers を新設し、 一覧系11エンドポイントと5画面に通した。★D-053 の「マスタが揃うまで削除」を解除★(D-138)
  • ★店舗検索を記号のゆれに強くした★(ドンキ で ドン・キホーテ戸塚 が引ける。5系統に分裂していた照合規則を1本に統一。D-129)。 ★記号だけの入力は0件にする★(D-134)
  • 対応機種レジストリと「未対応」バッジ(lib/config/index.ts の ROBOT_MODELS / D-087)、 (値なし) 表示とラベル折り返し(lib/format/raw-label.ts / D-089)、i18n の器(lib/i18n.ts)、 3桁区切り表示(lib/format/number.ts)

1-3. 判定ロジック

PR #3 で入れた lib/judge/ を土台に、★仕様確定にあわせて作り直した部分★が入っています。

  • ★★判定の境界を「含める」に変えた★★ — ★90.0 は ◎ / 70.0 は ○★。★× の境界だけは「10%未満」のままなので 10.0 は △★。 実装は lib/judge/grade.ts:31-33 の比較演算子だけ(rate >= excellent / rate >= good / rate < poor)。 ★GRADE_THRESHOLDS の 90 / 70 / 10 は変えていない★。★★D-020b(境界は下のランクに落ちる)は破棄★★。 D-139(ユーザー指示 2026-09-16「◯、×は90%以上のように境はふくめてください」)/追随漏れの片付けは指示書AM。 ★期待値表は lib/judge/__tests__/grade.test.ts:26,30,33 に固定済み★
  • ★月跨ぎ週を TeamWorks 方式(月境界で分割)に統一★ — 週の長さ1〜7日、月の週番号1〜6。 帰属ルールは lib/format/period.ts の weekOfMonth() だけが知る。2020〜2040の全252か月・1313週で総当たり検証済み。 D-071 / D-072 / D-073(指示書H)。D-067 の旧DFA案と D-024 は破棄。
  • ★清掃結果の判定を clean_status_histories.clean_error に置き換え★ — 出発失敗=clean_error === 1 または履歴行なし。判定入口は notExecutedFromHistory() 1本。D-106 / D-107(指示書U)
  • ★連続日数を施設単位に揃え、当期の分母から未来を外す★ — 分割キーは facility_id(serial_number は誤り)。 同一日は foldProblemDaysByDate() で畳む。施設ステータスに4値目 no_data を追加。D-108〜D-110(指示書V) ★表示上限を入れ、その1つだけをシステム管理画面から編集可能にした★(D-130)
  • ★要レスキュー店舗の判定を新設★ — lib/judge/rescue.ts。★3条件★(未実施3日連続 / 連続問題L3 / 完了率の前回比20%以上の急落)。 ★件数はAPIで切らず、初期5件+「もっと表示」は画面の責務★。D-135 / D-137。 ★★ユーザー原文の4条件目「出発失敗」だけ未実装★★ → §3 #6
  • ★エラー率の分子を本清掃側にした★(事前チェック行のエラーは数えない)。D-114 項2 / D-118
  • ★エラー種別は変換せず、DBの生値をそのまま表示★ — 7分類への寄せは破棄。D-077 / D-088(指示書K)

1-4. 認証とアクセス制限 ★新規★

PR #5 時点は「URLを知っていれば全画面が開く」状態でした。本PRで解消しています(D-101 / 指示書I)。

  • proxy.ts — ★middleware.ts ではない★(Next.js 16 で middleware は非推奨)。 ★実行順は 相関ID発行 → IP制限(403) → 認証(401 / /login リダイレクト)★。順序に意味があるので入れ替えないこと。
  • 簡易トークン + httpOnly / Secure / SameSite=Lax cookie(dwatch_session)。★トークン期限は実装しない★(D-030)。
  • IP制限 — ★X-Forwarded-For の「末尾」を見る★(nginx が追記する。先頭は詐称可能)。 TRUSTED_PROXY_HOPS = 1(LBと nginx は同一=1段。D-079 項1)。許可リストは lib/config/allowed-ips.ts(完全一致のみ、CIDR非対応)。
  • 相関IDと構造化ログ — lib/logger/、instrumentation.ts、docs/logging-design.md。 ★認証で弾いたリクエストにも request_id が付く★ように相関IDを最初に発行している。

1-5. セキュリティの作り込み(★本PRで後半に追加★)

指示書AE / AC の後追いで、★ユーザー提起「SQLをDBに流すようにしたならインジェクションやHTMLは大丈夫か」★に答えた記録が入っています。

  • ★★生SQL禁止を ESLint ルールとして機械化★★ — eslint.config.mjs:41-60 の no-restricted-syntax。 対象は app/** lib/** components/** scripts/** proxy.ts。★CI の npm run lint が落とす★。D-122
  • ★SQLインジェクション / XSS の成立経路は0件★ — 推測ではなく実際に攻撃文字列を投げ、発行SQLと生HTMLを見て確認。 ★ただし副産物として「実データを入れた日に初めて出る不具合」を3件見つけて潰した★(control_reports の全件読み、 備考保存の全件読み、顧客サマリを顧客で絞っていない)。D-132 / D-133
    • ★★take: 1 は1文字もSQLに降りない★★ — 本PRで最も失われやすい知見(D-133 項2)
  • パスパラメータ検証を parseInt の前方一致から全体一致に(lib/api/path-params.ts。D-123)/ PATCH /settings/advice の長さ上限(D-124)/CSVの数式インジェクション対策(D-125)。
  • ★セキュリティヘッダはアプリに入れず nginx への申し送りにした★(D-126。★実装ではなく「約束」であることに注意★)。

1-6. 運用の仕組み

  • docs/deploy.md — 本番デプロイ手順、nginx の XFF 追記要件、403 の切り分け表。 ★next start が XFF を自分で埋めるという実測上の落とし穴★を含む。
  • npm run check:env(scripts/check-env.ts + lib/config/env-check.ts)— 環境変数の検査。 ★ALLOW_ALL_WHEN_EMPTY を本番で false にし忘れると誰でも通る★ため、ビルド前に挟める終了コード設計。
  • .env.production.example — 実値を書かないテンプレート。
  • JSONストアのアトミック書き込み+.bak(lib/settings/json-store.ts / D-075 / 指示書J)。
  • CI は PR #2 で入れた .github/workflows/ci.yml(lint / test / build)をそのまま使用。 ★現時点では「結果を表示するだけ」で、赤くてもマージできます★(同ファイル冒頭コメント)。

1-7. 資料

★このPRの差分の約56%はドキュメントです(★86ファイル / +75,081行★)。★ レビュー負荷の見積もりに使ってください。

資料 実測(git diff --numstat main..develop) 中身
docs/decisions.md ★+10,872 / -5★ 決定台帳。★D-050〜D-141 を追加★
docs/handoff.md ★+4,824★ 作業ログ。★追記1〜52★
docs/handoff/ 配下 ★77ファイル / +55,635★ ★指示書 D〜AN の36本(AD は欠番)★、★TeamWorks とのやり取り 第1回〜第9回★、analysis/、pdf/(PDF本体は非コミット)、★セッション引き継ぎ_20260916.md★
docs/code-map.md +2,548 ★新規★。コードの地図
docs/architecture.md / docs/logging-design.md / docs/deploy.md +192 / +519 / +346 新規
d-watch-api.yaml ★+1,199 / -160★ API契約。コードより先にここを直す運用(D-040)
CLAUDE.md ★+170 / -11★ §4.1 判定境界 / §4.7 週 / §4.8 生値 / §7 禁止事項 などの追加

2. レビューで見るべき点

★13万行を全部読むのは不可能です。★ 以下の5点だけ見れば、このPRのリスクは概ね押さえられます。 逆に言うと、★それ以外(画面のCSS・fixtures・ドキュメント)は詳細レビュー不要★という提案です。

2-1. 判定ロジックと期待値表テスト ★最重要★

読むファイル:

  • lib/judge/(grade.ts / escalation.ts / kpi.ts / advice.ts / ★rescue.ts★)と lib/judge/__tests__/ (grade.test.ts ★9★ / rescue.test.ts ★48★ / ほか advice escalation kpi。数値は it(/test( の記述行数)
  • lib/db/report-mapping.ts と lib/db/__tests__/report-mapping.test.ts(★142★ — 単一ファイル最多)
  • lib/db/clean-log-mapping.ts と lib/db/__tests__/clean-log-mapping.test.ts(★68★)
  • lib/format/period.ts と lib/format/__tests__/period.test.ts(★33★ — 月跨ぎ週)
  • ★lib/clean-area/★ と その __tests__/(aggregate 21 / anomaly 5 / visibility 7。D-141)

見るポイント:

  1. ★境界値が期待値表として固定されているか★(完了率 90.0 / 90.1 / 70.0 / 70.1 / 10.0 / 9.9 / 0 / 未実施) — ★★D-139 で「90.0 は ◎ / 70.0 は ○ / 10.0 は △」に変わっています★★。古い理解(D-020b)で読まないこと
  2. ★丸める前の生値で判定しているか★(89.5 → 表示90% だが判定は ○)
  3. ★連続日数が「スケジュール存在日の連続」で数えられているか★(カレンダー日ではない)
  4. ★閾値がハードコードされていないか★(lib/config/ から読んでいるか)
  5. ★components/ に置いたテストは走らない★(vitest.config.ts の include は lib/** と app/** のみ)。 判定は純粋関数にして lib/ に置く、が守られているか

2-2. 書き込みが4カラムに限定されていることの確認 ★実測値つき★

CLAUDE.md §3 / §7 の「メインDBは読み取り専用。書き込みは4カラムのみ」が守られているかは、 ★次の2コマンドで機械的に確認できます★(lib/generated/prisma は Prisma の生成物なので除外)。

# 1. 生SQL は1件も無い(★さらに ESLint の no-restricted-syntax が構造的に禁止している。D-122★)
grep -rn '\$executeRaw\|\$queryRaw' --include=*.ts --include=*.tsx app lib components scripts prisma proxy.ts \
  | grep -v 'lib/generated'
# → 0件(ヒットするのは eslint.config.mjs のルール定義とコメントのみ)

# 2. アプリコード(app/ lib/ components/ proxy.ts)に Prisma の書き込み呼び出しは1件も無い
grep -rnE '(prisma|tx)\.[a-zA-Z_]+\.(update|updateMany|create|createMany|delete|deleteMany|upsert)\(' \
  --include=*.ts --include=*.tsx app lib components proxy.ts | grep -v 'lib/generated' | wc -l
# → 0

★初版の grep(\.(update|create|...)\( の素通し)は Object.create() や response.cookies.delete() を拾ってノイズになります。上の形に直してあります。★

★DBへの書き込みは scripts/seed-local-db.ts にしか存在しません★(実測 ★44箇所★、すべて scripts/ 内)。 これはローカル検証用DB dwatch_local を作り直すシードで、lib/db/local-guard.ts:23 の assertLocalDwatchDatabaseUrl() により★localhost かつ DB名 dwatch_local 以外では例外で止まります★ (scripts/seed-local-db.ts:79 で呼んでいる)。

★結論: 現時点では 4カラム(status / comment / responded_at / resolved_at)への UPDATE すら実装していません。★ ★★理由は「やり忘れ」ではなく、TeamWorks から★DB更新用アカウントを受領できていない★ためです★ (2026-09-03 から未受領。第5・6・8・9回で継続依頼中)→ §3 #7。 運用設定(アドバイス文言、D-Sec備考、連続日数の表示上限)は ★メインDBではなく /data 配下のJSONストア★に書いています (lib/settings/json-store.ts。/data は .gitignore 済み)。

2-3. 認証とIP制限

  • proxy.ts — ★実行順(相関ID → IP制限 → 認証)を確認★。
  • lib/config/allowed-ips.ts:35 — ★ALLOW_ALL_WHEN_EMPTY は ★★true のまま★★★。 ★本番で false にしないと誰でも通ります★(docs/deploy.md §3 / §5)→ §3 #10。
  • lib/auth/client-ip.ts と lib/auth/__tests__/client-ip.test.ts — ★XFF の末尾を取っているか★。 先頭を取ると偽装ヘッダで素通りします。偽装XFFが403になることは実測済み(docs/handoff.md 追記29)。
  • lib/proxy/__tests__/authenticate.test.ts。
  • ★未確認事項: 本番 nginx 配下での実測はまだ行えていません★(本番環境未構築)。

2-4. 機微情報がコミットされていないこと

git ls-files | grep -E '(^|/)\.env'
# → .env.example と .env.production.example の2本だけ(テンプレート。実値なし)
  • .gitignore に .env* があり、!.env.example / !.env.production.example で2本だけ除外しています。 ★.env.local(実際の接続情報)は追跡外★。
  • ★経緯の申告★: lib/db/__tests__/local-guard.test.ts に、TeamWorks から受領した参照用アカウントの ★実在のDBユーザー名が2箇所入っていました★。★コミット c832891(2026-09-10)で testuser に置換済み★です (実測: 現在は testuser:pw@example.com / testuser:s3cret@example.com の2箇所のみ)。
    • ★パスワードは架空値(pw / s3cret)、ホスト名は example.com★ でした。漏れたのは★ユーザー名のみ★。
    • ★履歴は書き換えていません★(a5502ac に残存。実測: a5502ac は develop の祖先)。 force push 禁止(CLAUDE.md §6-5)かつ並行セッションが動いていたためです。 ★本番接続前のパスワード再発行は D-078 項1 に記録済み。★
    • ★この1点だけはマージ方式の判断に影響します → §6 を参照★。
  • ★★あわせて渡すべきインフラ側の申し送り4件★★(docs/handoff.md 追記41。★期限は「ステージングに実データが入る前」★): ① テストDB 133.125.238.144 が公開IPに平文(have_ssl=DISABLED / require_secure_transport=OFF)/ ② npm audit が mariadb コネクタに「ssl: true でも平文パスワードが漏れる」を報告(★No fix available★)/ ③ 上記のGit履歴残存/④ 本番構成(VM+DBアプライアンス)の明示確認が未取得(D-100)。 ★★これらはアプリコードでは直せません★★(アプリコード側に危険な箇所は1件も見つかっていない)。

2-5. 自動生成物

  • ★lib/generated/prisma は追跡外★(.gitignore:47)。prisma generate で再生成されます。 grep するときは必ず除外してください(除外しないとノイズで結果を読み違えます)。
  • ★prisma/schema.prisma(+936行)は prisma db pull の生成物★です。★手で書き換えないこと★(D-056)。 レビューでは「実DDLと一致しているか」ではなく「生成物をそのまま入れているか」を見れば十分です (実DDLとの照合結果は docs/handoff/analysis/ddl-diff-dbpull.md)。
  • ★docs/handoff/pdf/*.pdf は追跡外★。原稿Markdownと make_*_pdf.py から再生成します。
  • package-lock.json(+853 / -124)は依存追加に伴う機械的差分です。

3. 意図的に未完成な部分 ★「作り忘れ」ではありません★

すべて★本書の更新時(2026-09-16 / 992a456)にコードを実測して確認★しています。 ★初版(2026-09-11)にあった5件(areas の501 / continuousProblems / ページ送り / メモリ絞り込み / DATETIMEのTZ)は 実装を確認したうえで本表から削除しました★(削除の根拠は §3-2)。

# 内容 実測での現れ方(file:line) 根拠
1 ★点検記録(inspections / inspectionDetail)が未実装★ lib/db/index.ts:1705 / :1708 で notImplemented()。画面は PlaceholderScreen(app/(dashboard)/inspections/page.tsx)。components/layout/AppShell.tsx:22 でメニュー行をコメントアウト ★ユーザー判断(2026-09-11)で後回し。サービス展開自体も保留★ D-111 項6 / D-083 / D-084
2 ★本番DBに未接続★(★テストDBには接続済み★) 実データの確認はすべて★テストDB 133.125.238.144(読み取りのみ)★と★ローカル dwatch_local★。本番のホスト・ポートは未受領 D-078 / D-111 項7 / docs/handoff.md 追記41
3 PATCH /prechecks/{id}/status が未実装 d-watch-api.yaml:630 にはあるが app/api/prechecks/ に route.ts は一覧用の1本のみ D-105。★#7(更新用アカウント)と同根★
4 契約フラグ(facilities.contract)によるメニュー表示制御が未実装 parseContract() の呼び出しは lib/db/index.ts:1632(施設行のフラグ化)だけ。AppShell.tsx のメニューは静的配列 メニューは施設横断のグローバルUIで「どの施設の契約か」が決まらない。★P2で顧客アカウント導入時に対応★ D-083 項5
5 B3(オフライン)のアドバイス発火文字列が空 lib/config/advice-rules.ts:124-125「現在リストは空のため発火しない(値が未判明)」 実データで値が判明するまで保留。D-078 項2 / D-082
6 ★要レスキュー店舗の4条件目「出発失敗」が未実装★ lib/judge/rescue.ts:34 の RescueReason は3値(not_executed_streak / escalation_l3 / completion_drop)。★「出発失敗」と「未実施」が notExecutedFromHistory() に潰れている★ ★実際の清掃開始時刻がDBに無く猶予30分を計算できない★。TeamWorks ★第9回 Q11-1★ 待ち。★回答が来たときの足し方は docs/handoff.md:4426-4440 に手順化済み★ D-135
7 ★4カラムの UPDATE が1本も書けない★ app/・lib/ に Prisma の書き込み呼び出し ★0件★(§2-2) ★★DB更新用アカウントが2026-09-03 から未受領★★(第5・6・8・9回で継続依頼) docs/handoff.md:2925-2943
8 ★面積の単位「㎡」に裏付けがない★ lib/config/index.ts:696 AREA_UNIT = "㎡" ★DFAの判断。TeamWorks 第9回 Q5-1 で照会中★。★画面・APIに "㎡" を直書きせず1行で直せるようにしてある★ D-141
9 ★サマリテーブルの面積列を使っていない★ lib/clean-area/ は常に clean_status_histories から集計 ★実測でサマリの task_area が全24行 1000 固定=合計になっていない★。第9回 Q2-1 で照会中 D-141
10 ★ALLOW_ALL_WHEN_EMPTY が true のまま★ lib/config/allowed-ips.ts:35 開発を止めないための既定値。★本番前に false にすること★ D-101
11 ★ページングは SQL に降りていない★ app/api/clean-logs/route.ts:98-104 — SQL で日付・施設まで絞ったあと、filterCleanLogs()(機種・シリアル・判定)と slice() はメモリ ★意図的な決定★。SQLでページングすると meta.total が判定フィルタ後の総件数と食い違う。現状の実データ量(1日あたり数十行)ならメモリで足りる。★施設数が増えたら見直す(D-131 申し送り④⑥)★
12 ★単一ID取得(/clean-logs/{id}/advice / /{id}/areas)の連続日数クエリだけ全期間のまま★ 対象ログの日付が引くまで分からず遡り範囲を決められない。★実測: エリアモーダル1,084行のうち約1,080行がこれ★ D-131 申し送り⑬
13 ★施設200件超で facility_id: { in: [...] } の実行計画が静かに劣化★ ローカル MySQL 8.4 で eq_range_index_dive_limit = 200 を実測(★MariaDB 10.11 の値は未確認★)。現状は施設12件 D-131 申し送り⑫ / D-132
14 ★索引が7件未追加★ 第9回1章で依頼中(うち2件は索引ゼロ列)。★2年検索(D-136 で上限731日に拡大)が重いまま★ 第9回 依頼①〜⑦
15 ★複数顧客の合算ができない★ group_summaries テーブルが無い。★確定期間KPIは対象顧客が1社に定まらないと 422★(lib/reports/target-customer.ts) ★これは仕様であって欠陥ではない★(D-138 項2)。合算の可否は第9回 Q6-1 / Q6-2
16 ★ブラウザでの目視確認が未実施★ ★追記50・51・52 の3回連続で未実施★(docs/handoff.md:4663 / :4717 / :4795)。確認は SSR HTML と API 応答まで ログインが要る環境のため。★「もっと表示」は実DB(施設10件)では踏めず USE_FIXTURES=true が要る★
17 ★スマートフォン幅(375px)のサイドバー220px・本文155px は未対応★ docs/decisions.md:10591 D-137

3-2. ★初版の「未完成」から削除した5件 — 削除の根拠(実測)★

初版 # 内容 決定 ★本書更新時に確認した実物★ 判定
#2 GET /clean-logs/{id}/areas が 501 D-119 app/api/clean-logs/[id]/areas/route.ts に ★501 を返すコードは無い★(grep -rn "501" app/api のヒットは同ファイル :13 のコメント「501 の仮実装(D-034)をやめて本実装にした」1件のみ)。db.areaCompletions() と lib/clean-logs/areas.ts を呼ぶ本実装 ★★解決済み★★
#3 continuousProblems が未実装 D-116 lib/db/index.ts:2302 に async continuousProblems(query) の Prisma 実装。★notImplemented() は :1705 inspections / :1708 inspectionDetail の2本だけ★ ★★解決済み★★
#4 一覧画面にページ送りが無い D-120 components/ui/Pagination.tsx と lib/pagination/page-items.ts(+ __tests__)が存在。★6画面が参照★(alerts / detail-log / ds-alerts / ds-logs / facilities / schedules) ★★解決済み★★
#5 全件取得してメモリで絞り込んでいる D-131 app/api/clean-logs/route.ts:82-92 — ★日付・施設IDは db.cleanLogs({ date_from, date_to, facility_ids }) で SQL に降りている★。DataSource 全メソッドが取得条件を必須引数で受ける ★★「全件取得」は解決済み★★。★ただしページングと一部フィルタは意図的にメモリのまま★ → §3 #11・#12 に移した
#6 DATETIME のタイムゾーンが未確定 D-114 / D-115 ★TeamWorks 第8回 A2 で「JST の壁時計値」と確定★。lib/format/date.ts:67 fromDbJst() / :95 jstIsoFromDb() が実在し、lib/db/index.ts:1308 ほかで使用 ★★解決済み★★

★★5件すべて、決定台帳の記載とコードの実物の両方を確認できました。「未検証」で残したものはありません。★★


4. 数字

★すべて develop = 992a456(2026-09-16)時点の実測★。再実測コマンドは付録。

4-1. 差分の規模(git -c core.quotepath=false diff --numstat main..develop)

区分 ファイル数 追加 削除
合計 ★393★ ★+133,051★ ★-1,117★
ドキュメント(docs/) ★86★ ★+75,081★ -19
└ うち docs/handoff/ 77 +55,635 0
└ docs/decisions.md 単体 1 +10,872 -5
└ docs/handoff.md 単体 1 +4,824 0
コード(app/ lib/ components/ scripts/ proxy.ts) ★291★ ★+54,299★ -712
└ うちテスト(__tests__/) ★95★ ★+24,579★ -23
d-watch-api.yaml 1 +1,199 -160
prisma/schema.prisma(db pull 生成物) 1 +936 -6
package-lock.json 1 +853 -124
CLAUDE.md 1 +170 -11

★ドキュメントが約56%、テストが約18%。実装コードの純増はおよそ29,700行★という内訳です。

★★--numstat は日本語ファイル名をクオートして出すため、-c core.quotepath=false を付けないと docs/ の集計が 37ファイル / +42,078 に見えます(実際は 86 / +75,081)。★★ 初版の数字はこの罠を踏んでいます。

4-2. コミット

  • ★★356コミット、マージコミット0本、履歴は一直線★★ (git rev-list --count main..develop → 356 / git log --oneline --merges main..develop | wc -l → 0)
  • 種別: docs: ★167★ / feat: ★127★ / fix: ★38★ / chore: ★6★ / test: ★3★ / refactor: ★3★ / style: ★1★ / ★規約に沿わない接頭辞 11本★(初期の移行期のもの)
  • ★356コミット中 ★104コミット★ がコミットメッセージ(件名)に決定番号(D-0xx / D-1xx)を含む★ → §6 の判断材料
  • 期間: ★2026-08-30 〜 2026-09-16★
  • ★★origin/develop は develop より71コミット遅れています★★(未push)。★PRを出す前に push が必要★

4-3. テスト ★実測★

  • ★★96ファイル / 2,141テスト 全パス★★(2026-09-16、992a456 で npm test を実行した実測値)
  • 参考(静的に数えた値): 追跡下のテストファイル ★96本★、it( / test( の記述行 ★1,640行★ (it.each / describe.each によるテーブル駆動があるため、展開後の 2,141 と整合)
  • 単一ファイル最多は lib/db/__tests__/report-mapping.test.ts(★142記述★)
  • ★★npx tsc --noEmit / npm run lint / npm run build は本書の更新作業では実行していません★★ → §8 で消化すること (直近の記録では tsc 0 / lint 0。docs/handoff.md:4161-4162 ほか)

4-4. 決定・資料

項目 実測
決定事項 ★★129件★★(`grep -oE '^#+ *D-[0-9]+[a-z]?' docs/decisions.md \ grep -oE 'D-[0-9]+[a-z]?' \ sort -u \ wc -l)。最終 ★★D-141★★。欠番あり。枝番はD-020b` の1件のみ
└ うち本PRで追加 ★D-050〜D-141★
指示書 ★★36本(指示書D 〜 指示書AN。AD は欠番)★★ + 管理画面_設計検討_20260910.md + セッション引き継ぎ_20260916.md
TeamWorks とのやり取り ★第1回〜第9回★(第1〜8回は回答受領済み。★★第9回の全20問+依頼9件は未回答★★)
作業ログ(docs/handoff.md) ★追記1〜52★(4,824行)
API ルート(app/api/**/route.ts) ★22本★
画面コンポーネント(components/**/*.tsx) ★26本★
画面ディレクトリ(app/(dashboard)/) ★11★(うち inspections はプレースホルダ)

★★docs/decisions.md は番号順に並んでいません。★★ 位置を取るときは grep -n "^### D-" docs/decisions.md を使ってください(★^### D-0 だと3桁台を拾えません★)。


5. PRのタイトル案

★どれも「実DB接続」を主語にしています。★ #1〜#5 が「M0:」「M1:」のようなマイルストーン接頭辞や 機能名で始めていたので、それに揃えました。

  1. 実DB接続・画面11枚・認証/IP制限・レポート/清掃面積・分類の復活を develop から取り込む(D-050〜D-141) — 中身を並べる案。最も情報量が多く、レビュアーが範囲を掴みやすい。★推奨★
  2. M2: fixtures から実DBへ — Prisma接続・レポート・認証・マスタ3画面・清掃面積(356コミット) — PR #1「M0:」/ #3「M1:」の連番を継ぐ案。台帳運用と相性が良い。
  3. develop を main に同期(2026-08-30〜09-16 の全作業。実DB接続・認証・レポート・決定D-050〜D-141) — 「溜まった差分の同期」であることを正面から書く案。★1本が大きい理由が伝わる★。

6. マージ方式 ★★ユーザー判断待ち — 本書では決めません★★

★★合意は未取得です。★★ 初版は「マージコミット推奨」と書いていましたが、 ★これは本書の筆者の提案であってユーザー判断ではありません★。 本節では ★この案件の実データ★ だけを並べます。★決めた結果は docs/decisions.md に決定として記録してください★。

6-0. 前提として確定している事実(実測)

事実 実測値 コマンド
main..develop のコミット数 ★356★ git rev-list --count main..develop
範囲内のマージコミット ★0本★ `git log --oneline --merges main..develop \ wc -l`
リポジトリ全体のマージコミット ★5本★(PR #1〜#5 のみ。★日常の作業は直接 push 運用でマージコミットが1本も無い★) `git log --oneline --merges \ wc -l`
main は develop の祖先か ★真(fast-forward 可能。競合なし)★ git merge-base --is-ancestor main develop
件名に決定番号を含むコミット ★104 / 356★ `git log --oneline main..develop \ grep -cE 'D-[0-9]{2,3}'`
docs/decisions.md が名指しするコミットハッシュ ★90個(7桁)。うち89個が実在し、★★89個すべてが main..develop の範囲内★★★ 付録参照
docs/handoff.md が名指しするコミットハッシュ ★176個。うち★174個が main..develop の範囲内★(残り2個は範囲外の既存コミット)★ 付録参照

6-1. ★マージコミット(--no-ff)を選んだ場合★

得られるもの

  1. ★356コミットの履歴が main に残る★。★104コミットが件名に決定番号を持つ★ので、 git log --grep 'D-071' で該当変更に辿り着けます。 docs/decisions.md はこのプロジェクトの★一次資料★で「なぜこの仕様か」を遡るときに読むと CLAUDE.md §5 が定めています。
  2. ★決定台帳・作業ログが名指しする ★263個★ のコミットハッシュ参照(89 + 174)が、★main の履歴から解決できます★。
  3. ★仕様が2〜3回ひっくり返っている箇所の経緯が追えます★ (月跨ぎ週: D-024 → D-067 → D-071 / エラー種別: D-057/D-067 → D-077 / ★判定境界: D-020b → D-139★ / ★連続日数の70.0の扱い: D-109 決定3 → D-139 で撤回★)。
  4. PR #6 という★1つの区切り点★が main に残り、「2026-08-30 から 09-16 までの塊」が後から特定できます。

引き受けるもの

  • ★c832891 で除去した実在のDBユーザー名が、それ以前のコミット(a5502ac)の履歴に残っています。★ ★履歴ごとマージすると、この文字列が main の履歴にも入ります。★
    • 入っているのは★ユーザー名のみ★。パスワードは架空値(pw / s3cret)、ホスト名は example.com。
    • このリポジトリは公開されていません。
    • 本番接続前にパスワードを再発行する方針が D-078 項1 に記録済み。
    • ★c832891 のコミットメッセージで「履歴は書き換えない」と明示的に判断済み★(force push は CLAUDE.md §6-5 と衝突)。
  • ★main の git log が356本増え、main だけを見ている人には読みにくくなります。★

6-2. ★squash を選んだ場合★

得られるもの

  • ★main の履歴が1コミットになり、PR単位で読めます。★ 変更内容は §1 と docs/ に残るので、 ★「何が入ったか」は失われません★。
  • ★a5502ac の実在ユーザー名が main の履歴に入りません。★(★ただし develop ブランチを残す限り リポジトリからは消えません★。消すには履歴の書き換えが要り、それは別の判断です)

引き受けるもの

  • ★104コミットの「決定番号 ↔ 変更」の対応が1個に潰れます。★ git log --grep 'D-071' が効かなくなります。
  • ★263個のハッシュ参照は main からは解決できません。★ (★develop を残せばリポジトリ内には残るので「死んだ参照」にはなりません。 ただし「main の履歴を辿って理由に行き着く」経路は切れます★)
  • ★1コミットの差分が +133,051 / -1,117 になり、git blame が全行この1コミットを指します。★

6-3. ★判断に必要な問い(ユーザーへ)★

  1. ★main の履歴を「決定を遡る経路」として使いますか★、それとも docs/decisions.md だけを一次資料としますか。
  2. ★マージ後に develop ブランチを残しますか★(残すなら squash でもハッシュ参照はリポジトリ内に残ります)。
  3. ★a5502ac のユーザー名残存を main に持ち込まないことを優先しますか★。

★★この3問への回答が決まるまで、本節はどちらにも倒しません。★★ なお ★rebase / force push はどちらの案でも不要★です(すでに一直線で、force push は CLAUDE.md §6-5 と衝突します)。


7. レビュー体制

★CLAUDE.md §6-5「PRは必ずもう1人がレビューしてからmainにマージする」★に従い、 ★このPRは作成者以外の1名のレビュー承認を得てからマージします。★

  • ★1人で13万行は読めません。★ §2 の5点(判定ロジックとテスト / 書き込み4カラム / 認証とIP制限 / 機微情報 / 自動生成物)に絞ってレビューしていただく想定です。
  • §2-2 と §2-4 は★コマンドの実行結果で確認できます★。コードを読む必要はありません。
  • ★実質的に読んでいただきたいのは §2-1 の判定ロジックとその期待値表テストです。★ ここだけは仕様のズレが直接バグになるため、目視でのレビューをお願いします。 ★特に D-139(判定境界を「含める」に変更)は、古い理解(D-020b)のまま読むと全部逆に見えます★。

8. マージ前チェックリスト

★消化状況は 2026-09-16 / 992a456 時点の実測です。★

  • ★指示書X 完了★(clean_timetables.precheck を分数そのものとして読む)
    — ★実測★: `git log --oneline main..develop --grep="指示書X"` で段階1〜4 + 完了記録の ★6コミット★
    (`d8064a1` / `1fafea7` / `0b48ce4` / `fc99231` / `628ba09`)。決定は ★D-113★(`docs/decisions.md:6851`)
  • ★指示書W 完了★(continuousProblems を実DBから作る)
    — ★実測★: 段階1〜7 + 完了記録の ★8コミット★(`e24e177`〜`b214449`, `d1b7fec`)。決定は ★D-116★(`:7302`)。
    コードは `lib/db/index.ts:2302`
  • ★§4-1 の差分規模と §4-2 のコミット数を再実測して差し替え★ — 本書で実施(356コミット / 393ファイル / +133,051)
  • ★§3 の未実装リストから、解消した項目を削除★ — 本書で実施(5件を削除。根拠は §3-2)
  • ★§4-4 の決定件数・最終決定番号・指示書本数を再実測して差し替え★ — 本書で実施(129件 / D-141 / 36本)
  • ★npm test★ — ★96ファイル / 2,141テスト 全パス★(2026-09-16、992a456 実測)
  • ★npm run lint / npx tsc --noEmit を実行し直す★ — ★本書の更新作業では未実行★。
    直近の記録では 0 / 0(`docs/handoff.md:4161-4162`)だが、★その後のコミットで再実測していない★
  • ★npm run build が通る★ — ★未実測★
  • ★★develop を push する★★ — ★origin/develop が71コミット遅れ★(git rev-list --count origin/develop..develop → 71)。
    ★push しないとPRに本書の内容が載りません★
  • CI(lint / test / build)が緑 — ★PRを作らないと走りません★(on: pull_request)。
    ★現状のCIは「結果を表示するだけ」で赤くてもマージできる★点に注意(`.github/workflows/ci.yml`)
  • レビュアー1名の承認(CLAUDE.md §6-5)
  • ★★マージ方式の判断★★(§6。★ユーザー判断待ち★。決めた結果を docs/decisions.md に記録)

★PRとは別に、マージ前に決めておくと良いもの(任意)★

  • TeamWorks ★第9回★(2026-09-15 / 716行)の★送付状況の確定と記録★
    — ★`grep -n "第9回" docs/decisions.md` で送付記録が見つからない★。過去に第1回・第4回で同じ記録漏れ
  • §2-4 の★インフラ申し送り4件★を正式に手渡す(期限: ステージングに実データが入る前)
  • §3 #16 の★ブラウザ目視確認★(fixtures 版と実DB版の2周)

付録: 本書の数字を再実測するコマンド

# --- 差分規模(★日本語ファイル名があるので core.quotepath=false は必須★)---
git -c core.quotepath=false diff --shortstat main..develop
git -c core.quotepath=false diff --numstat main..develop > /tmp/ns.txt
awk -F'\t' '{a+=$1;d+=$2;n++} END{print "TOTAL files="n" +"a" -"d}'                        /tmp/ns.txt
awk -F'\t' '$3 ~ /^docs\//    {a+=$1;d+=$2;n++} END{print "docs  files="n" +"a" -"d}'      /tmp/ns.txt
awk -F'\t' '$3 ~ /__tests__/  {a+=$1;d+=$2;n++} END{print "tests files="n" +"a" -"d}'      /tmp/ns.txt
awk -F'\t' '$3 ~ /^(app|lib|components|scripts)\/|^proxy\.ts/ {a+=$1;d+=$2;n++} END{print "code  files="n" +"a" -"d}' /tmp/ns.txt

# --- コミット ---
git rev-list --count main..develop                              # → 356
git log --oneline --merges main..develop | wc -l                # → 0
git log --oneline main..develop | grep -cE 'D-[0-9]{2,3}'       # → 104
git log --format='%s' main..develop | sed -E 's/^([a-z]+)(\(.*\))?:.*/\1/' | sort | uniq -c | sort -rn
git merge-base --is-ancestor main develop && echo "fast-forward 可能"
git log -1 main --format='%H %ad %s' --date=iso                  # main の最終更新
git rev-list --count origin/develop..develop                     # → 71(未push)

# --- §6 の材料: 台帳が名指しするコミットハッシュが範囲内かを数える ---
git rev-list main..develop > /tmp/range.txt
for f in docs/decisions.md docs/handoff.md; do
  grep -oE '`[0-9a-f]{7}`' "$f" | tr -d '`' | sort -u > /tmp/h.txt
  in=0; out=0; nf=0
  for h in $(cat /tmp/h.txt); do
    full=$(git rev-parse --verify -q "$h^{commit}" 2>/dev/null)
    if   [ -z "$full" ];                       then nf=$((nf+1))
    elif grep -q "^$full$" /tmp/range.txt;     then in=$((in+1))
    else                                            out=$((out+1)); fi
  done
  echo "$f: total=$(wc -l < /tmp/h.txt) inRange=$in outside=$out notFound=$nf"
done
# → docs/decisions.md: total=90 inRange=89 outside=0 notFound=1
# → docs/handoff.md:   total=176 inRange=174 outside=2 notFound=0

# --- 書き込み面(lib/generated は Prisma の生成物なので必ず除外する)---
grep -rn '\$executeRaw\|\$queryRaw' --include=*.ts --include=*.tsx app lib components scripts prisma proxy.ts \
  | grep -v 'lib/generated'
grep -rnE '(prisma|tx)\.[a-zA-Z_]+\.(update|updateMany|create|createMany|delete|deleteMany|upsert)\(' \
  --include=*.ts --include=*.tsx app lib components proxy.ts | grep -v 'lib/generated' | wc -l   # → 0

# --- 未実装の確認 ---
grep -n 'notImplemented(' lib/db/index.ts     # → :1705 inspections / :1708 inspectionDetail のみ

# --- 機微情報 / 自動生成物 ---
git ls-files | grep -E '(^|/)\.env'            # → .env.example と .env.production.example のみ
git check-ignore -v lib/generated/prisma       # → .gitignore:47

# --- 決定件数(★このファイルは番号順に並んでいない★)---
grep -oE '^#+ *D-[0-9]+[a-z]?' docs/decisions.md | grep -oE 'D-[0-9]+[a-z]?' | sort -u | wc -l  # → 129
grep -n "^### D-" docs/decisions.md | tail -3                # ★^### D-0 だと3桁台を拾えない★

# --- 資料の本数 ---
ls docs/handoff/指示書*.md | wc -l              # → 36(D〜AN。AD は欠番)
find app/api -name route.ts | wc -l             # → 22
find components -name '*.tsx' | wc -l           # → 26
git ls-files | grep -E '__tests__.*\.test\.tsx?$' | wc -l   # → 96

# --- テスト ---
npm test            # → 96ファイル / 2,141テスト 全パス(2026-09-16 / 992a456)
npm run lint
npx tsc --noEmit
npm run build