Skip to content

chore(monorepo): consolidate faultray-app and the Chrome extension into this repo - #206

Draft
mattyopon wants to merge 425 commits into
mainfrom
claude/admiring-curie-zs4811
Draft

mattyopon wants to merge 425 commits into
mainfrom
claude/admiring-curie-zs4811

Conversation

@mattyopon

Copy link
Copy Markdown
Owner

Summary

Makes mattyopon/faultray the single home for the FaultRay projects, keeping full history:

  • apps/web/ ← mattyopon/faultray-app (faultray.com, 415 commits)
  • archive/chrome-extension/ ← mattyopon/faultray-chrome-extension (3 commits, archived, not built)
  • mattyopon/faultray-landing is not imported. It is private and superseded by faultray.com, and importing it would publish its history.

The repo name, root layout, action.yml, tags and PyPI packaging stay as they are. Before importing, this PR fixes main's red CI and adds guards so that nothing under apps/ or archive/ can reach PyPI, the Docker image, or the core CI.

Merge with "Create a merge commit". Squash or rebase would flatten the imported histories.

Changes

  • ci: restore a green baseline…
    • Pin ruff to <0.16. ruff 0.17.1 reports 3,619 findings (CI run 37186040609); 0.15.x is clean.
    • Depend on sqlalchemy[asyncio], so greenlet is installed for the coverage job.
    • Move [tool.mutmut] to the mutmut 3 schema.
    • release.yml: trigger only on release: published. Publish only vX.Y.Z tags that equal the pyproject version, serialize runs per tag, and set skip-existing so the manual twine upload in release.sh doesn't fail the run. Before this, the extra tag trigger fired twice per release and also fired on the floating v1 Action tag.
  • chore(monorepo): guard… (no behavior change for today's tree)
    • sdist: exclude = ["/apps", "/archive"] with skip-excluded-dirs = true.
    • Path filters on ci.yml, license-audit, resilience-gate and faultray-pr-check.
    • gitleaks: set BASE_REF. The action scans <oldest commit>^.., which errors out on an imported root commit.
    • ZAP: weekly + manual instead of every push to main.
    • pre-commit: ruff and mypy skip apps/ and archive/.
    • .dockerignore entries for apps/, archive/, node_modules and .next.
  • Two import merges, done with git filter-repo --to-subdirectory-filter.
    • The trees are byte-identical to the sources: apps/web = d30593a1, chrome = 5c60a412.
    • Bare #N references in faultray-app commit messages were rewritten to mattyopon/faultray-app#N (N ≤ 193), so they don't link to unrelated issues here. All 21 issues referenced with closing keywords are already closed.
    • Commit SHAs and the Verified badges on imported commits differ from faultray-app; the originals stay there.
  • chore(monorepo): wire apps/web…
    • .github/workflows/web-ci.yml: faultray-app's CI, moved with git mv, scoped to apps/**, with working-directory apps/web.
    • New job: Supabase migration freeze. It flags migrations numbered past 026. main has no required checks, so this is a signal for reviewers, not a lock.
    • Drop the npm prepare → core.hooksPath wiring. The code-quality: postmortem_generator.py:966 の裸の # type: ignore を code 付きに (#48 補完) #87 pre-push .env scan now runs through root pre-commit.
    • Use gitleaks instead of the dead detect-secrets config; .gitleaks.toml and .gitleaksignore allow only reviewed placeholders.
    • Nested .gitignore negations and apps/web/pytest.ini.
  • chore(apps/web): build and deploy from apps/web
    • vercel.json ignoreCommand → scripts/vercel-ignore.sh. It skips non-web commits, diffing against VERCEL_GIT_PREVIOUS_SHA. It always builds on a branch's first deployment and on a redeploy of the deployed commit.
    • Pin the Next tracing and Turbopack roots to the app.
    • Update the docs to point at this repo.
  • docs(monorepo): a README "Repository layout" table (absolute links, because README is the PyPI description) and archive/chrome-extension/DEPRECATED.md.

Deliberately not in this PR:

  • Moving apps/web/.github/workflows/apply-migrations.yml and adding the Dependabot entries for apps/web. Both come in a follow-up right after the Vercel cutover; until then, production migrations keep running from faultray-app.
  • Any change to the faultray==11.0.1 pin used by apps/web. Moving it changes customer-visible scores.

Testing

  • pytest tests/: 34,104 passed, 32 skipped, 14 failed. All 14 are in tests/test_e2e_all_pages.py: connection reset or TLS timeouts reaching live faultray.com from the sandbox. Collection is 34,150 tests, the same as main, and none come from apps/ or archive/.
  • ruff check src/ tests/: All checks passed (ruff 0.15.22). Both CI mypy steps pass.
  • python -m build: the wheel is unchanged (568 files). The sdist equals main's file list plus web-ci.yml, .gitleaks.toml and .gitleaksignore. It stays clean even with planted apps/**/pyproject.toml, .env.example and node_modules.
  • apps/web: typecheck, lint, vitest (343), next build (93/93 pages, no root/lockfile warning) and pytest against faultray 11.0.1 (20) all pass.
  • actionlint passes on all workflows. gitleaks 8.18.4 and 8.24.3 find no leaks on eb2d2e5..HEAD. mutmut results loads the new config.
  • Fresh clone + pre-commit install: pushing with a fake key in apps/web/.env.local is blocked; a clean push passes.
  • Not run: the pgTAP RLS job, Playwright e2e, and a real Vercel build.

Before relying on this PR's checks: CI and License Audit are currently disabled_inactivity on GitHub, so they won't run on this PR until they are re-enabled. Run gh workflow enable ci.yml and gh workflow enable license-audit.yml, then push again or re-run.
Expected noise:

  • The CLA bot only inspects the first 100 commits.
  • PR-Agent will review a ~425-commit diff.

Related Issues

None. Owner steps after merge:

  1. Freeze faultray-app.
  2. Point the existing Vercel project at this repo with Root Directory apps/web.
  3. Open the follow-up PR that moves apply-migrations.
  4. Archive faultray-app, faultray-chrome-extension and faultray-landing (keep faultray-landing private).

Generated by Claude Code

mattyopon and others added 30 commits April 3, 2026 14:57
…L-10 Docsリンク追加

- HTTP-04/SEOMETA-06: case-studies, changelog, ringi, tokushoho, demo, dpa, service-level-agreement, status, supportに layout.tsx (canonical + robots) を追加
- LP-DETAIL-10: LP navbarに"Docs"リンクを追加(/featuresへ)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- reports/page.tsx: レポート言語selectにaria-label追加
- results/page.tsx: フィルターselectにaria-label追加
- logs/page.tsx: ログレベル・サービスフィルターselectにaria-label追加
- traces/page.tsx: サービス/ステータス/レイテンシフィルターselectにaria-label追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ERRMSG-08:
- settings/page.tsx: クーポン適用エラーを日本語化
- teams/page.tsx: 組織読み込みエラー・招待エラー・ネットワークエラーを日本語化
- login/page.tsx: マジックリンクエラーを日本語化

SEO-05 aria-label:
- teams/page.tsx: タスクステータス・役割・優先度・担当者selectに追加
- cost/page.tsx: 契約SLA selectに追加
- whatif/page.tsx: パラメーター selectに追加
- gameday/page.tsx: シナリオ選択 selectに追加
- simulate/page.tsx: プロジェクト選択 selectに追加
- remediation/page.tsx: タスクステータス selectに追加
- people-risk/blast-radius/page.tsx: メンバー選択 selectに追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SEO-05 aria-label追加:
- audit-log/page.tsx: 検索inputにaria-label追加
- traces/page.tsx: 検索inputにaria-label追加
- runbooks/page.tsx: 検索inputにaria-label追加
- templates/page.tsx: 検索inputにaria-label追加
- people-risk/systems/page.tsx: 検索inputにaria-label追加
- people-risk/members/page.tsx: 検索inputにaria-label追加
- teams/page.tsx: 組織名・招待メール・タスクタイトル・説明にaria-label追加

SEO-04:
- simulate/layout.tsx: authenticated pageなのでrobots noindexに変更

セキュリティ:
- en/page.tsx: dangerouslySetInnerHTMLをエスケープ文字列置換に変更

テスト改善:
- offline.test.ts: vi.useFakeTimers()でretry sleep()をスキップ
- xss.test.ts: テストケース追加
- api.test.ts: テストケース追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mers

- Use vi.useFakeTimers() in api.test.ts and offline.test.ts so that
  sleep() delays in apiFetch retry logic complete instantly in tests
- Add noop .catch() before vi.runAllTimersAsync() to suppress
  unhandled-rejection warnings during timer advancement
- Fix simulate() test: add availability_estimate to fakeResult so
  validateSimulationResult() passes validation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- logs/page.tsx: useLocale()を変数に代入、Showing/件数テキストとフッターを日本語対応
- simulate/page.tsx: YAMLエディターtextareaにaria-label追加
- iac/page.tsx: YAMLペーストtextareaにaria-label追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ページタイトル・サブタイトルを日本語化
- 統計ラベル(Total Entries, Errors, Warnings, Anomalies)を日本語化
- フィルターラベル・option値(All Levels, All Services)を日本語化
- 検索inputにaria-label追加
- 「異常のみ表示」「更新」ボタンテキストを日本語化
- 空状態メッセージ「No log entries...」を日本語化
- 関連リンク「Related/Traces/Reports」を日本語化
- フッターの時間範囲ヒントを日本語化

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ページタイトル・サブタイトルを日本語化
- 統計ラベル(Total Traces, Errors, Slow, Avg Latency)を日本語化
- フィルターラベル・option値(All Services, All Status, Any Latency)を日本語化
- 検索・フィルターのaria-labelを日本語対応に変更

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WCAG 2.1 SC 1.3.1「情報及び関係性」に準拠。
スクリーンリーダーがテーブル列の関係性を正しく読み上げられるよう、
31ファイルの全<th>要素にscope="col"を追加。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
言語サブパスページ(/en/*)でHTMLのlang属性を正しく設定。
de/ja/frには既に設定されていたが/enには不足していた。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SEOMETA-06: 各公開ページのtitleにブランド名「FaultRay」を追加して
検索結果での視認性を向上。

- contact/layout.tsx: "Contact — FaultRay"
- features/layout.tsx: "Features — FaultRay Chaos Engineering Platform"
- help/layout.tsx: "Help & User Guide — FaultRay"
- pricing/layout.tsx: "Pricing — FaultRay"
- privacy/layout.tsx: "Privacy Policy — FaultRay"
- terms/layout.tsx: "Terms of Service — FaultRay"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SEOMETA-06: title/descriptionが未設定だった公開ページに追加。
検索エンジンの表示品質向上。

- ringi/layout.tsx: IT補助金申請サポート
- service-level-agreement/layout.tsx: Service Level Agreement
- support/layout.tsx: Support
- dpa/layout.tsx: Data Processing Agreement
- status/layout.tsx: System Status
- demo/layout.tsx: Live Demo
- tokushoho/layout.tsx: 特定商取引法に基づく表示

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HTTP-04/SEO-04: アプリ内認証ページが誤ってインデックスされることを防止。
ルートlayout.tsxがindex:trueを設定しているため、
認証ページは個別にnoindexを設定する必要がある。

追加ファイル:
- admin/layout.tsx
- ai-reliability/layout.tsx
- compliance-report/layout.tsx
- external-impact/layout.tsx
- fisc/layout.tsx
- people-risk/actions/layout.tsx
- people-risk/blast-radius/layout.tsx
- projects/layout.tsx
- security-checklist/layout.tsx
- traffic-light/layout.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useLocale()を使ってエラーメッセージを日本語化
- 「Something went wrong」→「エラーが発生しました」
- 「Try again」→「再試行」
- 「Error ID」→「エラーID」

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ラベルとフォームコントロールが明示的に紐付いていないため、
スクリーンリーダーが入力目的を読み上げられなかった問題を修正。
全数値入力・スライダーにaria-labelを追加。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- projects/page.tsx: プロジェクト名・説明inputにaria-label追加
- whatif/page.tsx: パラメータスライダーにaria-label追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- メールアドレスinputにaria-label追加
- ファイルアップロードinputにaria-label追加(非表示inputだが補助技術がアクセス可能)
- YAMLエディターtextareaにaria-label追加(前コミット)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mattyopon and others added 28 commits June 18, 2026 17:04
…te (mattyopon/faultray-app#159)

checkCsrf() now rejects Origin-less requests whose Sec-Fetch-Site is cross-site/cross-origin (the CSRF signal), while leaving same-origin browser requests, non-browser/server-to-server callers, and the webhook/cron skip-paths unaffected. CI green + Vercel preview deploy succeeded.
…les (mattyopon/faultray-app#160)

Migration 023 re-establishes owner-scoped SELECT on all People-Risk tables (companies/members/systems/member_systems/actions/risk_snapshots) while keeping the public demo company readable, fixing the member_systems prod RLS drift documented in migration 018. Validated on local Postgres 16 (cross-tenant isolation + demo readability) and CI RLS pgTAP green.
…ttyopon/faultray-app#161)

Adds 6 behavioral pgTAP assertions (plan 104→110) for migration 023's owner-scoped + demo-readable member_systems SELECT, closing the U35 gap (People-Risk tables were RLS-untested). Validated 110/110 on a local Postgres16+pgTAP harness replicating the CI RLS job; CI RLS pgTAP green.
…p#162)

Adds expire_stale_org_invites() (SECURITY DEFINER, service_role-only) and wires it into the daily trial-expiry cron (non-fatal) so email-based pending org invites no longer stay acceptable forever. Validated on local Postgres 16 (stale→removed, fresh/active untouched, non-positive arg rejected); CI RLS pgTAP + full vitest green.
…on/faultray-app#163)

Two adjudicated review passes (codex gpt-5.5, Claude-verified) over the merged
security work surfaced and fixed these defects:

[HIGH] account/delete — subscription could keep billing after deletion. Stripe
cancellation now runs BEFORE the irreversible DB+auth delete; on cancellation
failure the request aborts with 502 and the account is NOT deleted. The
stripe_customer_id pre-read error is also captured — a transient profile-read
fault no longer masquerades as "no customer" and skips cancellation.

[MED] safe-redirect — extracted one shared same-origin validator
(isSafeInternalPath) used by both the login page and the OAuth callback (the
callback's narrower allow-list previously dropped valid deep links like
/people-risk/actions to /dashboard). Hardened it to reject percent-encoded /
double-encoded traversal, encoded backslash, internal whitespace, and malformed
percent-encoding, while still accepting encoded-space query values.

[MED] people-risk updateActionStatus — reverted a rows-affected throw that broke
the demo's status buttons (demo company is RLS read-only → 0 rows; the page
applies an optimistic per-session update). The company_id predicate is kept so a
cross-tenant write remains impossible; the forward path is documented in-code.

Verification: tsc=0, lint=0, full vitest 36 files / 326 tests pass; CI green
(Lint & Type Check, Vitest, RLS pgTAP, Python, Build, Vercel preview).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5Xh5wLd32gvMbcUHm2CJG
…/faultray-app#164)

Dual-model review (codex gpt-5.5 + Claude agents, adjudicated) of the whole app.

[CRIT] Trial/coupon revenue leak — subscription_status DEFAULT 'active' masked
non-paying trials/coupons from downgrade_expired_trials(); they kept paid plans
forever. Migration 025 downgrades expired trials with no stripe_customer_id and
stamps subscription_status='trialing' on grant.
[HIGH] Trial silently never granted — callback UPDATE of billing columns with the
user client was column-grant DENIED (mig013); now via provision_business_trial()
SECURITY DEFINER RPC.
[HIGH] Stripe webhook CHECK-constraint crash loop on raw subscription.updated
status — normalizeSubscriptionStatus() coerces to the allowed set.
[HIGH] Legacy /api/billing email-based plan hijack — rewrite removed, handler
decommissioned (410), email mutation deleted.
[HIGH] Simulation-run IDOR (engine.py) — verify project team membership + stamp
team_id before the service-role insert.
[MED] org-create rollback (org owner DELETE policy), coupon revoke race
(revoked=false predicate), actions.system_id cross-tenant guard, and
expire_stale_org_invites search_path — migrations 025/026.

Validation: tsc=0, lint=0, ruff=0, vitest 37/330, pytest 13/13, pgTAP 120/120
(+10 new assertions) idempotent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5Xh5wLd32gvMbcUHm2CJG
…, drop 'unsafe-inline' (mattyopon/faultray-app#165)

The nonce-based CSP path (per-request nonce in src/proxy.ts, stamped onto
every executable inline/third-party <script> in the root layout, with
Next.js auto-propagating it to its own framework scripts via the request
Content-Security-Policy header) was already fully wired but gated OFF behind
FAULTRAY_CSP_STRICT=1, so production still served script-src/style-src
'unsafe-inline' — the exact weakness ZAP flags in #172.

Flip strict mode to the DEFAULT via a new, testable cspStrictEnabled()
helper: 'unsafe-inline' is now removed from the live policy (script-src uses
'nonce-…' + 'strict-dynamic'; style-src uses 'nonce-…' with inline style
ATTRIBUTES still permitted via style-src-attr for React style={{…}}).

Tradeoff (documented in docs/csp-nonce-plan.md): nonce-based CSP forces
per-request dynamic rendering site-wide (no static/CDN caching). The
historical policy is retained as a rollback hatch — set FAULTRAY_CSP_STRICT=0
and redeploy to instantly restore 'unsafe-inline' + static rendering with no
code change, should a nonce-propagation issue surface on the live deploy.

- src/lib/csp.ts: add cspStrictEnabled() (strict unless FAULTRAY_CSP_STRICT=0)
- src/proxy.ts, src/app/layout.tsx: use the helper (default strict)
- tests/security/csp-builder.test.ts: cover the default + rollback switch
- docs/csp-nonce-plan.md: mark Phase 3 done; document the default + hatch

Closes mattyopon/faultray-app#85.


Claude-Session: https://claude.ai/code/session_01K5Xh5wLd32gvMbcUHm2CJG

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
mattyopon/faultray-app#166)

Issues surfaced by a three-engine (codex / kimi-for-coding / openrouter-fusion)
review of faultray-app, then validated against the code and fixed:

- engine: _authenticate() no longer fails OPEN when Supabase is unconfigured.
  A typo or failed secret fetch in prod previously disabled all auth; now
  requires an explicit FAULTRAY_ALLOW_UNAUTHENTICATED=1 dev opt-in.
- engine: AWS assumed-role credentials are no longer written to process-global
  os.environ unguarded. Scans are serialized and the env is snapshotted/restored
  instead of unconditionally popped — fixes cross-tenant credential bleed and
  the destruction of ambient instance-profile credentials.
- engine: validate Content-Length and cap the request body at 2 MB before
  reading it, preventing a memory-exhaustion DoS (the YAML limit ran only
  after the full body was already read).
- engine: constant-time comparison for FAULTRAY_ENGINE_SECRET (hmac.compare_digest).
- engine: guard r.cascade.effects against None in 4 result builders (no more 500s).
- settings: generate API keys with a CSPRNG (crypto.getRandomValues, unbiased)
  instead of the predictable Math.random.

Verified locally: pytest (13 passed), vitest (333 passed), tsc --noEmit, eslint
— all green.


Claude-Session: https://claude.ai/code/session_01Xd5aTJxTfMoW2pE3VExVFH

Co-authored-by: Claude <noreply@anthropic.com>
…ray-app#167)

* fix(app): triage review findings — api, lib, components, i18n, proxy, configs

Batch 2 of the three-engine (codex / kimi-for-coding / fusion) review triage,
covering high/medium/low findings. Each module triaged by a sub-agent: genuine
defects fixed, false-positives / pure-style skipped with rationale.

- api (compliance/realtime/reports/engine): Content-Length validation + body
  caps, request type validation, finite-number guards, temp-fd & HTTP-response
  leak fixes, whatif-nines unified with canonical formula, terraform/k8s input
  size limits, AWS credential-policy consistency, generic error responses
- src/lib: query-param/path encodeURIComponent (api.ts), abort-listener/timer
  leak fixes (api.ts, health-probes, useToast), 204 handling, crypto base64
  chunking, zero-decimal currencies, people-risk error propagation, locale guard
- src/components: auth-provider plan-leak/subscription-leak/stale-fetch race,
  null-guards in locale-driven maps, rel=noopener on external links, useId for
  stable SVG ids, button default type, pagination/tooltip/onboarding hardening
- i18n: missing IaC keys for de/fr/zh/ko/es/pt, locale validation + fallback,
  single source of truth for Locale
- proxy: fail-closed auth on getUser error, x-real-ip allowlist (anti-spoof),
  redirect query preservation; next.config CORS '*' + credentials guard
- supabase: service-role test claim reset (migrations left intact for
  idempotency / RLS / applied-history safety)

Verified locally: pytest 13, vitest 333, tsc --noEmit, eslint — all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5aTJxTfMoW2pE3VExVFH

* fix(app): make account-deletion Stripe cancellation resilient

Use Promise.allSettled when cancelling a customer's subscriptions during
account deletion so one transient failure no longer aborts the whole batch
and leaves a partially-cancelled state. An already-canceled / resource_missing
subscription is treated as success (desired end state is "not active"); only a
genuine failure on a still-active subscription aborts the deletion.

Verified: tsc --noEmit, account-delete-atomic.test.ts (7) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5aTJxTfMoW2pE3VExVFH

* fix(app): triage review findings across src/app pages & API routes

Batch 3 of the three-engine review triage (high/medium/low), covering all of
src/app in 4 parallel sub-agent passes. Genuine defects fixed; false-positives
(e.g. the modified Next.js `unstable_retry` error-boundary prop, global React
namespace) and speculative/cosmetic items skipped with rationale.

Highlights:
- API routes: authz lookups distinguish DB error (500) from "no membership"
  (was conflated); .single()->.maybeSingle()+error checks; owner-protection
  TOCTOU race (.neq role + 409); coupon redeem CAS re-asserts max_uses and
  rolls back on 0-row plan grant; stripe webhook customer_id mismatch guard;
  Slack mrkdwn escaping + webhook timeout; outcome/title type validation
- Pages: fetch responses validated (r.ok + shape) before replacing demo data;
  AbortError handled; mounted/stale-response guards (no setState-after-unmount
  / stale overwrite); CSV/HTML/ICS export escaping + formula-injection guards;
  open-redirect/javascript: scheme guards on checkout/advisor links;
  object-URL leak fixes; CSS percentage clamping; NaN/finite input guards;
  several hardcoded-Japanese strings localized
- Security: stored-XSS escaping in downloadable reports, rel=noopener,
  GA id validation + consent-gated loader

Verified locally: tsc --noEmit, eslint, pytest 13, vitest 333 — all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5aTJxTfMoW2pE3VExVFH

* test(app): fix env-stub teardown leaks in integration tests

Two genuine test-infrastructure bugs from the review (rest of the test-file
findings were coverage/assertion-strengthening suggestions, skipped to keep the
green suite stable):
- supabase-client.test.ts: use vi.unstubAllEnvs() (not unstubAllGlobals) so
  stubbed NEXT_PUBLIC_SUPABASE_* env no longer leaks across tests
- auth-flow.test.ts: add vi.unstubAllEnvs() + vi.resetModules() in afterEach so
  the dynamic auth-provider import isn't reused under a previous test's env

Verified: vitest 333, pytest 13 — green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5aTJxTfMoW2pE3VExVFH

* fix(app): address CodeRabbit + Codex review findings on mattyopon/faultray-app#167

Follow-up hardening from the automated PR reviewers (all small, behavior-preserving):

- Runtime response guards now validate nested item shapes before replacing demo
  data / casting (external-impact, security, score-detail, sla, sla-budget,
  vuln-priority, fmea) — malformed API payloads can no longer crash render.
- CSV formula-injection neutralization hardened: whitespace-prefixed payloads
  (audit-log) and LF-prefixed payloads (results), and the remediation
  expected-effect cell.
- Stale-state / lifecycle: projects[id] & projects list reset/guard on
  nav/unmount; runbooks copy-timeout ref+cleanup; remediation clipboard
  feature-detect + blob-revoke safety timer; shadow-it AbortError guard.
- auth-provider: preserve current plan on transient fetch failure (do NOT
  downgrade to "free"); only the latest request may mutate plan/loading; sync
  isAppPage public allowlist with layout (/status, /ringi).
- dashboard: clear profile-derived state when user is absent (no stale
  account leakage).
- audit-log: preserve the server's canonical action instead of relabeling
  unknown-but-valid actions to "LOGIN" (no audit-trail corruption).
- settings: per-key notification rollback (no clobber of concurrent changes).
- status: trust probeAll().overall; simulate: block run on YAML upload failure;
  pagination: floor pageSize before the >=1 check; timeline: deterministic ts parse.

Verified: tsc --noEmit, eslint, vitest 333 — all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5aTJxTfMoW2pE3VExVFH

---------

Co-authored-by: Claude <noreply@anthropic.com>
…y-app#168)

Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the dev-dependencies group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 25.9.3 to 26.0.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ray-app#170)

Bumps [stripe](https://github.com/stripe/stripe-node) from 22.2.1 to 22.2.2.
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](stripe/stripe-node@v22.2.1...v22.2.2)

---
updated-dependencies:
- dependency-name: stripe
  dependency-version: 22.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…/faultray-app#171)

Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 1.18.0 to 1.21.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.21.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…yopon/faultray-app#172)

apply-migrations requires the Supabase Session pooler connection string:
the direct db.<ref>.supabase.co host is IPv6-only and GitHub-hosted
runners have no IPv6, so a direct URL makes psql stall and then fail with
an opaque timeout.

Add a preflight to the Apply step that fails fast with an actionable
message when the secret is a direct connection string, and warns when the
transaction pooler (6543) is used instead of the Session pooler (5432).
The URL is matched with `case` and never echoed (it holds the password).


Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

Co-authored-by: Claude <noreply@anthropic.com>
…only (mattyopon/faultray-app#173)

The OWASP ZAP baseline scan (#182) flags missing
Cross-Origin-Resource-Policy and Cross-Origin-Embedder-Policy headers.

- CORP: enforce `same-origin`. Safe — the app already sends X-Frame-Options
  DENY / frame-ancestors 'none', CORP is browser-only (server-side OG/social
  crawlers unaffected) and does not constrain the third-party resources we
  load. Clears the CORP-missing finding on pages and fonts.
- COEP: enforcing require-corp/credentialless would gate Stripe Elements/
  Checkout iframes, GA4/Hotjar, Google Fonts and OG images on a CORP/COEP
  opt-in they don't all send — almost certainly breaking checkout + analytics
  (already documented as deferred in this file). Ship the non-enforcing
  Report-Only header instead so a future enforced rollout is data-driven.

No enforced change that can break runtime; CSP/COOP/HSTS unchanged.

Refs #182


Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

Co-authored-by: Claude <noreply@anthropic.com>
…attyopon/faultray-app#174)

Tone/terminology pass over the Japanese marketing copy, keeping the
intentional JP go-to-market wording (診断パッケージ・稟議書 等) and all
locale keys unchanged (8 locales stay at 185/185 key parity).

Terminology:
- "availability" unified to 稼働率 (comparison label 可用性証明 → 稼働率証明;
  稼働率 is the established product term, cf. 5層稼働率モデル)
- 影響レンジ → 影響範囲 (metadata/hero; matches agentResilience wording)
- "AI Agent モデリング" → AIエージェントモデリング (matches AIエージェント used elsewhere)
- AIエージェント耐久性 → AIエージェントレジリエンス

Notation / naturalness:
- 中黒 normalized to ・ (U+30FB); removed stray U+00B7 and extra spaces
  (予測・評価・管理の3ステップ; hero badge)
- full-width parens for the 外部SLA layer: (AWS・GCP・Stripe等)
- model name aligned to 5層稼働率上限モデル (dictionary + hardcoded ja features page)
- ライブパフォーマンス指標 → リアルタイムパフォーマンス指標

No behavior change; copy only. The hardcoded ja /features page is updated
in lockstep so terminology stays consistent with the dictionary.


Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…pp#175)

- Serverless auth parity: shared fail-closed api/_auth.py::authenticate() now gates every do_GET/do_POST in compliance.py and reports.py (were unauthenticated while engine.py enforced a gate); locked in by tests/test_serverless_auth.py
- Frontend wiring: authedFetch attaches the Supabase JWT to the gated endpoints; HTML compliance report opened via an authed blob URL (gesture-preserving window.open)
- i18n consistency: locale-aware date formatting (calendar/gameday/incidents); JP-only ringi banner gated behind locale==="ja"; case-studies disclaimer set to English to match the page; ratchet test caps inline locale ternaries at baseline
…ultray-app#176)

* fix: serverless auth parity + locale-aware dates/banner (quick wins)

- Serverless auth (api/_auth.py, compliance.py, reports.py): factor the
  Supabase-JWT / X-API-Key gate out of engine.py into a shared, fail-closed
  authenticate() and call it as the first line of every do_GET/do_POST in
  compliance.py and reports.py, which previously served unauthenticated. Robust
  dual-path import works under both Vercel and the spec_from_file_location test
  loader. (reports.py POST has no live webhook — billing is a 410 stub and
  Stripe webhooks live in /api/stripe/*.)
- i18n date formatting: pass the active locale to toLocaleString/
  toLocaleDateString in calendar, gameday and incidents (were hardcoded "en" /
  no-arg, so dates ignored the user's locale).
- i18n banner: gate the Japanese-only ringi banner on the English contact page
  behind locale === "ja".

Validated: eslint clean, tsc --noEmit clean for touched files, pytest green
(incl. new tests/test_serverless_auth.py).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* i18n: english case-studies disclaimer + ratchet on inline locale==='ja'

- case-studies is a fully-English static marketing page; the only Japanese
  element was the "想定事例" disclaimer banner, shown to every locale. Gating it
  by cookie would force dynamic rendering and still leave ja users an
  all-English page, so the disclaimer is now English to match the page.
- Add a ratchet test (tests/i18n/no-new-locale-ja.test.ts) capping the ~605
  inline `locale === "ja" ? ja : en` ternaries at the current baseline so the
  debt cannot grow. New copy must go through the 8-locale appDict instead. The
  full migration is a separate effort: those ternaries are inline JA/EN pairs
  not in appDict, so converting them needs de/fr/zh/ko/es/pt translations
  authored (out of scope for a mechanical change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* fix(auth): attach Supabase token to gated API calls (logged-in users)

The serverless auth gate added to compliance/reports 401'd logged-in users
because the frontend reached those endpoints via the cookie-auth proxy without
a bearer token (Codex P1 on PR mattyopon/faultray-app#175).

- api.ts: apiFetch now auto-resolves the Supabase access token via _authToken()
  when the caller didn't pass one, so every helper (reports/risk/finance/
  compliance) authenticates the logged-in user. The token is still only
  attached to the validated relative path on the trusted API base (SEC U28),
  and the GET cache is keyed by the resolved token.
- Add an exported authedFetch() for raw-Response call sites, and route the three
  bare fetch("/api/governance?action=...") calls (governance / sla / dora pages)
  through it so DORA/SLA/AI-governance no longer fall back to 401.

Validated: eslint + tsc clean; vitest unit suite (138) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* ci(security): scope prod DB secret + least-privilege permissions

From the CI/CD pipeline audit.

- apply-migrations.yml: move SUPABASE_DB_URL (production DB password) from
  job-level env to a step-level env on the Apply step only, so it is no longer
  present in the environment of checkout and the root apt-get install. Add
  top-level permissions: contents: read and a timeout on the apply job.
- ci.yml: add least-privilege top-level permissions: contents: read and a
  concurrency group (the lint/test/build jobs need no write-scoped token).

SHA-pinning of third-party actions is deferred (third-party repos unreachable
here to resolve commit SHAs; do via Dependabot/Renovate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* Keep HTML compliance report authenticated (fix logged-in 401)

The "Generate Report" button opened the HTML report via
window.open('/api/reports?...format=html'). That endpoint now enforces
the shared serverless auth gate (X-API-Key or Supabase Bearer JWT), but a
top-level browser navigation cannot carry the Authorization header that
apiFetch/authedFetch attach, so logged-in users got a 401 page instead of
their report.

Fetch the report with authedFetch (which attaches the Supabase access
token) and open a blob URL of the returned HTML instead:

- Open the viewer tab synchronously inside the click gesture (about:blank)
  so the popup blocker still permits it after the fetch await, then point
  it at the blob URL with location.replace().
- Sever window.opener on the placeholder to preserve reverse-tabnabbing
  protection (the previous noopener handle is no longer usable since we
  navigate the tab ourselves).
- AbortController + revoke-timer refs are cleared on unmount; a superseding
  click aborts the prior run and only the latest run clears the busy state.
- Distinct toasts for popup-blocked, 401 (sign in), and generic failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* i18n: route compliance-report toasts through appDict (fix ratchet)

The HTML-report auth fix added two new inline `locale === "ja"` toast
ternaries (sign-in-required, failure), tripping the no-new-locale-ja
ratchet (rose to 607 > baseline 605). Rather than raise the baseline,
migrate all four generate-report toasts (popup-blocked, sign-in-required,
report-generated, failure) into appDict across all 8 locales and read them
via the existing `t = appDict.complianceReport[locale]` accessor.

The success toast uses a `{framework}` placeholder so word order stays
correct per locale. Net effect lowers the ratchet 605 -> 603 (the two
previously-inline toasts are now localized for de/fr/zh/ko/es/pt too).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* fix(i18n+theme): acceptance-test fixes for locale leaks & theme drift

Browser acceptance testing of the public surface (headless Chromium across
en/ja) surfaced several locale/regression bugs in the recently-merged i18n
work. This addresses them:

- /features was hardcoded Japanese for every locale, so English (and all
  other non-JA) users saw a full-page wall of Japanese. Now bilingual
  (en/ja via useLocale; non-JA locales fall back to English) so non-Japanese
  users no longer see raw Japanese.
- /pricing leaked Japan-only blocks (日本語サポート badge, 請求書払い/インボイス
  notice, 特定商取引法 footer link) to every locale. Now gated to the ja
  locale; non-JA users see an English invoice note or nothing.
- The pre-hydration theme script in layout.tsx kept a stale, hand-edited
  public-page allowlist (missing /zh /ko /es /pt and /service-level-agreement),
  which force-darkened those marketing pages and triggered a client/server
  hydration mismatch. Extracted the canonical list to src/lib/public-routes.ts
  (now shared with proxy.ts) and serialize the complete set into the script so
  the two consumers can't drift again.
- social-proof: all three testimonials shared key={t.author} ("Aspirational
  scenario"), producing a duplicate-React-key warning on every landing page —
  switched to the unique company key. Also replaced a hardcoded Japanese beta
  paragraph with the existing dict.betaSubtitle (already translated in all 8
  locales) so the English landing no longer shows a stray Japanese line.

Verified: tsc + eslint clean; across en/ja no JP leak on /features or
/pricing under en, consistent light theme on all locale landings, and no
duplicate-key warning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* fix(i18n): fill missing app-dict translations for help + templates

A locale key-completeness audit across all 49 locale-keyed app-dict
sections (8 locales each) found two sections with gaps that would render
undefined for the affected locales:

- help.proTips / proTip1-3 existed only in `en` -> added ja, de, fr, zh,
  ko, es, pt (the Help page "Pro Tips" block was blank for everyone else).
- templates.noTemplatesFound / noTemplatesDesc existed only in `en` + `ja`
  -> added de, fr, zh, ko, es, pt (the Templates empty-state message).

All 8 locales now have identical key sets across every section (verified
by re-running the audit: 49/49 sections, 0 gaps). Formatting matches each
block's existing style (multi-line for help, single-line for templates).
tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* i18n: gate residual unconditional JP on people-risk + ROI calculator

Both files were already locale-aware but had a handful of hardcoded
Japanese fragments that leaked to non-JA users:

- people-risk/page.tsx: count suffix (件), trend labels (最新/週間で/改善),
  table headers (週/リスクスコア/システム数), empty state (データがありません)
- roi-calculator.tsx: slider unit suffixes (回/時間)

Wrapped each in the file's existing locale gate (locale==="ja" / isJa)
so non-JA users see English. Typecheck + eslint clean.

* i18n: localize simulate Suspense loading fallback (読み込み中→Loading)

The outer SimulatePage wrapper rendered a hardcoded Japanese loading
string in its Suspense fallback, shown to all users before the inner
client component mounted. Gate it via useLocale (already used in this
file) so non-JA users see "Loading...".

* i18n: address Codex review on mattyopon/faultray-app#176 (P1 ratchet baseline, P2 locale persistence)

P2 (features locale lost on direct load): the proxy strips the locale prefix
from localized app routes (/ja/features -> /features) and redirects before the
page mounts, so useLocale() found neither a path-segment locale nor a cookie and
fell back to English. Persist the stripped locale into NEXT_LOCALE (the
documented fallback) on that redirect, so a direct hit or new tab on
/ja/features renders Japanese.

P1 (ratchet baseline): converting the people-risk / ROI / simulate / pricing
pages' unconditional Japanese into gated `locale === "ja"` ternaries raised the
inline count 603 -> 614 (a net improvement — those leaks were previously
invisible to the counter and shown to all six other locales). Raise BASELINE to
the actual count; appDict migration that retires these remains the goal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* feat(revenue): self-serve Starter checkout + de-risk positioning copy

From the revenue audit (Fugu Ultra + multi-agent review):

- Starter ($99) had no self-serve path (CTA → /login). Wire it to Stripe
  checkout like Pro by widening the stripePlan / handleCheckout / checkoutError
  types to include "starter"; the createCheckoutSession API already supported it.
- Reframe the every-page disclosure banner from alarmist "research prototype —
  do NOT rely on outputs for compliance" to decision-support copy that keeps the
  legal substance (not legal advice / not an audit opinion / not certification;
  validate before submission) across all 8 locales. The old wording repelled the
  only buyers with budget (compliance officers). Styling softened to neutral.
- Replace self-sabotaging "aspirational / not real customer / illustrative"
  testimonials with honest example-scenario framing (product voice, sample
  topologies); fix the case-studies "Real results" vs "⚠ Illustrative"
  contradiction.
- Rename the "Research-prototype evidence export" feature label to
  "DORA-aligned evidence pack".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* fix(revenue): address Codex review — Starter checkout + honest case studies

- P1: the newly self-serve Starter CTA called createCheckoutSession("starter")
  but the checkout route rejected anything but pro/business (400 -> generic
  failure UI). Add Starter ($99/mo, $949/yr) to the inline price_data route so
  Starter checkout actually opens Stripe.
- P2: case-studies SEO metadata still said "Customer Success Stories" / "Real
  results from teams", contradicting the reframed body. Update title/description.
- P2: the case-study cards still read as real customer outcomes (role quotes,
  "audit passed", "deals unblocked") under a "not named customers" disclaimer.
  Relabel as explicitly illustrative example scenarios so the page is
  internally consistent.
- Reword a leftover "(Research prototype — not a certified compliance tool.)"
  parenthetical in the Pro->Business upsell; the site-wide banner carries the
  legal disclosure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…oning (mattyopon/faultray-app#180)

Persist path locale to NEXT_LOCALE cookie so locale-less CTAs stay in language; reframe site copy around the DORA pre-audit Evidence Sprint outcome with bilingual (EN+JA) hero/CTA/value-props, real translations for de/es/fr/ko/pt/zh, and the Evidence Sprint CTA wired into the custom /ja page.
…age (mattyopon/faultray-app#181)

* Add /evidence-sprint offer landing page and route the hero CTA to it

Give the primary "Book a 20-min Evidence Sprint scoping call" CTA a real
destination. Previously it pointed at /contact because no offer page
existed; the funnel is now hero CTA -> /evidence-sprint -> book ->
/contact.

- New /evidence-sprint route (page.tsx + layout.tsx) following the
  existing marketing-page convention: a client page with a typed
  bilingual CONTENT record (EN + JA) resolved via useLocale, with every
  non-JA locale falling back to English (same pattern as /features), and
  a layout.tsx exporting static metadata like the other marketing pages.
- Sections: hero, what-you-get (10 deliverables), DORA article coverage
  table (Art. 11/12/24/25/28/30), how it works (5 business days,
  sanitized data only, no production access, no PII), what we need from
  you, pricing ($2,500 fixed, 50% upfront), FAQ, closing CTA, and an
  honest disclaimer. The page's own CTAs book the call at /contact.
- Retarget the shared LandingPage and the custom src/app/ja/page.tsx
  primary hero CTA from /contact to /evidence-sprint.

Copy is honest: decision-support material for DORA pre-audit
preparation, not legal advice, an audit, or certification. No invented
customers, logos, testimonials, or metrics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* Route /evidence-sprint copy through the app dictionary (i18n ratchet)

The i18n ratchet test (tests/i18n/no-new-locale-ja.test.ts) failed
because the new /evidence-sprint page used an inline
`locale === "ja" ? ja : en` pattern, adding 2 counted ternaries
(614 -> 616).

Fix using the preferred approach: move the page's copy into the
8-locale app dictionary (src/i18n/app-dict.ts) under
`appDict.evidenceSprint`, and consume it with the repo's standard
dictionary-backed pattern `appDict.evidenceSprint[locale] ??
appDict.evidenceSprint.en` (as used by env-compare / bus-factor /
simulate and the mattyopon/faultray-app#180 hero). Real EN + JA copy; de/fr/zh/ko/es/pt hold
the English fallback, exactly like the mattyopon/faultray-app#180 hero keys. The homeHref
also drops its ternary (`/${locale}` — every locale has a route).

Rendered EN and JA copy is unchanged; non-EN/JA locales still fall back
to English. The counted `locale === "ja"` pattern drops back to the
baseline (614), so the ratchet passes without moving the baseline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

* Apply Evidence Sprint review fixes (DORA mapping accuracy, currency, delivery clock)

Address the valid Fugu review findings on the /evidence-sprint offer page.
EN and JA copy updated consistently (the six non-EN/JA appDict fallback
blocks regenerated from the corrected EN).

- DORA mapping table (factual accuracy per Regulation (EU) 2022/2554):
  precise article titles for Art. 11/12/24/25/28/30; move ICT
  concentration-risk evidence off Art. 28 (general principles) to a new
  Art. 29 (preliminary assessment of concentration risk) row; retitle the
  table "Indicative DORA preparation mapping" and reword the caption so it
  no longer implies coverage/completeness/certification.
- Currency: "$2,500"/"$" -> "US$2,500"/"US$" throughout (EU buyers).
- Delivery clock: hero priceNote, pricing terms, and FAQ now all state the
  same trigger -- "5 business days after we receive your sanitized inputs".
- Hero secondary link: vague "FaultRay" -> "See how it works" (JA:
  "進め方を見る") anchored to the on-page how-it-works section.

Hero "Audit-ready / 監査対応可能" wording intentionally left unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECuCCRtdriA2HG6FaKFC5i

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…block, sitemap (mattyopon/faultray-app#186)

* fix(proxy): stop auth-walling the public /evidence-sprint page

The /evidence-sprint offer page shipped in mattyopon/faultray-app#181 was unreachable in
production: every visitor got 307'd to /login?redirectTo=%2Fevidence-sprint,
killing the hero-CTA funnel the page exists for.

Root cause: APP_ROUTES protection in src/proxy.ts matches by prefix
(startsWith) — intentional, so "/security" covers "/security-checklist" —
and "/evidence-sprint" prefix-matches the protected "/evidence" app route.
The page was also never added to PUBLIC_PAGES.

Fix:
- Add "/evidence-sprint" to PUBLIC_PAGES (src/lib/public-routes.ts), which
  also fixes the pre-hydration theme script treating it as a dark app page.
- Give public pages precedence over the APP_ROUTES prefix match via a new
  segment-boundary isPublicPage() helper, so "/evidence" itself,
  "/evidence/...", and "/security-checklist" all stay protected.

Tests (tests/security/route-protection.test.ts) drive the real proxy() with
an unauthenticated mocked Supabase client: /evidence-sprint serves publicly,
/evidence and /security-checklist still redirect to login, /ja/evidence-sprint
canonicalizes with the locale cookie, and every PUBLIC_PAGES entry stays off
the login wall. Verified the suite fails against the unfixed proxy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ENSxwJtR8NuqXTFjKQeWc9

* fix(seo): unblock /evidence-sprint from robots disallow and add to sitemap

Same bug class as the auth-wall fix: robots.txt Disallow matching is
prefix-based, so the "/evidence" app-path entry also crawl-blocked the
public /evidence-sprint offer page. Add an explicit Allow carve-out (the
longer rule wins under longest-match precedence) and list the page in the
sitemap alongside the other core product pages.

Tests: a robots longest-match invariant asserts EVERY PUBLIC_PAGES entry is
crawlable — a future public route shadowed by an app-path disallow prefix
now fails in CI instead of shipping undiscoverable — plus a sitemap
presence check. Verified the invariant test fails without the carve-out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ENSxwJtR8NuqXTFjKQeWc9

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
main's CI has been red since ruff 0.17 landed (3,619 findings, run
37186040609), and the scheduled coverage and mutation jobs cannot start:

- pin ruff to <0.16 in [dev] (0.15.11 and 0.15.22 report "All checks
  passed!" on src/ tests/), matching the pre-commit rev
- depend on sqlalchemy[asyncio] so greenlet is installed for the async
  engine and coverage's concurrency=["thread", "greenlet"]
- move [tool.mutmut] to the mutmut 3.x keys (source_paths,
  pytest_add_cli_args_test_selection); with the 2.x keys mutmut 3.8 dies
  with "can only concatenate list (not "str") to list"

Once tests pass, release.yml's publish jobs run for real for the first
time, so tighten it in the same change:

- trigger only on `release: published`; the extra `push: tags: v*`
  trigger double-fired every scripts/release.sh run and also fired on the
  floating `v1` Action tag
- refuse anything but a vX.Y.Z tag that equals pyproject's version
- serialize per tag, and skip-existing on PyPI so the manual twine
  upload in release.sh does not fail the run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012X6dmvzMQ1BXq2eHS5Z5aF
No behavior change for today's tree; these make the next commits safe:

- sdist: exclude /apps and /archive and don't descend into them (hatch
  has no sdist include list, and the root .gitignore's negations would
  otherwise re-include files such as pyproject.toml under apps/)
- ci.yml, license-audit (push) and resilience-gate (push): ignore
  apps/**, archive/** and web-*.yml; faultray-pr-check and resilience-gate
  (pull_request) already use `paths`, so add `!apps/**` / `!archive/**`
- gitleaks: pass BASE_REF (PR base, previous push, or the default branch
  for new branches); gitleaks-action scans `<oldest commit>^..<newest>`,
  which errors out when the range contains an imported root commit
- zap-scan: weekly + workflow_dispatch instead of every push to main
  (push-time scans race the Vercel deploy and scan the previous build)
- pre-commit: ruff/ruff-format/mypy skip apps/ and archive/;
  forbid-env-files now covers nested dirs but allows *.example templates
- .dockerignore: keep apps/, archive/, node_modules and .next out of the
  Docker build context

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012X6dmvzMQ1BXq2eHS5Z5aF
Brings mattyopon/faultray-app (main @ d7b8307, 415 commits) into this
repository under apps/web/ with its full history, rewritten only by
`git filter-repo --to-subdirectory-filter apps/web`. The imported tree is
byte-identical to faultray-app's main (tree d30593a).

Commit messages were rewritten so bare `#N` references (N <= 193, the
highest faultray-app issue/PR number) read `mattyopon/faultray-app#N`
and keep pointing at faultray-app instead of unrelated issues here.
Commit SHAs (and the Verified badge on signed commits) differ from
faultray-app; the originals stay in the archived faultray-app repo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012X6dmvzMQ1BXq2eHS5Z5aF
…-extension

Brings mattyopon/faultray-chrome-extension (main @ e8b9eca, 3 commits)
under archive/chrome-extension/ with its history (tree 5c60a41,
identical to the source). It is archived, not maintained: it is not built
or tested in CI here and is excluded from the PyPI sdist; see
archive/chrome-extension/DEPRECATED.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012X6dmvzMQ1BXq2eHS5Z5aF
…scanning

- .github/workflows/web-ci.yml: faultray-app's CI (git mv, history kept),
  renamed "Web CI (apps/web)" so it no longer shares the core workflow's
  name and concurrency group, scoped to apps/web/** with
  working-directory apps/web and per-app dependency caches; main only
  (faultray-app's `develop` branch never existed)
- web-ci.yml also flags Supabase migrations past 026 until
  apply-migrations moves here (production migrations still run from
  faultray-app until the Vercel cutover; main has no required checks, so
  this is a reviewer signal, not a lock). The nested apps/web/.github files
  (apply-migrations, dependabot) are inert here and move in that step
- hooks: drop `npm prepare` (core.hooksPath would disable every hook in
  this repo); the pre-push .env scan (mattyopon/faultray-app#87) now runs
  from the repo root via pre-commit (default_install_hook_types:
  pre-commit + pre-push; default_stages: pre-commit so the existing hooks
  don't also run on push)
- secrets: gitleaks (pre-commit + workflow) now covers apps/web, so drop
  faultray-app's inert detect-secrets config and baseline; .gitleaks.toml
  and .gitleaksignore allow only the reviewed placeholders in the imported
  history (eb2d2e5..HEAD is clean on gitleaks 8.18.4 and 8.24.3)
- apps/web/.gitignore: re-include *.json and .env.*.example that the root
  .gitignore would hide; apps/web/pytest.ini anchors pytest at the app

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012X6dmvzMQ1BXq2eHS5Z5aF
Takes effect once the Vercel project is pointed at this repo with Root
Directory `apps/web` (until then faultray.com keeps deploying from
faultray-app, which is unchanged):

- vercel.json ignoreCommand -> scripts/vercel-ignore.sh: skip builds for
  commits that don't touch apps/web, diffing against the last successful
  deployment (VERCEL_GIT_PREVIOUS_SHA) rather than HEAD^ so an apps/web
  change is never lost behind a later core-only commit. It builds when
  there is no usable base (a branch's first deployment) and when the
  already-deployed commit is redeployed (env var changes, deploy hooks)
- next.config.ts: pin outputFileTracingRoot and turbopack.root to the app
  (Next infers the root from the nearest lockfile)
- README / CONTRIBUTING / DEPLOY / SECURITY / RLS test docs: clone and
  import mattyopon/faultray (Root Directory apps/web), PRs target main,
  security advisories live on this repo (faultray-app will be archived),
  the pgTAP job is in the root web-ci.yml

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012X6dmvzMQ1BXq2eHS5Z5aF
…sion

- README: "Repository layout" table (absolute links, since README is also
  the PyPI long description)
- archive/chrome-extension/DEPRECATED.md: why it is archived (its sign-in
  URL 404s, the credentials endpoint was never built, and long-lived AWS
  keys are rejected by APIVULN-01) and that nothing builds it here
- archive/chrome-extension/.gitignore: re-include its *.json files that
  the root .gitignore would hide

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012X6dmvzMQ1BXq2eHS5Z5aF
@github-actions

Copy link
Copy Markdown
Contributor

❌ FaultRay Resilience Gate: FAILED

Metric Value
⚠️ Resilience Score 76/100
Critical Findings 2
Warnings 2
Passed Scenarios 130/134

Generated by FaultRay Resilience Gate

@github-actions

Copy link
Copy Markdown
Contributor

Failed to generate code suggestions for PR

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants