Crowi QA (Bounded DevTools Charter Runner)
これは何か / 何ではないか
crowi-qa は 有限のブラウザ探索 QA を行う skill。.feature-state/specs/ feature-crowi-qa.md が正本設計で、本ファイルはそれを実行手順に落としたもの
(設計判断そのものを変えたい場合は spec を先に直す)。
- やること: 対象 worktree の proxy URL (anchor+3) に対し、9 個の有限
チャーター (§2) を順に実ブラウザで駆動し、証跡 (
.reviews/qa/<run-id>/) と findings を残す。--prod-buildで standalone ビルドの成立確認もする。 - やらないこと (
packages/e2eの責務との切り分け):packages/e2eの書き換え・Playwright テストの追加はしない。決定的回帰は E2E の役割のまま。- 新しいブラウザ自動化ライブラリを導入しない。chrome-devtools MCP と claude-in-chrome の既存ツールだけを使う。
- 永続的な QA 用 DB・製品側の QA 状態モデルを追加しない。
.claude/agents/feature-planner.md:84-90のクリティカルフロー表そのものを 拡張しない。人間がこの表を明示的に変えない限り、チャーターを増減しない。- リリースの自動 merge / tag / push はしない。
crowi-releaseの「merge / tag / push / publish はすべてユーザーの明示承認後」という鉄則も変えない。 packages/**の製品コードを変更しない。QA の結果バグが見つかった場合、 直すかどうかは fix-or-drop の対象だが、直す作業自体は呼び出し文脈 (人間 /integrate-worktree/crowi-release) が行う。- QA 用テストアカウントを自動登録しない (事前に用意された認証情報を要求する)。
- git push をしない (証跡はすべて
.reviews/qa/のローカル state。commit も しない —.reviews/は既存の gitignore 規約でカバーされる)。
起動構文
/crowi-qa # target 省略 = 現在の worktree
/crowi-qa main # main worktree
/crowi-qa <worktree-key> # 例: /crowi-qa admin-security
/crowi-qa --url https://staging.example.com # 明示 URL (registry を経由しない)
/crowi-qa <target> --prod-build # standalone ビルドスモークも実行
/crowi-qa <target> --charters 1,2,5 # 交差判定した charter だけに絞る (省略時は全 9)
/crowi-qa <target> --i-understand-destructive # 非ローカル --url で mutation charter を許可
target の解決順:
main(文字列そのまま)- worktree key (
scripts/dev-ports.mjs:45のnormalizeWorktreeKeyと同じ 正規化 — worktree ディレクトリ basename のcrowi-prefix を外したもの。crowi自体はmainに特殊化) --url <url>明示指定 (registry を経由しない。§1.7 のガードが適用される)- 省略時 = 現在の worktree (
git rev-parse --show-toplevelからnormalizeWorktreeKeyで導出)
--charters は選択的フック (integrate-worktree の交差判定、§2 相当) 用の
省略可能フラグで、指定チャーター番号 (カンマ区切り) だけを実行する。省略時は
9 チャーター全部 (full QA)。
§1. Target 解決 (registry は read-only)
1.1 anchor / proxy の解決
~/.crowi-dev-ports.json(scripts/dev-ports.mjs:32のDEFAULT_REGISTRY_PATH) を読み、portsForAnchor(anchor)(scripts/dev-ports.mjs:57) で{ api, web, site, proxy }を得る (proxy = anchor + 3)。実装コードを import せず、同等の内容を Bash /node -eで参照する:node -e " const raw = require('fs').readFileSync(process.env.HOME + '/.crowi-dev-ports.json', 'utf8'); const registry = JSON.parse(raw); const anchor = registry['<key>']; if (anchor === undefined) { console.error('not running'); process.exit(1); } console.log(JSON.stringify({ api: anchor, web: anchor + 1, site: anchor + 2, proxy: anchor + 3 })); "registry を 書き込まない —
allocateAnchor(scripts/dev-ports.mjs:223) は絶対に呼ばない。対象 worktree のpnpm devがまだ起動していない (keyが registry に無い) 場合は、新しいアンカーを推測で採番せずblocked: <target> is not running (start 'pnpm dev' in that worktree first)として終了する。proxy 以外へは QA しない: raw web port (anchor+1) には直接アクセスしない。
/collab//presence//notificationsの WS は同一オリジン proxy (anchor+3) 経由でしか成立しない (packages/web/src/lib/resolve-ws-url.tsの doc comment、scripts/dev-caddy.mjsのWS_NAMESPACES)。proxy 以外への QA は WS namespace を検証できないため許可しない。
1.2 stale registry entry の拒否
readRegistry (scripts/dev-ports.mjs:67) は gw end 済み・削除済み
worktree の残留 key をそのまま返す (pruning は組み込まれていない)。target
解決の一部として毎回:
git worktree list --porcelain | awk '/^worktree /{print $2}'
の各パスを normalizeWorktreeKey と同じ規則 (basename の crowi- prefix を
外す。crowi は main) で正規化し、解決対象 key がこの集合に含まれることを
確認する。含まれない場合は
blocked: stale registry entry (worktree '<key>' not found in 'git worktree list' — the worktree has likely been closed)
として中断する (registry への pruning 書き込みはしない — read-only を保つ)。
1.3 疎通確認
GET <proxy>/api/app/info が 200 を返すことを健全性の最低条件にする
(packages/e2e/playwright.config.ts の webServer readiness probe と同じ
エンドポイント)。200 でなければ blocked: proxy not responding at <proxy url>。
1.4 proxy identity 検証 (mutation を伴う charter のみ必須)
/api/app/info (packages/api/src/hono/handlers/app.ts) は title /
version / capabilities のみを返し、worktree key・cwd・branch・git sha を
含まない。gw end 後に別プロセスが同じ proxy ポートを再利用した、等の
ポート再利用ケースを 200 応答だけでは検出できない。したがって
mutation を伴う charter (#3・#4・#5・#9、認証 charter の mutating
サブフロー、--prod-build) を開始する前に:
PID=$(lsof -iTCP:<proxyPort> -sTCP:LISTEN -t)
CWD=$(lsof -a -p "$PID" -d cwd -Fn | sed -n 's/^n//p')
# $CWD が解決対象 worktree のディレクトリ配下であることを確認
一致しない、または lsof が使えない環境では
blocked: cannot verify proxy process identity for <target>
として該当 charter (または --prod-build) をスキップする。読み取り専用
charter (認証の login/logout/session サブフロー・検索・通知一覧表示・collab
の疎通確認のみ) はこの検証を必須としない — mutation のリスクが無いため。
1.5 dev infra チェック (Mongo/Redis)
packages/e2e/src/preflight.ts と同じ発想で、対象 worktree の Mongo
(27017) / Redis (6379) への TCP 到達性を確認する (DB 分離時は接続先ホスト自体は
同じ、db 名だけが違うので TCP 到達性チェックはポート単位でよい)。落ちていれば
blocked: dev infra down (docker compose up -d が必要)
として 全チャーターを実行せずに終了する。これは crowi-complete-feature
の infra-down 時の扱い (.claude/skills/crowi-complete-feature/SKILL.md —
fail 扱いにせず blocked として signal を立てず報告) と同じ方針。
1.6 active backend の preflight (API レベルの canary)
対象 worktree のランナー projectDir (dev は常に apps/crowi-runner) の
crowi.config.json を読み、storage.driver / search.driver を確認する —
これは「external driver かどうかの判定」だけに使い、接続先 URL・認証情報は
読まない (crowi.config.json にはそもそも無く、実体は Mongo Config 側に
あり暗号化される場合もある。admin API も hasValue に redact して返す —
packages/api/src/hono/handlers/admin/plugins.ts)。ES の _cluster/health
や S3 バケットへの直接到達確認はしない。
external driver を使う charter は、charter 自身の最初の 1 操作を canary として使う:
検索 charter (#6): 最初の検索クエリを実行する。
crowi.getSearcher()が未登録 (ES URL 未設定) ならSEARCH_UNAVAILABLE_BODY付き 503 (packages/api/src/hono/handlers/search.ts) が返る。この 503 を見た時点で 残りのバジェットを使わず即座にblocked: search backend unreachable (503)として charter を終える。添付・アップロード charter (#9): 最初のアップロード操作を canary として 実行する。S3 の bucket 未設定は
requireBucket(packages/plugin-storage-aws-s3/src/index.ts) の例外として現れるが、 ハンドラ側は他の失敗と区別せず汎用の 500 (UPLOAD_FAILED/INTERNAL_ERROR_BODY—packages/api/src/hono/handlers/attachment.ts) を 返すため、検索のような一意な信号が無い。最初の操作が 5xx で失敗した場合は 「backend 未接続」と決めつけず、blocked: attachment charter first action failed (<status>, see network.log — ambiguous: storage backend unreachable or product bug)として severity
highの finding として記録し、残りのバジェットを 使わずに charter を終える (製品バグの可能性を握り潰さない)。ローカル driver (storage-local / search-mongo 等) しか要求しない構成では、 この canary 分岐は発生せず charter は通常どおり全操作を実行する。
1.7 --url の扱い (destructive ガード)
--url 指定時は registry を経由せず、そのまま疎通確認 (1.3) のみ行う。
worktree 検証 (1.2)・proxy identity 検証 (1.4)・active backend preflight
(1.6) は可能な範囲で行い、できない部分は environment.json に
「worktree 検証: skipped」と明記する。
ローカル判定の定義 (dev-port registry にホスト名は保持されていないため read-only な導出をする): 以下のいずれかに一致すれば「ローカル」:
localhost/127.0.0.1かつポート番号が 解決済み anchor からportsForAnchorで導出される 4 ポート (api/web/site/proxy) のいずれか に一致する。resolveTailscaleHostname()(scripts/dev-ports.mjs:407—tailscale status --jsonから解決) が 実際に解決できた tailscale hostname に 一致する。
node -e "
try {
const out = require('child_process').execFileSync('tailscale', ['status', '--json'], { encoding: 'utf8' });
console.log(JSON.parse(out)?.Self?.DNSName?.replace(/\.\$/, '') ?? '');
} catch { console.log(''); }
"
どちらにも一致しない --url (社内ステージング・本番相当ドメイン等) は
非ローカルと判定する。
非ローカル判定の --url は、mutation を伴うチャーター (#3・#4・#5・#9、
認証チャーターの mutating サブフロー) と --prod-build を
blocked: destructive charter refused on non-local --url (pass --i-understand-destructive to override)
としてスキップする。--i-understand-destructive を明示した場合のみ実行する
(縮退実行や既定 override はしない)。読み取り専用チャーター (認証の
login/logout/session 遷移のみ・検索・通知一覧表示・collab の疎通確認のみ) は
--url のホスト制限を受けない。
§2. 9 チャーターと対応パス表
.claude/agents/feature-planner.md:84-90 のクリティカルフロー表を唯一の
正本として展開する (表そのものは拡張しない)。「対応パス」列は
integrate-worktree の選択的フック判定 (本節) に使う交差判定表:
| # | charter | 既存 E2E カバレッジ (サブフロー単位) | 対応パス (交差判定用) |
|---|---|---|---|
| 1 | 認証 (§3 で shared-DB 読み取り専用 / isolated-DB mutating の 2 層に分離) | partial: login/logout/session 遷移は auth-state.spec.ts がカバー (shared DB・読み取り専用)。installer 経由の admin 作成は onboarding.setup.ts が UI 経由で一度だけ通す happy path のみ。oauth / password reset / activation / email change は未カバー |
packages/api/src/hono/middleware/auth.ts, packages/api/src/hono/handlers/{tokenAuth,oauth,passwordReset,activation,emailChange,me,installer}.ts, packages/web/src/app/(public)/**, packages/web/src/app/(auth)/oauth/**, packages/web/src/lib/{use-auth,api-client}.ts |
| 2 | collab (+ presence) | partial: 2 窓間 edit propagation は collab.spec.ts がカバー。presence (viewer 一覧・indicator) は未カバー |
packages/api/src/collab/**, packages/api/src/presence/**, packages/api/src/hono/handlers/{page-collab,presence}.ts, packages/web/src/components/editor/**, packages/web/src/lib/{use-collab-document,use-presence,resolve-ws-url}.ts |
| 3 | ページ CRUD・rename・trash | partial: onboarding.setup.ts は API 経由 (createPageViaApi) で 1 ページを seed するのみ。UI 経由の CRUD/rename/trash は未保護 |
packages/api/src/hono/handlers/{page,backlink,page-portalize-twin}.ts, packages/api/src/models/{page,revision,backlink}.ts, packages/api-contract/src/contracts/page.ts, packages/web/src/app/(auth)/[[...slug]], packages/web/src/app/(auth)/trash, packages/web/src/lib/{use-page,use-page-mutations,use-page-list,use-page-children}.ts |
| 4 | エディタ save・draft | なし | packages/api/src/hono/handlers/{draft,revision,page-preview}.ts, packages/api-contract/src/contracts/page-preview.ts, packages/web/src/app/(auth)/%5Fedit, packages/web/src/components/editor/**, packages/web/src/lib/{use-drafts,use-page-revisions}.ts |
| 5 | コメント | なし | packages/api/src/hono/handlers/comment.ts, packages/api/src/models/comment.ts, packages/api-contract/src/contracts/comment.ts, packages/web/src/lib/use-page-comments.ts, packages/web/src/components/page-comments/** |
| 6 | 検索 | なし | packages/api/src/hono/handlers/search.ts, packages/api-contract/src/contracts/search.ts, packages/web/src/app/(auth)/%5Fsearch, packages/web/src/lib/use-search.ts, packages/plugin-search-* |
| 7 | 通知 | なし | packages/api/src/notifications/**, packages/api/src/hono/handlers/notification.ts, packages/api-contract/src/contracts/notification.ts, packages/web/src/app/(auth)/%5Fnotifications, packages/web/src/lib/{use-notifications,use-notifications-socket,resolve-ws-url}.ts |
| 8 | 管理設定 (§3 で既定 read-only) | partial: onboarding.setup.ts が mail SMTP 送信元アドレス保存・ユーザー招待・招待受諾をカバー。セキュリティ設定・プラグイン設定 (mail 以外)・crypto 等は未保護 |
packages/api/src/hono/handlers/admin/**, packages/api-contract/src/contracts/admin/**, packages/web/src/app/(admin)/** |
| 9 | 添付・アップロード | なし | packages/api/src/hono/handlers/attachment*.ts, packages/api/src/models/attachment.ts, packages/api-contract/src/contracts/attachment.ts, packages/web/src/app/(auth)/%5Fattachments, packages/web/src/lib/{use-attachments,use-attachment-usage}.ts |
横断 fanout パス (複数チャーターへ OR 条件で交差)
以下は単一チャーターへの割り当てだけでは交差判定が漏れるため、列記した すべてのチャーターを交差対象にする (表の割り当てに加えて OR 条件):
packages/web/src/lib/resolve-ws-url.ts—/collab//presence//notificationsが共有する WS URL 解決ロジック。charter 2・7 を 交差対象にする。packages/web/src/lib/api-client.ts,packages/web/src/lib/use-auth.ts(トークン取得・付与・refresh) — 認証済み API 呼び出しは全チャーターが この共有クライアント経由。charter 1〜9 すべてを交差対象にする。- ランタイム env 解決 (
window.__ENV注入元のレイアウト、NEXT_PUBLIC_*読み出し全般) — 同様に charter 1〜9 すべてを交差対象にする。 packages/api/src/util/fileUploader.ts(すべての put/get/delete をアクティブな storage driver に委譲する共有層) とpackages/plugin-storage-*/(実ドライバ 実装) — charter #9 の対応パスに追加する (表の #9 行はハンドラ/contract/ web レイヤーのみで、実際の読み書きを担うこの共有 storage 層が漏れていた)。
共有 runtime / proxy パス (個別割り当てを試みず全 9 charter = full QA)
以下は影響範囲が「どの charter に効くか」を OR 条件では判定しきれないほど 広く (全 namespace のルーティング・全ハンドラの登録・全アクティブ driver の 解決)、変更があれば charter を絞らず全 9 charter を対象にする:
packages/api/src/hono/index.ts—buildHonoAppが全 route / 全 admin サブリソースを登録する配線そのもの。packages/api/src/plugin/plugin-manager.ts— アクティブな storage / search driver の解決。packages/api/src/crowi/index.ts— boot sequence (Crowi.init全体)。scripts/dev-caddy.mjs— proxy の同一オリジンルーティング表 (API_HTTP_PATHS/pickProxyTarget)。ここが壊れると WS namespace を 含む全 charter の疎通そのものが壊れる。
§3. バジェットと incomplete / blocked の区別
charter 単位のデフォルトバジェット:
| 項目 | 上限 |
|---|---|
| ブラウザ操作 | 最大 10 回 |
| 失敗操作のリトライ | 最大 2 回 |
| 経過時間 (ソフト) | 5 分 |
| 経過時間 (ハード) | 8 分 |
上限到達時は incomplete (budget exhausted) としてそこまでの証跡を
確定し、粘らずに次の charter に進む。blocked (前提条件不足 — infra down /
資格情報なし / proxy identity 不一致 / 非ローカル --url 拒否 等) とは
明確に区別する:
blocked= 前提条件が満たせず charter を 開始できない、または途中で 前提が崩れた。incomplete= charter を実際に探索したが、バジェット (操作数/リトライ/ 時間) を使い切って 打ち切った。
サブフロー単位の E2E カバレッジ調整 (charter 全体を一括判定しない)
- smoke レベル (バジェットを半分程度に短縮): 認証 charter (#1) のうち
login/logout/session 遷移 (
auth-state.spec.tsがカバー)、collab charter (#2) のうち 2 窓 edit propagation (collab.spec.tsがカバー) のみ。視覚 / ログ / WS 証跡の確認に重点を置き、既存アサーションを再実装しない。 - 通常バジェット: 同じ charter 内でも installer 経由の admin 作成 (単発 happy path のみ検証済み)・oauth・password reset・activation・ email change・presence は E2E が守っていない (または単発 happy path に 留まる) ため通常バジェットで探索する。
- ページ CRUD・rename・trash (#3) / 管理設定 (#8): バジェットを縮小
しない。
onboarding.setup.tsがカバーする一部 (API 経由のページ seed、 mail SMTP 保存・ユーザー招待・招待受諾) の再確認は省略するが、それ以外 (UI 経由の CRUD/rename/trash、管理設定の他セクション) は未保護のフローと 同じ扱いで通常バジェットで探索する。
既存 E2E が守っているアサーションを crowi-qa が再実装することはない
(.feature-state/specs/dev-cycle-skills/04-e2e-targets.md のポイント
ポイント方針を踏襲)。
§4. 認証 charter (#1) の 2 層構成
4.1 shared-DB 読み取り専用サブフロー (既定・どの target でも実行可)
login / logout / session 遷移 (同一タブ切替・別タブ logout 伝搬・reload
保持)。環境変数 CROWI_QA_USER_EMAIL / CROWI_QA_USER_PASSWORD で受け取る
既存アカウントを使う。
4.2 isolated-DB 限定 mutating サブフロー
対象: POST /auth/register・POST /auth/activate・POST /auth/reset-password・PUT /me の email 変更申請 + POST /auth/confirm-email-change・POST /installer/createAdmin・oauth
authorize/device consent (いずれも User / Config / OAuth トークンレコードを
書き換える)。
既定 (共有 dev DB) では実行しない。共有 dev DB のユーザーレコードや installer 済み状態を書き換えると復元できない。
isolated DB (
dev.local.jsonでisolateDb: trueを宣言した worktree、または--prod-buildの per-run DB) でのみ実行してよい。 それ以外 (§1.7 の非ローカル--urlを含む) ではblocked: mutating auth subflow requires isolated DBとしてこれらのサブフローだけをスキップし、読み取り専用サブフロー (4.1) は 通常どおり実行する。isolated DB 側の資格情報は
CROWI_QA_ISOLATED_ADMIN_EMAIL/CROWI_QA_ISOLATED_ADMIN_PASSWORDで受け取る (自動登録はしない — 未設定 ならblocked: no credentials (isolated DB)としてスキップ)。Mailpit の稼働チェックが前提条件: password reset の forgot-password は mail を fire-and-forget して常に 200 を返す・register の activation mail・email change の確認 mail はいずれも mail 内 token リンクを踏まないと 完了しない。SMTP driver は host 未設定で例外を投げるため、mutating サブフローを始める前に
packages/e2e/src/preflight.tsのassertMailpitHttpと同じ検査 (SMTP 1025 / HTTP 8025 への TCP 到達 +GET {mailpitApiUrl}/infoまたは/messagesが 200) を行い、失敗すればblocked: mailpit unreachableとしてこれらのサブフローだけをスキップする。mail 内 token の捕捉:
packages/e2e/src/mailpit.tsのwaitForLatestMessageTo(email)と同じ方式 (Mailpit HTTP API/messagesを受信者アドレスでポーリングし最新メールを取得) で対象メールを取得し、 本文から${baseUrl}/reset-password?token=.../${baseUrl}/activate?token=.../${baseUrl}/confirm-email?token=...のいずれかのパターンに一致するリンクを正規表現で抜き出し、そのリンクへ ナビゲートして charter を進める (extractInviteLinkと同じ「既知の URL パターンへの正規表現マッチ」手法)。捕捉した token / リンクは §12 の redaction 対象に含める (生の値をログ・notes に残さない)。
§5. 認証情報の前提 (自動登録はしない)
crowi-qa は対象 dev DB に既存ユーザー/管理者の認証情報が用意されている
ことを前提にする。環境変数で受け取り、未設定の場合は 該当 charter だけ
blocked: no credentials として次に進む (全体を止めない):
| 環境変数 | 用途 |
|---|---|
CROWI_QA_USER_EMAIL / CROWI_QA_USER_PASSWORD |
一般ユーザー (auth 読み取り専用サブフロー・collab・ページ CRUD 等) |
CROWI_QA_ADMIN_EMAIL / CROWI_QA_ADMIN_PASSWORD |
管理設定 charter (#8、read-only 確認用) |
CROWI_QA_ISOLATED_ADMIN_EMAIL / CROWI_QA_ISOLATED_ADMIN_PASSWORD |
isolated DB (isolateDb: true worktree) 限定の mutating サブフロー用。この worktree でまだ資格情報が無ければ人間が一度だけ installer/register を回して用意する |
多くの worktree は既定で main と DB を共有しており (readDevLocalConfig が
返す isolateDb は既定 false)、テストアカウントの自動作成は共有 dev
データを汚すリスクがある — したがって どのケースでも自動登録はしない。
§6. run id・ページ作成 path prefix・cleanup
6.1 run id
<UTC yyyymmdd-HHMMSS>-<4 桁乱数 または pid>-<target> 形式 (例
20260705-134502-7931-main)。同一 target に対して同じ秒に複数の
crowi-qa プロセスが起動しても衝突しないようにする。証跡ルート
(.reviews/qa/<run-id>/) もこの run id を使う。
RUN_ID="$(date -u +%Y%m%d-%H%M%S)-$$-<target>"
6.2 ページ作成の path prefix
charter が作成するページ・コメント・添付は、必ず run 専用の一意な path
prefix 配下に置く: /qa/<run-id>/<charter>/...。
6.3 manifest と hard delete による cleanup
charter は作成したページ・コメントの id を、作成した その場で run の
エビデンスルート配下の manifest ファイル .reviews/qa/<run-id>/created.json
に追記する。配列の各要素は判別用の type フィールドを持つ: ページは
{ type: "page", pageId, path, charter }、コメントは
{ type: "comment", commentId, pageId, charter } (§6.4 の api-fixture
セットアップが seed-fixtures.mjs 経由で書き込む形)。type フィールドの
無い要素 (旧 run の manifest、または browser-editor セットアップが書く従来
形式) は読み取り時に type: "page" として扱う後方互換パーサを使う。
charter 終了時のクリーンアップは、この manifest に記録された type: "page" の page id のみを対象に hard delete (DELETE /pages を
completely: true で呼び、Page.completelyDeletePage に落とす) を行う。
completely: true はレビジョンチェックをバイパスし、bookmark・comment・
attachment (バッキング storage オブジェクトごと)・redirect origin・
activity を丸ごと削除するため、対象を誤ると共有 dev DB の既存データを永久に
失う。api-fixture セットアップ (§6.4) が作成したページ・コメントは QA
専有インスタンス上に存在し、ambient dev 向けのこの hard delete ループの
対象にはしない — それらは同じ run の DB drop (§6.4) でまとめて破棄される。
推奨実装 (実測済み): 認証済みブラウザセッションの
fetch()をevaluate_script経由で manifest の id ごとにループさせる — セッションの 認証がそのまま乗るので curl 用の JWT 取得が不要で、削除の 200/404 応答も その場で確認できる。
- fallback 確認: manifest が壊れている / 読めない場合に 限り、
path が
/qa/<run-id>/...prefix 配下 かつ creator がこの run の QA アカウントと一致するページを候補として列挙し、削除前にfindings.mdに 列挙して manifest 破損の事実とともに報告する (manifest が健全なときは この fallback 列挙は行わない)。この列挙はtype: "page"相当 (typeフィールドの無い旧形式を含む) のみを対象にし、type: "comment"は列挙 対象に含めない (コメントはページではなく、対応するページの hard delete に連鎖して消える)。どちらの経路でも一致しないページには削除も soft-delete もせず、既存データに一切触れない。 - 通常の soft delete は使わない理由:
Page.deletePage(completely省略) はstatus: deletedにして/trash/...へリネーム + redirect page を作るだけで実データ (ページ本体・コメント・添付) は残り、かつPage.pathは unique index なので同一パスを次回 run が再利用すると 衝突する。したがって manifest (または fallback で確認できた) ページは hard delete で完全に除去し、それ以外のページはゴミ箱にも入れず何もしない。 - hard delete に失敗した場合は
summary.mdに残留物 (page id / path) を 明記する (運用側が手動で削除するための情報)。
6.4 api-fixture セットアップと QA 専有インスタンス
table・backlink・検索結果・コメント・grant 可視性のように、検証対象が
「保存済みページの見え方」であるチャーターは、エディタでページ本文を手打ち
する代わりに .claude/skills/crowi-qa/scripts/seed-fixtures.mjs (Node 標準
fetch のみに依存 — Mongo driver・workspace パッケージの import には依存
しない) でページ/コメントをシードし、作成した URL へ直接ナビゲートして表示
確認から始める。エディタ状態そのものが検証対象のチャーター (#2・#4) は
このセットアップを使わず、従来どおり ambient dev に対するブラウザ/エディタ
操作でセットアップする。
6.4.1 setup mode ルーティング表
| charter | setup mode | 専有インスタンス起動不可時 | 理由 |
|---|---|---|---|
| collab / presence (#2) | browser-editor (ambient dev) |
— (専有インスタンスを使わない) | Yjs/エディタ状態と WS 挙動そのものが検証対象 |
| エディタ保存・draft (#4) | browser-editor (ambient dev) |
— (同上) | エディタ操作そのものが検証対象 |
| ページ CRUD・rename・trash (#3) のうち table/markdown 表示確認 | api-fixture (専有インスタンス) |
browser-editor へフォールバック |
保存済みページの表示確認であり入力操作は不要 |
| backlink 表示 | api-fixture (target→source の順序制約 + readiness polling 必須。6.4.4 参照) |
browser-editor へフォールバック |
save イベント由来の backlink 副作用を検証したい |
| grant 可視性 (シード本人が owner のページの表示確認) | api-fixture |
browser-editor へフォールバック |
単一 identity で作成・閲覧できる範囲に限定 (owner 視点のみ) |
| grant access-denied (別ユーザーからの拒否確認) | browser (api-fixture 化しない) |
変更なし | 第二 identity が必要で今回のスコープ外 |
| 検索結果 (新規保存したページがヒットするかの確認) | api-fixture (503 short-circuit + readiness polling 必須。6.4.4 参照) |
browser-editor へフォールバック |
実際のページ保存と検索側副作用が必要。per-run DB は search 未設定が既定なので 503 → 即 blocked が通常パス |
| 検索の grant フィルタリング (非 owner に非表示になることの確認) | browser (api-fixture 化しない) |
変更なし | 第二 identity が必要で今回のスコープ外 |
| コメント (#5) | api-fixture (同一 run が作った fixture ページに対してのみ) |
browser-editor へフォールバック |
コメント作成はセットアップ、UI 表示が検証対象 |
| 管理設定 (#8) | 既定 browser または read-only (§7) |
変更なし | ambient 共有 DB での config mutation は §7 で禁止 |
| 添付・アップロード (#9) | アップロード操作自体の検証は browser。既存ファイルの表示確認のみなら対応する API があれば api-fixture (対応する attachment API が今回未検証のため、現状は browser のまま) |
変更なし | アップロード操作自体が検証対象になり得る |
認証チャーター (#1) は既存の §4.1/§4.2 の 2 層構成をそのまま維持し、この 表の対象に含めない (すでに read-only/isolated-mutating の分離ルールが あるため)。
6.4.2 QA 専有インスタンス: 書き込み先を「判定」せず「所有」する
api-fixture セットアップのチャーターが 1 つでも run に含まれる場合、run
は最初に一度だけ QA 専有インスタンスを起動する。mutating なフィクスチャ
書き込みは常にこの専有インスタンスに対してのみ行い、ambient dev インスタ
ンス (共有 DB・isolated DB を問わず) には一切 mutating な書き込みをしない。
書き込み先の安全性は「環境の分類・判定」ではなく「自分で名前を決めて自分で
起動した」というコンストラクションで保証する — §14.3-§14.7 の
--prod-build 用インスタンスと同じ発想の dev 版であり、機構は流用する
(新設しない)。
起動は run につき最大 1 回: 複数の
api-fixtureチャーターが専有 インスタンス 1 本を共有する。api-fixtureチャーターが 1 つも無い run では起動しない (既存挙動に回帰なし)。--prod-buildとの関係:--prod-buildrun では §14 のインスタンス がそのまま seeding 対象になる。専有インスタンスを二重起動しない。DB 名:
crowi_qa_dev_<run-id>(§14.3 のcrowi_qa_prod_<run-id>と 同じ命名スタイル。run id は §6.1 の既存 run id)。MongoDB は最初の書き込み で DB を暗黙に作成するため、作成手順は不要。63 バイト制限に触れる場合は run id 部分を短縮ハッシュにする。env サニタイズ: §14.3 と同一の理由・同一の手順。
MONGOLAB_URI/MONGODB_URI/MONGOHQ_URLを明示的に unset した上でMONGO_URIだけ を設定して起動する。dev モードの api はapps/crowi-runnerを cwd にtsxで起動する (packages/api/package.jsonのdevスクリプトと同じ cwd 解決。tsx watchの watch は不要 — 専有インスタンスは 1 回起動して run 終了時に落とすだけなので、素のtsxでよい):cd apps/crowi-runner env -u MONGOLAB_URI -u MONGODB_URI -u MONGOHQ_URL \ MONGO_URI="mongodb://localhost/crowi_qa_dev_<run-id>" \ NODE_ENV=development PORT=<apiPort> \ npx tsx --tsconfig ../../packages/api/tsconfig.json \ --env-file-if-exists=../../.env \ ../../packages/api/src/app.ts--env-file-if-existsで読む repo-root.envにMONGO_URIが定義され ていても、Node の--env-fileはプロセスに 既に設定済みの 環境変数を ファイルの値で上書きしない (Node の仕様) ため、上記の明示MONGO_URIが 優先される。web はpackages/webを cwd にnext devを起動し、PORT_WEB=<webPort>/CROWI_API_URL=http://localhost:<apiPort>を渡す (scripts/dev.mjsがpnpm devに注入するのと同じ変数)。前段の同一 オリジン proxy はscripts/dev-caddy.mjsのstartNodeProxyFallback()を流用する (§14.5 と同じ — dev-port registry には登録しない使い捨て ポートを OS プローブで都度選ぶ)。unset が信頼できない場合は §14.3 と同じ く fail-closed でblocked: conflicting Mongo env var present — cannot guarantee isolationとして起動しない。起動後は接続先 DB 名をログで確認 してから provisioning に進む。provisioning: §14.4 と同一手順の dev 版。
GET <一時 proxy>/api/installerでinstaller_requiredを確認し、POST /installer/createAdminを run 専用の固定資格情報 (実運用の秘密情報では ない固定値。例crowi-qa-fixture@example.com/ 固定パスワード —packages/e2e/src/config.tsのe2eUsers.adminと同じ発想) で呼ぶ。already_installedが返る想定外のケースは同じ資格情報でログインを試し、 失敗すればblocked: qa DB in unexpected stateで打ち切る。後始末: §14.7 と同一機構。成否によらず api/web/proxy プロセスを終了 し、
crowi_qa_dev_<run-id>を drop する。drop 失敗時はsummary.mdに 残留 DB 名を明記する。フォールバック: 専有インスタンスの起動に失敗した場合 (ポート確保 不可・Mongo 不達等) は
blocked: qa-owned instance failed to start (<reason>)とし、該当api-fixtureチャーターは従来の browser-editor セットアップ (ambient dev 上でエディタ手打ち) へフォールバックする — 検証能力自体は後退しない。ambient dev への mutating write はこの フォールバックでも従来の境界 (§4.2・§5・§7) に従う。
6.4.3 seed-fixtures.mjs の使い方
QA 専有インスタンスへのログイン・フィクスチャ書き込み・readiness polling・
manifest 追記は .claude/skills/crowi-qa/scripts/seed-fixtures.mjs が HTTP
のみで行う (Mongo driver・workspace import には依存しない)。専有インスタン
スの起動・provisioning・DB drop はこのスクリプトの責務ではなく、上記
6.4.2 の手順どおり skill 側 (エージェントが Bash で駆動) が行う。
node .claude/skills/crowi-qa/scripts/seed-fixtures.mjs \
--proxy-url http://127.0.0.1:<一時 proxy port> \
--run-id <run-id> \
--charter <table|grant|backlink|search|comment> \
--email crowi-qa-fixture@example.com \
--password <6.4.2 で provisioning した固定パスワード> \
--manifest-path .reviews/qa/<run-id>/created.json
標準出力に作成したリソースの JSON summary (ok / charter / resources
/ 該当時 blocked / query) を 1 個だけ出力する。生の accessToken /
refreshToken はログ・標準出力のどこにも出力しない (§12 の redaction 方針
と同じ結論を、そもそも出力しない設計で満たす)。ok: false (exit code 1) は
readiness polling が blocked: ... に分類されたことを示す — この場合でも
作成済みのページ/コメントは manifest に記録済みで、失敗したのは「副作用が
観測できるまで待つ」ステップだけである。exit code 2 は用法/通信エラー
(必須フラグ欠落・ログイン失敗・HTTP エラー等)。
ブラウザ側の認証はこのスクリプトの accessToken を受け取らない — ブラウザ
は専有インスタンスのログイン画面から、同じ固定資格情報で通常の UI ログイン
を行う (§14.6 のログイン手順と同じ)。Node プロセスとブラウザプロセスの間で
トークンを受け渡す経路は作らない。
6.4.4 charter ごとのフィクスチャ内容と readiness polling
- table / grant:
/qa/<run-id>/table/displayまたは/qa/<run-id>/grant/visibleにページを 1 枚作成するだけ。readiness polling は不要 — 作成 API の応答が返った時点でそのまま表示確認できる。 - backlink: 必ず target を先に作成する
(
/qa/<run-id>/backlink/target)。次に source (/qa/<run-id>/backlink/source) を作成し、本文に target の正確な path へ解決される markdown リンク ([target](/qa/<run-id>/backlink/ target)) を含める — 裸の[[x]]wikilink は予約済みで解決されないため 使わない。この順序を逆にすると backlink 行は書かれず、後続のポーリングは 必ずタイムアウトする。両ページの保存後、GET /api/backlinks?page_id= <target のページ id>を有限回 (既定 300ms 間隔 × 最大 10 回) ポーリング し、backlinksに source ページが含まれることを確認する。タイムアウト 時はblocked: backlink side effect not observed within <timeout>として このチャーターの api-fixture セットアップだけを打ち切る (browser 側で 「backlink が表示されない」という偽陰性確認をさせない)。 - 検索: 対象ページ (
/qa/<run-id>/search/target) を本文に一意な トークンを含めて保存した後、GET /api/search?q=<token>を同様に 有限回ポーリングする。503 (SEARCH_UNAVAILABLE) を検知した時点で即座にblocked: search backend unreachable (503)(既存 §1.6 と同一分類) として 打ち切り、ポーリングを継続しない — 新規 per-run DB は search 未設定が 既定なので、これはこの環境での通常パス。200 応答で対象ページ id がまだ 現れない場合のみポーリングを続け、タイムアウト時はblocked: search index side effect not observed within <timeout>とする。 - コメント:
/qa/<run-id>/comment/threadにページを 1 枚作成し、 そのレスポンス (またはGET /api/pages?page_id=...) から revision id を取得した上でPOST /api/commentsを呼ぶ。同一 run 内でこの スクリプトが新規作成した fixture ページ (manifest 上type: "page") に のみコメントを追加する。readiness polling は不要 (コメント作成は 同期的)。
§7. 管理設定 charter (#8) は既定で read-only
セキュリティ設定は Config を永続変更し (packages/api/src/hono/handlers/ admin/security.ts)、crypto カードの再暗号化は sensitive config の値を
書き換える (packages/web/src/components/admin/crypto-status-card.tsx,
packages/web/src/lib/use-admin-crypto.ts, packages/api/src/hono/ handlers/adminCrypto.ts)。共有 dev DB (既定) ではこれらのグローバル設定を
実際に変更しない — 画面表示・入力バリデーション・保存前 confirm
ダイアログの確認までを行い、実際の保存 / 再暗号化ボタンは押さない。
Config を実際に変更する検証は、dev.local.json で isolateDb: true を
宣言した worktree に対してのみ実施してよい。
§8. 証跡モデル
8.1 ディレクトリ構造
.reviews/qa/<run-id>/
summary.md # charter 別 pass/blocked/incomplete + 全体 verdict
environment.json # target/proxy URL/git sha/driver/infra 状態/--prod-build 有無
findings.md # fix-or-drop の findings (§9)
created.json # manifest (§6.3)
flows/<charter>/
notes.md
screenshots/*.png
console.log
network.log
prod-build/ # --prod-build 実行時のみ
build.log
server.log
smoke-notes.md
screenshots/*.png
.reviews/ は既存の gitignore 済み local state 規約 (integrate-worktree
の .reviews/ / crowi-review の .reviews/crowi-review/) を踏襲する。
commit しない。
実測済みの注意: ハーネスの Write ガードが
findings.md/summary.md等の ファイル名 (report/summary/findings/analysis を含む .md) への Write ツール書き込みを 拒否することがある。その場合は Bash heredoc で書く (cat > ... <<'EOF')。 ファイル名自体は変えない (この構造が契約)。
summary.md の verdict には、--prod-build を実行した場合
prod build verified / prod build unverified: <reason> /
prod build skipped: <reason> も併記する。
8.2 保持ポリシー
crowi-qa は自動削除ロジックを持たない (誤削除を避ける)。同一 target の
直前 run は上書きせず残す (比較用)。肥大化したら運用側が手動で
.reviews/qa/ 配下を掃除する。
§9. findings と fix-or-drop
フィールド: id / title / severity (critical / high / medium /
low — crowi-review の findings schema と同じ enum) / flow /
environment / repro / expected / actual / evidence (スクショ・
ログへの相対パス) / disposition (fixed | dropped: <reason>)。
crowi-qa 自身は製品コードを直さない (このスキルはドキュメント/報告に閉じる)。
disposition の実行 (直す/捨てる) は呼び出し文脈の責務:
- 単体起動 (人間が直接
/crowi-qaを叩いた場合) — その場で直すか、 理由を添えて drop する。どこかへの退避は禁止 (退避先は存在しない)。 integrate-worktreeから呼ばれた場合 — Step 7 の simplify と同じ 「その場で直すか、報告 1 行で捨てる」運用に findings を合流させる。crowi-releaseの pre-flight から呼ばれた場合 — pre-flight は read-only (git 状態を変更しない) なので、未解決の high/critical findings は Go/No-Go 材料の「リスク / 未検証」欄にそのまま出す。 pre-flight 自身が直すことはしない。
findings は fix-or-drop。退避は全ケースで禁止 (全 skill 共通 方針)。
§10. ブラウザドライバの優先順位と証跡取得
優先順位: chrome-devtools MCP → claude-in-chrome → どちらも使えなければ
blocked: no browser driver available (証跡が取れない状態で QA を
続けない。縮退実行はしない)。
10.1 chrome-devtools MCP (優先)
navigate_page / click / fill / evaluate_script / wait_for で
charter の操作を行い、証跡は以下で取る:
- スクリーンショット:
take_screenshot - コンソール:
list_console_messages/get_console_message - ネットワーク:
list_network_requests/get_network_request
10.2 claude-in-chrome (フォールバック)
navigate / find / form_input / computer で charter の操作を行い、
証跡は以下で取る:
スクリーンショット:
computerツールをaction: "screenshot"で 呼ぶ (Anthropic computer-use tool の標準アクション)。戻り値の画像 (base64 PNG) をデコードし、chrome-devtools MCPのtake_screenshotと同じ命名規約で.reviews/qa/<run-id>/flows/<charter>/screenshots/ <連番>.pngに保存する:# $SCREENSHOT_BASE64 は computer(action="screenshot") の戻り値 echo "$SCREENSHOT_BASE64" | base64 -d \ > ".reviews/qa/<run-id>/flows/<charter>/screenshots/$(printf '%02d' "$N").png"撮影タイミングは chrome-devtools MCP 側と揃える: charter 開始直後の画面 ロード後・charter の各操作直後・finding を発見した時点、の最低 3 回。
コンソール:
read_console_messagesネットワーク:
read_network_requests画面構造 (要素特定・アサーション補助であり、スクリーンショットの代替では ない):
read_page
証跡フォーマット・バジェットはどちらのドライバでも変わらない。
environment.json に使用したドライバ (chrome-devtools-mcp /
claude-in-chrome) を記録する。
10.3 WS 証跡: 主 evidence と application-level fallback
実測済み (2026-07-06 初回 run): chrome-devtools MCP の
list_network_requests は resourceTypes:["websocket"] を指定しても
この driver + proxy 構成では WS entry を表示しない (collab が実際に動いて
いる状況下で 0 件)。したがって WS 証跡の探索に budget を使わず、最初から
下記の application-level evidence を主 evidence として使う。別 driver /
構成で WS upgrade (101) やフレームが network 証跡に見えた場合は、それを
追加 evidence として記録してよい (見えなくても製品バグではない)。
application-level evidence (namespace 別、budget 内に現れなければ
incomplete (budget exhausted) として §3 のバジェット定義と同じ扱い):
| namespace | fallback pass 条件 |
|---|---|
/collab |
charter #2 の 2 窓編集伝搬そのもの (一方のウィンドウでの編集がもう一方のウィンドウ内に budget 内で反映される) |
/presence |
2 つ目の独立セッション (§11 の 2-window 制約と同じ) を開いた状態で、一方の画面の viewer 一覧 / presence インジケータにもう一方のセッションが budget 内に出現する |
/notifications |
通知を発生させる操作 (例: 他ユーザーへのコメント mention) の後、リロードせずに通知ベルのカウント増加 または toast 表示が budget 内に現れる |
collab charter は CLAUDE.md の既存 2 窓スモーク手順をそのまま吸収し、
/pages/:id/yjs-token (packages/api/src/hono/handlers/page-collab.ts) /
/pages/:id/presence-token (packages/api/src/hono/handlers/presence.ts)
への往復を経て編集が伝搬することを確認する。
§11. 2-window collab charter の制約
JWT はブラウザプロファイル単位で共有される (タブ単位ではない —
packages/e2e/tests/auth-state.spec.ts のコメントが「同一タブで別ユーザー
としてログインすると既存のトークンペアが上書きされる」と明記している)。
したがって 2 ユーザー同時編集の伝搬確認には 同一プロファイル内の 2 タブ
では不十分で、独立したブラウザコンテキストが必要。
まず chrome-devtools MCP の new_page({ isolatedContext: "<name>" }) を試す
— 独立した cookie jar を持つ 2 セッションを 1 MCP セッション内に作れることを
実測済み (2026-07-06 初回 run で 2 窓検証をフル実行)。claude-in-chrome 等、
使用中のドライバに同等機能が無く 2 つ目の独立コンテキストを開けない場合のみ、
2-window 側の伝搬確認は
blocked: cannot isolate second session
とし、単一セッションでの疎通確認 (ページロード・エディタマウント・WS 接続) だけに縮退する。
§12. redaction ルール (証跡への機密漏洩防止)
管理設定チャーター (#8) を含む 全チャーターで、証跡 (network.log /
console.log / スクリーンショット) に機密が写り込むことを禁止する。
redaction は「書き出してから消す」のではなく、証跡を network.log /
console.log へ書き出す時点、かつ .claude/scripts/codex-run.sh への
offload (§13) より前に必ず適用する — 生の値が一度でもディスクに触れる
経路を作らない。
機械的な実装は .claude/scripts/redact-context.mjs を使う (stdin → stdout、--take N でコードポイント単位の切り詰め)。本節の規則 — Authorization / Cookie ヘッダ、password / accessToken / refreshToken / wsToken / token のフィールド単位置換、JWT・PEM 鍵など — をパターンとして実装済みで、その場しのぎの sed(行単位のため複数行 PEM を跨げない)や head -c(バイト単位のためマルチバイト文字を割って invalid UTF-8 を作る)を書かないこと。
- すべての HTTP リクエスト/レスポンスは、書き出し前に
Authorizationヘッダ とCookieヘッダ全体を[REDACTED]に置換する。Cookieはcrowi.accessToken(packages/web/src/lib/auth-token.tsが login/refresh で書き込む同一オリジン cookie) を含み、Authorizationが無い<img>等のリクエストでも api 側jwtAuthミドルウェアがこの cookie を bearer 相当として受理するため、ヘッダ名だけ見てAuthorizationのみを消すと Cookie 経由のトークンが残る。 - 認証系エンドポイントの request / response body も同様にフィールド
単位で redact する (ヘッダだけでは不十分):
POST /auth/loginの request body のpassword、response body のaccessToken/refreshToken。POST /auth/refreshの request body のrefreshToken、response body のaccessToken/refreshToken。- WS トークン応答:
GET /pages/:id/yjs-tokenのwsToken、GET /pages/:id/presence-tokenのtoken、GET /notifications/tokenのtoken。 - これらのフィールドは値の型 (JWT 文字列) ではなく フィールド名で機械的
にマッチして
[REDACTED]に置換する (password/accessToken/refreshToken/wsToken/tokenをキーに持つ JSON ノードを再帰的に 置換)。
- ブラウザ状態のダンプ (
localStorageのaccessToken/refreshToken、document.cookieの内容) を証跡として保存する場合も同じ redaction を 適用する —evaluate_script/computer等で状態を読んだ結果をそのままnotes.md/ ログに貼らない。 - 認証 charter の mutating サブフロー (§4.2) が Mailpit から捕捉するメール
本文・reset/activate/confirm-email の各リンク・そこに含まれる token も
同じ扱い — メール本文全体を
notes.md/ ログに貼らず、[REDACTED]に 置換した上で「メールを受信し token リンクへ遷移した」という事実だけを 記録する。 POST /admin/users/{id}/reset-passwordは平文パスワードを返す仕様 (packages/api-contract/src/contracts/admin/users.ts、UI 側は readonly input で表示 —packages/web/src/components/admin/ user-action-dialogs.tsx) であり、管理設定チャーターはこのアクションを 実行しない (fixture の既存ユーザーのパスワードをリセットしない)。 誤って実行した場合はレスポンスボディ・ログ・スクリーンショットの該当 箇所を証跡から削除する。- プラグイン設定の
@sensitiveマーカー付きフィールド (例packages/plugin-aws/src/index.tsの S3 secret access key、packages/plugin-slack/src/index.tsの bot token / signing secret — マーカーの定義はpackages/plugin-api/src/schema-markers.ts) の値はnetwork.logの request body / response body から[REDACTED]に置換 する。管理設定チャーターは既存のプラグイン設定値を 変更しない (フォームの到達・表示確認までに留め、実際の値の入力・保存は行わない — 更新 API は送信された値をそのまま Config へ書き込むため誤操作が本番 相当の設定を汚す)。 - スクリーンショットに上記の値やトークンが写る操作は撮影前に該当 フィールドをマスク (blur/hide) してから撮影する。
.claude/scripts/codex-run.shへのログオフロード (§13) は redaction 済みのログのみを渡す (生ログを外部プロセスに渡さない)。
§13. codex-run.sh へのログ解析オフロード
各 charter のコンソール/ネットワークログをキャプチャした後 (§12 の
redaction 済みログのみ)、.claude/scripts/codex-run.sh --sandbox read-only (exec モード) にプロンプト + strict JSON schema を渡して要約
させる (crowi-review の Stage 0 と同じ枠組みを踏襲)。
mkdir -p .reviews/qa/<run-id>/flows/<charter>
cat > .reviews/qa/<run-id>/flows/<charter>/prompt.md <<'PROMPT'
Analyze the following (already redacted) browser console log and network log
for a QA charter run. Report console errors, failed network requests, and the
WebSocket upgrade/frame status per namespace (collab/presence/notifications,
if applicable to this charter). Return JSON matching the output schema.
PROMPT
cat > .reviews/qa/<run-id>/flows/<charter>/schema.json <<'SCHEMA'
{ "type": "object", "required": ["consoleErrors", "failedNetworkRequests", "wsStatus", "summary", "severityCandidates"],
"additionalProperties": false,
"properties": {
"consoleErrors": { "type": "array", "items": { "type": "string" } },
"failedNetworkRequests": { "type": "array", "items": { "type": "string" } },
"wsStatus": { "type": "array", "items": {
"type": "object", "required": ["namespace", "status"], "additionalProperties": false,
"properties": { "namespace": { "type": "string" }, "status": { "type": "string" } }
} },
"summary": { "type": "string" },
"severityCandidates": { "type": "array", "items": { "type": "string" } }
}
}
SCHEMA
bash .claude/scripts/codex-run.sh --sandbox read-only --tier luna \
--prompt-file .reviews/qa/<run-id>/flows/<charter>/prompt.md \
--schema-file .reviews/qa/<run-id>/flows/<charter>/schema.json \
--out .reviews/qa/<run-id>/flows/<charter>/analysis.json --label crowi-qa
(schema は OpenAI strict mode 準拠 — additionalProperties: false + 全
property を required に。緩めると codex が 400 で落ちる。)
exit code で分岐:
- 0 → 結果を
findings.mdに合流。 - 2 (codex 不可) / 3 (出力不正) → Claude 側がその場で同じ
(redaction 済み) ログを読んで代替判定する (
crowi-reviewの fallback と 同じ — オフロードの失敗を run 全体の失敗にしない)。
§14. --prod-build モード
@crowi/web の standalone ビルドと @crowi/api の本番ビルドを実際に
起動し、ログイン/ホーム/管理/エディタの画面が本番相当の構成 (同一オリジン
proxy 経由) で動くかをスモーク確認する。mutation を伴うため §1.7 の
--url ガード対象で、--i-understand-destructive なしでは非ローカル
--url に対して実行しない。
以下 14.3-14.7 (run 専用 DB・provisioning・一時 proxy・後始末) は §6.4 の
api-fixture セットアップ用 QA 専有インスタンス (dev モード) が流用する
既存機構でもある — dev モードでの読み替えは §6.4.2 を参照。
14.1 web: standalone ビルド
pnpm --filter @crowi/web build
standalone tree (packages/web/.next/standalone/) は .next/static と
public/ を含まない (packages/web/Dockerfile が Docker で明示的にコピー
している挙動と同じ) ため、ローカル実行でも手動でコピー/シンボリックリンク
してから起動する:
cp -r packages/web/.next/static packages/web/.next/standalone/packages/web/.next/static
cp -r packages/web/public packages/web/.next/standalone/packages/web/public
PORT=<webPort> HOSTNAME=127.0.0.1 node packages/web/.next/standalone/packages/web/server.js
14.2 api: ランナー cwd からの本番起動
本番の plugin/config 解決はプロセスの cwd に依存する
(PluginManager.bootstrap() は projectDir 省略時に process.cwd() を
使い、@crowi/runner はそこから crowi.config.json を読んで plugin を
解決する)。したがって packages/api/dist/app.js を repo root や
packages/api から起動してはならない — apps/crowi-runner/ crowi.config.json の S3/Elasticsearch/renderer/slack plugin 選択を無視し、
crowi.config.json が見つからない cwd での暗黙のデフォルト driver で
起動してしまう。本番 Docker イメージも同じ理由でランナープロジェクトを
deploy し、ランナーの projectDir で起動している。
pnpm --filter @crowi/runner-app... build # @crowi/api + 依存 plugin 一式
cd apps/crowi-runner # dev の起動と同じ cwd (apps/crowi-runner/node_modules/@crowi/api は pnpm workspace symlink)
14.3 DB: run 専用の per-run isolated DB (サニタイズ済み環境)
対象 worktree の共有/isolated DB をそのまま使わず、run 専用の使い捨て
DB 名を使う: crowi_qa_prod_<run-id> (§6.1 の run id、命名 prefix
スタイルは isolatedDbName を踏襲)。固定名をやめることで、複数の
--prod-build run が同時に走っても installer state を共有せず、lock /
lease の類は不要になる。
api の起動はサニタイズした環境で行う: packages/api/src/crowi/ index.ts の Mongo URI フォールバック順は
MONGOLAB_URI || MONGODB_URI || MONGOHQ_URL || MONGO_URI || 'mongodb://localhost/crowi'
— MONGO_URI より 優先される 3 つの環境変数が呼び出し元のシェル/CI に
残っていると、MONGO_URI を渡しても無視されて意図しない DB (共有 dev DB
や本番相当環境) に接続してしまう。したがって起動プロセスには継承した環境を
そのまま渡さず、MONGOLAB_URI / MONGODB_URI / MONGOHQ_URL を明示的に
unset した上で MONGO_URI=mongodb://localhost/crowi_qa_prod_<run-id> だけを
設定する:
env -u MONGOLAB_URI -u MONGODB_URI -u MONGOHQ_URL \
MONGO_URI="mongodb://localhost/crowi_qa_prod_<run-id>" \
NODE_ENV=production PORT=<apiPort> \
node node_modules/@crowi/api/dist/app.js
呼び出し元の環境で unset が信頼できない (サブシェル分離ができない、値の 上書きを確認できない等) 場合は、実際にどの DB に繋がるか保証できないため fail-closed で
blocked: conflicting Mongo env var present (MONGOLAB_URI/MONGODB_URI/MONGOHQ_URL) — cannot guarantee isolation
として --prod-build を起動せずに打ち切る (推測で進めない)。起動後は
接続先 DB 名をログに出し、crowi_qa_prod_<run-id> に接続していることを
確認してから後続の provisioning 手順に進む。
14.4 DB ライフサイクル (provisi
…(truncated)