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
56 changes: 56 additions & 0 deletions docs/decisions/0007-gas-live-is-source.md
Original file line number Diff line number Diff line change
@@ -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)。

## 追記
54 changes: 54 additions & 0 deletions docs/decisions/0008-manual-link-by-name.md
Original file line number Diff line number Diff line change
@@ -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 の時点で閉じていない。

## 追記
46 changes: 46 additions & 0 deletions docs/decisions/0009-mobile-web-only.md
Original file line number Diff line number Diff line change
@@ -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` に `<queries>` が無いと `canLaunchUrl` が `false` を返しやすいので、`launchUrl` を直接呼ぶか `<queries>` を足すように、という内容だった。このとき、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 版だけで運用した。

## 追記
50 changes: 50 additions & 0 deletions docs/decisions/0010-prod-auto-restart.md
Original file line number Diff line number Diff line change
@@ -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 のチェックリストに残っている)。

## 追記
4 changes: 4 additions & 0 deletions docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) | 本番のサーバーが止まったときに、自動で起動し直すようにする | 提案 |
Loading