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
47 changes: 47 additions & 0 deletions docs/decisions/0011-shift-notice-dm-only.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 0011: シフト変更の Slack 通知はチャンネルに送らず、本人への DM だけにする

- 状態:採用
- 決めた日:不明(2026-04-12 以前の MT。この方針をコードに入れたのは、2026-04-12 のコミット 75649e1)
- 決めた人:45th の SeeFT の MT
- 決定の信頼度:記録なし
- 根拠:コミット 75649e1 のメッセージと差分には、決めたことと「MTの議論により」というコメントがあるが、どれだけ確かだと思っていたかは書かれていない。45th の PM の作業メモにも書かれていない。議論をした MT の資料は、まだ読めていない
- 出典:コミット 75649e1、45th の PM の作業メモ(公開していない)

## 背景

シフトが変わったことを Slack で知らせる仕組みを作るときに、送り先を決める必要があった。チャンネル(委員が全員見る場所)に投稿するか、シフトが変わった本人に DM で送るかである。

作りかけのコードは、同じ内容をチャンネルと本人の DM の両方に送っていた(75649e1 の変更前の `SendMessage`)。

## 候補

| 候補 | 良い点 | 悪い点 |
| --- | --- | --- |
| チャンネルと本人の DM の両方に送る | 誰のシフトが変わったかを、全員が見られる | 約 200 人分の変更がチャンネルに流れ、読まれなくなる |
| チャンネルにだけ送る | 本人の Slack ID を調べなくてよい | 自分に関係する変更を、大量の投稿の中から探すことになる |
| 本人の DM にだけ送る | 自分の変更だけが届く | 本人の Slack ID を DB に入れる必要がある([0012](0012-slack-id-linked-by-gas.md)) |

## 決定

チャンネルには送らず、シフトが変わった本人への DM だけにする。

コミット 75649e1 で、`SendMessage` のチャンネルへの送信をコメントにして止め、`NewSlackService` で `SLACK_CHANNEL_ID` を必須にするのをやめた。

## 理由

MT で議論して決めた。理由は、約 200 人分の変更がチャンネルに流れるとノイズになるから、と PM の作業メモ(2026-04-12 時点)に残っている。

コードに残っているのは「MTの議論により、チャンネルへのシフト変更通知は導入しない方針」というコメントだけである。MT の資料そのものは確かめていない。

## 前提

- 送信:`api/lib/externals/slack/slack_service.go#SlackService.SendMessage`。本人の Slack ID があるときだけ送る
- シフト変更の通知の呼び出し元:`api/lib/usecase/notification_usecase.go#notificationUseCase.processGroup`
- 本人の Slack ID が DB に入っていること([0012](0012-slack-id-linked-by-gas.md))
- #546(2026-09-20)から、レスキューの DM(`api/lib/externals/slack/slack_service.go#SlackService.SendRescueMessage`)も同じ `SendMessage` を使っている。チャンネルへの送信を戻すときに、コメントを外すだけにすると、レスキューの DM までチャンネルに流れる。戻すなら、シフト変更用の送信を別のメソッドに分け、`api/lib/externals/slack/slack_service.go#NewSlackService` で `SLACK_CHANNEL_ID` を必須に戻す(2026-09-30 に develop のコードで確かめた)

## 結果

45th の技大祭では、シフト変更は本人に DM で届いた。2026-09-18 には、スプレッドシートでセルを空欄にした変更が「(不明)」と表示された DM が届き、問い合わせが来た(#543。表示は PR #544 で直した)。

## 追記
48 changes: 48 additions & 0 deletions docs/decisions/0012-slack-id-linked-by-gas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 0012: Slack ID の紐付けは GAS の名簿送信で行い、API に一括のバッチは作らない

- 状態:採用
- 決めた日:不明(2026 年 8〜9 月ごろ。#476 に設計を書いたのは 2026-08-27、API のバッチを書きかけてやめたのは PR #522 を作っていた 2026-09 中旬)
- 決めた人:45th の PM
- 決定の信頼度:記録なし
- 根拠:#476 と PR #522 の本文には、決めたことと理由は書かれているが、どれだけ確かだと思っていたかは書かれていない
- 出典:#476、PR #522

## 背景

シフト変更は本人への DM だけで知らせる([0011](0011-shift-notice-dm-only.md))ので、本人の Slack ID(`users.slack_user_id`)が DB に要る。ところが API には、この列を書く処理が無かった(読むだけだった)。Slack のトークンを本番に入れても、DM は誰にも届かない状態だった。

Slack ID は、Slack の `users.lookupByEmail` でメールアドレスから引ける。ただ、本番の `users.mail` は 353 人全員が空だった(#476、2026-08-27 時点)。名簿送信の GAS はメールアドレスを送っていたが、API が受け取らずに捨てていた。

## 候補

| 候補 | 良い点 | 悪い点 |
| --- | --- | --- |
| API(Go)に一括のバッチを作り、DB のメールアドレスから Slack ID を引く | Slack を呼ぶ処理が API の中にまとまる | DB にメールアドレスが無いので、そのままでは引けない。名簿送信とは別に、本番のサーバーでバッチを動かす手順が1つ増える。本番のサーバーを操作する手段は限られていて、コンテナの中でコマンドを動かす運用は重い(PR #522 の本文) |
| GAS の名簿送信で Slack ID を引き、メールアドレスと一緒に API に送る | 名簿送信の1回で、メールアドレスと Slack ID の両方が DB に入る | GAS の6分の実行制限と、Slack の呼び出し回数の制限(1分に50回程度)に当たりうる |

## 決定

GAS の名簿送信で `users.lookupByEmail` を呼び、メールアドレスと Slack ID を名簿のデータに入れて API に送る。API は受け取って保存するだけにする。

- 空で送られたときは、DB にある値を消さずに残す。Slack の照会が時間切れで途中までしか引けなかった回や、トークンを入れていない回に送り直しても、消えないようにするためである
- 引けた人はシフトのスプレッドシートの `SlackID` シートに残し、送り直すときは Slack を呼ばない。照会は4分で打ち切り、残りは次の送信で引く
- トークンが無い・間違っているときも、名簿送信そのものは止めない。Slack ID だけを紐付けずに、画面で知らせる

## 理由

API にバッチを置くと、DB にメールアドレスが無いので、先にメールアドレスを入れる手順が要る。名簿送信のほかに、本番でバッチを動かす手順も増える。GAS なら、名簿送信の1回で両方が入る(#476 の「API 側に置かない理由」)。

PR #522 を作る途中で Go のバッチを書きかけたが、同じ理由でコミットせずにやめた(PR #522 の本文)。

## 前提

- GAS 側:`gas/shift/名簿タスク送信.js#attachSlackUserIds_`、`gas/shift/名簿タスク送信.js#lookupSlackUserIdByEmail_`
- API 側:`api/lib/usecase/user_usecase.go#userUseCase.UpdateUsersFromGAS`、`api/lib/internals/repository/user_repository.go#userRepository.UpdateWithSlackUserID`
- GAS のスクリプトプロパティに `SLACK_BOT_TOKEN`(スコープ `users:read.email`)が入っていること
- 本番で通知を有効にするときは、先に API にトークンを入れ、そのあとで名簿送信で紐付ける。逆にすると、溜まっていた過去のシフト変更が一斉に DM で届く(`gas/README.md` の「本番で通知を有効にする順序」)

## 結果

PR #522 を 2026-09-17(日本時間)にマージした。

## 追記
56 changes: 56 additions & 0 deletions docs/decisions/0013-rescue-dm-sent-immediately.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 0013: レスキューの対応状況の DM は、まとめずに、書き換えのたびにすぐ送る

- 状態:採用
- 決めた日:2026-09-20(#545 の本文の「見直した点」)
- 決めた人:45th の PM(メンバーが作っていた PR #546 を引き継いで決めた)
- 決定の信頼度:記録なし
- 根拠:#545 と PR #546 の本文には、決めたことと理由は書かれているが、どれだけ確かだと思っていたかは書かれていない。PR #546 の本文は、実際の Slack での送信はまだ確かめていない(確かめたのはユニットテストと lint まで)と断っている
- 出典:#545、PR #546

## 背景

レスキューは、委員がアプリから本部に送る問い合わせである。本部が対応状況を変えたり返答を書いたりしたら、送った人に Slack の DM で知らせる機能を作った(#545、PR #546)。

最初の案は、PR #546 の最初のコミット(f22d09c)にある。知らせる内容を DB のテーブルに溜め、30秒ごとに見回って、最後の書き換えから15秒たったものを1通にまとめて送る形だった。1通にまとめてほしいという要望が、機能を作ったメンバーのほかから出ていた記録は無い。

## 候補

| 候補 | 良い点 | 悪い点 |
| --- | --- | --- |
| テーブルに溜め、見回りで1通にまとめて送る | 続けて書き換えても、DM が1通で済む | 届くまでに15〜45秒かかる。確実にまとまるのは15秒以内の書き換えだけ。本番 DB(ほかのシステムと共有しているクラスタ)にテーブルを足す作業が要る |
| 書き換えのたびに、その場で送る | 本部が見たことがすぐ届く。DB を変えなくてよい | 続けて書き換えると、DM が複数届く |

## 決定

書き換えのたびに、その場で DM を送る。送信は API の応答とは別の goroutine で行い、失敗しても送り直さない。返答は、アプリの「本部からの返答」タブでも見られる。

あわせて、次のようにした。

- 更新の PUT に `notify` を足し、`false` なら送らない。GAS が押し直しで重なったレスキューをまとめるときの書き換えは、`notify: false` を送る。押し直した人に「対応が完了しました」が届かないようにするためである
- 本番に出すときは、GAS を先に、API を後にする。今の API は知らない JSON の項目を無視するので、GAS が先でも害は無い。逆にすると、その間に誤った DM が届く

## 理由

#545 の「見直した点」に、次の理由が書いてある。

- レスキューは急ぎで、「本部が見てくれた」は早く届いたほうがよい。まとめるには待つしかなく、待つ分だけ遅れる
- 返答を打つ時間を考えると、15秒以内に続けて書き換える場面は少ない
- レスキューは件数が少なく、シフト変更の通知のように大量の DM が届くおそれがない
- テーブルを足すと、本番 DB への作業が増える

## 前提

- 何を知らせるかの判定と送信:`api/lib/usecase/rescue_notification_usecase.go#rescueNotifier.notify`
- `notify` の扱い:`api/lib/entity/request.go#RescueNotifyOption.ShouldNotify`(省略したときは送る)
- GAS で重なったレスキューをまとめる処理:`gas/rescue/コード.js#closeDuplicateInDb_`
- レスキューの件数が少ないこと。大量に届くようになったら、まとめる形を見直す
- `SLACK_BOT_TOKEN` が無いとき、または `RESCUE_NOTIFICATION_DISABLED=true` のときは送らない(`api/lib/di/di.go#InitializeServer`)
- 送り先は本人の Slack ID で、シフト変更の通知と同じ `SendMessage` を使う([0011](0011-shift-notice-dm-only.md)、[0012](0012-slack-id-linked-by-gas.md))

## 結果

PR #546 を 2026-09-20(日本時間)にマージした。

既知の制約として、同じレスキューへの PUT が1秒未満で重なると、両方が同じ「更新前」の値を読み、DM が1通余分に届くことがある。CodeRabbit はトランザクションで囲むよう指摘したが、本部の書き込みは普通は数秒以上離れるので見送った。直すなら、更新と同時に古い値を返す1つのクエリにする(PR #546 の本文とレビューのスレッド。`api/lib/usecase/question_rescue_usecase.go#questionRescueUseCase.UpdateQuestionRescue` などの3種類の更新)。

## 追記
58 changes: 58 additions & 0 deletions docs/decisions/0014-manual-open-in-new-tab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 0014: マニュアルはアプリに埋め込まず、別タブで開く

- 状態:採用
- 決めた日:2026-08-20(スライド版。#444)、2026-09-13(ドキュメント版も別タブにした。#509)
- 決めた人:45th の PM
- 決定の信頼度:記録なし
- 根拠:#444・PR #451・#509・PR #510 の本文には、決めたことと、埋め込めないことを実測した結果が書かれている。どれだけ確かだと思っていたかは書かれていない
- 出典:#444、PR #451、#509、PR #510

## 背景

シフトカードとマニュアル一覧から、タスクのマニュアルを開けるようにしている。マニュアルは2種類ある。

- ドキュメント版:元の Google ドキュメント。45th の途中から、技大祭の Drive のフォルダに置いた PDF に切り替えた([0016](0016-simple-manual-pdf-on-drive.md))
- スライド版:SeeFT の API から配信する HTML([0006](0006-manual-serving-google-gate.md))

どちらも、閲覧できる人を nutfes の Google アカウントに限っている。

最初、ドキュメント版はシフトカードの中に埋め込み(iframe)で表示していた。2026-09-13、ドキュメント版を Drive の PDF に切り替えたあと、埋め込みの枠に Google のエラー画面(401)が出ると報告があった(#509)。

## 候補

| 候補 | 良い点 | 悪い点 |
| --- | --- | --- |
| アプリの中に埋め込む(iframe やモーダル) | アプリの画面から離れずに読める | 閲覧できる人を限ったファイルは、他サイトの枠の中では開けない(下の「理由」) |
| 別タブで開く | どのブラウザでも開ける | アプリの画面から離れる |

## 決定

マニュアルは、ドキュメント版もスライド版も、埋め込まずに別タブで開く。

- スライド版は、最初から別タブで開く作りにした(#444、PR #451)
- ドキュメント版の埋め込み(`ManualViewer`)は、PR #510 で消した

## 理由

閲覧できる人を限ったファイルは、SeeFT の画面の中の枠(他サイトの iframe)では開けない。次のどれか1つに当たると表示されない(#509 の調査、2026-09-13)。

- Safari は、他サイトの枠に Cookie を渡さない。Google から見ると、ログインしていない人になる
- Cookie が届いても、ブラウザの既定のアカウントが個人のアカウントだと、権限が無い。枠の中ではアカウントを切り替えられない
- Google のログイン画面は、枠の中に表示できない
- ドライブの `/file/d/<id>/view` は `X-Frame-Options: SAMEORIGIN` を返すので、他サイトの枠に表示されない

URL の形を変えても開けなかった。iPhone の Safari を装い、Cookie なしで他サイトの枠として取得して確かめた(#509、2026-09-13)。結果は、`/document/d/<id>/preview` が 302(ログイン画面へ転送)、`/file/d/<id>/view` が 403、`/file/d/<id>/preview` が 401 だった。

スライド版を決めた時点でも、認証の付いたページは埋め込めないことを実測していた(#444 の本文)。サードパーティ Cookie が遮断され、ログイン画面も枠の中に表示されないためである。

## 前提

- 開く処理:`mobile/lib/widgets/shift_card.dart#_ManualToggleState._open`
- 閲覧できる人を nutfes の Google アカウントに限っていること([0006](0006-manual-serving-google-gate.md))。誰でも読める配信に変えるなら、埋め込みも選べるようになる
- ブラウザが、他サイトの枠に Cookie を渡さない動きを続けていること

## 結果

PR #510 を 2026-09-13 にマージした。

## 追記
4 changes: 4 additions & 0 deletions docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,7 @@ python3 scripts/refcheck/refcheck.py docs/decisions/*.md
| [0008](0008-manual-link-by-name.md) | タスクとマニュアルの紐付けは、タスク一覧の URL の列と対応表で行い、キーはマニュアル名にする | 採用 |
| [0009](0009-mobile-web-only.md) | mobile は Flutter Web だけで配る | 採用 |
| [0010](0010-prod-auto-restart.md) | 本番のサーバーが止まったときに、自動で起動し直すようにする | 提案 |
| [0011](0011-shift-notice-dm-only.md) | シフト変更の Slack 通知はチャンネルに送らず、本人への DM だけにする | 採用 |
| [0012](0012-slack-id-linked-by-gas.md) | Slack ID の紐付けは GAS の名簿送信で行い、API に一括のバッチは作らない | 採用 |
| [0013](0013-rescue-dm-sent-immediately.md) | レスキューの対応状況の DM は、まとめずに、書き換えのたびにすぐ送る | 採用 |
| [0014](0014-manual-open-in-new-tab.md) | マニュアルはアプリに埋め込まず、別タブで開く | 採用 |
Loading