Crowi QA (Bounded DevTools Charter Runner)

SkillWeb & browsing

crowi-qa lets your AI quality-check critical user flows in a real browser. Each flow table is expanded into nine focused charters and run with limits on operations, retries, and time. It handles visual and state-dependent checks and production-build smoke tests while leaving your existing regression tests untouched.

Available today. Use it from your connected AI after setup.

Add the skill, then ask your AI to run a bounded QA pass over a critical flow you care about. It complements your existing regression tests rather than replacing them.

Then ask your AI: use the Crowi QA (Bounded DevTools Charter Runner) skill

What your AI can do with it

  • Test critical user flows in a real browser
  • Run checks through nine focused charters per flow table
  • Cap operations, retries, and time for each check
  • Verify visual and state-dependent behavior
  • Smoke test standalone production builds
  • Drive checks with Chrome DevTools, falling back to Claude in Chrome when needed

What this skill tells your AI

The instructions your AI receives, as published by crowi/crowi in .claude/skills/crowi-qa/SKILL.md and read by ahel’s review.

これは何か / 何ではないか

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 の解決順:

  1. main (文字列そのまま)
  2. worktree key (scripts/dev-ports.mjs:45normalizeWorktreeKey と同じ 正規化 — worktree ディレクトリ basename の crowi- prefix を外したもの。 crowi 自体は main に特殊化)
  3. --url <url> 明示指定 (registry を経由しない。§1.7 のガードが適用される)
  4. 省略時 = 現在の 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:32DEFAULT_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.mjsWS_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 を 外す。crowimain) で正規化し、解決対象 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_BODYpackages/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 な導出をする): 以下のいずれかに一致すれば「ローカル」:

  1. localhost / 127.0.0.1 かつポート番号が 解決済み anchor から portsForAnchor で導出される 4 ポート (api/web/site/proxy) のいずれか に一致する。
  2. resolveTailscaleHostname() (scripts/dev-ports.mjs:407tailscale 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
2collab (+ 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・trashpartial: 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.tsbuildHonoApp が全 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/registerPOST /auth/activatePOST /auth/reset-passwordPUT /me の email 変更申請 + POST /auth/confirm-email-changePOST /installer/createAdmin・oauth authorize/device consent (いずれも User / Config / OAuth トークンレコードを 書き換える)。

  • 既定 (共有 dev DB) では実行しない。共有 dev DB のユーザーレコードや installer 済み状態を書き換えると復元できない。

  • isolated DB (dev.local.jsonisolateDb: 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.tsassertMailpitHttp と同じ検査 (SMTP 1025 / HTTP 8025 への TCP 到達 + GET {mailpitApiUrl}/info または /messages が 200) を行い、失敗すれば blocked: mailpit unreachable としてこれらのサブフローだけをスキップする。

  • mail 内 token の捕捉: packages/e2e/src/mailpit.tswaitForLatestMessageTo(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_PASSWORDisolated 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 /pagescompletely: 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 ルーティング表
chartersetup 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-fixturebrowser-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-build run では §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.jsondev スクリプトと同じ 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 .envMONGO_URI が定義され ていても、Node の --env-file はプロセスに 既に設定済みの 環境変数を ファイルの値で上書きしない (Node の仕様) ため、上記の明示 MONGO_URI が 優先される。web は packages/web を cwd に next dev を起動し、 PORT_WEB=<webPort> / CROWI_API_URL=http://localhost:<apiPort> を渡す (scripts/dev.mjspnpm dev に注入するのと同じ変数)。前段の同一 オリジン proxy は scripts/dev-caddy.mjsstartNodeProxyFallback() を流用する (§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/installerinstaller_required を確認し、POST /installer/createAdmin を run 専用の固定資格情報 (実運用の秘密情報では ない固定値。例 crowi-qa-fixture@example.com / 固定パスワード — packages/e2e/src/config.tse2eUsers.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 の使い方

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
1k
Forks
165
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
crowi-qa
Source
github.com/crowi/crowi