diff --git a/docs/decisions/0007-gas-live-is-source.md b/docs/decisions/0007-gas-live-is-source.md new file mode 100644 index 00000000..8a9a946b --- /dev/null +++ b/docs/decisions/0007-gas-live-is-source.md @@ -0,0 +1,56 @@ +# 0007: GAS はライブの Apps Script を元データとし、gas/ はある時点の写しとして扱う + +- 状態:採用 +- 決めた日:2026-08-31(PR #481 をマージした日) +- 決めた人:45th の PM +- 決定の信頼度:記録なし + - 根拠:#466・#467・PR #469・PR #481・#482 には、決めたことと取り込みの手順は書かれているが、どれだけ確かだと思っていたかは書かれていない +- 出典:#466、PR #467、PR #469、PR #481、#482 + +## 背景 + +SeeFT の GAS は、スプレッドシートに付いた Apps Script のプロジェクトとして、Google のクラウド上で動いている。スプレッドシートのエディタから直接書き換えられるので、誰かがそこで直すと、リポジトリの `gas/` は変わらないまま、動いているコード(ライブ)だけが変わる。 + +2026-08-26 の夜(日本時間。以下同じ)に、`gas/task/` にあった 44th の送信用のファイルを、今のコードだと思い込んだ。そして、年度の値とシート名を直す issue(#466)と PR #467 を作った。ところが、ライブの GAS は年度をスクリプトのプロパティから読んでいて、シートも1枚にまとまっていた。直そうとした問題は、ライブにはどちらも無かった。日付が変わった直後(8/27 の 0 時半ごろ)に PR #467 を閉じ、#466 を書き直した。 + +この少し前(8/26)に、ライブの GAS にあってリポジトリに無かったファイル(名簿とタスクの送信、人数チェックなど)を PR #469 で取り込んでいた。その後もライブは変わり続け、PR #481(8/31)で取り込み直したときには、ライブのファイルは8つになっていた。 + +## 候補 + +| 候補 | 良い点 | 悪い点 | +| --- | --- | --- | +| 今のまま、決まりを作らない | 手間がない | リポジトリのコードを今のコードだと思い込む事故がまた起きる | +| リポジトリを元データにし、ライブへはリポジトリからだけ反映する | リポジトリを読めば今のコードが分かる | エディタから直接書き換えることは止められない。GAS を見る CI も無い | +| ライブを元データとし、リポジトリはある時点の写しとして扱う。作業はライブを取ってくることから始める | 事故の原因(写しを今のコードだと思うこと)を手順で防げる | リポジトリだけを読んでも今のコードは分からない | + +2つ目の案を具体的に比べた記録は無い。 + +## 決定 + +GAS の元データは、ライブの Apps Script とする。`gas/` は、ある時点でライブから取ってきた写しとして扱う(PR #481)。 + +`gas/README.md` に、次のことを書いた。 + +- `gas/` のコードを読んで「今はこう動いている」と判断しない +- GAS の作業は、`clasp clone` でライブを取ってくることから始める(リポジトリの外の一時ディレクトリで) +- `gas/task/` は 44th のもので、今は使っていない +- ライブに反映する前に、全ファイルをつなげて構文を確かめる +- ライブを取り込むコミットに、修正を混ぜない + +## 理由 + +リポジトリを元データにする案を選ばなかった理由の記録は無い。 + +確かめられる事実は2つある。スプレッドシートのエディタからの書き換えは止められない。`.github/workflows/` には GAS を見るワークフローが無く、リポジトリとライブがずれても何も知らせない。この2つがある限り、リポジトリを元データと決めても、ずれは起こりうる。そのため、ずれることを前提にして、作業の手順で事故を防ぐ方を選んだ、というのは推測である。 + +## 前提 + +- 手順の本体は `gas/README.md` +- GAS を見るワークフローが `.github/workflows/` に無いこと。GAS を CI から反映する仕組みを作るなら、この ADR を見直す +- 45th のシフトのスプレッドシートの GAS は `gas/shift/` に写してある + +## 結果 + +PR #481(2026-08-31)で、ライブの8ファイルを取り込み直した。その後も、ライブで直してからリポジトリに写す PR を出している(例:PR #550、レスキューの GAS)。 + +## 追記 diff --git a/docs/decisions/0008-manual-link-by-name.md b/docs/decisions/0008-manual-link-by-name.md new file mode 100644 index 00000000..18e5d13a --- /dev/null +++ b/docs/decisions/0008-manual-link-by-name.md @@ -0,0 +1,54 @@ +# 0008: タスクとマニュアルの紐付けは、タスク一覧の URL の列と対応表で行い、キーはマニュアル名にする + +- 状態:採用 +- 決めた日:2026-08-27(PM の作業メモ)。キーをマニュアル名にそろえたのは 2026-09-01(`docs/development/manual-html-operations.md`) +- 決めた人:45th の PM +- 決定の信頼度:記録なし + - 根拠:#452・PR #465・#466・PR #473・#497・PR #498 と `docs/development/manual-html-operations.md` には、決めたことと理由は書かれているが、どれだけ確かだと思っていたかは書かれていない +- 出典:#452、PR #465、#466、PR #473、#497、PR #498 + +## 背景 + +45th では、シフトのカードから、そのタスクのマニュアル(ドキュメント版とスライド版)を開けるようにしたかった。そのためには、タスクごとにマニュアルの URL を SeeFT に送る必要があった。 + +シフトのスプレッドシートのタスク一覧には、M列にそのタスクのマニュアル名が入っている(例:`45th_企画マニュアル_縁日`)。一方、マニュアルの割り当てを管理する別のスプレッドシートでは、マニュアル名が短い形(例:「配線マニュアル」)で、局ごとに付け方が違った。 + +## 候補 + +| 候補 | 良い点 | 悪い点 | +| --- | --- | --- | +| タスク一覧に URL の列(R列・S列)を足し、「マニュアルURL」シート(対応表)を M列のマニュアル名で引く | 対応表に1行足すと、同じマニュアル名のタスク全部に URL が入る。人が入力する場所が対応表だけになる | 列を足す手間がかかる。マニュアル名が変わると引けなくなる | +| GAS がマニュアルの割り当てのスプレッドシートを直接読んで、名前で突き合わせる | 列も対応表も要らない | 割り当て側の名前は局ごとに付け方が違い、M列の名前と機械的に突き合わせられない | +| マニュアルをアップロードするときに、タスクを指定して紐づける(#452) | 置いた瞬間に紐づく | 1つのマニュアルを使う複数のタスクを、1つずつ指定する必要がある | +| M列に入っている URL をそのままキーにする | 入っている値をそのまま使える | 入っていた URL は 44th のドキュメントを指していて、どのみち使えない | + +## 決定 + +シフトのスプレッドシートのタスク一覧に、R列(ドキュメント版の URL)と S列(スライド版の URL)を足す。どちらも「マニュアルURL」シートを、M列のマニュアル名で VLOOKUP して埋める。GAS のタスク送信が、この URL を SeeFT に送る(PR #465・PR #473)。 + +M列のキーはマニュアル名にそろえる。M列に URL が入っている行は、担当する局のタスクのファイル側で、45th のマニュアル名に書き換える(2026-09-01、PR #498)。 + +URL を直すときは、対応表を直して送り直す。SQL で `tasks` の URL を直接書き換えない。次の送信で、スプレッドシートの値に上書きされるためである。 + +## 理由 + +割り当て側の名前は、局ごとに付け方が違い、人が付けた名前に頼る突き合わせは当日まで不安が残る。M列のマニュアル名なら、タスク一覧の中で1つの形にそろっているので、対応表を1枚置けば引ける。対応表に1行足すだけで、同じマニュアルを使うタスクすべてに広がる(縁日なら1行で7タスク)。 + +2026-09-01 に確かめたとき、M列に URL が入っていた行は 66 行あり、どれも 44th のドキュメントを指していた(`docs/development/manual-html-operations.md`)。そのため、M列の URL はキーにしなかった。 + +## 前提 + +- URL の列を埋める GAS:`gas/shift/調査_マニュアルURL.js#fillManualUrlFormulas` +- 対応を確かめる GAS は2つある。`gas/shift/調査_マニュアルURL.js#checkManualUrlMapping` は、タスクを送る前に、対応表に無いマニュアル名・空白や全角の違いで引けない名前・対応表の重複・URL の欠けを洗い出す。`gas/shift/調査_マニュアルURL.js#inspectManualUrlLookup` は、R列と S列の VLOOKUP が引けているかを見る一時的な調査用で、確認が済んだら消してよいとコメントに書いてある +- URL を送る GAS:`gas/shift/名簿タスク送信.js#buildTaskChanges_` +- 受け取る API:`api/lib/usecase/task_usecase.go#taskUseCase.UpdateTasksAndPlacesFromGAS`、`api/lib/internals/repository/task_repository.go#taskRepository.UpdateWithManualURL` +- 運用の手順は `docs/development/manual-html-operations.md`(「タスクへ紐付ける」と「どのタスクがどのマニュアルに対応するかの決め方」) +- マニュアルのドキュメント名を変えると、M列の値も変わり、対応表で引けなくなる。エラーは出ない + +## 結果 + +45th の技大祭では、この形でシフトのカードからマニュアルを開けるようにした。 + +アップロードするときに紐づける案の issue(#452)は、2026-09-30 の時点で閉じていない。 + +## 追記 diff --git a/docs/decisions/0009-mobile-web-only.md b/docs/decisions/0009-mobile-web-only.md new file mode 100644 index 00000000..f25403a3 --- /dev/null +++ b/docs/decisions/0009-mobile-web-only.md @@ -0,0 +1,46 @@ +# 0009: mobile は Flutter Web だけで配る + +- 状態:採用 +- 決めた日:記録なし(45th より前から。この扱いを確かめたのは 2026-07-11) +- 決めた人:記録なし +- 決定の信頼度:記録なし + - 根拠:Web だけで配ると決めたときの記録が見つかっていない。出典の PR #417 は、この扱いを確かめたときの記録で、決めたときの記録ではない +- 出典:PR #417 + +## 背景 + +mobile は Flutter で書いてあり、同じコードから、ブラウザで開く Web 版と、Android・iOS のアプリを作れる。SeeFT は、参加者がスマホのブラウザで開く Web 版だけを配っていて、アプリストアでは配っていない。 + +2026-07-11、マニュアル一覧に検索を足す PR #417 に、CodeRabbit が指摘を付けた。「Android 11 以降でリンクが無反応になる可能性があります」という指摘で、`AndroidManifest.xml` に `` が無いと `canLaunchUrl` が `false` を返しやすいので、`launchUrl` を直接呼ぶか `` を足すように、という内容だった。このとき、Web だけで配っていることを前提に、この指摘をどう扱うかを決める必要があった。 + +## 候補 + +| 候補 | 良い点 | 悪い点 | +| --- | --- | --- | +| Android・iOS のアプリとしても配る | ブラウザを開かずに使える。プッシュ通知などが使いやすい | ストアへの登録と、OS ごとのビルドと審査の手間がかかる | +| Web 版だけを配る | ビルドと配布が1つで済む。更新はサーバーの入れ替えだけで全員に届く | ブラウザの制限を受ける | + +## 決定 + +mobile は Flutter Web だけで配る。 + +そのため、Android や iOS のアプリにしか関わらない指摘(`AndroidManifest.xml` や `Info.plist` の設定など)は、対象外として扱う。Web 版では、これらのファイルは実行時に読まれず、ビルドした成果物にも入らない。 + +## 理由 + +Web 版だけにした当初の理由の記録は無い。 + +PR #417 の指摘を対象外にしたのは、Web 版でリンクを開く処理はブラウザの新しいタブを開くだけで、`AndroidManifest.xml` を読まないからである。PM がそう返信したあと、CodeRabbit は指摘を取り下げた。 + +## 前提 + +- mobile のビルドが Web 版だけであること:`mobile/Dockerfile` +- 本番の配信が、ビルドした Web 版のファイルを `mobile/python/server.py` で配る形であること:`docker-compose.prod.yml` +- `.github/workflows/` に、Android・iOS のアプリを作るワークフローが無いこと +- アプリとしても配ると決めたら、この ADR を置き換え、対象外にしていた指摘を見直す + +## 結果 + +45th の技大祭も、Web 版だけで運用した。 + +## 追記 diff --git a/docs/decisions/0010-prod-auto-restart.md b/docs/decisions/0010-prod-auto-restart.md new file mode 100644 index 00000000..ff750ac9 --- /dev/null +++ b/docs/decisions/0010-prod-auto-restart.md @@ -0,0 +1,50 @@ +# 0010: 本番のサーバーが止まったときに、自動で起動し直すようにする + +- 状態:提案 +- 決めた日:未定(提案したのは 2026-09-15) +- 決めた人:未定 +- 決定の信頼度:中 + - 根拠:2つの設定を足せば、ノードが戻ったときに CT とコンテナが起動するところまでは見込める。ただし、本番と同じ構成では試していない。とくに、api を先に起こす順番を `restart:` だけで守れるかを確かめていない(下の「前提」) +- 出典:#557、`docs/operations/deploy.md`(「自動では戻らない」)、`docs/operations/incidents-45th.md`(9/15) + +## 背景 + +2026-09-15 の未明、本番のサーバーが載っている物理ノードが、インフラ側のメンテナンスで止まった。本番は約3時間止まり、ノードが戻っても自動では戻らなかった。 + +本番は、コンテナ(CT)の中で Docker Compose を使って動いている。CT には、ノードが起動したときに一緒に起動する設定が無かった。`docker-compose.prod.yml` の4つのサービスにも、`restart:` の設定が無かった。そのため、ノードを起こす、CT を起こす、コンテナを起こす、の3段階すべてを人が手で行う必要があった。 + +ノードのメンテナンスは、SeeFT のコードやデプロイとは関係なく行われる。止まるたびに担当者が夜中に起きて復旧するのは、続けられない。 + +## 候補 + +| 候補 | 良い点 | 悪い点 | +| --- | --- | --- | +| 今のまま、止まったら人が起こす | 設定を変えなくてよい | 止まるたびに人手が要る。気づくまで止まったまま | +| CT の自動起動と、compose の `restart: unless-stopped` を設定する | ノードが戻れば、人がいなくても本番が戻る | 起動の順番(api を先に起こす)を自動で守れるかを確かめる必要がある | + +ほかの方法(監視して通知する、など)は比べていない。 + +## 決定 + +提案:次の2つを設定する。 + +- CT に、ノードの起動と一緒に起動する設定(`onboot: 1`)を足す +- `docker-compose.prod.yml` の4つのサービス(cloudflare・mobile・api・admin)に `restart: unless-stopped` を足す + +compose の DB のサービス(`nutfes-seeft-db`)は、本番では起こさない。本番は compose の外の DB を使っている。 + +## 理由 + +止まったのはインフラ側の都合で、SeeFT の側では止まること自体を防げない。防げない以上、止まっても人手なしで戻る方がよい。2つとも設定を1行ずつ足すだけで済む。 + +## 前提 + +- 本番の compose:`docker-compose.prod.yml`。2026-09-30 の時点で、どのサービスにも `restart:` は無い +- 手で復旧するときは、api を先に起こし、ログに `http server started` が出てから mobile などを起こしている(`docs/operations/deploy.md`)。api は起動のたびにインターネットからモジュールを取ってコンパイルし、DB につなぐ(`api/lib/externals/server/server.go#RunServer`、`api/lib/externals/db/db.go#ConnectMySQL`)。`restart:` だけで、この順番がなくても問題なく戻るかは確かめていない。設定したら、本番と同じ構成の検証環境で、CT ごと止めて戻るかを試す +- CT の設定はリポジトリの外にある。設定を変えられる人と手順は、非公開の別紙にある + +## 結果 + +2026-09-30 の時点で、どちらも設定していない(#557 のチェックリストに残っている)。 + +## 追記 diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 92107eed..ea30910b 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -106,3 +106,7 @@ python3 scripts/refcheck/refcheck.py docs/decisions/*.md | [0004](0004-develop-merge-by-admin.md) | develop の保護ルールを残したまま、PM が admin 権限で自分の PR をマージする | 採用 | | [0005](0005-task-year-id-not-filtered.md) | tasks の seed 由来の行は年度をまたいで使い、名前で引くときに year_id で絞らない | 採用 | | [0006](0006-manual-serving-google-gate.md) | マニュアルは SeeFT の API から、nutfes の Google アカウントに限って配信する | 採用 | +| [0007](0007-gas-live-is-source.md) | GAS はライブの Apps Script を元データとし、gas/ はある時点の写しとして扱う | 採用 | +| [0008](0008-manual-link-by-name.md) | タスクとマニュアルの紐付けは、タスク一覧の URL の列と対応表で行い、キーはマニュアル名にする | 採用 | +| [0009](0009-mobile-web-only.md) | mobile は Flutter Web だけで配る | 採用 | +| [0010](0010-prod-auto-restart.md) | 本番のサーバーが止まったときに、自動で起動し直すようにする | 提案 |