Commit Graph

278 Commits

Author SHA1 Message Date
dependabot[bot] 8623e4e80a chore(deps): Bump the npm_and_yarn group across 3 directories with 3 updates
Bumps the npm_and_yarn group with 2 updates in the / directory: [fast-xml-builder](https://github.com/NaturalIntelligence/fast-xml-builder) and [fast-uri](https://github.com/fastify/fast-uri).
Bumps the npm_and_yarn group with 2 updates in the /website/client directory: [fast-uri](https://github.com/fastify/fast-uri) and [@babel/plugin-transform-modules-systemjs](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-modules-systemjs).
Bumps the npm_and_yarn group with 2 updates in the /website/server directory: [fast-xml-builder](https://github.com/NaturalIntelligence/fast-xml-builder) and [fast-uri](https://github.com/fastify/fast-uri).


Updates `fast-xml-builder` from 1.1.4 to 1.1.7
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-builder/blob/main/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-builder/compare/v1.1.4...V1.1.7)

Updates `fast-uri` from 3.1.0 to 3.1.2
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2)

Updates `fast-uri` from 3.0.6 to 3.1.2
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2)

Updates `@babel/plugin-transform-modules-systemjs` from 7.25.9 to 7.29.4
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.29.4/packages/babel-plugin-transform-modules-systemjs)

Updates `fast-xml-builder` from 1.1.4 to 1.2.0
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-builder/blob/main/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-builder/compare/v1.1.4...V1.1.7)

Updates `fast-uri` from 3.1.0 to 3.1.2
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: fast-xml-builder
  dependency-version: 1.1.7
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: fast-uri
  dependency-version: 3.1.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-uri
  dependency-version: 3.1.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: "@babel/plugin-transform-modules-systemjs"
  dependency-version: 7.29.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-xml-builder
  dependency-version: 1.2.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-uri
  dependency-version: 3.1.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-10 05:49:37 +00:00
dependabot[bot] 4492598a84 chore(deps): Bump the npm_and_yarn group across 3 directories with 3 updates
Bumps the npm_and_yarn group with 2 updates in the / directory: [hono](https://github.com/honojs/hono) and [ip-address](https://github.com/beaugunderson/ip-address).
Bumps the npm_and_yarn group with 1 update in the /website/client directory: [serialize-javascript](https://github.com/yahoo/serialize-javascript).
Bumps the npm_and_yarn group with 2 updates in the /website/server directory: [hono](https://github.com/honojs/hono) and [ip-address](https://github.com/beaugunderson/ip-address).


Updates `hono` from 4.12.14 to 4.12.18
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.14...v4.12.18)

Updates `ip-address` from 10.1.0 to 10.2.0
- [Commits](https://github.com/beaugunderson/ip-address/commits)

Updates `hono` from 4.12.14 to 4.12.18
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.14...v4.12.18)

Updates `ip-address` from 10.1.0 to 10.2.0
- [Commits](https://github.com/beaugunderson/ip-address/commits)

Updates `serialize-javascript` from 6.0.2 to 7.0.5
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.2...v7.0.5)

Updates `hono` from 4.12.16 to 4.12.18
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.14...v4.12.18)

Updates `ip-address` from 10.1.0 to 10.2.0
- [Commits](https://github.com/beaugunderson/ip-address/commits)

Updates `hono` from 4.12.16 to 4.12.18
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.14...v4.12.18)

Updates `ip-address` from 10.1.0 to 10.2.0
- [Commits](https://github.com/beaugunderson/ip-address/commits)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.18
  dependency-type: indirect
- dependency-name: hono
  dependency-version: 4.12.18
  dependency-type: direct:production
- dependency-name: ip-address
  dependency-version: 10.2.0
  dependency-type: indirect
- dependency-name: ip-address
  dependency-version: 10.2.0
  dependency-type: indirect
- dependency-name: serialize-javascript
  dependency-version: 7.0.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-09 16:04:53 +00:00
Kazuki Yamada a27cf29a4e Merge pull request #1550 from yamadashy/renovate/npm-hono-vulnerability
chore(deps): update dependency hono to v4.12.16 [security]
2026-05-09 21:17:37 +09:00
Kazuki Yamada 5a1423e118 chore: Replace npm run with node --run inside package.json scripts
Follow up to commit 042750c which only converted workflow-level
invocations. With Node.js 22 as the floor, the chained scripts inside
each package.json can also use `node --run` directly, dropping the
intermediate npm process when these scripts run.

- root `prepare`
- browser `build-all`, `lint`
- website/client `lint`
- website/server `lint`
2026-05-09 19:55:06 +09:00
Kazuki Yamada 042750cb4e chore(ci): Replace npm run with node --run in workflows
Now that the minimum supported Node.js version is 22, `node --run` is
available everywhere. It avoids the npm process-spawn overhead and
matches the style already used in package.json scripts.

Affects all GitHub Actions workflows that invoke npm scripts and the
website/server Dockerfile bundle step. `npm ci` is left as-is since it
is npm-specific.
2026-05-09 19:08:10 +09:00
renovate[bot] 1f080c1163 chore(deps): update dependency hono to v4.12.16 [security] 2026-05-08 12:18:55 +00:00
Kazuki Yamada 54c6a3d238 fix(website): Address claude third-pass review on siteverify metric
Six items from claude's incremental review (`12:48:43Z`):

- monitoring/dashboard.json: Group the outcomes widget by both
  `metric.label.outcome` and `metric.label.reason`. Previously all
  failures collapsed into a single `turnstile_failed` series, which
  contradicted the README claim that the `reason` label drives the
  breakdown.
- monitoring/metrics/*.yaml: Narrow the metric filter to
  `jsonPayload.event=("turnstile_siteverify" OR "pack_completed")`.
  Without this anchor, any future code path attaching
  `siteverifyDurationMs` to an unrelated log silently joins the
  distribution and creates new metric label values.
- usePackRequest.ts: Mirror `progressMessage.value = null` alongside
  the `progressStage.value = null` clear on token-acquisition aborted /
  error branches. Prevents a future edit setting a verifying message
  from leaking prior-run state.
- turnstile.test.ts: Add a focused `describe` block with five tests
  asserting `siteverifyDurationMs` is attached to every post-siteverify
  log (one success path + four reject branches). The metric YAML
  filters on field presence, so a refactor that drops the field on any
  branch would silently break the metric without other tests failing.
  Uses the existing `vi.spyOn(logger, ...)` pattern; no clock injection
  needed.
- monitoring/README.md: Note that the metric filter pins
  `service_name="repomix-server-us"`, so future regions (`-eu`,
  `-asia`) silently drop out until the filter is broadened or
  per-region counterparts applied.
- monitoring/README.md: Add a `gcloud logging metrics describe` snippet
  for verifying a YAML edit was actually applied (gcloud update is
  silent on no-op vs effective change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:59:53 +09:00
Kazuki Yamada fa06e5059c fix(website): Address PR review feedback on siteverify metric
Six items from gemini, claude initial review, and claude follow-up:

- turnstile.ts: Update misleading comment that claimed the metric filters
  on `event=turnstile_siteverify` and `outcome=success`. The actual
  Cloud Monitoring metrics in `monitoring/metrics/` filter on
  `siteverifyDurationMs` field presence, which uniformly captures both
  the parallel success log (event=turnstile_siteverify) and the four
  rejectAndLog failure paths (event=pack_completed). The comment
  contradicted README and YAML and would mislead future readers.
- turnstile.ts: Wrap rejectAndLog in a local `rejectWithDuration` helper
  so every post-siteverify branch automatically carries
  `siteverifyDurationMs`. Prevents drift if a fifth reject reason gets
  added later.
- client.ts: Split the wire-protocol `PackProgressStage` (server-emitted
  SSE values) from the display-only `DisplayProgressStage` superset that
  adds `verifying`. Keeping the synthetic stage out of the wire type
  prevents silent divergence with the server's `PackProgressStage`.
- usePackRequest.ts, TryItLoading.vue, TryItResult.vue: Switch the
  display-side type to `DisplayProgressStage`. `onProgress` callbacks
  still take the wire `PackProgressStage`.
- usePackRequest.ts: Clear `progressStage` on token-acquisition failure
  branches (aborted / error). Functionally invisible since loading=false
  hides the loading UI, but prevents the next submit's verifying flash
  from briefly showing the previous run's stale state.
- monitoring/metrics/turnstile_siteverify_duration.yaml: Retune the
  exponential bucket layout for the 100ms-1s SLO band where decisions
  get made. Doubling buckets only placed ~3 boundaries between 100ms
  and 1s; growthFactor=1.5 with scale=10 places ~8 boundaries there.
  18 finite buckets cover 10ms to ~9.85s, comfortably above the 5s
  siteverify timeout so timeouts don't land in overflow.
- monitoring/README.md: Document that pre-network rejections
  (secret_missing, missing_token, token_too_long) intentionally don't
  carry siteverifyDurationMs, so they're excluded from both metrics
  but still appear in the existing pack_requests metric.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:42:21 +09:00
Kazuki Yamada 76522fc13e chore(monitoring): Check in Turnstile siteverify metric definitions
Move the metric setup from prose-only README instructions to checked-in
YAML files under `monitoring/metrics/` so the dashboard, the metrics it
depends on, and the apply commands all live next to each other.

- `turnstile_siteverify_duration.yaml`: distribution metric on
  `jsonPayload.siteverifyDurationMs`, exponential buckets 1ms-32s.
- `turnstile_siteverify_outcomes.yaml`: counter metric with `outcome`
  and `reason` labels for the success-vs-failure breakdown widget.

README updated with the gcloud commands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:24:48 +09:00
Kazuki Yamada aea2401bfc chore(monitoring): Add Turnstile siteverify dashboard widgets
Wire up two new tiles to surface the `siteverifyDurationMs` field added
in the previous commit:

- "Turnstile siteverify latency P50 / P95 / P99" — line chart with a
  1s threshold marker so a steady regression jumps off the chart.
- "Turnstile siteverify outcomes (by outcome)" — stacked area
  breaking down success vs turnstile_failed counts over time.

Both depend on log-based metrics `turnstile_siteverify_duration`
(distribution) and `turnstile_siteverify_outcomes` (counter) that need
to be created once in the GCP Console — README documents the filter,
field, and label extractors so the setup is reproducible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:21:47 +09:00
Kazuki Yamada 35c56abd02 perf(website): Show verifying step + emit siteverify duration metric
Two changes targeting the visible "..." gap between Pack click and the
first SSE progress event observed after PR #1544 landed:

- Client: add a synthetic `verifying` PackProgressStage so the loading
  UI displays "Verifying request..." while the server runs Turnstile
  siteverify (typically 100-1000ms before the first 'cache-check' SSE
  event arrives). The first onProgress callback from handlePackRequest
  overwrites it with the real server-reported stage.

- Server: time the siteverify round-trip in `turnstileMiddleware` and
  emit `siteverifyDurationMs` on every outcome (success / network
  failure / rejected / action mismatch / hostname mismatch). Success
  path adds a structured log with `event: turnstile_siteverify` so
  Cloud Monitoring can build a log-based distribution metric for
  p50/p95/p99 latency and alert on regressions during Cloudflare
  incidents.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:19:01 +09:00
autofix-ci[bot] ac91b99397 [autofix.ci] apply automated fixes 2026-05-03 16:17:10 +00:00
Kazuki Yamada b58644154e fix(website): Address claude iteration 2 review
intent(maintainability): claude のフォローアップレビューで残った Improve / test gap / nit を対応。production 影響はないが将来の保守性を上げる。

fix(server/log-helper): turnstile.ts で 7 回繰り返されていた `{ event, outcome, reason, requestId, source, ...cf }` の log payload 構築を `rejectAndLog(reason, message, level, extra)` ヘルパーにまとめた。約 40 行短縮、新しい reason を追加するときに envelope を取りこぼすリスクが消える。
fix(server/extract-siteverify): 同ファイル内に `runSiteverify` を抽出して try/catch を内包。中身が無関係な fail-closed 経路 (network error, JSON parse error) を Error sentinel として返し、middleware 本体はポリシー(reject vs pass)だけに集中させる。
fix(client/cli-fallback): script-blocked エラー時のメッセージに `npx repomix --remote owner/repo` の CLI 案内を追加。privacy extension で詰まったユーザーへの逃げ道を提示。
fix(client/file-size): useTurnstile.ts が 286 行 → 250 行ガイドライン超過していたため、script load 部分を `useTurnstileScript.ts` に分離。useTurnstile 側は widget lifecycle / token request / abort に集中。

test(turnstile): 18 → 20 cases。
- siteverify が non-JSON (HTML 5xx ページ等) を返した時に runSiteverify が Error sentinel に変換し、middleware が 403 fail-closed する経路をカバー
- cross-stack contract: server の EXPECTED_TURNSTILE_ACTION (export) と client の useTurnstile.ts に埋め込まれた `action: 'pack'` 文字列が一致する、を regex で検証。片側 rename で他方が silent break する drift を防ぐ

skipped (with reason):
- Log sampling (claude #1): 現状スケール (21K req/day = 0.24 req/sec) で Cloud Logging quota 影響なし、metric 移行のときに併せて検討
- secretMissingLogged tautology (claude #2): 既に `0b09cf9d` で logger spy に置き換え済み
- hostname check (claude #3): 既に `0b09cf9d` で ALLOWED_HOSTNAMES として実装済み
- siteverify timeout fake-timer test: codex も round 2 で skip 推奨、setup が複雑で得る保証が小さい
- Client useTurnstile.ts component test: Vue test 環境が未整備
- External /api/pack consumer release note: code change ではないので別途
- secret_missing alert: out of scope の follow-up issue

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 01:15:51 +09:00
autofix-ci[bot] 74312781f4 [autofix.ci] apply automated fixes 2026-05-03 14:29:56 +00:00
Kazuki Yamada 0b09cf9dfa fix(website): Address codex re-review (round 2)
intent(robustness): codex の再レビューで残った blocker と should-fix を全て対応。

fix(client/stale-callback): submitRequest の onSuccess/onError/onAbort/onProgress と getToken catch ブロック内の state mutation 全てを `isCurrent()` (= `requestController === controller`) でガード。連打で新 request が始まった後、旧 request の callback が新 request の loading/result/error を上書きする race を解消。
fix(client/abort-during-script-load): getToken に signal が渡された場合、ensureWidget() を AbortSignal-aware な Promise.race に変更。script load 中の cancel/timeout が即座に拾える。listener 登録直前にも signal.aborted を再チェック。
fix(client/site-key-throw): production で VITE_TURNSTILE_SITE_KEY 未設定なら useTurnstile() で throw。前は console.error だけだったので smoke test で見落とし得たが、throw にすればフォーム初期化が即失敗して misconfig が unmissable になる。
fix(server/hostname-claim): siteverify レスポンスの hostname を ALLOWED_HOSTNAMES (`['repomix.com']`) と照合。leaked sitekey が攻撃者ドメインで使われた場合に弾く。test sitekey は hostname を返さないので undefined は backward-compat で許可(action 検証と同じパターン)。
fix(client/window-callback-cleanup): READY_CALLBACK が success path でも `delete` されるように。stale closure 残存を完全に排除。

test(turnstile): 14 → 18 cases に拡張。
- secretMissingLogged を logger spy で「dev/test では 1 回、production では毎回」を直接 assert に変更(前は expect(true) のプレースホルダだった)
- hostname allowed / mismatch / omitted (backward-compat) を追加

skipped (codex 認定): middleware reorder。codex も「siteverify 外部呼び出しを増やすリスクがあるので見送り妥当」と再確認。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:19:00 +09:00
Kazuki Yamada e96835a0ad fix(website): Address claude review feedback on Turnstile
intent(robustness): claude のレビューで追加指摘が複数あったので、cancel UX と script-load 失敗時の冗長性を改善する。

fix(client/abort-signal): getToken に AbortSignal を追加。usePackRequest が controller.signal を渡すことで、cancel/timeout 中の Turnstile challenge が即座に中断される。前は getToken が hung している間(最大 15s)cancel が反映されない UX gap があった。
fix(client/onload-cleanup): resetForRetry で window[READY_CALLBACK] も delete する belt-and-suspenders。late-arriving script load が stale closure を解決する事故を排除。
fix(client/error-message): production で getToken 失敗時のエラーメッセージを 2 種類に分岐。"script/load/missing" を含むエラーは「ad blocker / privacy extension を無効化してリロード」、それ以外は generic verification failure。recovery path を提示してサポート負荷を減らす。
fix(client/cancel-during-getToken): getToken が abort で reject された場合、cancel reason に応じて「Request was cancelled」「Request timed out」を表示。短絡で handlePackRequest をスキップしてもメッセージは一致させる。

test(turnstile): remoteip omission/inclusion・secretMissingLogged 連続呼出・error-codes 配列で middleware が壊れない、を追加。10 → 14 cases。

skipped (low ROI):
- verifyResult の zod validation: Cloudflare API は安定、過剰防衛
- hostname の cross-check: Site Key が Cloudflare 側で hostname-pinned 済み
- siteverify timeout の fake-timer test: 設定が複雑な割に得られる保証が小さい

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:02:35 +09:00
autofix-ci[bot] 33260d42ea [autofix.ci] apply automated fixes 2026-05-03 13:59:00 +00:00
Kazuki Yamada 3c07e5a424 fix(website): Harden Turnstile integration per codex review
intent(robustness): codex の追加レビューで複数の race condition / セキュリティ穴 / config bug が指摘されたため、production 適用前に潰す。

fix(client/timer-leak): useTurnstile の Promise.race timeout が clearTimeout されておらず、stale timer が次の getToken の pendingResolve/pendingReject を消す race を解消。generation counter (currentGen) を導入し、callback / timeout / supersede 全てを gen 検査でガード。
fix(client/finally-race): submitRequest の finally が後続リクエスト中の共有状態 (loading, requestController) を上書きする問題を解消。`if (requestController === controller)` で local controller が現役のときだけ reset。
fix(server/prod-fail-closed): production で TURNSTILE_SECRET_KEY 未設定のときは fail-open ではなく 403 fail-closed (`reason: secret_missing`)。dev/test は従来通り skip。`isProduction` を deps として注入することでテスト容易性を確保。
fix(server/action-claim): siteverify の `action` claim を `pack` と一致するか verify。token を将来の別エンドポイントから replay されないようにする。client widget 側に `data-action='pack'` を付与済み。Cloudflare のテストキーは action を返さないため undefined は backward-compat で許可。
fix(server/token-validation): token を trim、空白 only / 2048 文字超を siteverify 前に 403。Cloudflare の上限を超える token は guaranteed-invalid なので siteverify 呼ばずに弾く。
fix(client/prod-fallback-warn): VITE_TURNSTILE_SITE_KEY 未設定の production ビルドで console.error。test sitekey が本番に混入したまま deploy される事故を smoke test で気づけるようにする。
fix(client/prod-fail-fast): production で getToken 失敗時は /api/pack を呼ばずユーザー向けエラー表示。サーバ 403 確定なので origin hit を節約。dev/preview では従来通り token なしで続行。

rejected(middleware-reorder): codex は Turnstile を rate limit *前* に置けば token 無し bot が Upstash daily limit を消費しない、と指摘していたが、middleware の登録順を変える影響範囲が大きく、token max length pre-check で大半の cheap reject は実現できているため見送り。

test(turnstile): production fail-closed / whitespace token / oversized token / action mismatch / action absent (backward-compat) を追加。10 cases pass。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:57:47 +09:00
Kazuki Yamada 9d430f7923 fix(website): Address PR review feedback on Turnstile integration
intent(robustness): PR #1538 のレビューで複数の resilience / race condition の指摘を受けたため、Turnstile 統合の堅牢性を上げる。
fix(client/race): submitRequest 内で requestController を local const にキャプチャ。await turnstile.getToken() 中に cancelRequest() が走ると requestController=null になり、後続の requestController.signal アクセスで TypeError が出ていた (devin 指摘)。
fix(client/script-retry): scriptPromise を rejection 時に reset し、dead <script> タグを DOM から除去。CDN blip で 1 回失敗するとセッション中ずっと getToken が永久に rejection を返す問題を解消 (coderabbit 指摘)。
fix(client/hang): getToken に 15s の Promise.race timeout 追加。Cloudflare の timeout-callback は invisible widget では発火しないため、CDN stall 時にローディングスピナが永久に止まらない問題を解消 (coderabbit 指摘)。
fix(client/unmount): onBeforeUnmount で pending getToken を reject。コンポーネント破棄後も await 側にハングしていた問題を解消 (gemini 指摘)。
fix(server/remoteip): clientInfo.ip が '0.0.0.0' (=IP 取得失敗時の sentinel) の場合は siteverify の remoteip フィールドをオミット。Cloudflare の risk scoring を混乱させない (gemini 指摘)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:27:35 +09:00
Kazuki Yamada 599b8df6cb fix(website): Allow X-Turnstile-Token in CORS preflight
intent(cors): X-Turnstile-Token ヘッダを `/api/pack` で受け入れるため、CORS の allowHeaders に追加。
constraint(preflight): Custom request header はブラウザが自動的に preflight (OPTIONS) を発行する。allowHeaders に列挙されていないと preflight が失敗し、本リクエスト (POST) が一切送信されない — Turnstile を入れても発火しない死に体になる。
learned(deployment-checklist): 新しいヘッダを使うミドルウェアを追加するときは、CORS 設定にもセットで追加するのを忘れない。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:03:44 +09:00
Kazuki Yamada 184fb4b0b8 chore(website): Wire TURNSTILE_SECRET_KEY into Cloud Run deploy
intent(deployment): /api/pack の Turnstile 検証を本番で有効化するため、新しいシークレット参照を Cloud Build の deploy ステップに追加。
constraint(secret-manager): GCP Secret Manager 上に `turnstile-secret-key` を事前作成、Cloud Run のサービスアカウント (`{PROJECT_NUMBER}-compute@developer.gserviceaccount.com`) に `roles/secretmanager.secretAccessor` を付与済み。
decision(secret-version): `:latest` で参照することで、Secret 値のローテーション(version 追加 → 旧版 destroy)を deploy なしで反映できるようにする。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:58:34 +09:00
Kazuki Yamada 523fb07111 feat(website): Add Cloudflare Turnstile verification to /api/pack
intent(pack-defense): Repomix を「コード抽出 API」として大量に叩く匿名クローラ対策。GA + Cloud Run logs の調査で 2026-04 末に 21K+ unique GitHub repos を 7 日で系統列挙されたインシデントが確認された。IP ベースの rate limit (3/min, 30/day) は機能していたが residential proxy + Tencent Cloud SG で 2,256+ unique IPs に分散され実質無力化されていた。
decision(turnstile-vs-asn): Turnstile (JS challenge) を採用。ASN ブロックは residential proxy で無力、daily limit 強化は IP 数で水増し可能。invisible JS challenge は residential proxy 経由でも通せないので一番 cost asymmetric。
constraint(scope): /api/pack のみ。docs / health / ホームページ閲覧は無関係 — Googlebot / GPTBot / ClaudeBot 等は GET HTML しか叩かないので SEO/LLMO に影響なし。
decision(fail-policy): TURNSTILE_SECRET_KEY 未設定なら fail-open(dev/preview を壊さない、警告ログは出す)。設定済みでトークン欠落・siteverify 失敗・ネットワーク失敗は全て fail-closed の 403。
decision(token-transport): X-Turnstile-Token ヘッダで送信。FormData フィールドにすると packRequestSchema (valibot) を汚染するため、cross-cutting concern として layer を分離。
decision(client-widget): invisible 不可視ウィジェットを TryIt.vue マウント時にレンダ、submit 直前に turnstile.execute() で 1-shot トークン取得。トークンは 5 分有効・1 回限りなので毎 pack で reset → execute。
rejected(form-field-token): cfTurnstileToken を FormData に入れる案 — packRequestSchema が strict object のため新フィールド追加が必要、ビジネスロジックと認証が混ざる。
rejected(asn-block): Tencent Cloud SG (AS132203) WAF ブロック — バルク部分には効くが residential proxy 部分(家庭 ISP・モバイル・大学ネット)が世界中に散らばっており ASN 単位で弾けない、正規ユーザを巻き込むリスク。
rejected(daily-limit-tightening): 30 → 10/day per IP — IP 数で水増しできる相手には無意味、人間ユーザの体験のみ悪化。
constraint(observability): outcome="turnstile_failed" として既存の pack_completed イベントに乗せる。新 metric 不要、既存ダッシュボードに自動で reject reason として現れる。
learned(rate-limit-effectiveness): スパイク期間中の SG pack 成功率は 0.15% (32/21,501)。app-level rate limit は処理は止めていたが入口の負荷(TCP/TLS/Upstash check)は受けていた。Turnstile は CDN 層に近く、より早く弾ける利点がある。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:41:04 +09:00
Kazuki Yamada 6dc0b0d446 Merge pull request #1497 from yamadashy/refactor/website-server-valibot
refactor(server): Migrate request validation from zod to valibot
2026-04-19 22:46:59 +09:00
Kazuki Yamada 65b6f115eb test(server): Anchor leading-prefix check on the exact defect shape
decision(assertion-precision): switch the "no stray leading \`: : \`" guard from \`not.toContain(': : ')\` to an anchored regex \`/^Invalid request: : /\` — both catch the defect today, but the anchored form documents exactly which prefix shape we're guarding against and rules out any legitimate \`: : \` that might appear later in the message

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 22:31:15 +09:00
Kazuki Yamada 61e853e318 test(server): Guard classifier against valibot PathItem drift
intent(drift-guard): claude's follow-up review flagged that the hand-rolled schemaErrorWith fixture can't catch a change in valibot's internal PathItem shape — fixture-based tests would stay green while production fails, so add one real-valibot-issue case
decision(fixture-vs-real): keep the existing fixture-based tests as the bulk of coverage (cheap, focused on classifier logic) and layer a single v.safeParse-driven test on top — the fixtures stay fast, the new test catches shape drift

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 22:16:46 +09:00
Kazuki Yamada fb23b963d7 refactor(server): Use v.trim() instead of v.transform(trim)
decision(idiomatic-valibot): replace three `v.transform((val) => val.trim())` sites in packRequestSchema with valibot 1.x's first-class `v.trim()` pipe action — purely cosmetic (behavior is identical) but aligns with how the rest of the schema uses dedicated pipe actions (v.minLength, v.maxLength, v.regex) rather than hand-rolled transforms

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:58:34 +09:00
Kazuki Yamada b25f4446d8 test(server): Relocate server tests into website/server
intent(test-ownership): move tests into website/server/tests/ so they collocate with the code under test and stop reaching up through three parents; reviewer follow-up wanted dedicated coverage and the root vs. website/server package boundary makes collocation the right long-term layout
decision(vitest-config): give website/server its own vitest.config.ts + `test` script; root's existing tests/**/*.test.ts include no longer catches server tests since they moved outside that tree, so the two test runs stay independent
decision(tsconfig-test): add tsconfig.test.json extending the build config and lift lint-tsc to `-p tsconfig.test.json` — the build tsconfig's rootDir: "./src" excludes tests/, so a single lint command wouldn't have type-checked them
learned(valibot-instanceof): with tests now resolving valibot from the same website/server/node_modules as validateRequest, the cause-check can go back to `instanceof v.ValiError` — the duck-type workaround was only needed when the root harness and server pulled different valibot copies
constraint(ci-website): added a `test-website-server` job that links the local repomix build the same way lint-website-server does; tests don't actually import repomix today, but colocation means they easily could later and the link step keeps parity
2026-04-19 21:47:29 +09:00
Kazuki Yamada f327590555 fix(monitoring): Correct dashboard legend template variable syntax
intent(readability): dashboard panels for options usage and cache hit
  ratio were rendering with empty legend values (e.g. "compress=",
  "removeComments=") making it impossible to distinguish true vs false
  series. Every other signal on the dashboard was fine — this was a
  subtle API mismatch
decision(labels-plural): GCP Cloud Monitoring widget legend templates
  use `${metric.labels.X}` (plural). The dashboard had
  `${metric.label.X}` (singular), which silently resolves to the empty
  string instead of erroring out. Replaced all 5 occurrences

Applied to the live GCP dashboard via `gcloud monitoring dashboards
update` before committing — legends now render correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:39:43 +09:00
Kazuki Yamada e53502eb46 fix(server): Address PR review feedback
fix(pack-event-test): migrate tests/website/server/packEventSchema.test.ts to the valibot-shaped `{ name, issues: [{ message, path: [{ key }] }] }` fixture so the invalid_format case — which relies on the path fallback — actually exercises the post-migration classifier
decision(path-formatting): replace the hand-rolled `path.map/filter/join` in both packEventSchema.ts and validation.ts with `v.getDotPath(issue)` — the valibot built-in does the same reconstruction and eliminates the two-site drift risk reviewers flagged
fix(validation-error-message): drop the leading `": "` when a valibot issue has no path (top-level checks like MISSING_INPUT / BOTH_PROVIDED), so the rendered AppError message reads cleanly
rejected(cause-walk-depth): coderabbit suggested walking `.cause` recursively up to depth 3 — declined because validateRequest wraps exactly once by contract and no second wrapper exists in the chain; defensive recursion would add surface area for no caller

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:23:45 +09:00
Kazuki Yamada 3c3d9ab1cd refactor(server): Migrate request validation from zod to valibot
intent(server): complete the project-wide zod → valibot swap started by the CLI config schema in #1489
constraint(pack-event-schema): valibot issue paths are `{ key }` objects, not primitives, so classifyRejectReason's `path === 'format'` fallback had to extract `segment.key` before joining
decision(validation-helper): handle ValiError via `instanceof` rather than the duck-typing used in src/shared/errorHandle.ts — website/server owns its own error chain with no worker-boundary crossings, so the cross-library duck check is unnecessary noise here
learned(server): website/server has no test suite (lint is `tsgo --noEmit` only), so behavior parity was verified manually via a one-off tsx smoke script hitting each reject-reason bucket; MCP tool schemas stay on zod because @modelcontextprotocol/sdk's `AnySchema` is `z3.ZodTypeAny | z4.\$ZodType` with no standard-schema support yet

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:44:48 +09:00
Kazuki Yamada 10846fbf8d refactor(server): Extract validation messages to shared constants
Addresses PR #1493 round 5 follow-up (schema-drift tracking) from claude.

intent(eliminate-drift): prior reviews flagged the string-coupling
  between packRequestSchema (produces zod messages) and
  classifyRejectReason (matches them back to a metric label) as
  fragile — a message rewrite in one place would silently land
  requests in the 'other' bucket, quietly mislabeling the dashboard.
  Extract all 11 messages to a shared MESSAGES module so the
  producer, the consumer, and the test's expected values all
  reference the same constants
decision(map-over-switch): classifyRejectReason's 11-case switch
  becomes a MESSAGE_TO_REASON lookup table. Same runtime behavior,
  easier to scan, and the keys are compile-time-typed via the
  shared constants (so a typo in either side is a TS error)
decision(preserve-invalid_format-path): 'invalid_format' is keyed by
  path (`format`), not by message text. Left as path-match so it
  continues to work even if zod's default enum error message changes
improve(test-references-constants): test.each cases now reference
  MESSAGES directly. The test continues to catch classifier-logic
  drift (wrong label for a known key, missing entry in the lookup)
  while schema/classifier drift becomes impossible by construction

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 23:07:25 +09:00
Kazuki Yamada 43eb51cac4 test(server): Add classifyRejectReason tests + document Boolean() intent
Addresses PR #1493 round 3 review from claude.

intent(catch-schema-drift): every previous review round flagged the
  fragile string coupling between classifyRejectReason and the zod
  messages in packRequestSchema. The earlier response was that adding
  tests would require new infrastructure in website/server/ — but
  tests/website/cliCommand.test.ts already tests website/ code under
  the root vitest harness. Extended the same pattern to cover every
  rejection path through the real schema so message edits surface as
  CI failures instead of silent 'other'-bucket dashboard drift
decision(table-driven-through-real-schema): the tests invoke
  validateRequest(packRequestSchema, input) rather than constructing
  synthetic ZodError objects — this catches both classifier drift AND
  schema drift in one assertion. 13 tests covering 11 reject labels
  + cause-chain extraction + non-error input
improve(boolean-intent-comment): claude noted that Boolean() collapses
  `undefined` (user didn't send field) with `false` (user explicitly
  disabled), losing that distinction in the pack_options_usage metric.
  This is intentional — the metric answers "what % of packs had
  compress enabled" where both "off" and "unspecified" correctly mean
  the feature wasn't active. Added a comment so future contributors
  don't "fix" it

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:45:43 +09:00
Kazuki Yamada 3d7e9fbaf5 fix(server): Address follow-up review — survivorship bias + dead branch
Addresses PR #1493 follow-up review from claude.

intent(fix-survivorship-bias): `packOptions` was only logged on success,
  but OOM-triggering repos land in pack_error. The whole point of
  logging options alongside size metrics is to correlate "which options
  were active when things went wrong" — without pack_error coverage
  the dashboard answers the question backwards (shows options that
  worked, not options that failed)
decision(lift-packoptions): compute `packOptions` once after validation
  (same site as `inputType`/`repoHost`) and reference it from both the
  success and pack_error logs. No duplication, no survivorship bias.
  The `metrics` block (totalTokens/totalFiles) correctly stays on
  success only since those values don't exist on failure
improve(remove-dead-branch): after commit 2 the JSON.parse catch
  hardcodes `rejectReason: 'invalid_json'` directly, so the
  `error.message === 'Invalid JSON in options'` branch in
  classifyRejectReason was unreachable. Removed it and added a comment
  explaining that pre-zod paths set the label at the call site
improve(errorOptions-type): replaced the hand-rolled
  `{ cause?: unknown }` option type on AppError with the built-in
  ES2022 `ErrorOptions` — more self-documenting and keeps future
  cause-chain additions compatible

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:49 +09:00
Kazuki Yamada a85a63e183 fix(server): Address PR review feedback
Addresses PR #1493 review comments from gemini-code-assist,
devin-ai-integration, coderabbitai, and claude.

intent(classifier-fix): fix classifyRejectReason returning 'unknown'
  for every validation_error — gemini and devin both flagged that
  validateRequest wraps ZodError in AppError, which strips the
  .issues array the classifier needs. This made the new "Validation
  rejections (by reason)" dashboard panel effectively useless, all
  rejections collapsing into one 'unknown' bucket
decision(cause-chain): use the native `Error.cause` option
  (ES2022) to attach the original ZodError when wrapping in AppError.
  Less invasive than adding a custom field; classifyRejectReason now
  checks both `.issues` directly on the error AND on `.cause`, so
  callers don't need to know which layer wrapped the error
decision(json-catch-preserve): change the JSON.parse bare `catch {}`
  to `catch (jsonError)` and pass the original SyntaxError to
  logError. Without this the `invalid_json` bucket shows only a
  count, leaving an operator no breadcrumbs when it spikes. Also
  drops the classifyRejectReason round-trip — rejectReason is
  statically known at this call site
rejected(packOptions-snake-case): gemini suggested renaming the
  packOptions log fields to snake_case so they literally match the
  GCP metric label names. Keeping camelCase in TS code (project
  convention) + snake_case in GCP labels (platform convention) is
  deliberate — the `EXTRACT(...)` path on the metric bridges them
  explicitly. Forcing one to match the other breaks one of the two
  ecosystems' idioms
improve(options-panels): coderabbit noted only `compress` had a
  dedicated panel even though pack_options_usage exposes 4 labels.
  Split into four quarter-width tiles covering compress,
  removeComments, outputParsable, and hasIncludePatterns. Also
  dropped the "(Tree-sitter)" parenthetical from the compress title
  — meaningful to the author, confusing to an on-caller

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:17:01 +09:00
Kazuki Yamada 628227cdde feat(server): Log pack options and validation reject reasons
intent(observability): answer three questions that the initial
  observability PR couldn't:
    (1) what drives OOM risk — size distribution heatmap needs the
        data out of pack_completed's `metrics.totalTokens` as a
        distribution metric, not just a scalar log field
    (2) which differentiating features users actually adopt
        (compress / removeComments / outputParsable / custom
        include-patterns)
    (3) what inputs validation rejects and why — surfaces feature
        requests (non-github hosts, oversized files) and abuse patterns
decision(packOptions-in-log): flatten option booleans under a
  `packOptions` nested object on pack_completed success logs so a
  log-based metric with option labels can extract them without
  exploding the existing pack_requests cardinality. Patterns are
  logged as `hasIncludePatterns` / `hasIgnorePatterns` booleans only —
  raw user input never enters metric labels
decision(rejectReason-enum): classifyRejectReason is a switch on zod
  error message strings since the messages are stable (defined in
  packRequestSchema.ts in this repo). Non-zod errors (notably the
  pre-zod `Invalid JSON in options` path) get an explicit branch so
  they don't fall into the generic `unknown` bucket
decision(invalid-json-logging): the JSON-parse catch previously
  returned 400 without logging, so `rejectReason=invalid_json` would
  have been invisible. Added an explicit logError call on that path
rejected(per-option-metrics): creating separate metrics like
  `pack_with_compress` was considered and rejected — 1 metric with
  4 boolean labels gives 16 series total (manageable) and lets the
  dashboard group by any option without schema sprawl

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:06:55 +09:00
Kazuki Yamada eba8a841f2 Merge pull request #1483 from yamadashy/feat/log-cloudflare-source
feat(server): Log Cloudflare source and metadata for bot detection
2026-04-18 14:40:37 +09:00
Kazuki Yamada 54d6cf9ba0 docs(server): Clarify PACK_EVENT naming and cached=false rationale
intent(readability): address non-blocking reviewer observations — add
  one-line notes where the current behavior looks like an oversight on
  first read: (1) why `pack_completed` covers non-completion outcomes
  like `rate_limited`, and (2) why `processZipFile` hardcodes
  `cached: false` while `remoteRepo` uses the cache

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:37:08 +09:00
Kazuki Yamada 0dc9140ca5 perf(server): Memoize getClientInfo on the Hono context
intent(cleanliness): eliminate the 3x duplicate getClientInfo call per
  request (cloudLogger, rateLimit, packAction each ran ~7 header
  lookups). Not a performance issue at current scale, but the
  redundancy makes tracing "which field came from which call" harder
decision(lazy-memoization): add the cache inside getClientInfo rather
  than a dedicated middleware. cloudflareGuardMiddleware is the first
  consumer on /api/* but runs before cloudLogger, so a "populate
  clientInfo here" middleware would need to be inserted in front of it.
  Lazy memoization keeps existing call sites unchanged and just makes
  subsequent calls cheap
decision(stash-type): declare `clientInfo` in hono's ContextVariableMap
  in clientInfo.ts itself, next to the type it stashes — colocating the
  augmentation with the type keeps future schema changes in one file

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:30:53 +09:00
Kazuki Yamada a78b2e92d4 fix(server): Emit durationMs on pack_error for latency breakdown
intent(observability): make pack failure latency visible in the
  pack_duration distribution metric so fast errors (permission / invalid
  ref) can be distinguished from slow errors (timeouts, OOM-triggered
  terminations). Previously only `success` emitted durationMs
decision(startTime-scope): move `startTime` declaration outside the
  try block so the catch path can reference it — keeps the single
  source of truth for the pack-operation timer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:28:46 +09:00
Kazuki Yamada 2df042e50f fix(server): Address PR review feedback
Addresses PR #1483 review comments from coderabbitai, devin-ai-integration,
and claude.

intent(observability): unify the pack_completed log schema across all
  emitters so every log-based metric label (`source`, `outcome`,
  `limitKind`) actually populates — previously `source` was only emitted
  by rateLimit.ts, and the `pack_requests` metric would have had empty
  `source` labels on 95%+ of events
decision(schema-location): extract PACK_EVENT + PackOutcome + getRepoHost
  to `actions/packEventSchema.ts` (new) and packRequestSchema to
  `actions/packRequestSchema.ts` (new). This addresses three reviewer
  findings at once: (1) PACK_EVENT duplicated across packAction and
  rateLimit, (2) PackOutcome union missing 'rate_limited', (3)
  packAction.ts exceeding the 250-line project limit (276 → 204)
decision(buildCfLogField-location): move `buildCfLogField` from
  cloudLogger.ts to logger.ts so rateLimit.ts can reuse it — rate-limit
  hits are exactly the events where Cloudflare country/ASN matters most
  for triage (bots disproportionately hit the limit). Also added early-
  return for the all-undefined case per the review suggestion
decision(validation-error-source): move `getClientInfo(c)` above the
  try/catch in packAction so the validation_error log can attach
  `source` (the other labels — inputType/repoHost/format — genuinely
  can't exist before validation, so they're only on success/pack_error)
decision(clientInfo-caveat): add explicit NOTE comment stating cf-ray /
  cf-ipcountry / cf-asn / source are spoofable and must never be used
  for auth or rate-limiting. Trust is anchored by cloudflareGuard +
  CLOUDFLARE_ORIGIN_SECRET, not these headers
rejected(getRepoHost-fallback-rename): keep the fallback default
  'github.com' for unparseable URLs. Repomix's own shorthand accepts
  'owner/repo' and resolves to github.com — the fallback matches that
  behavior. Changing to 'github.com (shorthand)' would create two
  semantically-identical buckets in the metric

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:21:34 +09:00
Kazuki Yamada c95034bafc chore(monitoring): Keep dashboard only, drop metric YAMLs
intent(scope-reduction): this repo operates a single GCP project — IaC
  for log-based metrics adds maintenance cost (Console drift, stale
  YAMLs) without payoff. Metrics are recreated ad-hoc from the Console
  when needed
decision(keep-dashboard): the dashboard definition is still worth
  committing because it's the user-facing layout, easy to damage by
  accidental Console edit, and trivial to restore via
  `gcloud monitoring dashboards create --config-from-file=dashboard.json`
decision(readme-slim): strip README to just the restore command —
  longer prose belonged to the earlier IaC-lite layout that's now gone

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 13:50:32 +09:00
Kazuki Yamada bd8e069440 chore(monitoring): Add log-based metric definitions and apply script
intent(observability): make the new pack_completed / source / durationMs
  log fields queryable as time series in Cloud Monitoring without
  re-running ad-hoc gcloud logging read pipelines during incidents
decision(iac-lite): store metric definitions as standalone YAMLs under
  website/server/monitoring/metrics/ + an idempotent bash apply script,
  rather than Terraform. The project has no existing IaC for monitoring
  and adding a full Terraform setup would dwarf the actual change
decision(labels): constrain pack_requests labels to
  outcome/source/input_type/cached/format (max 96 time series). Omitted
  repoHost because its cardinality is unbounded — raw value remains in
  the log jsonPayload for ad-hoc queries
decision(oom-split): separate oom_terminations and container_killed
  counters. They fire on distinct textPayloads from Cloud Run's memory
  enforcement paths; keeping them separate makes alert tuning easier
rejected(repoHost-as-label): too many unique hostnames — would create a
  time series per repo host and cost scale with traffic diversity

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 12:38:52 +09:00
autofix-ci[bot] bf30414798 [autofix.ci] apply automated fixes 2026-04-18 03:37:06 +00:00
Kazuki Yamada ee1b1b4a97 feat(server): Add unified pack_completed event with outcome labels
intent(observability): make pack-request analytics queryable without
  log-regex-spelunking — one event name `pack_completed` + an `outcome`
  label covers the full terminal states (success / validation_error /
  pack_error / rate_limited), enabling log-based metrics by outcome
  without rewriting filters each time
decision(schema): add `event`, `outcome`, `repoHost`, `durationMs`,
  `cached` as top-level jsonPayload fields so Cloud Logging log-based
  metrics can extract them as labels directly. `durationMs` is numeric
  (in addition to the existing human-readable `duration`) so it can be
  used as a DISTRIBUTION metric value. `repoHost` captures the pack
  target's hostname (or `upload` for ZIP) — useful to spot "all traffic
  to one repo" (bot signal) vs organic diversity
decision(cache-reporting): thread a `cached: boolean` flag through
  processRemoteRepo/processZipFile by returning `ProcessPackResult =
  { result, cached }` instead of `PackResult` directly. Previously cache
  hits were invisible in logs; now we can measure cache hit rate per
  outcome/repoHost
decision(rate-limit-logging): emit a `pack_completed` log entry on 429
  paths too (both short-term and daily), keyed by `limitKind`. Without
  this the rate-limit outcome was invisible in aggregate analytics
rejected(metadata-polluting): attaching `cached` to PackResult.metadata
  — that type represents data returned to the client, and a behavioral
  flag shouldn't leak into the API response

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 12:36:05 +09:00
Kazuki Yamada 0be2e87706 Merge pull request #1480 from yamadashy/renovate/website-non-major-dependencies
chore(deps): update website non-major dependencies
2026-04-18 12:13:51 +09:00
Kazuki Yamada b86ed4a83a feat(server): Log Cloudflare source and metadata for bot detection
intent(observability): make bot-vs-legit traffic filterable in Cloud Logging
  without per-IP whois/ASN lookups — distinguish Cloudflare-proxied requests
  from direct-to-origin hits (which are typically bots that found the Cloud
  Run URL and spoof the Host header)
decision(log-schema): add top-level `source` ('cloudflare' | 'direct') plus
  nested `cf.{ray,country,asn}` so queries like
  `jsonPayload.source="direct"` or `jsonPayload.cf.country="SG"` work
  without parsing user agents
constraint(cf-asn): `cf-asn` is not a default Cloudflare header — it
  requires a Transform Rule injecting `ip.src.asnum`; the field is logged
  only when present so nothing breaks before the rule is configured

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 12:04:59 +09:00
renovate[bot] b4e91358cb chore(deps): update website non-major dependencies 2026-04-18 00:49:06 +00:00
dependabot[bot] 877ac9de88 chore(deps): Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the / directory: [hono](https://github.com/honojs/hono).
Bumps the npm_and_yarn group with 1 update in the /website/server directory: [hono](https://github.com/honojs/hono).


Updates `hono` from 4.12.12 to 4.12.14
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.12...v4.12.14)

Updates `hono` from 4.12.12 to 4.12.14
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.12...v4.12.14)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.14
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: hono
  dependency-version: 4.12.14
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-16 02:23:26 +00:00
Kazuki Yamada b9e687ca92 Merge pull request #1448 from yamadashy/perf/website-server-stream-gzip
perf(website): Compress streaming pack responses with per-line gzip flush
2026-04-11 18:26:39 +09:00
Kazuki Yamada 2c30a975f4 fix(website): Address PR review feedback on gzip streaming
- Register a gzip 'error' listener so zlib failures don't crash the
  Node process via an unhandled EventEmitter error (flagged by
  gemini-code-assist and devin-ai-integration).
- Replace the pendingWrites array with a serialized promise chain so
  slow-client backpressure bounds memory; compressed chunks are now
  awaited one at a time rather than queued unbounded (flagged by
  coderabbitai).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 18:14:27 +09:00