Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ Claude Code は `/model` コマンドを自分では実行できないため、
過去の手戻り・障害から導いた再発防止ルール。**領域固有の項目は各 scoped rule に集約済み**(対象パス編集時に自動ロードされる)。ここには領域横断(常に効かせたい)ものだけを残す。

- **テストで DB をモックしない**: 統合テストは実 DB(テスト用 SQLite セッション)に当てる。モック/本番乖離でマイグレーション失敗を見落とした実績がある。
- **ネイティブ/コンテナ起動はスモークテストで必ず検証する**: pytest(`make test-backend`)は標準 SQLite で動き、本番イメージのビルド・libsql ネイティブドライバ・alembic マイグレーション・uvicorn 起動の実行パスを通らない。この穴は CI の `smoke-backend` ジョブ(本番イメージ build → 実 libSQL 起動 → `/health` 200 を検証)で塞ぐ。backend の Dockerfile / 依存 / 起動経路を変えたら smoke-backend が green であることを確認する(Python 3.14 bump で libsql が起動時 segfault した事象の再発防止)。詳細: `.claude/rules/backend/test.md` / `database.md`
- **新規ブランチは `origin/main` 起点で切る**: リリース前は全てを `main` にマージする運用。以前は `origin/dev` 起点だったが dev 環境作業の名残で、現在は廃止。

領域別の再発防止ルールは各 scoped rule に集約(対象パス編集時に自動ロード):
Expand Down
4 changes: 2 additions & 2 deletions .claude/rules/backend/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ paths:
- **ALTER COLUMN(型・制約変更)**: libSQL は非対応。`batch_alter_table`(テーブル再作成)で行う
- **DROP COLUMN は原則 `op.drop_column` を直接使う**: SQLite/libSQL 3.35+ は `ALTER TABLE ... DROP COLUMN` をサポートする。インデックス・FK・制約のない素のカラムはこれで消せる(テーブル再作成不要)
- **FK 参照される親テーブルに `batch_alter_table` を使わない**: batch は「新テーブル作成 → 旧テーブル DROP → リネーム」で動くため、`users` のように子テーブルから FK 参照される親を batch で触ると、旧テーブル DROP 時に libSQL(`foreign_keys=ON`)で `FOREIGN KEY constraint failed` になる。標準 SQLite ドライバは `foreign_keys` がデフォルト OFF で通ってしまい差異を見落とすので注意。子テーブル(他から参照されない)の `drop_column` でのみ batch は安全
- **マイグレーションは `make test-backend` では検証されない**: テストは `conftest.py` の `Base.metadata.create_all` でスキーマを作るため alembic を通らない。migration の upgrade/downgrade **実 libSQL に対して**確認すること:
- **マイグレーション/libsql ネイティブ経路は `make test-backend`(pytest)では通らないが、CI の `smoke-backend` ジョブで検証する**: pytest は `conftest.py` の `Base.metadata.create_all` でスキーマを作るため alembic を通らず、libsql ネイティブドライバの実行パスも踏まない。これを補うため CI は本番イメージを build → 実 libSQL を起動 → alembic 適用 → `/health`(DB 接続を検証)が 200 を返すことを確認する(`.github/workflows/ci.yml` の `smoke-backend`)。ローカルで個別に migration の upgrade/downgrade を確認したい場合は **実 libSQL に対して**行うこと:
- offline SQL の事前確認: `nix develop --command bash -c "cd backend && TURSO_DATABASE_URL='file:///tmp/x.db' .venv/bin/python -m alembic upgrade <from>:<to> --sql"`
- 実適用: docker stack を起動(`make dev-build`)し `docker compose logs api` で適用成功を確認する
- 実適用: docker stack を起動(`make dev-build` / 本番 arch で再現したいときは `make dev-amd64-build`)し `docker compose logs api` で適用成功を確認する
- 失敗した `batch_alter_table` が残す `_alembic_tmp_<table>` テーブルは `turso db shell http://localhost:8080 "DROP TABLE IF EXISTS _alembic_tmp_<table>"` で掃除する

## Turso (libSQL) 接続方式
Expand Down
6 changes: 5 additions & 1 deletion .claude/rules/backend/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ paths:
- **既存エンドポイントの契約変更**: ステータスコード / レスポンス body / 副作用が変わる場合、既存テストの assert を見直す(旧契約を固定化したテストが残ると意図が後退する)
- **リポジトリ層・サービス層のロジック変更**: 該当ユニットテスト(`tests/test_<module>.py` / `tests/services/`)を更新
- **タスクハンドラの追加・変更**: `tests/test_worker_extended.py` または `tests/test_worker_timeout.py` に状態遷移(`processing` → `completed` / `dead_letter` / `retrying`)のテストを追加
- **マイグレーション追加**: 実 DB に対する upgrade/downgrade が通ることを `make test-backend` で確認
- **マイグレーション追加**: upgrade/downgrade `make test-backend`(pytest)では検証されない(後述「pytest が通らない経路」参照)。実 libSQL に対する適用は CI の `smoke-backend` で確認され、ローカルでは `database.md` の手順(offline SQL / `make dev-build`)で確認する
- **暗号化・認証関連**: `tests/test_auth.py` / `tests/test_encryption.py` / `tests/test_oauth_flow.py` を必ず回す

## 実行コマンド
Expand All @@ -27,6 +27,10 @@ make test-backend # 全テスト
nix develop --command bash -c "cd backend && .venv/bin/python -m pytest tests/test_worker_extended.py -q"
```

### pytest が通らない経路(コンテナ起動スモーク)

pytest は標準 SQLite + `Base.metadata.create_all` で動くため、**本番イメージのビルド・libsql ネイティブドライバ・alembic マイグレーション・uvicorn 起動の実行パスは検証しない**。この経路は CI の `smoke-backend` ジョブ(本番イメージ build → 実 libSQL 起動 → `/health` 200)でカバーする。backend の Dockerfile・依存・起動経路(`scripts/entrypoint.sh` / `app/db/bootstrap.py`)を変えたら smoke-backend が green であることを確認すること。ローカルで本番 arch(amd64)の起動を再現したいときは `make dev-amd64-build`。

Comment thread
coderabbitai[bot] marked this conversation as resolved.
## OK 基準(達成条件)

以下をすべて満たして初めて「テスト OK」と判定する:
Expand Down
10 changes: 10 additions & 0 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@
matchPackageNames: ["ghcr.io/tursodatabase/libsql-server"],
pinDigests: true,
},
{
// backend の Python ベースイメージは 3.14 未満に固定する。
// libsql-experimental(sqlalchemy-libsql 経由)が Python 3.14 未対応で、
// 3.14 にするとマイグレーション時にネイティブ拡張が segfault する(exit 139)。
// upstream 対応待ち: tursodatabase/libsql-experimental-python#106。
// 3.13.x の patch 追従と digest 更新は許可し、3.14 への bump のみ抑止する。
matchManagers: ["dockerfile"],
matchPackageNames: ["python"],
allowedVersions: "<3.14",
},
{
// Nix: flake.lock の locked input(nixpkgs unstable / flake-utils)を追従。
// Mend hosted app では nix manager が動作する。
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,74 @@ jobs:
exit 1
fi

# コンテナ起動スモークテスト。
# test-backend は uv + 標準 SQLite で動くため、本番イメージ(Dockerfile)の
# ビルド・libsql ネイティブドライバ・alembic マイグレーション実行・uvicorn 起動という
# 実行パスを一度も通らない。実際にイメージを build → libsql を立てて起動し、
# /health(DB 接続を検証する)が 200 を返すことを確認することで、
# 「ビルドは通るが起動時にネイティブ拡張が segfault する」種の不具合を CI で検知する。
# (例: Python 3.14 への bump で libsql-experimental が segfault した事象)
smoke-backend:
runs-on: ubuntu-latest
needs: detect-changes
timeout-minutes: 20
if: needs.detect-changes.outputs.app == 'true'

steps:
- name: Checkout
# スモークテストは repo への push をしないため認証情報を保持しない(サプライチェーン保護)。
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: 起動用の一時シークレットを生成 (.env)
run: |
# 起動検証専用の使い捨て鍵。永続化せず CI 実行限りで破棄する。
PRIV="$(openssl genrsa 2048 2>/dev/null)"
PUB="$(printf '%s' "$PRIV" | openssl rsa -pubout 2>/dev/null)"
# docker-compose の .env は複数行値を扱えないため、PEM を \n エスケープした
# 1 行値にする(settings.get_jwt_private_key が \n をアンエスケープする)。
PRIV_ESC="$(printf '%s' "$PRIV" | awk 'BEGIN{ORS="\\n"}{print}')"
PUB_ESC="$(printf '%s' "$PUB" | awk 'BEGIN{ORS="\\n"}{print}')"
# Fernet 鍵 = 32 バイトの urlsafe base64
FERNET="$(openssl rand -base64 32 | tr '+/' '-_')"
cat > .env <<EOF
TURSO_DATABASE_URL=http://libsql:8080
TURSO_AUTH_TOKEN=
FIELD_ENCRYPTION_KEY=${FERNET}
JWT_PRIVATE_KEY=${PRIV_ESC}
JWT_PUBLIC_KEY=${PUB_ESC}
CORS_ORIGINS=http://localhost:5173
ENVIRONMENT=local
LLM_LOCAL_OLLAMA=1
EOF

- name: イメージを build してスタックを起動
run: docker compose up -d --build api

- name: /health が 200 を返すまで待機(起動成功を検証)
run: |
for i in $(seq 1 60); do
if curl -fsS http://localhost:8000/health; then
echo
echo "✅ backend 起動成功"
exit 0
fi
state="$(docker inspect -f '{{.State.Status}}' devforge-api 2>/dev/null || echo missing)"
if [ "$state" != "running" ]; then
echo "::error::api コンテナが起動状態ではありません (state=$state)。Segfault / マイグレーション失敗などの可能性。"
break
fi
sleep 2
done
echo "----- api logs -----"
docker compose logs api
exit 1

- name: 後片付け
if: always()
run: docker compose down -v

deploy-web:
runs-on: ubuntu-latest
needs: [detect-changes, test-web, test-backend, codegen-drift]
Expand Down
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: help \
setup install-hooks install-backend install-web generate-keys \
dev dev-build dev-down dev-web preview-web dev-proxy dev-proxy-only stripe-webhook \
dev dev-build dev-down dev-amd64 dev-amd64-build dev-web preview-web dev-proxy dev-proxy-only stripe-webhook \
test test-backend test-web \
lint lint-backend lint-web lint-web-messages lint-fix \
format format-check \
Expand All @@ -27,6 +27,8 @@ help:
@echo " dev docker-compose で API を起動"
@echo " dev-build 再ビルドして起動"
@echo " dev-down docker-compose を停止"
@echo " dev-amd64 本番と同じ amd64 で API を起動 (native 不具合の再現用 / Mac では低速)"
@echo " dev-amd64-build amd64 で再ビルドして起動"
@echo " dev-web Frontend 開発サーバーを起動 (Vite / localhost:5173)"
@echo " preview-web ビルド済みを wrangler でローカル提供 (HMR なし / localhost:8788)"
@echo " stripe-webhook Stripe Webhook を localhost:8000 へ転送 (要 stripe login / whsec を .env へ)"
Expand Down Expand Up @@ -103,6 +105,12 @@ dev-build:
dev-down:
docker compose down

dev-amd64:
docker compose -f docker-compose.yml -f docker-compose.amd64.yml up

dev-amd64-build:
docker compose -f docker-compose.yml -f docker-compose.amd64.yml up --build

stripe-webhook:
nix develop --command stripe listen --forward-to localhost:8000/api/billing/webhook

Expand Down
4 changes: 2 additions & 2 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ============================================================================
# Build stage: libsql-experimental など Rust ビルドが必要な wheel を作成する
# ============================================================================
FROM python:3.14-slim@sha256:44dd04494ee8f3b538294360e7c4b3acb87c8268e4d0a4828a6500b1eff50061 AS builder
FROM python:3.13-slim@sha256:c33f0bc4364a6881bed1ec0cc2665e6c53c87a43e774aaeab88e6f17af105e4f AS builder

WORKDIR /build

Expand All @@ -27,7 +27,7 @@ RUN pip wheel --no-cache-dir --wheel-dir=/build/wheels -r requirements.txt
# ============================================================================
# Final stage: 実行に必要なシステムライブラリと wheel のみを含む軽量イメージ
# ============================================================================
FROM python:3.14-slim@sha256:44dd04494ee8f3b538294360e7c4b3acb87c8268e4d0a4828a6500b1eff50061
FROM python:3.13-slim@sha256:c33f0bc4364a6881bed1ec0cc2665e6c53c87a43e774aaeab88e6f17af105e4f

ARG APP_VERSION=dev
ENV APP_VERSION=$APP_VERSION
Expand Down
15 changes: 15 additions & 0 deletions docker-compose.amd64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 本番(Cloud Run / CI = linux/amd64)と同じ CPU アーキテクチャで api を動かすための
# オーバーレイ。ベースの docker-compose.yml に重ねて使う:
#
# docker compose -f docker-compose.yml -f docker-compose.amd64.yml up --build
# (make dev-amd64 / make dev-amd64-build が上記をラップする)
#
# 目的: ネイティブ拡張(libsql-experimental / WeasyPrint 等)が絡む不具合を、
# 本番と同じ amd64 上で再現・検証するため。
# 注意: Apple Silicon では Rosetta/qemu エミュレーションで動くため遅い。
# 日常開発はベースのまま(ホスト native = arm64)の方が速い。
#
# libsql / redis は HTTP / TCP 接続でありアーキ非依存のため、api だけを amd64 に固定する。
services:
api:
platform: linux/amd64
Loading