コールセンターをフルIaCで建てて通話の感情をリアルタイム分析する crossbar_telepath の最終回である。プロジェクトをアーカイブした。
技術は完走した。死因は技術ではなく、電話番号だった。この記事はその死因の解剖記録である。同じ轍を踏む人を減らすために書く。
発端: 番号を1本も持っていないのに「枠超過」
8月頭、実呼テストのためにdestroy済みのConnect基盤を terraform apply で建て直した。インスタンス・コールフロー・Lambda・SQSまでは何事もなく生えた。電話番号だけが生えなかった。
このリソースが FAILED 状態で作られる。CLIで直接取りにいっても同じで、search-available-phone-numbers は候補を返すのに、claim-phone-number が枠超過で落ちる。
ここで頭が混乱する。このアカウントが保有している電話番号は0本である。 クォータ(Phone numbers per instance、既定5)のどこを消費しているのか。
謎解き: 解放した番号は180日間「居座る」
答えはドキュメントの一文にあった。releaseした電話番号は、解放後180日間、クォータにカウントされ続ける。
つまりこうだ。このプロジェクトは開発の流儀どおり、検証のたびに apply → 架電E2E → destroy を繰り返してきた。7月に2回、8月に1回。番号はそのたびに取得され、解放された。手元には何も残っていないが、枠の上では3本ぶんの幽霊が180日間居座っている。
さらに追い打ちがある。取得に失敗した試行もまた枠を食うのだ。「枠が空いてないだけかもしれない」とリトライを重ねると、失敗のたびに状況が悪化する。私はこれで残りの枠も溶かした。
AWSの立場で考えれば、このルールは正しい。番号を取っては捨てを高速に繰り返せるなら、迷惑電話業者は着信拒否リストを永遠にすり抜けられる。180日ルールは番号ローテーション悪用への対策であり、電話網の側の防衛としては真っ当である。
ただしこれは、IaCの「作って壊す」様式と根本的に相性が悪い。 サーバーは壊して建て直せば同じものが手に入るが、電話番号は壊した瞬間に180日分の枠を持ち逃げする。
対策1: 番号の常設化 — 正しかったが、遅かった
気づいた時点で方針を転換した。番号は使い捨ての設定値ではなく資産である。維持コストを実測すると:
- 電話番号: $0.06/日 ≒ $1.8/月
- KVS暗号化のKMSキー: $1/月
- Connectインスタンス自体・待機中のKVS/Lambda/SQS: ほぼゼロ
月$3弱で「取得ガチャ」と180日ルールから解放される。 destroyの対象から番号を外し、Connect基盤ごと常設にする——これが正解だった。
問題は、正解に気づいたのが枠を使い切った後だったことだ。常設にしようにも、その最初の1本が取れない。 幽霊が消えるのは2027年1月末。5か月先である。
対策2: クォータ引き上げ申請 — 個人開発とKYCの壁
残る道はクォータ引き上げ(L-8F812903、5→15)だった。Service Quotasから申請すると、サポートケースに変換される。
やりとりは3往復あった。
- インスタンスARNの照会 → 提供
- 「内部チームに回した。最長5営業日待て」
- そして最後に来たのがこれである:
A valid business document with the business number partially redacted (showing only the last four digits). Please ensure the business name and all four corners of the document are clearly visible.
A brief description of your business and what it does.
The business use case for this limit increase request.
事業証明書類の提出要求だ。電話番号の取得に事業者確認(KYC)が要るのは知っていたが、クォータの引き上げ審査にも同じ土俵が適用される。番号という資源に触る以上、そこに個人と事業者の区別ができる抜け道は用意されていない、ということだろう。
個人事業主の開業届で応じる道はあった。書類も用意した。だが冷静に考えた。これは趣味の技術検証プロジェクトである。検証用の番号1本のために事業書類を審査に載せ、以後もPSTNの制度側と付き合い続ける——そのコストは、このプロジェクトが検証したかったこと(それはもう検証し終わっている)に対して重すぎる。
ここで店じまいを決めた。
埋葬式: destroyとシークレット監査
畳むと決めたら手際よくやる。消したら困るからと分けて建てたCognito(認証基盤)は、deletion_protection = "ACTIVE" で自らのdestroyを拒否するように作ってあった。これを外すところから埋葬が始まるのは、少し皮肉だった。
Connectインスタンス0件・Cognitoプール消滅・KMSエイリアス0件をAPIで確認し、課金は完全にゼロになった。番号を持っていないことが、畳むときだけは楽だった。
リポジトリは公開に切り替えるため、全69コミットの履歴を監査した。APIキー・秘密鍵・JWT・個人情報(電話番号・住所)のパターンを全リビジョンに対して走査する。
ヒットなし。.env.example は歴代12バージョンすべてで OPENAI_API_KEY= が空であることも確認した。
到達点 — 技術は完走している
供養として、このプロジェクトが持ち帰ったものを並べておく。どれも電話番号がなくても再現できる(そのための合成通話フィクスチャまで作った)。
- KVSの音声はMKVコンテナで、CodecIDが”A_AAC”を詐称して中身は生L16 PCMだった。トラック1=相手、トラック2=自分で、最初から話者分離が要らない
- 通話路を覗くな、シグナリングを引け — 呼の存在はストリーム一覧から推測せず、コールフローのLambda→SQSで通知する。電話網のシグナリング/通話路分離そのもの
- gpt-audioへの出力形式指示はuserターンに置かないと効かない(0/5→5/5)
- 声は言葉より早い — 声のトーン判定は、テキスト判定が警報を出す2発話前に怒りを検出した
- 怒った通話をTTSで合成し、KVS互換MKVとして流す — 実架電せず、怒鳴らずに全パイプラインを検証する
最終形はReact+TypeScript+ViteのSPAにCognito認証(SRP)とロール別認可(監視卓/応対者)まで載り、バックエンド53本+フロント22本のテストとCIが守っている。動くものは録音リプレイで今日でも動く。
学び
- 電話番号はサーバーではない。資産である。 IaCの「作って壊す」に番号を巻き込んではならない。最初の1本を取ったら
lifecycle { prevent_destroy = true }を張り、destroyの対象から外す。月$2の維持費は、180日の呪いと引き換えなら安い - 失敗した操作が枠を消費する設計がある。 「もう一回試せばいいか」が通用しない資源では、リトライは無料ではない。連打する前にドキュメントの課金・クォータの節を読む
- PSTNは事業者の世界である。 番号に触る個人開発は、技術ではなくKYCが律速になることを予算に入れておく。これは規制の失敗ではなく、迷惑電話対策として合理的な制度がそこにあるだけだ
- 元・電話網エンジニアとして言えば、これは既視感のある話だった。電話番号が「加入権」という資産で、施設設置負担金を払って手に入れるものだった時代がある。クラウドのAPIの向こうでも、番号は今なお資産として制度設計されている。APIで一瞬で取れることと、使い捨てにしてよいことは違う
転生の可能性
心理分析のコア——話者別ストリーム→文字起こし→テキスト+声のトーンの融合判定→リアルタイムUI——はPSTNに依存していない。マイク入力や会議音声に載せ替えれば、電話番号なしで生き返る。埋葬とは、そういう可能性ごと土に還すことではなく、掘り起こせる場所に記録を残すことである。
リポジトリ: crossbar_telepath(埋葬にあわせて公開)


コメント