AI WF2Des — テストケース
- VARIANT/TEXT定義による余剰copy検査の回避を禁止し、BOOLEAN可視性制御の経路を維持します。英数字ID・別container・完全一致copyは値と単位に分割しません。
- localな同一controlのstate整合性、明示state優先、曖昧な競合、disabled差分、別groupを検証します。反復actionは同一copy/style/state/構造と一意なcaptured sizeでのみ再利用し、競合候補や不明sizeでは変更しません。
- 未検証のtextless whole-controlは元を保持し、検証済みartwork bindingはnativeを維持します。単一bound labelのtabはpaddingを保って高さを拡張し、収まるlabel・複合control・元cloneは変更しません。
任意の寸法・role・反復familyでwhole-control obligationを検証します。描画面と単一stateful図形/ラベル、 整合するnative候補があればowner decisionを要求し、子だけのdecisionは欠落を隠せません。table cell、card、 複数control行、不明な追加図形、候補なしunitは除外します。元variant/graphic overrideを保持し、差分レビューは 正当な上位native所有と明示fallbackを認めます。request上限を増やさず推測matchを自動生成しません。
Library fidelity 回帰 gate
- Style capture は local token と固定 board root、参照 master/variant、mixed text run、paint が使用する正確な remote binding を含む。ID は一度だけ解決し、local を優先、衝突する remote alias は除外。読めない参照でも他の capture を失わない。ライブラリ import、Figma 変更、無関係なページ走査、色からの binding 推測は禁止。
-
style/master/variable の読み込み停止で component sweep の enqueue を止めない。各 read は5秒、capture 全体は60秒を上限とし、読み取れた binding を保持して runtime に未完了を通知する。never-settling read と全体予算切れをテストする。
-
実描画 review は pin 済み native compound の装飾・再配置を許容し、全 source copy・独立 action・state は必須。所有部品の変更/除去・action 非表示で旧 coverage を解除し source navigation を復元する。未変更/再証明済み coverage、差分 merge の所有関係、旧決定的 drop の再検証を確認し、無関係な annotation drop は維持する。
-
独立した単一/複数 action strip 前の footer 昇格は一意の全文と native action coverage を要求する。後続一般文章・実 field・glyph 証拠欠落・曖昧な optional 設定は保留。非表示/別 variant の footer action は source navigation を消さず、copy と action 数を固定しない。
-
単一brandの上部barをheader/navigation両role・異なるbrand copy/IDで検証する。capture済みbrand-media選択とhash検証済み単一graphic header variantが必要。独立menu/action/copy、非brand画像、曖昧/未取得profile、不明visibility、追加描画unit、明示unmatched/親の個別styleは保留。単一brandの完全証明でのみstyleなしcomposeを再調整し、atomic wordmarkの文字同値性を推測せずnative headerでbrandを一度だけ表す。
-
単一行 table header は compose/native cell 混在でも高さを共有する。部分・重複・span・並べ替えは変更前に拒否し、既存 body の複数行同期は維持する。
- 単一 control の高さ下限は長文を切らずに領域を維持し、一般 section は HUG を維持する。
- 部分 border は compose・nested frame・materializer で保持し、省略時の従来挙動も検証する。
-
covered geometry と sibling gap の交差区間のみを union して減算し、余白・注釈・overlay は維持する。
-
同名でも key・snapshot・軸名が異なる部品は独立候補として保持し、keyless 名から identity を推測しない。同一 source の metadata 補完は維持。
- secondary media を含む文字なし atomic artwork は拡縮可能とし、非 media compound の scope 拒否は維持。
- proxy INSTANCE の BOOLEAN 値と visibility reference は master 定義なしでも利用可能。不明・曖昧・nested scope の binding から非表示を断定しない。
- optional control は sample content のみ除去でき、必須 source copy は保持。重複 navigation は可視 copy を検証し、label+icon action は native 構造証拠も要求。無関係な artwork を保持。
- native actionのcoverageは、ボタン内/wrapper内の追加opaque controlを保持する。ink flagが未取得/default falseのINSTANCEも対象とし、明示的に空のFRAME/GROUPだけを無視できる。未取得censusは空の証明にならない。
- component set と root component の native action を coverage に使う場合、true と証明された visibility binding が
ScreenPlanと serializedcomponent_propertiesに保持されることを確認する。既存 override を維持し、false・不明・default 欠落・曖昧な action では coverage を許可せず、保存可能な owner がない bound action の重複除去は保留する。 - opacity が 0 の text・root・ancestor・nested instance・native action の label/glyph は、BOOLEAN が true でも可視 copy/coverage の証拠に使用しない。正の opacity は可視証拠として扱えることを確認する。
- 9画面 batch 前に2画面を本番 plugin 経由で新規生成し、native footer、全 login provider icon、copy/state、余白・overflow を確認。未使用画面も追加し、synthetic test/replay のみを視覚合格としない。
Source fidelity 回帰テスト(2026年9月)
| Case | 必須検証 |
|---|---|
| Component snapshot | 有料選択前に欠落・未対応 URL・破損・hash 不一致を検出し resync を案内。一時的 storage error は retry。integrity と inspection の byte budget は分離する。 |
| Filtered flow | 最後の overlap を除去した縦横 flow の順序/gap を保持し layer path は不変。残存 modal は座標/z-order、明示的 auto-layout は authored order を維持。 |
| Fallback override | 同一 file の実際の source instance を clone し、非表示要素・文字・nested state を保存。不明/別 file の ID は別 node に解決しない。 |
| Graphic override 証拠 | 可視・描画ありの graphic に exact ID で一致する fill/stroke/opacity/style override(inheritFillStyleId を含む)のみ has_graphic_overrides を設定。灰色のみ・文字/形状のみ・非表示 ancestor・対象不明・不正 entry は設定しない。探索上限で終了し、false は省略、旧 artifact は読取可能。role 付与後も true と元 tree を保持。 |
| Selection fidelity | passive table content を action に変換しない。control 数と区別された state を保持し、明示 state を default より優先。表現できない compound は compose。 |
| 反復 painted cell | 一部/全部への compact badge 適用で full-height painted cell を失わない。同じ source master/state/surface 群を text override だけで異なる塗りの variant にしない。適合する native cell・実際の state 差・inset badge・profile 証拠欠落・無関係な list を保持し、差分 review と直接 stitch が同一 guard を使う。 |
| Shared table track | 列形式の table は行 minimum/intrinsic height を共有し末尾 total を許可。中間欠落・path 重複・row span・列 overlap・不明な padding は marker を発行しない。旧 spec は追加 field を省略。 |
synthetic test と read-only replay だけで visual parity は保証しない。新しい plugin render も確認する。
atomic fallbackはscope・不明component・原文を隠すvisibility・余剰copy・fidelityの拒否、および 明示/旧版の空composeを、トップレベルとnestedの両方で検証する。元instanceのidentity・state・lineage、 不透明なFRAME/GROUPのglyph source、単色fillも持つ画像の保持経路を維持する。通常の文章・分解可能な instance・真に空のlayout cellは従来動作を保ち、差分reviewの不明componentは既存の選択を上書きしない。
parse cache 回帰: source hash が同じでも generation_parse@0.3 以前の stamp は @0.4 で再抽出します。
現行版 cache は download と有料 role 付与を省略し、component/rule processor version は変更しません。
意味的セクションレイアウトの回帰マトリクス(2026年9月)
複数の画面ファミリーと汎用IDを使用し、本番画面/ノードの許可リストに依存しない。
以下は section-layout@1 の受入契約であり、見た目の同等性を保証するものではない。
| ケース | 必要な検証 |
|---|---|
| 根拠のあるグループ化 | 適用可能な縦方向フローの連続する直接兄弟をまとめ、原文・操作・画像・メンバー順序を重複なく保持する。 |
| 不正なスコープ | 不明/欠落/重複/逆順/非連続メンバー、グループの重複、grid・絶対配置・instance境界の横断はfindingと元配置fallbackになる。 |
| 選択後の網羅性 | 部品置換、メンバー削除、出力構造の変更後に以前のグループが無関係な内容を暗黙に取り込まない。 |
| レイアウトの所有権 | 部品内部と塗りのあるコンテナのpaddingを維持し、境界paddingを考慮して余白を二重化しない。chrome境界と意図的な元gapを保持する。 |
| 構造的 clip | 内容が収まる明示的な縦 wrapper は clip を変更せず section spacing と native height authority を伝える。overflow/crop・artwork・角丸/fixed viewport・overlay は対象外。rule で証明された余剰 neutral edge padding は joint 一箇所で管理し、rule のない余白と入れ子の独立 painted inset は保持する。 |
| ルールの権限 | 対象が明確なルールで実効セクション間隔を決め、曖昧/競合時はfallbackする。ルール不在時は測定済み形状を保ち、役割の推測から間隔を創作しない。 |
| Viewportの権限 | 正の明示 viewport_width_px のみ出力幅へ適用し、0以下を拒否する。参考base viewport・content幅・paddingだけでは画面幅を変えない。 |
| 部品の高さ | 適用可能なinstanceは実際の高さを使い旧占有高さのspacerを追加しない。印のないinstanceや非縦方向/絶対配置では従来動作を維持する。 |
| リプレイの同一性 | 同じpinsとplanから生成・変更なしレビューが同一specを作り、追加candidateやstate書き込みを生じない。 |
| 差分修正 | 選択変更時は影響する網羅性を再検証し、候補に整合したplanと新しい不変state hashを保存する。修正1回/2回目は検証のみの制約を維持する。 |
| 旧版互換 | layout_plan のないstate、height_authority のないspecは旧リプレイ/実体化動作を保つ。ノード種別は既存5種類のままである。 |
決定的engineテスト、test_render_review.py の固定スコープ/変更なしレビューfixture、実際の生成経路を検証する。
pluginではフローの実寸と旧占有高さの動作を検証する。間隔・overflow・見た目の欠落は構造assertだけでは
証明できないため、未使用wireframeのFigma実画像による確認も必要である。
実レンダリング検証と汎化の回帰テスト(2026年9月)
test_render_review.py / test_render_review_repo.py はジョブ・tenant・attempt・親の一致、
仕様/assembly/画像のハッシュ、PNG上限と完全性、根付きmanifest、選択とプロパティの差分統合、
修正1回の上限、スタイルのみの差分、モデル障害、古い実行、重複リース、所有者限定書き込みを対象とする。
backend は認証・スコープ・画像保存・キュー契約、plugin は元フレーム保持・衝突を避けた候補配置・
検証ID・キャンセル・ポーリング失敗・検証専用モード・合格後のみのキャプチャ送信を別途検証する。
実在画面のIDに依存しない反例で、ベクターのみの部品、古いplaceholder推論、空スロットと未指定の違い、 composeでの文章/画像保持、明示/推定の読み順、改行とフォント、既知BOOLEANでの任意表示制御、 ページ分割付きの実在variant照会、スナップショット内の未信頼文字列を確認する。 リプレイは元のpinsと証拠の来歴を記録し、既存ジョブと決定台帳を書き換えずに未使用ワイヤーフレームも試せる。 テスト成功や文章・部品数は見た目の同等性を証明しない。合格判定にはFigma実画像の比較が必要である。
追加の反例では、別ファイルの同一node ID、出典ファイル不明、公開キーによる別ファイル部品の解決、
形状推定が明示されたvariant軸を上書きしないことを確認する。文章スロットの書き込み失敗や未解決warningが
あればモデルのpassでも合格させず、実値を保持できたスタイルの通知はinfo扱いとする。wordmarkの対応付けは
非表示の図形・表示上書き・空の出典・部品変更・ブランド以外の文章を拒否し、原文と証拠ハッシュを保持する。
control_artworkは元画像と実画像、固定variantの照会、描画済み図形、単一controlの正確なラベルを必要とします。
ブランドでない図形文字も汎用fixtureで確認します。名前・形だけ、非表示図形、編集可能なTEXT、追加ラベル/control、
不明ink census、変更されたhash/variant、非画像レビューは元文章を保持します。成功時のみ原文metadataを保持して
生成済み単一instanceの with_text wrapperを外し、固定済み再実行でも同じ結果にします。元ツリーと無関係な
table/描画wrapperは変更しません。
新しいlineageのschema既定値は 1.4 とし、明示された旧version文字列も引き続き読み取れます。
table内の描画面は元の境界が一致する透明・余白なし・単一子wrapperだけを通じて共通行高に追従します。 direct/複数行でFRAME面のみ伸長しnative iconは保持します。内側配置、余白、複数子、絶対配置、不明/重複/ 未描画surface pathは拒否または未指定のままです。旧行の空surface metadataは省略します。
複数対象のfindingは汎用IDと独立した領域で検証する。明示 affected_node_ids の全報告領域を修正でき、
従来の単一 node_id も動作すること、root・不明ID・detail 内だけのID・infoは修正権限を広げないことを
確認する。不正な型・上限超過のID一覧はschemaで拒否する。元から明示された各領域を新たな回帰と誤判定せず、
既知領域と無関係な領域を混在させた新findingは回帰として検出する。対象なしの旧診断カテゴリ比較も維持する。
このページは apps/wf2des/ の test plan です。generation/internal-event contract、DocumentDB/S3 write、webhook effect、failure row、fence は下記 case に対応させます。
規約: テスト ID は
WF-<層>-<NN>形式に従います。層は U(unit — 純粋関数、I/O なし。PydanticAI / Figma REST / Webhook は SDK 境界でフェイク)、C(component — 実行ファミリーごとの end-to-end なprocess_recordを、実 localstack S3 + testcontainer Mongo / DocumentDB 互換イメージに対して実行。LLM [PydanticAI]、Figma REST、ai-status Webhook は SDK 境界で モック)、E(end-to-end — 実devクラウド、スモークのみ)。各ケースの 期待結果 列がそのアサーション契約です — そこに書かれていないことは 検証しません。
テスト層とツーリング
これらのケースは、マージ前またはリリースチェックリストとして実行する 手動 / ローカル の計画です。自動 CI ゲートではありません。
| レイヤー | スコープ | ツール |
|---|---|---|
| Unit (U) | 純粋関数: メッセージスキーマ検証、決定的 WFNode 抽出、memo ステータス導出、screen_id 文法 + variation_label 分割、role/kind candidate gate、決定台帳の参照 + スコアラチェットの書き戻し、K サンプル自己一貫性投票、決定的な選択ガード、縫合、rules validator、決定的なルールフラグメントマージ、計算された confidence、content_hash 正規化、マニフェスト + Webhook ペイロードビルダ |
pytest、ネットワークなし。PydanticAI / Figma REST / httpx Webhook は SDK 境界でフェイク |
| Component (C) | 実行ファミリーごとの end-to-end な process_record。実 localstack S3 + testcontainer Mongo(DocumentDB 互換イメージ)。モック PydanticAI agent(role/intent、section-selection、ボードごとの rule-extraction)、モック Figma REST、モック ai-status Webhook httpx client |
pytest、testcontainers、localstack、respx / unittest.mock |
| End-to-end (E) | 実 dev AWS — 実 generation SQS + wf2des-events キュー + EventBridge、S3、DocumentDB、LLM dev キー、実バックエンド ai-status Webhook / PostgreSQL |
pytest の @e2e マーカー、スモークのみ |
フィクスチャ
build_parse_msg(...) と build_assemble_msg(...) が 2 つの generation ペイロード形状の唯一の真実の源です — dict を手書きするのではなく、必ずここから合成してください。
| フィクスチャ | 用途 |
|---|---|
build_parse_msg(**overrides) |
正しく整形された generation parse SQS メッセージ(wf2des_id、attempt、nonce、wireframe_img_url、wireframe_json_url、snapshot_scope、wf_content_hash、screen_id、任意の pin URL、auto_confirm) |
build_assemble_msg(**overrides) |
正しく整形された generation assemble メッセージ(wf2des_id、attempt、新規 nonce、confirm{attempt, parse_artifact_hash}) |
event_rule_upload(...) |
wf2des-events の rule-upload イベント(event_type、design_rule_id、figma_file_key、選択されたガイドラインノードの board_node_ids[] — ボードフレーム、またはワーカーが展開する CANVAS/SECTION コンテナ(最低 1 つ)、org/project) |
event_frame_reg(...) |
wf2des-events の plugin frame-registration イベント(event_type、figma_file_key、node_id、snapshot URL、org/project) |
event_resync(...) |
component_sweep へルーティングされる wf2des-events の plugin resync イベント |
eventbridge_invoke() |
component_sweep 用のボディなし EventBridge スケジュール呼び出し |
wireframe_snapshot_s3 |
S3 上の WF snapshot JSON(フレーム + 包含する board section、memo ノード、取込済ステータスフレーム、隠しレイヤー、画像塗りの矩形、vector の葉) |
figma_component_json |
sweep 巡回用の Figma COMPONENT / COMPONENT_SET REST JSON(公開済み + ローカル、variant props、text/image スロットレイヤー) |
figma_rule_boards(...) |
rule_process 用のモック Figma REST: 選択されたガイドラインボードのタイトルを運ぶ get_file_nodes JSON + images API 越しのボードごとの PNG レンダー |
component_registry(...) |
design_component コンテキストドキュメント + pin 済みプラットフォーム design(type=component)セット — variant_properties、slots、default_size によるインスタンス化コンテキスト |
design_resolution_doc(...) |
_id = {org}_{project}_{section_signature} でキー付けされた design_resolution 決定台帳ドキュメント — {case, platform_design_id, component_name, variant_policy, provenance(mined \| voted), score}。制御可能なスコアの mined / voted バリアント |
design_rule_doc(...) |
(design_rule_id, content_hash) でキー付けされたコンパイル済み design_rule リビジョンドキュメント(マージ済みの spacing / typography / colors / component_specs / screen_group_policies + boards[] のボード画像参照)。加えて no-rules バリアント |
config_json(design_area=...) |
S3 上のプロジェクトごとの config.json(project_figma_file.config_url から参照)— 既知の DESIGN エリアの矩形(design_area)+ memo マーカー(memo_resolved_markers)+ 任意の variation_separator + 任意の annotation_section_markers |
mock_llm(role_intent=..., selection=..., rule_extraction=...) |
3 つの LLM ステップ(role/intent、section-selection、ボードごとの rule-extraction)用の構造化出力モック — ツリー形状や confidence の自己申告は決して返さない |
mock_ai_status(http_status=200) |
外向きの POST /v1/webhooks/ai-status ペイロード(エンベロープ + マニフェストキー)をキャプチャし、指定したステータスを返す |
カバレッジマトリクス
横軸 = I/O 定義 の契約領域。縦軸 = 結果。各セルは 1 つ以上のテスト ID を指します。— はその結果がその領域に該当しないことを意味します。
| 契約領域 | 正常系 | 異常系 | 冪等性 / フェンシング |
|---|---|---|---|
| Generation メッセージスキーマ(parse + assemble) | WF-U-01, WF-U-02 | WF-U-03, WF-U-04 | WF-U-05 |
| WFNode 抽出 & サマリー | WF-U-06, WF-U-07, WF-U-08, WF-U-09, WF-U-10 | — | — |
Memo ステータス / screen_id / variation_label |
WF-U-11, WF-U-12, WF-U-13, WF-U-14 | — | — |
| Parse パイプライン(抽出 + LLM roles/intent + parse キャッシュ) | WF-C-01, WF-C-01a, WF-C-02, WF-C-04 | WF-C-03, WF-C-03a, WF-C-04a, WF-C-04d, WF-C-05 | WF-C-01b, WF-C-02, WF-C-11a, WF-U-06 |
| Confirm / reject 遷移 | WF-C-04b, WF-C-10, WF-E-01 | WF-C-05a, WF-C-11b | WF-E-05 |
| 候補セット / 縫合 / validator / confidence | WF-U-15, WF-U-16, WF-U-17, WF-U-18, WF-U-19, WF-U-20 | — | — |
| 選択のトラスト境界 + プロジェクト config | WF-U-29, WF-U-30 | WF-U-29, WF-U-31, WF-U-32 | — |
| ロール割り当てフェンス + terminal サーフェスガード | WF-U-34 | WF-U-35 | WF-U-35 |
| セクション並列選択 | WF-U-36 | WF-U-36 | — |
| 決定台帳 / K サンプル投票 / 決定的ガード | WF-U-37, WF-U-39, WF-U-40, WF-C-06f | WF-U-40 | WF-U-37, WF-U-38, WF-C-06g |
| 5 つの spec ノードケース | WF-U-21, WF-U-22, WF-U-23, WF-U-24, WF-U-25 | — | — |
| Assemble(pin 再水和 + 候補セット + 台帳参照 + K サンプル選択 + ガード + 縫合 + validator + confidence + 台帳書き戻し) | WF-C-06, WF-C-06b, WF-C-06c, WF-C-06d, WF-C-06e, WF-C-06f, WF-E-02 | WF-C-07, WF-C-08, WF-C-06h, WF-C-06i | WF-C-06a, WF-C-06g, WF-C-11 |
| 結果アーティファクト + マニフェスト + ai-status Webhook | WF-C-06, WF-C-09a, WF-U-27, WF-U-28 | WF-C-08, WF-C-09 | WF-C-11, WF-E-04 |
wf_parse(wireframe ドキュメントのみ) |
WF-C-12 | WF-C-13 | WF-C-14 |
rule_process(ハッシュごとのイミュータブルリビジョン) |
WF-C-15, WF-C-15a, WF-U-26, WF-U-33 | WF-C-15a, WF-C-16 | WF-C-17 |
component_sweep(+ レジストリマニフェスト、single-flight) |
WF-C-18, WF-C-20a, WF-E-03 | WF-C-20 | WF-C-19 |
| Figma OAuth 認証情報(Admin token provider・リフレッシュ・フォールバック) | WF-C-28, WF-C-34 | WF-C-32, WF-C-33, WF-C-35 | WF-C-29, WF-C-30, WF-C-31 |
| Figma OAuth 接続フロー(Admin API の authorize + callback) | WF-C-36 | WF-C-37, WF-C-38, WF-C-39, WF-C-40 | — |
スナップショット範囲指定(sectionNodeId の高速経路 + フォールバック) |
WF-C-41 | WF-C-42 | — |
| 選択ガードの層(kind と size のゲーティング) | WF-C-43, WF-C-45 | WF-C-44, WF-C-46 | — |
project_figma_file の自己登録 |
WF-C-47 | WF-C-47 | — |
| 余剰テキストの保持 | WF-C-48 | — | WF-C-48 |
Intake ディスパッチ(event_type → 実行ファミリー) |
WF-C-27 | — | — |
| Lineage / provenance エンベロープ | WF-C-26 | — | — |
テナンシー(organization_id + project_id) |
WF-C-24 | — | WF-C-24 |
| ワーカーは PostgreSQL に決して書き込まない | WF-C-25 | WF-C-25 | — |
Unit (U) ケース
純粋な Python、ネットワークなし。LLM / Figma / Webhook は SDK 境界でフェイク。
メッセージスキーマ(schemas/)
| ID | 意図 | 期待結果 |
|---|---|---|
WF-U-01 |
正しく整形された parse メッセージを受理 | 有効なモデル。wf2des_id、attempt、nonce、wireframe_img_url、wireframe_json_url、snapshot_scope{frame_node_id, section_node_id}、wf_content_hash、screen_id、auto_confirm がすべて揃っている。任意の pin URL はデフォルトで不在 |
WF-U-02 |
正しく整形された assemble メッセージを受理 | 有効なモデル。confirm{attempt, parse_artifact_hash} が存在。snapshot/URL フィールドはなし(すべて後で再水和) |
WF-U-03 |
必須フィールドを 1 つでも欠く parse メッセージを拒否 | 各必須フィールドをパラメトライズ → ValidationError。フィールド名がエラーに現れる |
WF-U-04 |
不正な snapshot_scope / 非 s3:// URL を拒否 |
不正な snapshot_scope、および s3:// URL でない wireframe_json_url で ValidationError |
WF-U-05 |
Open-generation の dedupe 形状 | メッセージから導出可能な (job_id, attempt, nonce) タプルが正しく整形されている。attempt は単調増加の整数。nonce はエンキューごとのトークンで、2 回の build_parse_msg 呼び出しで異なる |
決定的 WFNode 抽出(figma.py)
| ID | 意図 | 期待結果 |
|---|---|---|
WF-U-06 |
snapshot から WFNode ツリーを抽出 | 可視の FRAME / GROUP / INSTANCE / TEXT の各子孫 + 画像塗りの矩形 → WFNode。同じ snapshot の 2 回の実行でツリー形状がバイト安定 |
WF-U-07 |
vector / shape の葉は親に折りたたまれる | vector / 非画像 shape の葉は WFNode を生成しない。親ノードに折りたたまれる |
WF-U-08 |
隠しレイヤーはスキップ | 可視性オフのレイヤーは WFNode を生成しない |
WF-U-09 |
画像塗りの矩形はノードになる | 画像塗りを持つ矩形は WFNode になる。単なる塗りの矩形は折りたたまれる |
WF-U-10 |
導出サマリー | summaries.element_type_histogram + summaries.node_count をツリーから計算、決定的 |
Memo ステータス、screen_id、variation_label
| ID | 意図 | 期待結果 |
|---|---|---|
WF-U-11 |
Memo ステータスは決定的 | 取込済フレーム内部の memo → status=resolved。resolved の memo はライブ intent から除外。ステータスは LLM 出力ではない |
WF-U-12 |
screen_id 文法一致 |
[A-Z]{2,3}_[A-Z0-9]+ アンカーなし、フレーム → board → page 名の順で検索。最初の一致を返す |
WF-U-13 |
screen_id 文法ミス |
一致なし → screen_id=null かつ 取り込み issue を staging/issues.json にログ。エラーではない(wf_parse) |
WF-U-14 |
variation_label 分割 |
会員登録TOP|案1 → 案1。決定的なフレーム名分割、LLM ではない |
決定的な候補セット、縫合、validator、confidence
| ID | 意図 | 期待結果 |
|---|---|---|
WF-U-15 |
決定的 role/kind candidate gate | registry を tenant-scope、dedup、hygiene collapse し、本番 default gate で section ごとに role-admissible kind のみに限定。size は signal で gate ではない。similarity/vector は使わず count/truncation を記録 |
WF-U-16 |
決定的な縫合 | 固定されたセクションごとの判断が与えられると、spec ツリー + spec_nodes_flat + style_bindings マップがバイト単位で再現される。style_bindings は project_figma_file.style_captures から導出 |
WF-U-17 |
Validator の自動修正 / フラグ | 安全な違反は auto_fixed、安全でないものは flagged。validator_report.status = ok、violations[] は {rule, layer_path, action, detail} を運ぶ |
WF-U-18 |
Validator の no-rules 経路 | ルール未登録 → no-op。validator_report.status = no_rules。全 ノードに rules_unvalidated フラグ |
WF-U-19 |
Confidence は計算され、決して自己申告ではない | Confidence は validator 違反 + unmatched/composed カウント + slot の曖昧さ + component-fit から導出。モデル供給の confidence フィールドは無視。formula_version をスタンプ |
WF-U-20 |
compose は instance より低く上限 | それ以外が等しい入力に対し、compose ノードの計算された confidence は同等の instance ノードより厳密に低い |
5 つの spec ノードケース
| ID | 意図 | 期待結果 |
|---|---|---|
WF-U-21 |
layout_frame ノード |
{auto_layout{direction,gap,padding,sizing}, lineage_wf_node_ids, children} を発行 |
WF-U-22 |
instance ノード |
component_key、platform_design_id(= design_component._id、安定した validator キー)、variant_props、text_slots、bbox、source{kind: registry \| wireframe} を発行 — source.kind = wireframe はフォールバック(wireframe 自身の componentId + variants)で、常にフラグ付き、wireframe より悪化しない |
WF-U-23 |
compose ノードは常にフラグ付け |
無条件に flagged == true。source{kind:composed}。children はインライン化。source の fill/stroke/radius/clipping を保持 |
WF-U-24 |
text ノード |
原文の content、根拠のある書式値とフィールド別変数参照を発行。キャプチャ語彙だけで全体共通 style_token を割り当てない。明示的な文字スタイルは実体化時の種別検証付きで保持する |
WF-U-25 |
unmatched は決して削除されない |
placeholder{role,text,bbox}、flagged=true、source{kind:none} を発行。ノードは決して静かに削除されない |
content_hash 正規化 + ビルダ
| ID | 意図 | 期待結果 |
|---|---|---|
WF-U-26 |
ルール content_hash 正規化 |
マージ済み RuleSet に対して計算される、ソート済みキーの正規 JSON への sha256。バイト単位で異なるが正規的に等しい 2 つのマージ済みルールセットは同じハッシュになる — 同一の抽出は収束する(rule_process) |
WF-U-27 |
結果マニフェストビルダ | {manifest_schema_version, job_id, attempt, nonce, job_type:"generation", result_url, flag_count, row_effects.wf2des{status, result_url, flag_count}} を構築。job_type はエンベロープの type とは別物 |
WF-U-28 |
ai-status エンベロープビルダ | parse_done エンベロープは result_manifest_url を運ばない。succeeded/failed は運ぶ。判別子 type = "wf2des"。manifest_schema_version が存在。error は failed のみに存在 |
選択のトラスト境界 + プロジェクト config
| ID | 意図 | 期待結果 |
|---|---|---|
WF-U-29 |
選択エコーをレジストリの事実にサニタイズ | ハルシネーションされた variant 軸/値、またはレジストリドキュメントの text_slots/image_slots の layer path にない text_slots/image_slots キーは DROP され、インスタンスは FLAG される(何かを drop したら必ず flag。忠実なエコーは flag なしで通過)。候補にない platform_design_id の捏造はセクション全体を unmatched にダウングレード |
WF-U-30 |
プロジェクト config のオーバーライドがパイプラインに到達 | config.json のオーバーライドテーブル(memo_resolved_markers、variation_separator、annotation_section_markers)はプラットフォームデフォルトを置き換え、決定的な導出(memo ステータス、variation_label 分割、注釈セクション検出)に到達する。パイプライン自体はデザインシステム非依存のまま(クライアント語彙をハードコードしない)。空の style_captures は設定不足を flag するが、未解決のスタブを作らない。キャプチャ済みトークンが存在しても全テキストに同一スタイルを割り当てない |
WF-U-31 |
config.json の不在 vs 破損 |
不在の config ⇒ {}(プラットフォームデフォルト: マーカー、区切り文字、注釈セクションマーカー)。存在するが壊れている場合(不正な JSON / オブジェクトでない / 非リストの memo_resolved_markers や空の variation_separator など不正なオーバーライド値)は RAISE — デフォルトへの黙ったフォールバックは決してしない |
WF-U-32 |
validator は未解決・種別不一致のトークンを発行しない | 未キャプチャのトークンや変数 ID への文字スタイル自動置換は禁止。変数のみの語彙では文字ごとの誤検出ではなく、検証不能を一件報告する。数値フォントスケールは変数 ID のみを受け付け、数値名のスタイルを除外する。旧データの変数による全文字参照を除去しても、リテラルとフィールド別参照は保持する。プラグインはスタイル適用先および FLOAT/STRING の種別不一致を変更前に拒否する。 |
WF-U-33 |
決定的なルールフラグメントマージ | merge_rule_fragments はボードごとの RuleSet フラグメントに対する純粋関数で、ボードは node_id でソートして処理: spacing.scale と許可された色は UNION してソート、スカラーフィールド(section_gap)は最初の非 null 値を採用、typography は style_token ごとに first-occurrence-wins。空の入力はデフォルトの RuleSet を返す |
WF-U-34 |
ロール割り当てフェンス | LLM が埋めるのは role/intent のみ: モデルがエコーし損ねた id は role="other" にデグレード(ログ記録)、鋳造された id はノードを追加しない。ツリーの形状・bbox・テキストは決定的抽出とバイト単位で一致 |
WF-U-35 |
terminal サーフェスのガード | terminal に到達した attempt の重複配送は、再実行なしで保存済みドキュメントからマニフェスト + Webhook を再発行(parse と assemble)。失敗 terminal は何も再発行しない。superseded な attempt は古いサーフェスなしでスキップ。assemble のスコープ不一致は -failed.json + failed マニフェスト + failed Webhook のサーフェスで fail closed |
WF-U-36 |
セクション並列選択 | セクションは 1 つのイベントループ上で並行に実行される(オーバーラップを観測 — wall-clock は最も遅いセクションであり、合計には決してならない)。SECTION_SELECTION_CONCURRENCY で上限。失敗したセクションは兄弟を完了させ(キャンセルなし)、セクション id 付きでログに記録されてから transient として re-raise(SQS 再配送、terminal サーフェスなし) |
決定台帳、自己一貫性投票、決定的ガード
design_resolution 台帳は自律的な品質ラチェットです — 人間による修正ループは存在しないため、品質はマシン側で収束します。section_signature は構造的で LLM 非依存(キットコンポーネント名・正規化名・子タイプ形状・テキスト数に対する sha256)であり、同じセクション KIND はどの画面でも再実行でも同じエントリにヒットします。
| ID | 意図 | 期待結果 |
|---|---|---|
WF-U-37 |
決定台帳の参照: LOCK vs FLOOR | 構造的 section_signature で参照し、ガードより 前 に適用: mined エントリ、または score ≥ トラスト閾値のエントリはセクションを LOCK する(LLM 呼び出しなし、決定をそのまま採用)。閾値未満の voted エントリは FLOOR — セクションは再投票し、厳密により高いスコアが勝つ(より低い再投票は floor を維持)。デフォルト variant の instance エントリは参照時にスコープ再検証され、古い/スコープ不一致のエントリは投票にフォールスルーする |
WF-U-38 |
台帳書き戻し + スコアラチェットフェンス | すべての非 unmatched セクション(および compose-child)の決定は決定的なスコアと共に design_resolution を upsert する。ラチェットフィルタは、保持中のエントリが mined で なく かつ厳密により低いスコアの場合に のみ voted 書き込みを着地させる。等しいか高いエントリ、およびすべての mined エントリに対する voted 書き込みは REJECT される。mined 書き込みはフェンスなし。フェンスされた upsert 上の DuplicateKeyError は KEPT を意味する(成功として返され、決してエラーとして表面化しない)。同一のエビデンスは同一にスコアリングされる ⇒ 再実行は updated_at を churn させない |
WF-U-39 |
K サンプル自己一貫性投票 | section-selection ステップは K サンプルを引き、(case, platform_design_id) の 多数決 を取る。集計された勝者(どの単一サンプルでもない)がセクションの決定。同点は決定的ガードで裁定され、最初のサンプルを選ぶことは決してない。K は config から読まれ、投票は K 個の構造化出力の純粋関数 |
WF-U-40 |
投票に対する決定的ガード | 投票された決定に対して(エコーは決して信頼しない): variant_props は何かが読む前にレジストリ軸に CLAMP される。対称的なスコープガード(セクションより著しく大きいコンポーネント → unmatched、複数ユニットのセクションを主張する小さなコンポーネント → compose でユニットを再選択)。wireframe 名エビデンスでの重複主張の重複排除。kit-name オーバーライド(1 つの名前付きキット要素で ある セクションはその名前のレジストリコンポーネントを採用し、キット自身の variant 値を持ち越す)。ガードは台帳 floor の 後 に実行され、最終決定権を保持する |
Component (C) ケース
実 localstack S3 + testcontainer Mongo。LLM(PydanticAI)、Figma REST、ai-status Webhook は SDK 境界でモック。実行ファミリー ごとにグループ化。各ケースが process_record を駆動します。
Generation — Parse
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-01 |
キャッシュミスの正常系 | まず inputs を pin。複合 _id = {project_id}_{figma_file_key}_{node_id} による parse キャッシュルックアップがミス。WF snapshot をダウンロード。決定的抽出。mock_llm.role_intent が role + intent のみを割り当て。wireframe ドキュメント(LWW)+ 結果ドキュメントの parse ブロック + イミュータブルな parse.json((_id, attempt) に対する CAS)を書き込む。parse_done Webhook が result_manifest_url なしで ちょうど 1 回 発火。timings.created_at + parse_done_at + フェーズごとの <phase>_ms 所要時間を書き込む(created_at ≠ lineage.processed_at) |
WF-C-02 |
キャッシュヒットのコピー経路 | 同じ wf_content_hash が wireframe に存在。前回の parse アーティファクトをこの実行自身の parse.json にコピー。LLM 呼び出し なし、S3 snapshot ダウンロード なし。結果ドキュメントの parse ブロックを書き込む。parse_done Webhook は依然として発火 |
WF-C-03 |
Snapshot GET エラー → SQS リトライ | wireframe_json_url の GET が 404/500 → SQS が再配送するよう例外を再 raise。wireframe 書き込みなし、結果ドキュメントの parse ブロックなし、Webhook なし |
WF-C-04 |
auto_confirm=false は awaiting_confirm で待機 |
parse_done Webhook が呼び出しのターミナルな行為。assemble は連鎖 しない。キャプチャされたエンベロープは status="parse_done" を持つ |
WF-C-05 |
Webhook 送信が raise → SQS リトライ | parse_done での mock_ai_status(500) → 送信が raise → 再配送のためレコードを再 raise。DocDB parse 書き込みは既に永続化済み(再配送時に再収束) |
WF-C-10 |
auto_confirm=true は assemble を連鎖 |
parse の後 assemble が 1 回の呼び出しで連続実行。parse_done の待機 なし。succeeded Webhook のみが発火 |
WF-C-11 |
CAS フェンスが破棄済み / ゾンビ書き込みを拒否 | 古い (_id, attempt)(ドキュメントより低い attempt)に対する書き込みは拒否。ゾンビワーカーの DocDB コミットとその遅延 Webhook は no-op |
WF-C-01a |
parse は最新のルールリビジョンを pin | parse はプロジェクトの 最新 design_rule リビジョンを inputs に pin(最大の version、同点なら最新の lineage.processed_at)。リビジョン未登録 ⇒ pin は None(assemble は validator の no_rules 経路を取る) |
WF-C-03a |
Memo の Figma-REST フォールバック | snapshot に memo ノードが欠けている → ワーカーがサービスアカウント Figma REST で memo をフォールバック取得(メディア経路ではない)。抽出は続行。REST 失敗は再配送のため再 raise |
WF-C-04a |
行の読み込みミスは実際のエラー | 実行アイデンティティが不明で結果ドキュメントを pin できない parse メッセージはハードエラー(row-first-then-SQS)であり、永遠にリトライする結果整合性ではない |
WF-C-04d |
fail-closed な parse は terminal 化する | parse 中の ContractFailure(解決不能な pin、テナンシースコープ不一致 — 再配送で決して解決しない)は、assemble とまったく同じく terminal な …-failed.json + …-failed-manifest.json + failed Webhook(status="failed" + error)を発行する。transient な parse エラーは従来どおり re-raise し、terminal サーフェスなし(WF-C-03 と対比) |
WF-C-01b |
Memo テキストの snapshot は再 parse を生き延びる | parse が結果ドキュメントの parse.memo_influences を書き込んだ後、memo を変えた同じ wireframe _id の再 parse(LWW wireframe ドキュメントをバンプ)を行っても、最初の実行の結果ドキュメントの parse.memo_influences テキストとその parse.json は 変更されない — memo テキストは snapshot 化され、wireframe の上書きを生き延びる |
Generation — Confirm / Reject
confirm/cancel の CAS はエンキュー前に バックエンド エンドポイントが適用します。これらのケースは、結果として生じるメッセージに対して ワーカー が何をするかをアサートします(enum 遷移そのものはバックエンド所有 — スコープ外 参照)。
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-04b |
Confirm は新規 nonce で assemble をエンキュー | assemble メッセージは同じ wf2des_id、confirm の attempt、新規 nonce、confirm.parse_artifact_hash を運ぶ。ワーカーはその pin された parse.json に対して再水和 |
WF-C-05a |
Reject は Webhook ではない | parse 却下は plugin が wf2des-api に書き込む(parse.rejected = {reason_code, note})— ワーカーは reject Webhook を 発行しない。却下された実行に対して assemble は決してエンキューされない |
WF-C-11b |
完了した generation は決して再処理されない | ターミナルマニフェストを持つ実行への再配送は、再実行ではなくそのマニフェストを適用。ワーカーは完了した実行を新しい非決定的な spec で上書きしない |
Generation — Assemble
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-06 |
Assemble の正常系 | pin を rehydrate し、決定的 role/kind gate、決定台帳の参照、K サンプル自己一貫性投票による section selection と投票に対する決定的ガード、deterministic stitch/validator、computed confidence、台帳書き戻し(スコアラチェットフェンス)を実行。spec、screen-plan/layout evidence、hash、assembly artifact、result/manifest を CAS で書き、design_resolution を upsert し、succeeded webhook を 1 回送信 |
WF-C-07 |
再水和はハッシュ不一致で fail closed | inputs の pin の保存済みハッシュが一致しなくなった → fail closed。古いルール/コンポーネントへの フォールバックなし。実行は failed で終了、…-failed.json + failed Webhook |
WF-C-08 |
assemble 後の generation 失敗 | assemble ステージのエラー → …-failed.json + …-failed-manifest.json。failed Webhook が error + 失敗マニフェスト URL を運ぶ。succeeded Webhook なし |
WF-C-09 |
Validator の no-rules 経路(assemble) | design_rule 未登録 → validator_report.status = no_rules、全ノードに rules_unvalidated フラグ。実行は依然として成功。flag_count がフラグを反映 |
WF-C-06a |
再配送は pin を再利用し、不在時のみ再選択 | 再配送時、既存の inputs pin を再利用。選択はブロックが不在の場合のみ再実行 — クラッシュリトライが異なるコンポーネントやルールリビジョンを静かに選ぶことは決してない |
WF-C-06b |
大きな spec は S3 にスピル | spec > ~1MB → spec ブロックが …-spec.json にスピルし、artifact_urls.spec が設定される。spec_nodes_flat + サマリーはインラインのまま。結果ドキュメントは依然として検証を通る |
WF-C-06c |
候補の切り詰めはフラグ付け | あるセクションの候補セットが切り詰められると、selection.truncated がそれを記録し、影響を受けるノードは静かに削除されずフラグ付きで浮上 |
WF-C-09a |
flag_count がマニフェスト経由で行にミラー | confidence.flag_count がマニフェストの row_effects.wf2des.flag_count と succeeded エンベロープのマニフェストにコピーされ → バックエンド行の flag_count へ |
WF-C-06d |
placement.design_area を config.json から解決 |
config.json(project_figma_file.config_url 経由)に既知の DESIGN エリア矩形をシード。書き込まれた placement.design_area {x,y,w,h} がその矩形と等しい — ワーカーがそれを 解決 し、決して捏造しないことを証明 |
WF-C-06e |
ルールリビジョンのボードを選択に添付 | ガイドラインボードは pin された design_rule リビジョンに載って届く: parse が 最新 リビジョンを pin し、assemble はその 1 つの pin から RuleSet とリビジョンの N 個のボード画像の 両方 を再水和し、各セクション選択呼び出しに N 個の vision BinaryContent として添付(アドバイザリなリファレンス。決定的な縫合/validator/confidence は不変)。pin されたドキュメントの欠落またはボード画像の欠落 ⇒ fail closed |
WF-C-06f |
決定台帳の参照がセクションを LOCK / FLOOR | セクションの section_signature に対して design_resolution をシード: mined エントリ(または score ≥ トラスト閾値)はセクションを LOCK する — そのセクションに対して mock_llm.selection は 呼ばれず、台帳の決定をそのまま採用。より弱い voted エントリは FLOOR — セクションは再投票し厳密により高いスコアが勝つ(より低い再投票は floor された決定を維持)。古い/スコープ不一致のデフォルト variant エントリは投票にフォールスルー。参照は organization_id + project_id でフィルタ |
WF-C-06g |
台帳書き戻し + ラチェットが再収束 | assemble 成功後、各非 unmatched セクションは決定的なスコアと共に design_resolution を upsert する。より低いスコアの voted エントリに対する再実行は REPLACE する(単調改善)。スコアが等しいか低い再実行は REJECT され保持中のエントリは KEPT(フェンスされた upsert 上の DuplicateKeyError はエラーではなく成功を返す)。保持中の mined エントリは voted 書き込みで決して上書きされない。同一エビデンスの再実行は updated_at を変更しない(churn なし) |
WF-C-06h |
terminal provider configuration/account error | credit 枯渇、無効な認証/アクセス、unavailable model は failed artifact + manifest を書き、安全な error の failed webhook を送り message を consume |
WF-C-06i |
transient provider failure | 通常の 429 rate limit / 5xx は terminal surface を作らず SQS redelivery に返す |
wf_parse
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-12 |
wireframe ドキュメントのみを書き込む | frame-registration イベント → 決定的抽出 + memo ステータス + screen_id 文法 + variation_label + mock_llm.role_intent。wireframe ドキュメント のみ を書き込む。結果ドキュメント なし、parse.json なし、プレビュー なし、Webhook なし、PG 効果 なし |
WF-C-13 |
Snapshot GET エラー → SQS リトライ | 登録 snapshot URL の GET が失敗 → wf2des-events の再配送のため例外を再 raise。wireframe 書き込みなし |
WF-C-14 |
(source_hash, processed_at) に対する LWW フェンス |
より古い/等しい processed_at かつ source_hash 不変の再配送は上書きしない。新しい source_hash またはより新しい processed_at は上書きする。同じ _id に対する generation-parse + wf_parse は非衝突を意図 |
rule_process
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-15 |
(design_rule_id, content_hash) ごとのイミュータブルリビジョン |
選択されたガイドラインボードを get_file_nodes で解決(タイトル取得)。各ボードを Figma images API(サービスアカウント PAT)でレンダーし、PNG を S3 に保存(wf2design/rule-boards/{file_key}/{node_id}.{sha256}.png)。ボードごとに 1 回の mock_llm.rule_extraction vision 呼び出し(slot_constraints は常に空)。フラグメントを決定的にマージ。マージ済み RuleSet に対する正規の content_hash。version = max+1 を割り当て。マージ済みルールと boards[] {node_id, title, image_url, content_hash, mime} + 抽出 provenance {extraction_model_id, extraction_prompt_version, extracted_at} + draft_source="llm_extracted" の両方を運ぶ 1 つのイミュータブルな design_rule リビジョンドキュメントを書き込む。Webhook なし、PG 効果 なし |
WF-C-15a |
コンテナ選択はボードフレームに展開される | 選択された CANVAS(ページ)や SECTION は子の FRAME に展開される(ネストした SECTION は再帰。フレーム以外の子はボードではない)。リビジョンの boards[] はコンテナではなくフレームを保持。空のコンテナはハードエラー — デザイナーの選択の解決であり、決してキュレーションではない |
WF-C-16 |
欠落 / レンダー不能なボードはレコードを失敗させる | ファイルに存在しない選択ボードノード(get_file_nodes のミス)や images API の null レンダーはハードエラー → レコードが失敗(再配送のため再 raise)。部分的な design_rule リビジョンは書き込まれない |
WF-C-17 |
同一のマージ済みルールの再登録 = 同じリビジョン | マージ済み ルールが同一になるボードの再登録は既存の (design_rule_id, content_hash) リビジョンにマップ。新しい序数は発行 されない。変更されたルールは次のバージョンを発行(バージョンの増加は許容済み)。並行する同一の登録は単一のイベントで直列化 |
component_sweep
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-18 |
フル sweep の正常系 | schedule/resync → sweep_marker を取得。Figma REST を巡回(/components 公開済み + フルファイルフォールバック経由のローカル)。LLM なし。COMPONENT/COMPONENT_SET ごとにコンテンツアドレス指定の S3 snapshot を書き込み + 決定的な variant_properties/slots/default_size を持つ design_component コンテキストドキュメント(_id = プラットフォーム design id)を upsert。project_figma_file.style_captures をキャプチャ。発見コンポーネント sweep マニフェスト を発行。components_synced_at を 最後に スタンプ |
WF-C-19 |
Single-flight が並行 sweep を直列化 | ライブな sweep_marker が保持中の 2 つ目の sweep はリリース/失効までブロック。expires_at を過ぎたマーカーは再取得可能(クラッシュセーフ)。design_component upsert は _id に対して |
WF-C-20 |
Sweep 失敗はファイルドキュメントをスタンプ | 巡回中の Figma REST エラー → project_figma_file.sweep_error をフィールドレベルで設定、マーカーをリリース/失効。取りこぼしたマニフェスト適用は次の sweep で再収束。ai-status Webhook ではない |
WF-C-20a |
ai-status ではなくレジストリマニフェストを発行 | component_sweep は Webhook フリーでは ない: 発見コンポーネントマニフェスト(id · name · type=component · image_url · json_schema_url · status)を発行。/v1/webhooks/ai-status には決して POST しない。ワーカーは PG に触れない(バックエンドがマニフェストを適用) |
Figma OAuth 認証情報(スケジューラの権限)
スケジューラはボディなしの EventBridge 呼び出しであり、ユーザーもリクエストもありません。Figma は authorization code フローのみをサポートするため、認証情報は管理者が 一度だけ 認可するグラントです。 保存とリフレッシュは Admin API が担当し、ワーカーは有効なアクセストークンだけを取得します。この境界の 問題は原因を誤認しやすいため、以下で固定します。
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-28 |
健全なトークンはコストゼロ | Admin が失効まで REFRESH_SKEW(1 日)超のアクセストークンを検出 → そのまま返す。Figma への往復も figma_oauth_grant への書き込みも 発生しない |
WF-C-29 |
失効間近のリフレッシュはリフレッシュトークンを保持する | skew 内 → Admin が組織の advisory lock を保持し、grant_type=refresh_token で /v1/oauth/token を呼び、暗号化した新しい access_token + expires_at を保存。レスポンスに含まれない場合、保存済み refresh_token は暗号化されたまま変更しない |
WF-C-30 |
リフレッシュ競合の勝者はちょうど 1 つ | skew 内の provider 呼び出し 2 つは PostgreSQL の transaction advisory lock で直列化される。Figma を呼ぶのは 1 つだけで、後続は更新済みの行を読み、再リフレッシュせず返す |
WF-C-31 |
失敗したリフレッシュはロックを解放する | リフレッシュ失敗または Admin リクエスト終了 → トランザクション終了時に PostgreSQL が advisory lock を解放する。永続マーカーがグラントをロックし続けることはない |
WF-C-32 |
グラント不在は運用エラーでありリトライではない | PostgreSQL に当該組織の figma_oauth_grant 行が無い → Admin provider は接続フローを示すエラーを返す。OAuth 設定時にワーカーは PAT へ暗黙にフォールバックしない |
WF-C-33 |
鍵のローテーションは大きく失敗する | Admin が別の鍵で書かれたグラントを復号できない → 後で紛らわしい Figma 403 になるゴミデータではなく、明確な provider エラーを返す |
WF-C-34 |
認証情報の選択とフォールバック | OAuth 設定済み → Authorization: Bearer、X-Figma-Token は付かない。OAuth 未設定で figma_pat あり → X-Figma-Token(未移行のデプロイも動作し続ける)。どちらも無い → 両方の選択肢を示す設定エラー |
WF-C-35 |
トークンは必要な境界内に留まる | ログ・Webhook・マニフェスト・公開 API レスポンス・DocumentDB にトークンを含めない。PostgreSQL は暗号文を保存し、内部 provider はアクセストークン + 有効期限だけを返し、リフレッシュトークン・クライアントシークレット・暗号鍵は返さない |
Figma OAuth 接続フロー(Admin API)
コールバックは 必然的に未認証 です — Figma からのブラウザリダイレクトは Cognito トークンを伴いません —
したがって state がセキュリティ境界のすべてです。
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-36 |
接続できるのは組織管理者のみ | 組織 read/write を持つ Admin Cognito セッションは許可。組織 read/write のないロールは 403。組織全体の認証情報をアプリユーザーが置き換えることはできない |
WF-C-37 |
state は偽造も改ざんもできない |
別のシークレットで署名した state、署名を保持したままペイロードの organization_id を差し替えた state、不正な形式の state、期限切れの state はすべて 401。組織 ID は 署名済みペイロード から取得し、クエリパラメータからは決して取得しない |
WF-C-38 |
コールバックは偽造の判定オラクルにならない | 署名エラーと不正形式は 同一の メッセージを返し、どちらの検査で落ちたか攻撃者に判別させない |
WF-C-39 |
拒否とコード失効を正直に報告する | Figma が error=access_denied を返す → 拒否を明示して 400。30 秒の寿命を過ぎた認可コード → やり直しを促す 400。いずれもサーバー障害としては報告しない |
WF-C-40 |
Admin が PostgreSQL 書き込み前に暗号化する | コールバックはプラットフォームの ENCRYPTION_KEY で両トークンを暗号化し、組織ごとに 1 行を upsert する。ENCRYPTION_KEY 未設定なら平文保存ではなく保存を 拒否 し、AI には鍵もリフレッシュトークンも渡さない |
スナップショット範囲・ガード層・管理用ドキュメント
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-41 |
指定された sectionNodeId がスナップショット取得範囲を限定する |
フィールドが設定され、かつフレームがそのサブツリー内にある場合、GET /files/{key}/nodes?ids= のみが呼ばれ、GET /files/{key} は 決して 呼ばれない。実測された動機: 実際の 31 ページファイルでファイル全体のレスポンスは 255MB / 67.5 秒 であり、あるトリガーは 140 秒でソケットが閉じられ失敗した |
WF-C-42 |
誤った・古い範囲指定は劣化するが破損させない | フレームを含まない sectionNodeId、または削除済みノードを指す場合、wf2desSnapshot.scopeRejected / fullDocumentFetch を記録してファイル全体走査にフォールバックする。得られる snapshot_scope と wf_content_hash はフィールドを省略した場合と同一である — このハッシュは parse キャッシュのキーであり、誤った範囲が別画面の parse を返すことは決してあってはならない |
WF-C-43 |
KIND ガードはバリアント指定済みの選択も判定する | variant_props を反映し、かつ領域のロールが許可しない kind のコンポーネントを選んだ instance は compose に降格され、selection_kind_mismatched が記録される。回帰ガード: このチェックは以前サイズ層の not variant_props 条件の内側にあり、実際の選択の 57%(実測 57 件中 33 件)に対して無効だった |
WF-C-44 |
SIZE ガードはバリアント未指定の選択に限定され続ける | ロールが kind を許可する overscoped なコンポーネントは、バリアント指定時には通過し、未指定時には捕捉される(unmatched)。default_size はデフォルトバリアントのみを表すため、この層を広げれば正しい細バリアントの選択を捨ててしまう |
WF-C-45 |
kind = other は決してカテゴリ誤りではない |
other と記録されたコンポーネントはすべてのロールで許可される。ROLE_CANDIDATE_KINDS はどのロールにも other を列挙しないため、不一致として扱えば該当する 19 件すべてがあらゆる場所で拒否される — 実測では header 領域で logo が拒否された |
WF-C-46 |
レジャーは scope だけでなく kind も再検証する | ロールが許可しない kind の保存済みエントリは参照時に ledger_entry_misselected reason=kind_mismatched で拒否され、セクションは再投票される — エントリがバリアントを指定し、スコアが信頼閾値を超えていても同様である。これがない場合、スコア 0.88 の誤った選択が 3 つのセクションをロックし、連続 2 回の実行がバイト単位で同一の出力を生成した |
WF-C-47 |
管理用ドキュメントは存在を前提とせず作成される | project_figma_file ドキュメントが存在しない状態で、レジストリ enrichment パスがこれを作成し($setOnInsert、role = 1)、その slot_roles 書き込みが 反映される。再実行は冪等であり、登録によって設定された role / config_url を上書きしない。回帰ガード: ドキュメント不在時は 5 つの書き込み経路すべてが黙って no-op となり、同期あたり 104 回の LLM 呼び出しが捨てられていた |
WF-C-48 |
余剰テキストは破棄されず保持される | 選択されたコンポーネントのスロット容量をテキスト数が超えるセクションは、[preserved_text…, instance] を持つラッパーを出力する(スロットに充填された全テキストより前に位置する場合は見出しを上に配置)。ログは text_leftover を報告し(1 件なら INFO、フラグ閾値では WARNING)、text_preserved が結果を明示する。呼び出し元が保持するテキストについて、イベントが破棄を主張してはならない |
ファミリー横断のコンポーネントチェック
| ID | 意図 | 期待結果 |
|---|---|---|
WF-C-24 |
すべての読み書きがテナンシーでフィルタ | 各ファミリーで、DocDB のクエリと書き込みフィルタの両方が organization_id + project_id を含む。別プロジェクトのドキュメントは決して読み書きされない |
WF-C-25 |
ワーカーは PG 接続を決して開かない | PG ドライバが到達不能 / インポート時に大きな声で失敗するガード環境変数の状態で各ファミリーを実行。どの経路も PostgreSQL 接続を開かず、PG 認証情報を保持しない。すべての効果は DocDB / S3 / Webhook のみに着地 |
WF-C-26 |
Lineage エンベロープが投入される | 書き込まれるすべての DocDB ドキュメント(wireframe / design_rule / design_component / result)が投入済みの lineage{source_url, source_hash, processor_version, index_schema_version, processed_at, job_id} を運ぶ。job_id = 生成元の実行 id |
WF-C-27 |
Intake の event_type ディスパッチ |
各 wf2des-events の event_type がちょうど正しい実行ファミリーへルーティング(rule→rule_process、frame-reg→wf_parse、resync→component_sweep)、EventBridge invoke → component_sweep。rule イベントは決して component_sweep をトリガーしない |
End-to-end (E) ケース
実 dev に対するスモークセットのみ。コストはゼロではない — スイートは小さく保つこと。
| ID | 意図 | 手順 | 期待結果 |
|---|---|---|---|
WF-E-01 |
インタラクティブ generation の往復 | バックエンドが wf2des 行を作成 + parse をエンキュー → confirm エンドポイント → assemble |
予算内で: parse_done の後 succeeded Webhook を受信。S3 に結果アーティファクト + マニフェスト。wf2des 行 → status '1' completed + 結果参照 + flag_count、phase クリア |
WF-E-02 |
auto_confirm のワンショット |
auto_confirm=true の parse メッセージをエンキュー |
parse が 1 回の呼び出しで assemble にそのまま連鎖。succeeded Webhook のみが観測される。行は awaiting_confirm での待機なしで status '1' に到達 |
WF-E-03 |
実 component_sweep |
登録済みファイルを持つプロジェクトで resync をトリガー(または EventBridge スケジュールを待つ) | design_component ドキュメント + コンテンツアドレス指定 snapshot を書き込み。発見コンポーネントマニフェストがプラットフォーム design(type=component)行に適用される。components_synced_at が進む |
WF-E-04 |
Webhook が失敗し続けたときの DLQ | バックエンドの ai-status Webhook を全リトライを通じて 5xx に強制 |
generation メッセージはリトライ後 DLQ へ。ターミナル S3 マニフェストが永続記録として残る。2 番目のネットは存在しない: マニフェストをリプレイする stuck-generation sweep は未実装のため、行は手動で解消されるまで非終端のまま残る |
WF-E-05 |
Awaiting-confirm のセーフティネット | 実行を awaiting_confirm で待機させ、その後 confirm タイムアウトを超過させる |
バックエンドの awaiting-confirm sweep(例: 72h)が実行を fail closed。assemble はエンキューされない |
横断的チェック
該当する全ケースで成り立つべき不変条件。これらは単独の行ではありません — 上記のケースを実行しながら確認します。
- ワーカーは PostgreSQL に決して書き込まない(
WF-C-25): 各ファミリーを PG ドライバが到達不能 / ガード環境変数の状態で実行。どの経路も PostgreSQL 接続を開かず、PG 認証情報を保持しない。契約内のすべての PG 効果はバックエンド側。 - ai-status Webhook は generation 専用でターミナル遷移ごとにちょうど 1 回: generation のみに発火。
wf_parse/rule_processは決して発行せず、component_sweepは代わりに発見コンポーネントマニフェストを発行。1 回のprocess_record内では、parse_done(待機)またはsucceeded/failed(ターミナル)のちょうど 1 つが送られる — reject Webhook は決してない。 - LLM フェンス: role/intent(parse +
wf_parse)、K サンプル自己一貫性投票としてのセクションごとの選択(assemble)、ボードごとのルール抽出(rule_process、取り込み時)。抽出、memo ステータス、screen_id、variation_label、role/kind candidate gate、台帳参照、投票に対する決定的ガード、台帳スコア + 書き戻し、縫合、validator、ルールフラグメントマージ、content_hash、sweep は決定的。component_sweepは LLM を呼ばない。Confidence は計算される(formula_version)— モデルの自己申告は決してない。section_signatureは LLM 非依存。 - compose は常にフラグ付け、unmatched は決して削除されない(
WF-U-23、WF-U-25、WF-C-06): すべてのcomposeノードはflagged=trueを運ぶ。すべてのunmatchedノードは可視のプレースホルダになる。 - テナンシー(
WF-C-24): すべての読み込みとすべての書き込みがorganization_id+project_idでフィルタリング。プロジェクト横断アクセスはデフォルトでは決して起こらない。 - フェンシング: 結果ドキュメントの
(_id, attempt)に対する CAS(generation、WF-C-11)。wireframeの(source_hash, processed_at)に対する LWW(WF-C-14)。single-flightsweep_marker(WF-C-19)。rule のコンテンツアドレス指定一意性(WF-C-17)。design_resolutionのスコアラチェット upsert —voted書き込みは非minedかつ厳密により低いスコアのエントリに対してのみ着地し、フェンスされた upsert 上のDuplicateKeyErrorは KEPT を意味する(WF-U-38、WF-C-06g)。破棄された attempt の DocDB 書き込みと遅延 Webhook は no-op。 - 冪等なコンテンツアドレス指定書き込みは再配送時に再収束(
WF-C-05、WF-C-14、WF-C-17): 再配送された内部実行イベントやリトライされた generation 書き込みは同じドキュメントに収束。完了した generation は決して再処理されない — ターミナル行は最終であり、再実行は新しい行を作成する。
意図的にスコープ外
将来のテストが正しいバケットに着地するよう列挙します。これらは別の場所が所有します。
- バックエンドの
wf2desPostgreSQL 行ステートマシン + 全エンドポイント認証:status/phase/attempt遷移、confirm/cancel/placement/feedback エンドポイント、およびそれらの認可は バックエンド テストに存在。ワーカーは Webhook 経由で報告するだけ。 - ai-status Webhook ハンドラのマニフェスト↔ペイロード検証 + マニフェスト読み込みの 5xx: ハンドラがマニフェストの
job_id/job_type/ S3 プレフィックスをペイロードに対して検証し(マニフェスト読み込み失敗時に 5xx を返す)ことは バックエンド ロジック — ワーカーはマニフェスト + エンベロープを構築するだけ(WF-U-27、WF-U-28)。 - plugin の Figma materialization + placement write: spec read、native Figma build、intrinsic-height footprint preservation、bounded correction/promotion、7 種の materializer event は plugin test に存在。
- sweep マニフェストからのプラットフォーム
design(type=component)レジストリ upsert: 発見コンポーネントマニフェストをdesign行 upsert として適用することは バックエンド の関心事 — ワーカーはマニフェストを 発行 するだけ。 - Figma サービスアカウント認証 / シークレット管理: wf2design 自身のシークレットストアの PAT とそのローテーションはインフラであり、ワーカーロジックではない。
- DLQ / SQS / EventBridge インフラ: キューの配線、redrive ポリシー、スケジュールの cadence はインフラテスト。ワーカーは再 raise / 再配送の挙動のみ検証。
- テナント間 RBAC: 純粋に上流の認可。ワーカーテストはメッセージが既にその
organization_id+project_idに対して認可済みであることを前提とする。 - LLM の実際の選択品質: ここでは構造化出力でモック。選ばれたコンポーネント/variant/ラベルが 良い かどうかは、これらのテストではなく別個の人間レビュープロトコルで評価される。