Skip to content

fix(text2speech): route synthesis through Smalruby CORS proxy (#859)#861

Merged
takaokouji merged 3 commits into
developfrom
topic/autopilot-859
Jul 1, 2026
Merged

fix(text2speech): route synthesis through Smalruby CORS proxy (#859)#861
takaokouji merged 3 commits into
developfrom
topic/autopilot-859

Conversation

@smalruby3-editor-bot

Copy link
Copy Markdown

Summary

音声合成拡張(text2speech / 「音声で話す」)も翻訳(#857)と同じ CORS 構造で、Scratch の synthesis
サービス(synthesis-service.scratch.mit.edu)直叩きが CORS で失敗する。#857 と同方針で Smalruby
プロキシ経由
に切り替える。synthesis 用プロキシは未整備だったので新設した。

translate(テキスト)との最大の違いは 応答がバイナリ音声(mp3) な点。プロキシは Base64 で返却し
isBase64Encoded: true)、API Gateway HTTP API v2 がクライアントへバイト列にデコードする(既存
cors-proxy のバイナリ Base64 化と同じ考え方)。

Changes Made

1. synthesis プロキシ Lambda を新設(infra/smalruby-api

  • lambda/scratch-api-synthesis.ts を新規作成(scratch-api-translate.ts を雛形に)。
    • GET /scratch-api-proxy/synth?locale=&gender=&text= を受け、https://synthesis-service.scratch.mit.edu/synth?...
      へサーバ側 fetch。成功時はバイナリ音声を Base64 返却(Content-Type: audio/mpeg)、
      上流エラーは status を透過して JSON で返す。locale/gender 欠落は 400、fetch 失敗は 502。
  • lib/smalruby-api-stack.ts に Lambda + ルート /scratch-api-proxy/synth(GET) を追加。
  • lambda/tests/scratch-api-synthesis.test.ts(mocked fetch): 400/正常 Base64/URL エンコード/上流エラー透過/502 の 6 ケース。

2. text2speech 拡張をプロキシに向ける(scratch-vm

  • scratch3_text2speech/index.jsSERVER_HOSThttps://api.smalruby.app/scratch-api-proxy に上書き。
    拡張が ${SERVER_HOST}/synth?... を組むため base 差し替えのみで proxy 経由になる。
  • upstream ファイルなので Smalruby マーカーで囲み、docs/maintenance/smalruby-markers-vm.md に記録。
    マーカー無し上書きが v13.7.2 マージで静かに revert された translate(fix(translate): 翻訳拡張が CORS で失敗(Scratch 直叩き→Smalruby プロキシ経由に) #857) の再発を防ぐガードテスト
    test/unit/extension_text2speech_proxy.js を追加。

Test Coverage

  • infra/smalruby-api: npm test -- scratch-api-synthesis → 6/6 pass
  • scratch-vm: npm exec tap -- test/unit/extension_text2speech_proxy.js → 3/3 pass
  • scratch-vm lint: 0 errors / prettier OK、infra/smalruby-api tsc --noEmit clean

🙋 HITL — 人間にお願いしたいこと(deploy + 実機確認)

issue の制約どおり、autopilot はコード実装まで。以下は人間の作業(devpod ワークフロー、AWS SSO 要):

  1. CDK deploy(stg → prod):
    cd infra/smalruby-api
    rm .env && ln -s .env.stg .env      # stg
    npx cdk diff && npx cdk deploy
    npm run test:integration            # /scratch-api-proxy/synth を含む確認
    rm .env && ln -s .env.prod .env     # prod
    npx cdk diff && npx cdk deploy
    api.smalruby.app は既存カスタムドメイン import 方式なのでルート追加のみ。.env/CI への影響は無し。
  2. 実機確認: smalruby.app で「音声で話す」ブロックが実際に鳴ること(本番 Chrome)。

Related Issues

Closes #859
関連: #857(translate。同じ根本原因・方針)

Scratch's synthesis service (synthesis-service.scratch.mit.edu) is CORS-locked to
scratch.mit.edu, so smalruby.app is blocked when calling it directly — same root
cause as translate (#857). Route text2speech through a new Smalruby proxy endpoint.

infra/smalruby-api: add scratch-api-synthesis Lambda + GET /scratch-api-proxy/synth
route. Unlike translate (text), synthesis returns binary audio (mp3), so the proxy
Base64-encodes it (isBase64Encoded: true) and API Gateway decodes it for the client
(same approach as cors-proxy binary handling). Adds mocked-fetch unit tests.

scratch-vm: override SERVER_HOST in scratch3_text2speech (upstream file) with Smalruby
markers so ${SERVER_HOST}/synth targets the proxy. Guard test + smalruby-markers-vm.md
entry so future upstream merges detect the override (as happened to translate).

CDK deploy and real-device audio playback are left to a human (HITL) per the issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@smalruby3-editor-bot smalruby3-editor-bot Bot added the 🤖 autopilot autopilot(AI)が管理する PR/Issue(AI処理対象) label Jul 1, 2026
@smalruby3-editor-bot

smalruby3-editor-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Author

🤖 autopilot status

field value
Status Review
AI Status
HITL Yes
Size middle

Linked issue #859. Maintained by autopilot (single writer); do not edit.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚀 Preview deployed: https://smalruby.jp/smalruby3-editor/topic/autopilot-859/

The synthesis proxy override (#859) turns text2speech into an upstream
modification, but its feature doc still declared it upstream-as-is. Update
the badge to 🔧 and document the CORS proxy routing, mirroring translate
(#857) so the DoD upstream-divergence rule is satisfied.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

🤖 敵対的レビュー結果(autopilot review フェーズ)

別レビュアー視点で差分(8 ファイル、synthesis CORS プロキシ)を検証しました。

✅ 確認できたこと

  • 正確性: SERVER_HOST の唯一の利用箇所(${SERVER_HOST}/synth?...)が正しくプロキシ base に向く。text の encode 往復(拡張 encodeURIComponent → API GW デコード → Lambda URLSearchParams 再エンコード)は単一エンコードで整合。
  • バイナリ返却: Buffer.from(arrayBuffer).toString('base64') + isBase64Encoded: true は同スタックの実績パターン(cors-proxy.ts)と同一。translate より堅牢(status レンジ判定+上流エラー透過)。
  • セキュリティ: 送信先ホスト固定(SSRF なし)、URLSearchParams で注入なし、秘密情報なし、認可は translate と同じく公開プロキシ。
  • 規約/テスト: upstream ファイルは Smalruby マーカーで囲み smalruby-markers-vm.md に記録、新テストを .prettierignore ホワイトリストへ追加。ガードテストで upstream マージ時の silent revert(fix(translate): 翻訳拡張が CORS で失敗(Scratch 直叩き→Smalruby プロキシ経由に) #857 再発)を防止。
  • ローカル再実行: scratch-vm proxy テスト 6/6、synthesis Lambda 6/6、scratch-vm lint 0 errors / prettier OK、infra tsc --noEmit clean。

🔧 レビューで自分で修正した点(1 件)

⏳ 人間への依頼(HITL)

PR 本文どおり CDK deploy(stg→prod)と実機での音声再生確認 は AWS SSO を要する人間作業です。手順は PR 本文に記載済み。

@smalruby3-editor-bot smalruby3-editor-bot Bot added the 🙋 HITL 人間の対応待ち(レビュー/判断/マージ) label Jul 1, 2026
@smalruby3-editor-bot smalruby3-editor-bot Bot marked this pull request as ready for review July 1, 2026 10:32

@takaokouji takaokouji left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確認してほしいのですが fix(translate): 翻訳拡張が CORS で失敗(Scratch 直叩き→Smalruby プロキシ経由に)
#857 で利用しているプロキシは汎用的な proxy になっているはずで、サービスごとにつくらなくてもいいはずです。そのため、単純に同じ proxy を指定するだけで、パスが異なるために動作するはずです。確認して、私の認識があっていたら、実装を変更してください。

@takaokouji takaokouji removed the 🙋 HITL 人間の対応待ち(レビュー/判断/マージ) label Jul 1, 2026
…d synth Lambda

レビュー指摘 (#861) を受けて、音声合成 (text2speech) の CORS 回避を専用 Lambda では
なく Smalruby の**汎用** cors-proxy (`GET /cors-proxy?url=<encoded 合成URL>`) の再利用に
切り替える。汎用 cors-proxy は既に `audio/*` をバイナリ判定して Base64 返却するため、
専用 `scratch-api-synthesis` Lambda・ルート・integration の追加もインフラの再デプロイも
不要になる。

- `scratch3_text2speech/index.js`: `SERVER_HOST` を upstream 値に戻し、合成 URL 組み立て
  箇所だけを generic cors-proxy でラップ (Smalruby マーカーで囲む)。upstream 差分が最小化。
- `infra/smalruby-api`: 追加した `scratch-api-synthesis.ts` / そのテスト / stack のルートを削除。
- docs (extension-text2speech / smalruby-api / markers-vm) を汎用プロキシ方式に更新。
- ガードテスト `test/unit/extension_text2speech_proxy.js` を cors-proxy 経路の検証に更新。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

@takaokouji ご指摘ありがとうございます。確認した結果を報告します。

確認結果

ご認識はおおむね正しく、専用 Lambda は不要でした。実装を切り替えました 👍

ただし細部で 1 点補足があります:

  • 汎用プロキシは存在します: GET /cors-proxy?url=<対象URL>infra/smalruby-api/lambda/cors-proxy.ts)が任意 URL をサーバ側で fetch する汎用プロキシで、audio/*バイナリ判定して Base64 返却します(音声合成にそのまま使えます)。しかも prod に既にデプロイ済みです。
  • ただし fix(translate): 翻訳拡張が CORS で失敗(Scratch 直叩き→Smalruby プロキシ経由に) #857 (translate) はその汎用プロキシを使っていません: /scratch-api-proxy/translate は専用 Lambda (scratch-api-translate.ts) で、/scratch-api-proxy/* の各パスはそれぞれ別 Lambda に紐づく hardcoded ルートです(汎用パススルーではない)。なので「同じ proxy にパスだけ変えて指す」だと結局 synth 用の Lambda が必要でした。
  • ご意図(サービスごとに作らない)は達成できます: 音声合成を汎用 cors-proxy?url= 経由にすればよく、これに切り替えました。

変更内容 (commit 5894bb0e60)

  • scratch3_text2speech/index.js: SERVER_HOST を upstream 値 (synthesis-service.scratch.mit.edu) に戻し、合成 URL の組み立て箇所だけを https://api.smalruby.app/cors-proxy?url=<encoded 合成URL> でラップ(Smalruby マーカー内)。upstream 差分が最小化されます。
  • infra/smalruby-api: 新設していた scratch-api-synthesis.ts / そのテスト / stack のルートを削除
  • docs(extension-text2speech / smalruby-api / markers-vm)とガードテストを汎用プロキシ方式に更新。

効果

  • 専用 Lambda の追加なし
  • インフラの再デプロイ不要cors-proxy は prod に既存)。→ 当初 HITL でお願いしていた「CDK deploy (stg→prod)」は不要になり、残る人間作業は実機での音声再生確認のみです。

ローカル確認: scratch-vm proxy テスト 4/4 pass、scratch-vm lint 0 errors / prettier OK、infra/smalruby-api テスト 23/23 pass・tsc --noEmit clean。

補足: 一貫性の観点では translate も同様に汎用 cors-proxy へ寄せられますが、#857 で既にマージ済みのため本 issue (#859) の範囲外とし、別途フォローアップにするのが安全と考えます。ご希望あれば対応します。

@smalruby3-editor-bot smalruby3-editor-bot Bot added the 🙋 HITL 人間の対応待ち(レビュー/判断/マージ) label Jul 1, 2026
@takaokouji takaokouji merged commit e3d6772 into develop Jul 1, 2026
9 checks passed
@takaokouji takaokouji deleted the topic/autopilot-859 branch July 1, 2026 14:01
github-actions Bot pushed a commit that referenced this pull request Jul 1, 2026
…ot-859

fix(text2speech): route synthesis through Smalruby CORS proxy (#859)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 autopilot autopilot(AI)が管理する PR/Issue(AI処理対象) 🙋 HITL 人間の対応待ち(レビュー/判断/マージ)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(text2speech): 音声合成も CORS 対策(synthesis プロキシ新設→プロキシ経由に)

1 participant