AI WF2Des — I/O 定義
決定的assemblyでも選択整合性を検証します。VARIANT/TEXT定義は余剰内容を隠せる証拠ではなく、BOOLEAN可視性制御のみを認めます。 値・単位分割は同じ親のslot、数値のみの値、数字を含まない単位、完全一致slotがない場合に限定します。 同一local group内の同じ外観・stateのcontrolに矛盾する選択stateを付けません。曖昧なら元の外観を保持します。 反復actionのnative再利用には同一copy、元style/state/構造、captured size variantが必要です。 文字slotのないwhole-controlは検証済みrepresented-text bindingがなければ元を保持し、図形の横にラベルを重複追加しません。 単一の直接TEXTをbindしたnative button/tab/navigationは幅・書体・余白を保ち高さを拡張できます。DB fieldとmodel callは追加しません。
whole-screen planner(screen-review@3)入力の required_control_owners は、描画済みの単一control面、短いラベル1つまたは
正の証拠を持つatomic control図形1つ、寸法が整合する登録済み非placeholder control/selection候補から、
元unitと正確な子ID・候補aliasを列挙します。table cell、content card、複合control、既知control内の中立な装飾子孫、
不明な追加図形は除外し、
元state/overrideを保持します。名前や色からstateやmatchを推定しません。選択済みの上位部品が供給しない限り
owner自体の明示decisionが必要で、子だけのdecisionでは足りません。初期planの差分レビューも未決定ownerを
既存のvalidation/request上限内で検証し、追加の有料completion loopは起動しません。明示fallbackは許可し、
候補hintによる自動選択やgrounding guardの迂回は行いません。
保守的compose fallbackは、同じ正の証拠を持つowner自身の面だけに元の高さのminimumを保持します。
祖先・table track・native instanceの寸法は変更せず、native置換のintrinsic geometryを優先します。
部品 identity と optional content(2026年9月)
候補の重複排除・metadata 補完は同じ pin 済み snapshot identity(旧未 pin record は同じ publish key)
を必須とし、同名だけでは統合しません。異なる snapshot の variant 軸・key・default は独立です。
hash 検証済み preflight から root BOOLEAN 定義、text path の visibility binding、限定された native
navigation action の証拠をメモリ上に導出します。INSTANCE 値は master default ではなく observed 値として
区別し、選択時に明示送信します。既存 component_properties を使用し、DB migration・registry 書換えは不要です。
node または祖先の明示的な opacity=0 は、BOOLEAN visibility binding が true でも文字・native action の
可視証拠に含めません。正の opacity だけから非表示とは判断しません。
optional override 後も可視と証明できる slot だけが重複 navigation の coverage を供給できます。
source に存在する default copy を非表示にする提案は拒否します。単一 label+glyph の navigation action は、
対応する native action の capture 証拠がある場合だけ吸収できます。文字列一致だけでは artwork を削除しません。
全ての非TEXT leafをaction本体・唯一のglyph・明示的に空と証明されたlayout cellのいずれかとして説明します。
ボタン内部/外側に追加のopaque instanceやcontrolがある場合、ink flagが欠落/falseでもcoverageを拒否します。
ink証拠がないことを空の証明には使用しません。
native action が coverage を供給する場合、既に true と証明された BOOLEAN visibility binding を
owner の既存 component_properties に明示保存し、renderer の sample cleanup による非表示を防ぎます。
非表示・不明・曖昧な action を有効化して coverage を成立させません。binding を保存できる serializable な
owner decision がない場合、binding を持つ action の重複除去は実施しません。
文字を含まない atomic artwork は primary/secondary semantic kind が media の非 placeholder 部品を使用可能です。
compound content の scope guard は維持します。
独立した小型・全幅 action strip の直前にある footer は、capture 済み optional-content 設定が footer 全文と隣接 native navigation action を一意に供給する場合だけ昇格可能です。strip は別途保持し、 一般の後続 content・field・未説明 artwork・曖昧な設定・非表示 action は対象外です。chrome 重複除去は specimen default ではなく、選択 variant と BOOLEAN 適用後の可視 slot を使用します。
navigationと分類された画面上部の全幅barでも、元内容の全体が単一の小さなbrand clusterで、 既にcapture済みbrand mediaを選択している場合はnative headerの領域所有を利用できます。 viewportに適合する一意のheader variant、capture済みbrand/header semantics、およびhash検証済みの 単一描画graphicのみの構造が必須です。可視文字・独立control・不確かなvisibility bindingを持つ候補、 追加の元copy/action、曖昧なvariant、明示unmatched decision、親の個別styleは元compositionを保持します。 styleのないcompose decisionは単一brandの全機能を証明できる場合だけ再調整し、元のpixel高さの違いだけで wireframe chromeを要求しません。 これはcompound領域の所有であり、wordmarkと文字の同値性を新規推定しません。名前だけでは許可せず、 証拠はメモリ上で導出し、DB migrationやschema追加は不要です。
Source fidelity / evidence guard(2026年9月)
有料選択の前に pin 済み component snapshot の存在と canonical hash を registry lineage に照合します。欠落・未対応 URL・破損・hash 不一致は component library の resync/sweep と新規生成を案内して失敗させます。一時的 storage/access failure は retry 対象です。metadata-only capture や design-rule 抽出では raw snapshot を復元できません。
free-form の順序と layout は同一の retained sibling 集合から決定します。冗長/注釈 node の除去で最後の overlap が消えた場合は flow 軸で整列し、gap と layer-path identity を保持します。残存 overlay は座標/z-order、明示的 auto-layout は authored order を維持します。
既存の layout_frame/compose に省略可能な
table_layout: {rows: [{cell_paths, min_height, gap_after?}]} を追加します。
source の table 領域内で horizontal column・vertical cell flow・整列した行境界・完全な
cell coverage が確認できる場合だけ発行します。不明な grid/row span は source を維持します。
renderer は各行の source minimum と intrinsic height の最大値に揃え、native artwork は伸ばしません。
各行の任意 cell_surface_paths: {cell_path: surface_path} は、元の境界がcellと一致する
透明・余白なし・単一子flow wrapperの奥にあるcompose済み描画面を示します。空なら省略します。
rendererは変更前に正確な子孫chainを検証し、そのFRAME面だけを共通行高まで伸ばします。
native instance、内部icon、無関係なcard、意図的に内側へ配置した内容は伸ばしません。
未指定cellは従来どおりで、長い文章は下限を超えて伸長できます。
table_layout.rows が1行の場合、整列した horizontal table row(headerを含む)の
全 direct cell を参照できます。部分的・並べ替え済みの行は変更前に拒否します。
compose cell のみ共通高さに伸ばし、native instance の形状は保持します。
任意の auto_layout.min_height は compose された単一 control の元の高さを下限にします。
上限ではなく、折り返す内容は伸長できます。セクション全体には適用しません。
任意の stroke_edges: [top, right, bottom, left] は部分境界線を保持し、省略時は従来の四辺です。
coverage で吸収済みの section の占有区間だけを sibling gap から除外し、未使用の余白と注釈間隔は保持します。
registry button の visible direct TEXT が1つだけで元ラベルの binding が成功した場合は、
継承された specimen の省略記号を無効化し、幅・書体・余白を保持して縦方向の伸長を許可します。
compound control と source clone は変更しません。明示的な placeholder 名の textless artwork は
削除せず unmatched として報告します。名前は注意喚起の根拠であり、削除の許可ではありません。
同一 file の wireframe fallback は master default ではなく実際の source instance を clone し visibility・nested override・state を保持します。passive table content を action に置き換える選択や control 数の不一致を guard し、明示された source state を default より優先します。
整列した反復 painted table cell は coverage 前と deterministic stitch 時に row family として検証します。 capture 済み部品の intrinsic height が元 cell の painted track 全体を覆えない場合、compact 部品で cell 全体を 置換しません。高さの検証は capture 済み文字列が不変で live 幅が狭まらない場合に限り、折り返しによる 伸長の可能性を surface 欠落の証拠にしません。同じ source master/state と surface を持つ cell 群に、 表示文字の違いだけで異なる capture 済み surface treatment を導入しません。矛盾した選択だけを診断付き fallback とし、適合する native cell・実際の source state 差・無塗装/inset badge・無関係な list は独立して選択可能です。欠落した兄弟選択を創作せず、 数字から state を推測しません。
WFNode.has_graphic_overrides は追加の source 証拠で、既定値は false、false 時は省略します。
INSTANCE の明示的な fills、strokes、opacity、inheritFillStyleId、inheritStrokeStyleId
override が、正確な ID で可視かつ描画される graphic descendant に一致する場合のみ true です。
override と descendant の探索数を制限し、灰色・名前・隣接文字・形状から操作 state を推測しません。
文字のみ・非表示・対象不明・形状のみの override は対象外です。vector の IR 統合で失われる証拠を
保持する追加 field であり、旧 artifact は migration なしで読み取れます。
parse processor は generation_parse@0.4 / wf_parse@0.4 とし、新規 run は source hash が同じでも
旧 cache tree を再抽出します。欠落した override 証拠を暗黙の false として再利用しません。
既存の不変 run artifact は書き換えず、DB migration・rule/component 再抽出は不要です。
ライブラリ候補を拒否しても出典を失わない。分解可能な子を持たない元instance・描画済み/不透明な artworkを、空のcomposeへ変換してはならない。モデルが明示的にcomposeを求めた場合も、既存の source-instance/glyph保持経路で元の見た目を残し、fallbackとしてflagを維持する。子を持つコンテナと 通常の文章はcompose、真に空のlayout cellは元の寸法を保つ。画面ID・node ID・操作名に依存しない共通規則である。
意味的セクションレイアウトの追加契約(2026年9月)
ScreenPlan.semantic_sections は任意のグループ化要求リストで、未指定時は空とする。
各要求は {parent_node_id, member_node_ids, role} であり、role は
content|form|table|list|actions|header|footer|navigation の閉じた集合である。
ID は出力Figmaノードではなく固定済みの元ツリーを参照する。メンバーは適用可能な単一の縦方向フロー内で、
元の順序を保った連続する直接の兄弟に限る。grid・絶対配置領域・instance境界を横断せず、
内容の並べ替え・削除や寸法の創作を禁止する。部品選択後も出力メンバーの網羅性と順序を検証し、
不正・曖昧な要求はfindingを残して元レイアウトを保持する。モデルに任意のツリー変更権限は与えない。
生成は決定的に検証した SectionLayoutPlan を不変の assembly_state.layout_plan に保存する。
version は section-layout@1、レポートは requests、sections、joints、findings、viewport
を含み、採用したスコープ・レイアウトの根拠・fallbackを記録する。生成と実画像の修正レビューは
同じ適用経路を使う。レビューの差分は現在の部品選択との整合性を検証し、候補の新しいassembly-state hashと
ともに更新済みplanを保存する。変更のないレビューは同一specを再現する。layout_plan のない旧stateは
リプレイ時にも旧動作を維持し、新しいレイアウト変換を暗黙に適用しない。
ScreenGroupPolicy.viewport_width_px は任意で、指定時は正数のみ受け付ける。出力viewportとして
明記されたこの値だけが元の幅を上書きできる。globals.base_viewport_px は引き続き参考値であり、
カード/コンテンツ幅やpaddingからviewportを推測しない。適用対象のポリシーが競合した場合は
findingを残して元レイアウトへfallbackする。ルールに基づくセクション間隔は境界の実効間隔に適用し、
根拠がなければ元の測定済みgapを使う。chromeとの境界、塗りのあるコンテナのpadding、部品内部の形状を保護し、
同じ余白を外側marginと内側paddingの両方に重複適用しない。
clip 付き構造 wrapper は、明示的な縦 layout と source/emitted 双方の bounds から、内容が内側に収まり、 重なり・artwork・角丸 crop・fixed/absolute sizing がないと証明できる場合だけ section flow に参加できる。 clip 自体は変更しない。独立した painted container の inset は保護する。適用可能な section-gap rule により neutral な入れ子 edge padding が joint を過大にすると証明できる場合だけ、joint を単一の spacing owner にする。 rule のない余白・曖昧な crop・部品内部の padding は保持し、証明済み flow 内の registry instance は古い source spacer ではなく部品自身の高さを使用する。
materializerのspecノード種別は既存の5種類のままである。
任意の InstanceNode.height_authority="component" は未適用時には省略する。
適用可能な意味的な縦方向フロー内では部品の実際の高さで配置し、旧wireframeの占有高さを埋めるspacerを
追加しない。部品内部の変更や意図的な元の間隔の削除は許可しない。未指定時は従来動作である。
table描画面/control artworkの追加的なdocument形状を持つ新しい書き込みは index_schema_version=1.4 とする。
旧documentは引き続き読み取り可能であり、破壊的DB migrationや新しいSQS endpointは不要である。
実レンダリング検証の追加契約(2026年9月)
parse 時に inputs.llm.screen_review_model_id、screen_review_reasoning_effort、
screen_review_enabled を固定する。元画像は実行単位の SHA-256 付きアーティファクトへコピーする。
assemble は spec_hash、assembly_state_hash、artifact_urls.assembly を保存する。
assembly は元ツリー、選択、グループ、除外、スロット役割、スタイル、確認状態、注釈、プランの再実行可能な入力である。
既存データは引き続き読めるが、検証用入力がない場合は cannot_verify を返す。
assembly は validator が使った正確な section_ids も固定する。検証はそのスコープと根拠のある追加のみを使い、
欠落・型違い・元ツリーにないIDをモデル呼び出し前に拒否する。結果公開のI/O障害時は所有中のリースのみ解放する。
plugin は完了レビュー採用前にPGと結果の現在のattempt/元spec hashを再確認し、再実行との競合を拒否する。
instance の任意の represented_text_bindings は空なら省略し、ワードマークで表した正確な元文字列・IDと
部品/variant/subtree/元画像/実画像のハッシュを保存する。実画像レビュー中の成功した部品照会と、
描画済みグラフィックの正の証拠が必要である。同じ元単位・文字列・部品・variantの場合のみ固定済みのbindingを再利用する。
任意の見出し・本文・操作ラベルを消す権限ではなく、画像での合格を意味しない。
bindingの representation_kind は control_artwork も許可し、省略時は従来の wordmark です。
実画像レビューで選択済みcontrolの描画が元の正確なラベルを表すと確認し、現在の固定variant/subtreeを
照会でき、元が追加の内容単位を持たない単一ラベルcontrolの場合に限ります。元roleが other の場合は自身の
描画面を持つ単一ラベルflowが必要です。元roleに関わらず登録済み非placeholderのcontrol/selection意味分類も
必要です。名前、正方形、空のtext slot
だけでは同一性を証明できません。非表示/表示binding付き図形、追加control、文字列・variant・hash変更は
拒否します。原文はinstance metadataに保持します。成功した対応付け後、そのinstanceと対応する余剰TEXTだけを
包む生成済み with_text wrapperを外し、controlの余白の二重計上を防ぎます。無関係な元wrapper/tableは保持し、
画像証拠のない初回assembleは未検証ラベルを保持します。
認証・プロジェクトスコープ付き
POST /organizations/{organization_id}/projects/{project_id}/wf2des/{id}/render-review
は attempt、baseSpecHash、outputNodeId、outputPngBase64、renderManifest、
materializerReport、任意の parentReviewId を受け取り、{reviewId} を返す。
PG ジョブの ID・組織・プロジェクトと結果の attempt/hash を検証してから画像を保存する。
PNG は 4 MiB・一辺8000・3200万画素、manifest は512 KiB・5000ノードを上限とする。
保存先は {org}/{project}/wf2des/render-reviews/{encoded-job}/{attempt}/{reviewId}/。
manifest は出力ノードID、親子ID・layer_path・名前・型・bounds・テキスト、実体化診断を含む。
キューイベントは event_type="render_review"、tenant IDs、event_run_id、wf2des_id、
attempt、base_spec_hash、output_node_id、render_url/hash、manifest_url/hash、
任意の parent_review_id を持つ。ID は rr- と canonical JSON
[org,project,job,attempt,baseHash,renderHash,parentOrNull,outputNodeId,manifestHash] の SHA-256。
ワーカーもスコープ、ハッシュ、PNG 構造、manifest の根付きツリーを再検証する。
コンポーネント詳細ツールは固定されたスナップショットのみをハッシュ検証して読む。
既存のイベント状態ポーリングで review_id、wf2des_id、attempt、parent_review_id、
base_spec_hash、output_node_id、verification_only、verdict、findings を返す。
verdict は pass|needs_changes|cannot_verify。初回のみ candidate_spec/hash(最大1 MiB)、
assembly_state_url/hash を返せる。親は同じジョブ・attempt の初回修正である必要があり、
2回目は検証専用で追加候補を生成しない。元フレームを保持して修正版を隣に配置する。
修正仕様だけでは合格にせず、実画像の合格後にのみ学習用キャプチャを送る。
失敗・期限切れ・古い attempt・入力不足は未検証として表示する。
実描画 review は出典の content・機能・state と pin 済み部品/rule を検証し、wireframe の画素・装飾構造の
一致を要求しません。native compound は証明済み navigation・legal copy を再配置できますが、独自 control、
文字欠落・誤 state・重複・未要求の機能追加は検出します。既存 screen_plan.covered_sections に決定的な
navigation 所有関係も記録し、差分 correction で所有 component/variant/可視 property が変わる・消える場合は
coverage を再検証し、証明がなければ source navigation を復元します。旧 artifact は pin 済み source と選択から
所有関係を復元し、無関係な annotation drop は変更しません。新 field・DB migration は不要です。
finding は既存の任意 node_id に加え、affected_node_ids: string[] を持てる
(省略時は空、最大64件)。固定された元ツリーの明示IDのみを用い、出力ノードIDや文章から推測した
IDは使わない。warning/error の既知かつroot以外のIDの和集合だけが、既存の祖先・子孫・反復兄弟の
限定的な修正範囲を与える。不明IDや画面rootは範囲を広げない。検証では有効な全対象IDを確認し、
元から複数領域にあった問題を新たな回帰と誤判定しない。一方、無関係な領域も含むfindingは回帰として
検出する。従来の単一IDと対象なし診断のカテゴリ比較を維持する。この追加レビューartifact項目は
出典・レジストリの変更を許可しない。
元の生成結果・決定台帳を上書きせず、PG Webhook も発行しない。
イベントは組織・プロジェクトとランダムトークンで保護した1時間のリースを使う。
完了した重複は再課金せず、処理中の重複は再配信し、古い所有者の書き込みは拒否する。
状態TTLは6時間、結果とassemblyは内容ハッシュ付きアーティファクトに保存する。
ドキュメントのスキーマは 1.4。追加変更のみで破壊的マイグレーションは不要。
共有仕様に text の font_family、line_height、line_height_pct、max_lines と、
instance の component_properties(この処理では既知の BOOLEAN のみ)を追加する。
instance の任意フィールドcomponent_file_keyとspecのsource_file_keyで、Figma node IDのファイル範囲を
明示する。登録部品は部品ファイル、出典の部品・画像は固定されたwireframeファイルを使用する。出典不明や
別ファイルの同一node IDを偶然の一致でcloneせず、公開部品キーを別ファイル解決に使う。旧データで任意項目が
ない場合はシリアライズにも追加しないが、出典を証明できない旧参照は明示的に失敗させる。
ローカル開発でfigma.fileKeyが取得できない場合は、既存の利用者入力による「現在のファイルキー」を明示的な
申告として使用し、specの出典キーから現在のファイルを推測しない。APIキーを取得できる場合は優先し、不一致の申告は拒否する。
wireframe fallbackはlineage_wf_node_idsの出典instanceを出典ファイル内で解決してからmain componentを参照する。
masterのnode IDは別ライブラリに属する可能性があるため、出典ファイル内のローカルIDとして検索しない。
実体化診断は name_fallback|ordinal_fallback|build_error|font_fallback|prop_rejected|unmatched|preserved。
スタイル参照がなくても描画済みの直接値を保持できた場合は preserved とし、描画失敗と区別する。
このページは、apps/wf2des/(guinness-ai-v2)に実装された wf2des ワーカーの 公式 I/O 契約 です。この単一ワーカーが担う generation とすべての discriminated wf2des-events message を対象とします。フィールド、形状、ステータス値、Webhook の効果、storage、失敗モードのいかなる変更も契約変更にあたり、コードをマージする前に、必ずこのページと test-case.ja.md に反映してください。DocumentDB ドキュメントの形状を変更したときは、schemas/ の該当コレクションの index_schema_version をバンプしてください。
読み順: 概要 → 共有コントラクト基盤 → generation/internal-event セクション(Generation — Parse フェーズ → Generation — Assemble フェーズ → wf_parse → rule_process → component_sweep)→ エラーハンドリング → 冪等性とフェンシング。末尾近くの フィールド参照 はルックアップ用です。
概要
flowchart LR
subgraph BE["guinness-backend (product control plane — PostgreSQL)"]
api["Backend wf2des API<br/>POST/GET/confirm/cancel/<br/>placement/feedback + frame-reg"]
RDB[("PostgreSQL<br/>wf2des row (status/phase/attempt)")]
DREG[("platform design table<br/>type=component — SHARED registry<br/>(consumed, not a wf2des table)")]
WH["POST /v1/webhooks/ai-status<br/>(X-API-Key) — generation flip<br/>+ component-registry apply"]
end
Q[("SQS wf2des generation queue<br/>(backend-owned)")]
SQSE[("SQS wf2des-events intake queue<br/>(AI-owned — backend send-only)")]
EB["EventBridge schedule<br/>(AI-owned) — component resync sweeps"]
W["apps/wf2des worker<br/>parse · assemble · wf_parse ·<br/>rule_process · component_sweep ·<br/>component_capture · render_review"]
DDB[("DocumentDB guinness_v2<br/>6 collections: wireframe · design_rule ·<br/>design_component · project_figma_file ·<br/>design_generation_result ·<br/>design_resolution (decision ledger)")]
S3[("S3<br/>snapshots · diffs ·<br/>{org}/{proj}/wf2des/{id}-{ts}-{result|failed}.json + manifest")]
api -->|"INSERT wf2des row status='0' phase=parse (row FIRST)"| RDB
api -->|"SendMessage parse/assemble {wf2des_id, attempt, nonce, input URLs, snapshot scope+hash}"| Q
api -->|"SendMessage thin event {event_type, refs, URLs}<br/>(rule · frame-reg → wf_parse · resync → component_sweep)"| SQSE
Q --> W
SQSE --> W
EB -->|"invoke (resync sweep)"| W
W -->|"UPSERT/LWW context docs + design_generation_result (fenced on attempt)"| DDB
W -->|"PutObject result/failed artifact + manifest"| S3
W -->|"POST /v1/webhooks/ai-status {job_id, attempt, nonce, status, error?, result_manifest_url?} — GENERATION only (raises on failure)"| WH
W -->|"component_sweep: sweep manifest (discovered type=component)"| WH
WH -->|"attempt-guarded flip: wf2des status/phase + result refs + flag_count (one PG txn)"| RDB
WH -->|"UPSERT design rows type=component (from component_sweep manifest)"| DREG
バックエンドの 3 つのリカバリ sweep は未実装です
本ドキュメントは awaiting-confirm タイムアウト sweep、stuck-'0' sweep、
stuck-generation sweep(ターミナルマニフェストのリプレイ)を記述していますが、
3 つとも現在のバックエンドには存在しません — スケジュールジョブも EventBridge
ターゲットもエンドポイントもなく、ターミナルマニフェストを求めて S3 を走査する処理も
ありません。以下の記述は稼働中の挙動ではなく要件です。実装されるまで、Webhook の
取りこぼしやエンキュー失敗は行を非終端のまま残し、自動復旧はありません。
| 項目 | 値 |
|---|---|
| ワーカー | guinness-ai-v2 の apps/wf2des(Python 3.12 Lambda コンテナイメージ。handler/service/schemas/repo レシピ。ReportBatchItemFailures による部分バッチ SQS) |
| Processing path | generation(parse + assemble フェーズ)+ 6 種の wf2des-events: frame_registration(→ wf_parse)、rule_upload(→ rule_process)、resync(→ component_sweep)、component_upload(→ スコープ付き component_sweep)、component_capture、render_review |
| トリガー | バックエンド所有の generation SQS キュー(parse / assemble)。AI 所有の wf2des-events 取り込みキュー(rule / plugin イベント)。AI 所有の EventBridge スケジュール(component resync のみ) |
| 読み込み | DocumentDB guinness_v2(6 コレクション)+ S3(snapshot、config)+ Admin 内部 token provider + Figma REST(サービスアカウントの OAuth アプリ許諾(Authorization: Bearer、figma_pat はフォールバック) — sweep、rule_process のボードノード読み込み / スタイルトークン / 画像レンダリング、snapshot / memo フォールバック) |
| 書き込み | DocumentDB ドキュメント + S3 アーティファクト。generation は加えて S3 結果マニフェストを発行 |
| RDB 書き込み | なし — ワーカーは PostgreSQL に 絶対に 書き込まず、PG 認証情報を保持しません。あらゆる PG 効果はバックエンド側で、ai-status Webhook ハンドラ(完了時の唯一の PG 書き込み者)または confirm/placement/feedback エンドポイントを通じて適用されます |
| Webhook | POST /v1/webhooks/ai-status(X-API-Key)— generation のみ。wf_parse / rule_process は Webhook を持ちません。component_sweep は ai-status Webhook ではなく、別個の発見コンポーネント sweep マニフェストを発行します |
| LLM 呼び出し | ステップが明示的にそう述べる箇所のみ。それ以外はすべて決定的です。Parse/wf_parse: role + intent(fast tier)。Assemble: K サンプルの自己整合投票としてのセクションマッチング(strong tier、vision 対応)+ 画面全体のプランニング。ルール取り込み(rule_process): comprehension/extraction/consolidation。レジストリ enrichment と render review はそれぞれ設定されたモデルを使用。REST sweep(component_sweep)と component-capture マージは決定的なまま — LLM なし。投票に対する決定論的ガードと confidence 数式は LLM フリー |
| テナンシー | すべての読み込みと書き込みが organization_id + project_id でフィルタリングされます。 DEFAULT_ORGANIZATION_ID=1(現時点でシングルテナント)。プロジェクト横断の読み込みは意図的な将来判断であり、デフォルトにはなりません |
| DocumentDB | guinness_v2(AWS DocumentDB 5.0)、共有プラットフォーム DB。6 つの wf2des コレクションはプレフィックスなしの対等な存在。コレクション名は環境変数で上書き可能。index_schema_version はコレクションごと |
実行マトリクス — この 1 つのワーカーが担うすべての経路。wf2des_id = 実行 id = wf2des PostgreSQL 行 id(generation のみ)。internal event は product row を持たず、tracked event は event_run_id と wf2des_event_status を使います。
| 実行 | 起動元 | 消費対象 | DocDB 書き込み | S3 書き込み | PG 効果 |
|---|---|---|---|---|---|
| Generation — parse | バックエンド所有の generation parse SQS キュー(row-first-then-SQS。auto-confirm は同一呼び出し内で assemble を連鎖) | Parse メッセージ(wf2des_id、attempt、nonce、snapshot scope + wf_content_hash、事前収集済み URL) |
wireframe キャッシュドキュメント(LWW)+ design_generation_result の inputs+parse ブロック((_id, attempt) に対する CAS) |
parse.json(イミュータブル) |
ai-status ハンドラ経由の wf2des.phase → '1' awaiting_confirm(インタラクティブのみ。auto-confirm ではなし) |
| Generation — assemble | バックエンド confirm エンドポイント(awaiting_confirm + attempt に対する CAS)または auto-confirm 時に同一呼び出し内で連鎖 |
Assemble メッセージ(wf2des_id、attempt、新規 nonce、confirm 判断)。pin は結果ドキュメントの inputs から 再水和 |
design_generation_result の spec/spec_nodes_flat/selection/validator_report/confidence/placement.design_area((_id, attempt) に対する CAS)+ design_resolution レジャー upsert(スコアラチェットフェンス) |
…-result.json(または …-failed.json)+ 任意の …-spec.json スピル + 結果マニフェスト |
ai-status ハンドラ経由で wf2des 行 → status '1' completed + 結果参照 + flag_count、phase クリア(1 つの PG トランザクション) |
wf_parse |
wf2des-events 取り込みキュー — plugin frame-registration イベント |
登録イベント(figma_file_key、node_id、バックエンドがキャプチャした snapshot URL) |
wireframe キャッシュドキュメント(LWW) |
自身のものはなし | PG フリー |
rule_process |
wf2des-events 取り込みキュー — rule-upload イベント |
Rule-upload イベント(design_rule_id、figma_file_key、board_node_ids[])。選択されたガイドラインボードを Figma REST(PAT)で読み込み |
design_rule イミュータブルなリビジョンドキュメント — マージ済みルール + boards[] + 抽出来歴(マージ済み RuleSet の (design_rule_id, content_hash) ごとに冪等) |
ボードレンダリングを wf2design/rule-boards/{figma_file_key}/{node_id}.{sha256}.png に |
PG フリー |
component_sweep |
EventBridge スケジュール(AI 所有)または wf2des-events plugin resync イベント |
スケジュール呼び出し(ボディなし)またはシンな resync イベント。自身で Figma REST を巡回 | design_component コンテキストドキュメント(_id に対する upsert + single-flight)+ project_figma_file のフィールド単位更新 |
コンテンツアドレス指定のコンポーネント snapshot + 発見コンポーネント sweep マニフェスト | バックエンドがマニフェストからプラットフォーム design 行 type=component を UPSERT(レジストリであり、wf2des 行の反転ではない) |
component_upload |
wf2des-events — 選択ボードのアップロード |
選択されたボード id + ファイルキー + 対象レジストリ | スコープ付き component sweep + 任意のレジストリ enrichment | capture/snapshot は S3 にスピルする場合あり | PG フリー(通常のバックエンドによるレジストリマニフェスト適用を除く) |
component_capture |
wf2des-events — plugin が観測した capture |
インラインの capture、またはちょうど 1 つの captures_url スピル |
design/wireframe コンポーネントレジストリへの単調なフィールド単位マージ | 指定時はスピルを読み込み | PG フリー |
render_review |
wf2des-events — plugin の actual-render エビデンス |
ハッシュフェンス付き PNG + render マニフェスト、attempt/spec/output の同一性、任意の親 review | wf2des_event_status の lease/result。元の generation は書き換えない |
review/候補 assembly アーティファクト | PG フリー。検証済み候補を昇格させるのは plugin のみ |
ステータス / フェーズ enum(wf2des PG 行 — generation のみ)。status: '0' processing · '1' completed · '2' failed · '3' rejected(デザイナーが parse を却下。failed ではない)· '4' cancelled。phase は status '0' の 内側 で名前空間化され、status コードとは独立です: '0' parse · '1' awaiting_confirm · '2' assemble — 行がターミナルステータスに達したらクリア(NULL)されます。内部実行はいずれも持ちません。
共有コントラクト基盤
ここで一度だけ述べ、各実行セクションは繰り返さずにこれらを参照します。
取り込みキュー — wf2des-events(AI 所有)
AI 所有の wf2des-events キューは、シンなイベントメッセージ {event_type, ...refs, pre-collected S3 URLs} の(event_type による)discriminated union です: frame_registration → wf_parse、rule_upload → rule_process、resync → component_sweep、component_upload → スコープ付き component_sweep、component_capture、render_review。バックエンドは SQS 送信権限のみ を保持します(AI 所有インフラに対する唯一の送信権)。ワーカーが各イベントを直接処理し、event_run_id をキーとする TTL 付き wf2des_event_status レコードを所有します。scheduled resync はその id を省略でき、untracked です。
Feedback はイベントを発行しません。Generation は バックエンド所有 の generation SQS キュー(parse / assemble)によって駆動され、wf2des-events や EventBridge では決して駆動されません。internal event は wf2des 行を持たず、永続的な実行台帳も持ちません — TTL 付きステータスレコードを除けば、出力ドキュメントの存在 + 鮮度が実行ステータスそのものです。
ai-status Webhook エンベロープ + 認証(generation のみ)
generation のみが Webhook を発行します。ワーカーは次のように POST します:
POST /v1/webhooks/ai-status
Content-Type: application/json
Accept: application/json
X-API-Key: <shared-service-api-key>
ボディは type に対する判別可能ユニオンです — プラットフォーム ai-status ユニオン上の wf2des generation の type。generation エンベロープ:
{
"job_id": "<wf2des_id>", // = 実行 id = wf2des PG 行 id
"attempt": 1, // フェンシングのペアメンバー(SQS メッセージからエコー)
"nonce": "<per-send token>", // (job_id, attempt, nonce) 重複排除キーの一部
"status": "…", // parse-done → phase。succeeded/failed → ターミナル行反転
"error": { "message": "…" }, // 失敗時のみ存在
"result_manifest_url": "…", // parse-done では不在。succeeded/failed でのマニフェストポインタ(ハンドラの行効果入力)
"manifest_schema_version": 1
}
認証マトリクス(関連するサーフェス): worker → webhook = X-API-Key(Secrets Manager、タイミングセーフ比較)。backend → wf2des-events = SQS 送信権限。plugin → internal-api wf2des ルート = セッション発行のデータプレーントークン {org_id, project_id, exp}(read+write)。backend/MCP → internal-api = 共有 X-AI-Service-Token(read ルートのみ)。worker → Admin token provider = IAM 保護の Lambda Function URL + X-AI-Service-Token。サービスアカウント Figma REST = provider が発行した Authorization: Bearer を使用し、デプロイ時のフォールバックとしてワーカーの figma_pat シークレットを使用します。
ai-status ハンドラは 完了時の唯一の PG 書き込み者 です: (job_id, attempt, nonce) で重複排除し、マニフェスト自身の job_id/job_type/S3 パスプレフィックスをペイロードに対して検証し(エンベロープの判別子 type = wf2des は、マニフェストの job_type = generation(実行タイプ)とは別物です)、マニフェスト読み込み失敗時は 5xx を返し、attempt ガード付きの wf2des 反転を 1 つの PostgreSQL トランザクションで適用します。wf_parse / rule_process はこれに到達することは決してありません。
S3 キー規約
| 目的 | キー | 生成元 |
|---|---|---|
| コンテンツアドレス指定 snapshot(ワーカーがキャプチャした全ソース) | wf2design/snapshots/{figma_file_key}/{node_id}.{content_hash}.json |
component_sweep(コンポーネントノード)。backend / trigger snapshot キャプチャ(WF フレーム) |
| クライアント向け結果アーティファクト(generation) | {org}/{proj}/wf2des/{wf2des_id}-{ts}-result.json(または …-failed.json) |
Generation — assemble |
| Generation 結果マニフェスト(Webhook 行効果入力) | {org}/{proj}/wf2des/{wf2des_id}-{ts}-manifest.json(または …-failed-manifest.json) |
Generation — assemble |
| 中間アーティファクト(内部 wf2des プレフィックス) | {org}/{proj}/wf2des/{wf2des_id}-{ts}-{spec\|parse\|feedback}.json |
Generation(parse.json、spec.json スピル、feedback diff) |
| ガイドラインボードレンダリング(ルール取り込み) | wf2design/rule-boards/{figma_file_key}/{node_id}.{sha256}.png |
rule_process(Figma images API レンダリング、PNG scale 2。design_rule.boards[].image_url から参照) |
| プロジェクト config ホーム | {org}/{proj}/wf2des/config.json(project_figma_file.config_url から参照) |
Curation / plugin |
アーティファクトキーの例: 1/3/wf2des/8f14e4…-20260706T093000Z-result.json。結果アーティファクトは wf2des-api の presign またはバックエンド自身のロールを介してのみ読み込み可能です。
PostgreSQL 分離(プラットフォームの厳格ルール)
ワーカーは PostgreSQL に絶対に書き込まず、PG 認証情報を保持しません。ワーカーは自身の DocDB コミットを (wf2des_id, attempt) でフェンシングし、両方を Webhook 経由で報告します。バックエンドが attempt ガード付きで行を進めます。このドキュメント内のあらゆる PG 効果はバックエンド側で適用されます — ai-status Webhook ハンドラ(generation 完了 / component-registry 適用)または confirm / placement / feedback エンドポイントによって — ワーカー自身によっては決して行われません。
Generation — Parse フェーズ
wireframe フレームを構造化された parse アーティファクトに抽出し、実行を confirm チェックポイントで待機させる(インタラクティブ)か、そのまま assemble に連鎖させます(auto-confirm)。ワンショット Lambda 呼び出しで、プラットフォームの部分バッチ SQS ハンドラ(ReportBatchItemFailures。service.py:process_record、契約サーフェスとしての schemas/)を介して消費されます。
消費 / 入力
バックエンドが発行する generation SQS ペイロード。バックエンドは まず wf2des PG 行を書き込み(status '0'、phase parse)、トリガー時点の WF snapshot をキャプチャし、その後 エンキューします。インタラクティブ parse = 同じエンベロープで auto_confirm=false。SQS メッセージがワーカーの PostgreSQL への唯一の窓です — ワーカーは PG を決してクエリしません(存在 / テナンシーは送信前にバックエンド側で解決済み)。
| フィールド | 型 | 必須 | 検証 | 備考 |
|---|---|---|---|---|
wf2des_id |
string (uuid) | ✓ | = wf2des PG 行 id。Webhook では job_id としてエコー |
実行アイデンティティ / フェンシングペアメンバー |
attempt |
integer | ✓ | 単調増加。DocDB コミットをフェンス((_id, attempt) に対する CAS) |
現状は 1 固定。これをバンプするものは存在しない |
nonce |
string | ✓ | エンキューごとに新規。(job_id, attempt, nonce) 重複排除キーの一部 |
|
wireframe_img_url |
string (s3://) |
✓ | バックエンドが事前収集 | トリガー時点の WF 画像 snapshot |
wireframe_json_url |
string (s3://) |
✓ | 事前収集。トリガー時点の WF snapshot(フレーム + 包含する board section、memo を含む) | 抽出用の S3 ダウンロードソース |
snapshot_scope |
object {frame_node_id, section_node_id} |
✓ | フレーム + それを包含する board section | |
wf_content_hash |
string (sha256) | ✓ | snapshot キャプチャ時に計算。parse キャッシュキー | ハッシュ不変時に LLM をショートサーキット |
structure_file_url |
string (s3://) |
✗ | pin 用 | |
detail_design_file_url |
string (s3://) |
✗ | pin 用 | |
design_file_url |
string (s3://) |
✗ | pin 用 | |
screen_id |
string | ✓ | generation 行は常にこれを持つ(リクエストから) | |
prompt |
string | null | ✗ | 選択入力。memo intent より下位でランク付け | |
placement_target |
string (node_id) | null | ✗ | 結果ドキュメントの request ブロックにエコー | |
auto_confirm |
boolean | ✓ | false 通常 / true auto → parse+assemble を 1 回の呼び出しで |
{
"wf2des_id": "…", "attempt": 1,
"nonce": "…",
"wireframe_img_url": "s3://…",
"wireframe_json_url": "s3://…",
"snapshot_scope": { "frame_node_id": "…", "section_node_id": "…" },
"wf_content_hash": "…",
"structure_file_url": "s3://…",
"detail_design_file_url": "s3://…",
"design_file_url": "s3://…",
"screen_id": "…", "prompt": "…", "placement_target": "…", "auto_confirm": false
}
読み込み。(1)wireframe_json_url を介した S3 からのトリガー時点 WF snapshot(フレーム + 包含する board section、memo ノードを含む)— parse キャッシュ ミス時のみ ダウンロード。(2)parse キャッシュ: wireframe コレクションにおける複合 _id = {project_id}_{figma_file_key}_{node_id} による主キールックアップ。wf_content_hash と照合。(3)キャッシュヒット時: 前回実行の parse アーティファクトをコピー(LLM なし、S3 ダウンロードなし)。memo の注意点: snapshot に memo ノードが欠けている場合はサービスアカウント Figma REST フォールバック。(4)ルール pin 用に、プロジェクトの 最新 design_rule リビジョンを design_rule コレクションから読み込み(repo.find_latest_design_rule: 最大の version、同点なら最新の lineage.processed_at)— リビジョンなし ⇒ pin は None(validator の no_rules 経路)。旧 design_rule_file_url メッセージフィールドは削除済み — バックエンドが送り続けても未知フィールドとして無害に無視されます。任意の事前収集済み structure/detail_design/design ファイル URL は引き続き pin に利用可能。(5)config.json(project_figma_file.config_url 経由)から memo-status マーカーとフレーム名バリエーション区切り文字を取得 — 不在 ⇒ プラットフォームデフォルト、存在するが壊れている場合 ⇒ run は大きく失敗。すべての読み込みが organization_id + project_id でフィルタリングされます。
処理契約
- メッセージ受信(
wf2des_id+attempt+nonce)。 - 入力を PIN して結果ドキュメントの
inputsブロックへ — 呼び出しの最初の行為(pin モーメント)。parse 呼び出しは parse 入力(wireframe アイデンティティ /source_hash)を pin します。design_rulepin = プロジェクトの 最新design_ruleリビジョン(repo.find_latest_design_rule: 最大のversion、同点なら最新のlineage.processed_at)— リビジョンなし ⇒ pin はNone→ validator のno_rules経路。インラインのルールファイルは決して使いません。 - parse キャッシュチェック: 複合
_idによる主キールックアップ、同一wf_content_hash。却下後のリトライではキャッシュを スキップ します(新規 parse がその目的)。 - キャッシュ HIT → 前回の parse アーティファクトをこのジョブ自身の
parse.jsonにコピー — LLM 呼び出しなし。ステップ 8 へジャンプ。キャッシュ MISS → S3 から WF snapshot をダウンロード(memo ノードを含む、board-section スコープ)。 - 決定的 WFNode 抽出: 可視の FRAME / GROUP / INSTANCE / TEXT の各子孫 + 画像塗りの矩形 → WFNode。vector/shape の葉は親に折りたたむ。隠しレイヤーはスキップ。ツリー形状は決定的です。
- 決定的 memo ステータス: 取込済フレーム内部の memo ⇒ resolved。resolved の memo はライブ intent から除外。
screen_id(リクエストから)とvariation_label(決定的なフレーム名分割)は決定的 — LLM ではありません。 - LLM(fast tier) — ノードの
role(共有の要素タイプ語彙から — WFNode role 値の閉じた集合。prompts.pyのROLE_VOCABULARYに固定)+intent(OPEN memo + variant ラベルから)のみを割り当てます。ツリー形状は 決して 割り当てません。低温度。これは parse における 唯一の LLM ステップです。 wireframeキャッシュドキュメントを書き込む((source_hash, processed_at)に対する LWW フェンス)。- 結果ドキュメントの
parseブロック + イミュータブルなparse.jsonアーティファクトを書き込む((_id, attempt)に対する CAS)。 - Webhook: parse done(失敗時に raise)。
auto_confirmで分岐:false→ バックエンドがwf2des.phase = awaiting_confirmに反転、plugin が wf2des-api 経由でプレビュー。true→ assemble が同一呼び出し内で連続実行。
flowchart TD
A["message received<br/>(_id + attempt + nonce)"] --> P0["PIN inputs into the result doc<br/>(first act of the invocation)"]
P0 --> C{"parse cache hit?<br/>primary-key lookup by composite _id,<br/>same content hash"}
C -->|"hit (skipped on retry-after-reject)"| R["copy prior parse artifacts —<br/>no LLM call"]
C -->|miss| D["download WF snapshot from S3<br/>(memo nodes included: board-section scope)"]
D --> E["DETERMINISTIC WFNode extraction<br/>visible FRAME / GROUP / INSTANCE / TEXT<br/>+ image-fill rects; vectors collapse; hidden skipped"]
E --> F["memo statuses — resolved excluded<br/>from live intent (deterministic)"]
F --> G["LLM (fast tier): roles + intent ONLY<br/>never the tree shape"]
G --> I["write wireframe cache doc<br/>(LWW fence: source_hash + processed_at)"]
R --> J
I --> J["result doc parse block +<br/>immutable parse.json artifact<br/>(CAS on _id + attempt)"]
J --> K["webhook: parse done<br/>(raises on failure)"]
K --> L{"auto_confirm?"}
L -->|no| M["backend flips phase = awaiting_confirm<br/>plugin previews via wf2des-api"]
L -->|yes| N["assemble runs back-to-back<br/>in the same invocation"]
発行 / 出力
DocDB 書き込み (A): wireframe キャッシュドキュメント — (source_hash, processed_at) に対する LWW フェンス。_id = {project_id}_{figma_file_key}_{node_id}。
| フィールド | 型 | 入手元 |
|---|---|---|
_id |
string | 複合 {project_id}_{figma_file_key}_{node_id}。figma_file_key/node_id は split('_', 2) で導出 — 別途保存しない。別個の wireframe_id なし |
organization_id, project_id |
integer | テナンシースコープ |
name |
string | 完全な Figma フレーム名(日本語は PG frame_name の 100 を超える場合あり) |
type |
number | 0 page · 1 component(platform wireframe コレクションの形状) |
based_on |
number | 0 imported · 1 design · 2 wireframe |
screen_id |
string | null | generation 時のリクエストから(NULLABLE) |
variation_label |
string | 決定的な名前分割 |
structure |
object | WFNode ツリー { node_id, name, figma_type, role (LLM), bbox {x,y,w,h}, intent (string\|null, LLM), text?, children[], component_id?, component_variants?(ワイヤーフレームインスタンス **自身** の componentId + バリアント値 — **保存** される。フォールバック/キャリーオーバーのソース), layout_mode, item_spacing, padding[4], primary_align, counter_align, layout_wrap, counter_gap(auto-layout キャプチャ), fill, corner_radius, font_size, font_weight, text_color(スタイリングキャプチャ。LINE/薄い RECT のディバイダー形状はリーフとして保持) } — role/intent 以外はすべて決定的 |
memos |
object[] | { memo_node_id, text, status }(status は決定的: 解決マーカーを持つコンテナ内なら resolved) |
board_regions |
object[] | 理解済みボード。領域ごとに {node_id, name, figma_type, x, y, features, marker_hit, resolved_marker_hit, is_registered_frame, text_sample[], role, refers_to}。ボードのレイアウトは固定ではない(ワイヤーフレームのみ / 注釈やメモを伴う)ため、領域に分割して 1 つずつ分類する。memos はここから導出される — intent はボード上のあらゆる文字列ではなく、デザイナーがその画面について注記として書いたものから取る |
summaries |
object | { element_type_histogram, node_count }(計算値) |
lineage |
object | { source_url (RAW REST snapshot), source_hash (≡ inputs.wireframe.source_hash), processor_version (例: wf_parse@1.2), index_schema_version, processed_at, job_id } |
DocDB 書き込み (B): design_generation_result ドキュメント — inputs + parse ブロック — (_id, attempt) に対する CAS。_id = wf2des 行 id。
| フィールド | 型 | 入手元 |
|---|---|---|
inputs |
object | pin 済み(最初の行為): wireframe {figma_file_key, node_id, source_hash}。design_rule {design_rule_id, content_hash}(parse 時点のプロジェクト最新リビジョン。リビジョンなし ⇒ pin は None)。components [{platform_design_id, content_hash}]。llm {model_id, prompt_version, screen_review_model_id, screen_review_reasoning_effort, screen_review_enabled} |
parse |
object | { confirmed (bool, インタラクティブでは false), confirmed_by (null), confirmed_at (null — 確認タイムスタンプの唯一の置き場所), rejected (null。却下時は構造化 {reason_code: wrong_roles|wrong_memos|wrong_sections|other, note}), memo_influences: [{memo_node_id, text}] (TEXT スナップショット済み — wireframe 再 parse を生き延びる) } |
attempt |
integer | エコー |
organization_id, project_id |
integer | テナンシースコープ |
screen_id |
string | null | エコー |
request |
object | {screen_id, prompt, placement_target, auto_confirm} エコー |
artifact_urls |
object | {parse} — この実行のイミュータブルな parse.json ポインタ(正式な parse) |
timings |
object | {created_at (実行 START アンカー。lineage.processed_at とは別), parse_done_at, <phase>_ms} — parse は自身のフェーズ所要時間(snapshot_ms、parse_ms)もスタンプします |
lineage |
object | wireframe snapshot を指す |
この実行が parse した完全な WFNode ツリー = artifact_urls.parse(イミュータブル)。wireframe コレクションのドキュメントは後続の再 parse で上書きされる可能性があるためです。design_rule pin は常に rule_process が書き込んだ既存のイミュータブルなリビジョンを参照します — generation ワーカーがルールをインラインで parse することはありません(その経路は削除されました)。
S3。 parse.json — 使用されたイミュータブルな完全 parse WFNode ツリー + memo。内部 wf2des プレフィックス。artifact_urls.parse で {org}/{proj}/wf2des/{wf2des_id}-{ts}-parse.json として参照(例: 1/3/wf2des/8f14e4…-20260706T093000Z-parse.json)。これは実行が使用した正式な parse です — プレビューおよび memo レビューのサーフェスは、結果ドキュメント + このアーティファクトを読み込み、上書き可能な wireframe キャッシュドキュメントは 決して 読み込みません。
Webhook — parse done。 POST /v1/webhooks/ai-status、X-API-Key。エンベロープは 共有コントラクト基盤 の通り、status: "parse_done" 付き:
{
"type": "wf2des", // プラットフォーム ai-status ユニオン上の判別子
"job_id": "<wf2des_id>", // = wf2des PG 行 id
"attempt": 1,
"nonce": "<per-send token>",
"manifest_schema_version": 1,
"status": "parse_done" // phase シグナル → ハンドラが wf2des.phase = awaiting_confirm に設定。parse-done ではマニフェストなし(ターミナル行反転なし)
}
失敗時に raise するので SQS が再配送します。効果: ハンドラが wf2des 行を phase = awaiting_confirm で待機させます(attempt ガード付き、(job_id, attempt, nonce) で重複排除。行は status '0' のまま)。auto_confirm 経路では parse-done の待機は なく、assemble が同一呼び出し内で実行され、succeeded Webhook のみが発火します。 すでに terminal に到達した attempt の重複配送では、保存済みの結果ドキュメントから terminal のマニフェスト + Webhook を再発行します(LLM は決して再実行しない)— 結果書き込みと Webhook の間のクラッシュウィンドウを閉じます。バックエンドの (job_id, attempt, nonce) 重複排除 + attempt ガードにより、純粋な重複送信は no-op です。 fail-closed な parse 失敗(解決不能な pin、テナンシースコープ不一致 — 再配送で決して成功しない ContractFailure)は、DLQ に流れて行を status '0' のまま放置する代わりに、assemble 失敗とまったく同じように terminal な …-failed.json + failed マニフェスト + failed Webhook(status: "failed" + error → 行は status '2' に反転、phase クリア)を発行します。transient な parse 失敗(snapshot GET、fast-tier LLM、一時的 I/O)は従来どおり raise して SQS が再配送し、terminal サーフェスは発行されません。
PG 効果。 ai-status Webhook ハンドラ経由の wf2des.phase → '1' awaiting_confirm(attempt ガード付き、1 つの PG トランザクション)。待機中、行は status '0' processing のまま。phase が '0' 内での実行位置を特定します。plugin はその後 GET をポーリングし(行 '0'、phase=awaiting_confirm)、wf2des-api 経由でプレビューします。インタラクティブ confirm は 別個の バックエンドエンドポイントです(phase awaiting_confirm + attempt に対する CAS → phase '2' assemble → assemble メッセージをエンキュー)。ワーカーは PG に決して書き込みません。auto_confirm では phase 反転はなく、parse が直接 assemble に連鎖します。
Generation — Assemble フェーズ
セクションごとにコンポーネントを選択し、決定的な spec を縫合し、ルールに対して検証し、confidence を計算し、ターミナルな結果アーティファクト + マニフェストを生成します。ワンショット Lambda 呼び出し(pin は結果ドキュメントから再水和)。同じプラットフォーム部分バッチ SQS ハンドラのレシピ。
消費 / 入力
バックエンド confirm エンドポイントによってエンキュー(インタラクティブ: wf2des 行の phase awaiting_confirm + attempt 一致に対する CAS → phase '2' assemble → エンキュー)または auto_confirm 経路で同一 parse 呼び出し内に連続して連鎖。メッセージは wf2des_id + attempt + 新規 nonce + confirm 判断を運びます — それ以外はすべて結果ドキュメントの inputs ブロックから 再水和 されます。
| フィールド | 型 | 必須 | 検証 | 備考 |
|---|---|---|---|---|
wf2des_id |
string (uuid) | ✓ | = wf2des PG 行 id。job_id としてエコー |
実行アイデンティティ |
attempt |
integer | ✓ | (_id, attempt) に対する DocDB CAS をフェンス |
|
nonce |
string | ✓ | エンキューごとに新規。Webhook 重複排除 (job_id, attempt, nonce) の一部 |
|
confirm 判断 attempt |
integer | ✓ | エンキュー前に バックエンド が CAS チェック(confirm ペイロード {attempt, parse_artifact_hash})。不一致で 409、再送で 200 エコー |
|
confirm 判断 parse_artifact_hash |
string | ✓ | エンキュー前にバックエンドが parse.json アーティファクトに対して CAS チェック |
{
"wf2des_id": "…", "attempt": 1,
"nonce": "…",
"confirm": { "attempt": 1, "parse_artifact_hash": "…" }
}
読み込み。(1)結果ドキュメントの inputs ブロックから pin を 再水和 — いずれかのハッシュ不一致で fail closed: wireframe {figma_file_key, node_id, source_hash}、design_rule {design_rule_id, content_hash}、components [{platform_design_id, content_hash}]、llm {model_id, prompt_version, screen_review_model_id, screen_review_reasoning_effort, screen_review_enabled} — さらに confirm 自身の parse_artifact_hash を再水和した parse.json に対して再チェック。(2)プロジェクトのコンポーネントレジストリ — inputs.components に pin されたプラットフォーム design(type=component)セット。assembly/instancing コンテキストは design_component コレクションから読み込み(_id = {project_id}_{figma_file_key}_{node_id} でキー付け): kind、component_key(nullable/local)、name、variant_properties、text_slots[{layer_path, default_text, ambiguous}]、image_slots[{layer_path, ambiguous}]、default_size。(3)pin 済みの design_rule リビジョン、(design_rule_id, content_hash) でキー付け — _rehydrate_rules は RuleSet(validator プログラム)と そのリビジョンのガイドラインボード画像の 両方 を返します。pin 済みドキュメントの欠落 ⇒ ContractFailure、fail closed。(4)project_figma_file.style_captures(導出された style_bindings マップのソース)と config.json(project_figma_file.config_url 経由)で DESIGN エリアの矩形を placement.design_area に解決。(5)pin 済みの design_rule リビジョンはガイドラインボードの画像(boards[]、S3 から読み込み)も供給し、各セクション選択呼び出しに vision リファレンスとして添付されます。ボード画像の欠落 ⇒ ContractFailure、fail closed。(6)design_resolution 決定台帳 — 構造的で LLM フリーな section_signature(sig@1:…)でキー付けされた、過去のセクション種別 → コンポーネント解決。選択の 前 に参照(後述の assemble 発行を参照)。すべての読み込みが organization_id + project_id でフィルタリングされます。registry hygiene はグローバル(重複排除・ハイジーン集約済み)で、その後、本番 default の決定的 role→kind gate が section ごとに再現可能な admissible set を構築します。選択 LLM(K サンプル投票)はその set を各候補の完全な構造でふるいにかけます。size は selection signal であり gate ではなく、similarity/vector scoring はありません。
処理契約
パイプラインは 2 つあり、現行は NODE パイプライン。
config.rule_pipeline_nodes(既定 true)がcomprehend → ロール別 extract → 決定的な権威ゲート付きマージを経路にし、レンダーではなくボードのノードツリーを読む。 実データでの A/B で選定: 色 105 対 93、トークン名の正確さ 91% 対 84%、約 1.8 倍高速、決定的、かつ捏造なし (コーパス逐語一致ガードで担保)。pin はbc@0.2(理解)、sys@0.3(システム/画面グループ)、cmp@0.3(コンポーネント仕様+スロット規則)、tok@0.2(トークン)。 以下に記す vision パスはオプトアウト用のフォールバックとして残るのみ。
- メッセージ受信。
- 結果ドキュメントの
inputsブロックから pin を 再水和。いずれかのハッシュ不一致で fail closed。再配送時は既存の pin を再利用 — ブロックが不在の場合のみ選択を再実行。 - registry set を構築:
organization_id+project_idで scope、dedup、hygiene collapse。vector/similarity scoring はなし。 - section ごとの candidate set を構築: 決定的 role→admissible-kind gate(default
CANDIDATE_KIND_GATE=true)を適用。size は selection signal で gate ではない。count はselectionに記録し、truncation は flag。無効化は controlled evaluation のみ。 - 決定台帳の参照(
design_resolution、構造的なsection_signatureでキー付け):provenance = minedまたはそのscore ≥信頼しきい値のエントリはそのセクションを ロック(LLM なし)。より弱いエントリは フロア となり(セクションを再投票し、スコアの高い判断が勝つ。決定論的ガードの 前 に適用)。instance エントリは参照時にスコープ再検証(デフォルトバリアントのエントリのみ)。古い/スコープ不整合のエントリは投票に落ちます。 - LLM — 自己整合投票つきのセクション並列選択(低温度、エビデンス pin 済み。セクションごとに K サンプル、
(case, platform_design_id)の多数決)。セクションごとの判断のみ: instance / compose / unmatched。component key。variant props。text-slot 塗り。LLM への入力: memo intent > リクエスト prompt。rules digest はアドバイザリ(validator が常に上書き)。pin 済みdesign_ruleリビジョンのガイドラインボード画像を vision プレフィックスとして添付(アドバイザリなリファレンス — ボードに合うコンポーネント/バリアントを優先。id/slot を捏造しない)。これは assemble における 唯一の LLM ステップです(下の focused バリアント解決パスを除く)— K サンプル投票は 1 つの呼び出しファミリー(strong tier は vision 対応必須)。セクションは独立 → 実時間 = 最も遅いセクション。Prompt キャッシュが静的プレフィックス(指示 + rules digest)を共有します。 - 投票に対する決定論的ガード(信頼境界 — LLM のエコーは決して信頼されない): variant props はレジストリの軸に クランプ(何かが読み込む前に)。対称的な スコープガード(セクションより著しく大きいコンポーネントは
unmatchedに降格。マルチユニットセクションを主張する小さなコンポーネントはcomposeに降格し、そのユニットが再選択)。ワイヤーフレーム名エビデンスによる重複主張の dedupe。kit-name オーバーライド(1 つの名前付き kit 要素そのものであるセクションは、その名前のレジストリコンポーネントを、kit 自身のバリアント値を引き継いで採用)。仕様不足の instance に対する集中的な variant 解決パス。screen-facts パス(共有バリアント軸を画面ごとに一度解決)。台帳のフロアは これらのガードの前 に適用 — ガードが最終決定権を保持します。 - 決定的な縫合(レイアウト保存): spec ツリー +
spec_nodes_flat+style_bindingsマップ(project_figma_file.style_capturesから生成)。同じ判断から再現可能。判断は元のワイヤーフレームツリーに再ネストされる。compose の子はレジストリに対して再選択され、ワイヤーフレームバリアントのキャリーオーバー、差別化保存(ワイヤーフレームが区別する兄弟は同一インスタンスに潰れない)、そっくりさん重複クレーム降格が適用される。無スタイル/スタイル付きセクションは単一パディングオーナーの band/card フレームにラップ。子ごとの gap リストは 発行された 子に整列。
管理用ドキュメントは自己登録されるようになった。
project_figma_fileへの書き込み(slot_roles、style_captures、components_synced_at、sweep_error、sweep マーカー)はすべて upsert なしのupdate_oneである。これはプラグインのファイル登録がドキュメント作成を担う前提だったためだが、Arch A にはそのルートが存在しない(バックエンドはGET …/figma-filesのみを公開)。結果としてドキュメントは作成されず、これらの書き込みはすべて何にもマッチせず黙って捨てられていた。実際の同期での実測では、enrich_registryが 8 通のメッセージすべてで完全な slot-role 分類を再実行し、104 回の LLM 呼び出し・入力約 91k / 出力約 69k トークンを毎回まったく同じ内容で消費していた。これは自身の過去の判定をこのドキュメントから読み戻す設計であり、ドキュメントが存在しなければその記憶が永久に空になるためである。そのためレジストリ enrichment パスはslot_rolesを読む前にensure_project_figma_fileを呼ぶ:$setOnInsertのみ、role = 1(library — その時点でこのファイルは構造上コンポーネントのソースである)。これにより、後に正式なルートで登録されたドキュメントはrole/config_urlの所有権を保持する。修正後、同一の同期は 13 回の呼び出しを 1 度だけ行い以降は 0 となり、capture 段階は約 6 分から約 1 秒へ短縮された。なお登録ファイルが 1 件もない場合、sweep のファイル一覧は空となりcomponent_sweepはwf2des.component_sweep.no_filesを記録して何も行わない — その状態でレジストリを構築していたのはプラグインの capture アップロード経路である。 9. 決定的な rules validator + リズムパス: 違反は安全な範囲で自動修正、そうでなければフラグ付け。リズムパスは gap を hygienic スペーシングスケールにスナップし(許容範囲内のみ)、内部 セクションジョイントを規定のセクションマージンにバインドする。ルール未登録 → no-op + 全 ノードにrules_unvalidatedフラグ(validator_report.status = no_rules)。 10. 計算された confidence(モデルの自己申告は決して使わない): validator 違反 + unmatched/composed カウント + slot の曖昧さ + component-fit(role 一致)。compose は instance より低く上限を設ける、が数式に組み込まれている。 11. 台帳への書き戻し:unmatchedでない全セクション(および compose-child)の判断は、決定論的なスコア とともにdesign_resolutionに upsert — ラチェット でフェンス(votedの書き込みはminedでなくスコアが厳密に低いエントリの上にのみ着地。minedはフェンスなし。フェンスされた upsert でのDuplicateKeyErrorは 保持 を意味し、エラーではない)。 12. 結果ドキュメントの書き込みspec/spec_nodes_flat/selection/validator_report/confidenceブロック((_id, attempt)に対する CAS)。ワーカーはplacement.design_areaも書き込む(config.jsonから解決)。 13. S3: イミュータブルな結果アーティファクト + 結果マニフェスト(スキーマバージョン付き)。 14. Webhook: succeeded + マニフェストキー → バックエンドがwf2desを completed + 結果参照 +flag_countに反転。
compose セクションは 常に フラグ付けされ(最も根拠が弱い)、unmatched ノードはプレースホルダになります — 静かに削除されることは決してありません。
flowchart TD
A["message received"] --> B["REHYDRATE pins from result doc inputs<br/>fail closed on any hash mismatch"]
B --> C["registry set = tenant-scoped + deduped<br/>per-section deterministic role/kind gate<br/>(no similarity; size is a signal, not a gate)"]
C --> D["per-section candidate set<br/>(counts recorded in the selection block;<br/>truncation flagged)"]
D --> L0["DECISION LEDGER consult (design_resolution)<br/>mined or score ≥ trust threshold → LOCK (no LLM)<br/>weaker entry → FLOOR (re-vote, best score wins)"]
L0 --> E["SECTION-PARALLEL selection — LLM<br/>K-sample SELF-CONSISTENCY vote per section:<br/>instance / compose / unmatched,<br/>component key, variant props,<br/>text-slot fills<br/>(inputs: memo intent > prompt; rules digest advisory)"]
E --> G0["DETERMINISTIC guards (echo never trusted)<br/>variant clamp · symmetric scope guards ·<br/>duplicate-claim dedupe · kit-name override ·<br/>variant resolution · screen facts"]
G0 --> F["DETERMINISTIC stitching (layout-preserving)<br/>spec tree · spec_nodes_flat · style_bindings map<br/>compose-child re-selection + variant carry-over +<br/>differentiation preserve · band/card wrapping ·<br/>gaps aligned to emitted children"]
F --> G["RULES VALIDATOR + RHYTHM PASS (deterministic)<br/>violations: auto-fix where safe, else flag<br/>gap snapping (hygienic scale) + interior<br/>section-joint binding<br/>no rules registered: no-op +<br/>every node flagged rules_unvalidated"]
G --> H["computed confidence<br/>validator violations + unmatched/composed counts<br/>+ slot ambiguity + component-fit (role agreement)<br/>never model self-report"]
H --> L1["LEDGER write-back (design_resolution)<br/>ratchet fence: voted lands only over a<br/>non-mined, strictly-lower-scoring entry"]
L1 --> I["result doc spec / validator / confidence<br/>(CAS on _id + attempt)"]
I --> J["S3: immutable result artifact +<br/>result manifest (schema-versioned)"]
J --> K["webhook: succeeded + manifest key<br/>backend flips wf2des to completed<br/>+ result refs + flag_count"]
発行 / 出力
DocDB 書き込み — design_generation_result spec / spec_nodes_flat / selection / validator_report / confidence / placement.design_area ブロック。(_id, attempt) に対する CAS。_id = wf2des 行 id。
| フィールド | 型 | 入手元 |
|---|---|---|
spec |
object (DesignSpec) | { spec_version, parse_confirmed (ミラー), style_bindings (token → Figma style/variable id、project_figma_file.style_captures から導出), root } — 自己完結型。下記の 5 つのノードタイプ |
spec.root → layout_frame |
object | {node, layer_path, auto_layout, fill, fill_opacity, stroke, stroke_weight, corner_radius, clips_content, bbox, lineage_wf_node_ids, children} — auto_layout = {direction, gap, padding(一様または [top,right,bottom,left]), sizing, gaps[](子ごとの実測 gap。**発行された** 子に整列), primary_align, counter_align, wrap, counter_gap}。direction=none + sizing=fixed は、重なり合うレイヤー(背景/scrim/modal)をドキュメントの z-order と座標のまま保持する。fill_opacity は実効 paint alpha、stroke / stroke_weight は authored boundary、clips_content は固定 viewport/card の clipping を保持する。bbox は埋め込まれ、spec は 自己完結(マテリアライズに snapshot 不要) |
spec.root → instance |
object | {node, layer_path, component_key, platform_design_id (= design_component._id、安定した validator キー), component_node_id, component_name, variant_props, text_slots, bbox, confidence, flagged, source{kind: registry \| wireframe, component_key}, lineage_wf_node_ids} — source.kind = wireframe は フォールバック: ワイヤーフレーム自身のコンポーネントインスタンス(その componentId + variants + テキストオーバーライド)。デザインマッチが存在しない場合、または決定的降格がワイヤーフレームの明示的な区別を保存した場合に使用。常にフラグ付き、ワイヤーフレームより悪くなることはない |
spec.root → compose |
object | {node, layer_path, source{kind:composed}, auto_layout(compose セクション **自身** のリズム — direction/gaps/padding。nullable), fill, fill_opacity, stroke, stroke_weight, corner_radius, clips_content, bbox, children (インライン化), confidence, flagged=常に true, lineage_wf_node_ids} — 単一の registry component が適合しない場合でも compose は source container であり、modal/card/footer の surface contract を保持する |
spec.root → text |
object | {node, layer_path, content, style_token, font_size, font_weight, color, bbox, confidence, flagged, source, lineage_wf_node_ids} — ワイヤーフレームのテキストスタイリングを埋め込み。空の style_token は根拠のあるリテラル書式とフィールド別 style_refs を使用する意味で、未解決のプレースホルダーではない。キャプチャ語彙は画面全体への単一スタイル割当を意味しないため、text という変数名や最初の本文スタイルで計測済みの文字階層を上書きしない。明示的なスタイル参照は引き続き利用でき、プラグインは解決後の種別と適用先を検証する。 |
spec.root → unmatched |
object | {node, layer_path, placeholder{role,text,bbox}, confidence, flagged=true, source{kind:none}, lineage_wf_node_ids} |
spec_nodes_flat |
object[] | [{layer_path, case, component_key, wf_role (parse ツリーから join), confidence, flagged, lineage_wf_node_ids}] — コンテンツノードごとに 1 つ |
selection |
object | { sections, candidates_considered (マップ), composed_count, unmatched_count, truncated } |
screen_plan |
object | null | replay 用に保持する grounded whole-screen review と accepted/rejected proposal evidence |
spec_hash |
string | null | materialized base spec の canonical SHA-256 identity |
assembly_state_hash |
string | null | actual-render review が使う pin 済み stitch/validation state の hash fence |
layout_fidelity |
object | null | {frames_total, frames_preserved, wrap_total, wrap_preserved, units_collapsed, lost[]} |
validator_report |
object | { status (ok \| no_rules), violations:[{rule, layer_path, action (auto_fixed\|flagged), detail}], unfixed_flagged } |
confidence |
object | { min, avg, flag_count, formula_version } — flag_count は完了 Webhook を介して wf2des.flag_count にミラー |
placement.design_area |
object | { x, y, w, h } — 解決された DESIGN エリアの矩形。config.json からワーカーが書き込み(placement の残りは後で plugin が書き込み: placed_node_id、materialized_at、materializer_report) |
artifact_urls |
object | assemble は result、assembly(+ spec > ~1MB spill 時は spec)を書き込む。全体は {result, assembly, spec, parse, feedback_diff}。assembly は bounded render review 用 stitch input を pin |
timings |
object | {created_at, parse_done_at, assembled_at, <phase>_ms} — assemble は assembled_at + 自身のフェーズ所要時間(selection_ms、assemble_ms、validate_ms)をスタンプします。<phase>_ms = フェーズごとのレイテンシ |
lineage.processed_at |
timestamp | ターミナル書き込み |
design_rule の書き込みは rule_process だけのものです — generation のどのフェーズも design_rule コレクションに書き込みません(parse 側のインライン経路は廃止済み)。
DocDB 書き込み — design_resolution 決定台帳ドキュメント(assemble 専用。自律的な品質ラチェット — 人手による修正ループは存在しないため、品質はマシン側で収束させる必要があります)。解決されたセクション 種別 ごとに 1 ドキュメント:
| フィールド | 型 | 入手元 |
|---|---|---|
_id |
string | {organization_id}_{project_id}_{section_signature} |
organization_id, project_id |
integer | テナンシースコープ |
section_signature |
string | 構造的な セクション同一性、バージョン付き(sig@1:…)— セクションの kit-component 名・正規化名・子タイプの形状・テキスト数に対する sha256。決定的で LLM フリー(LLM が割り当てた role は決して使わない)なので、同じセクション 種別 はすべての画面・再実行で同じエントリにヒット |
case |
string enum | instance | compose |
platform_design_id |
string | null | 解決されたコンポーネント(= design_component._id) |
component_name |
string | 表示用の便宜 |
variant_policy |
object | 軸 → 値。書き込み前にレジストリの軸に クランプ |
provenance |
string enum | mined(ファイル内のデザイナー製デザインから読み込み — 投票を上回る唯一の権威)| voted(K サンプル選択投票) |
score |
float | 決定論的な 判断品質(base + modeled + variant-completeness + scope-fit。mined = 1.0)— ラチェットの通貨 |
prompt_version |
string | 投票の来歴 |
updated_at |
timestamp | 最後に受理された書き込み |
参照セマンティクス(assemble ステップ 5): mined または score ≥ 信頼しきい値 → ロック(そのセクションで LLM なし)。しきい値未満 → フロア(再投票。スコアの高い判断が勝ち、決定論的ガードの 前 に適用されるためガードが最終決定権を保持)。instance エントリは参照時にスコープ再検証(デフォルトバリアントのエントリのみ — default_size はデフォルトバリアントについてのみ語る)。書き込みフェンス: 冪等性とフェンシング を参照。
S3。 2 つの出力。(1)クライアント向けのイミュータブルな結果アーティファクト {org}/{proj}/wf2des/{wf2des_id}-{ts}-result.json(失敗時 …-failed.json)— Webhook 配送とは独立した永続的な完了記録。artifact_urls.result でも参照(および spec > ~1MB スピル時は artifact_urls.spec を {org}/{proj}/wf2des/{wf2des_id}-{ts}-spec.json に)。(2)結果マニフェスト(スキーマバージョン付き)を {org}/{proj}/wf2des/{wf2des_id}-{ts}-manifest.json に(失敗時 …-failed-manifest.json)— Webhook ハンドラの行効果入力。succeeded/failed Webhook の result_manifest_url から参照。その内部の result_url は出力(1)を指します。
Webhook — succeeded。 POST /v1/webhooks/ai-status、X-API-Key、判別された type、status: "succeeded":
{
"type": "wf2des",
"job_id": "<wf2des_id>",
"attempt": 1,
"nonce": "<per-send token>",
"manifest_schema_version": 1,
"status": "succeeded", // wf2des 行 status '1' にマップ
"result_manifest_url": "{org}/{proj}/wf2des/{wf2des_id}-{ts}-manifest.json"
}
参照される S3 マニフェスト(ハンドラの行効果入力。システムが生成する唯一のマニフェスト):
{
"manifest_schema_version": 1,
"job_id": "…", "attempt": 1,
"nonce": "…",
"job_type": "generation",
"result_url": "{org}/{proj}/wf2des/{wf2des_id}-{ts}-result.json",
"flag_count": 3,
"row_effects": { "wf2des": { "status": "1", "result_url": "…", "flag_count": 3 } }
}
失敗時に raise するので SQS が再配送します。失敗バリアント(同じエンベロープ): status: "failed" + error、result_manifest_url は失敗マニフェスト …-failed-manifest.json を指す(その内部の result_url = …-failed.json)→ ハンドラが行に error を記録し、phase をクリアし、status '2' に反転。(parse の却下は Webhook では ありません — plugin が parse.rejected を wf2des-api に書き込み、その後バックエンド confirm エンドポイントが status '3' に反転します。) すでに terminal に到達した attempt の重複配送では、保存済みの結果ドキュメントから terminal のマニフェスト + Webhook を再発行します(LLM は決して再実行しない)— 結果書き込みと Webhook の間のクラッシュウィンドウを閉じます。バックエンドの (job_id, attempt, nonce) 重複排除 + attempt ガードにより、純粋な重複送信は no-op です。
PG 効果。 wf2des 行 → status '1' completed + 結果参照(result_url)+ flag_count(confidence.flag_count の完了時コピー)+ phase クリア(NULL)。ai-status Webhook ハンドラ — 完了時の唯一の PG 書き込み者 — によって適用され、(job_id, attempt, nonce) で重複排除、attempt ガード付き、1 つの PostgreSQL トランザクションで、マニフェストの row_effects.wf2des から。これは行作成を超える唯一の wf2des 書き込みです(placement/feedback plugin 反転を除く)。マニフェストが永続記録、Webhook が高速経路ですが、その隙間を埋めるリプレイ — stuck-generation sweep — は 未実装 です。Webhook が取りこぼされた場合、ターミナルマニフェストは S3 に残るだけで、行は反転されません。破棄された attempt の Webhook は no-op です。ワーカーは PG に決して書き込みません。
wf_parse
plugin が WF フレームを登録し、ワーカーがそれを wireframe キャッシュドキュメントに parse します。generation-parse のステップ 4→9 と同じ parse パイプライン(決定的抽出、memo ステータス、LLM role/intent)— ただし 結果ドキュメントなし、プレビューなし、Webhook なし: wireframe ドキュメントが記録そのものです。wf2des-events 取り込みキュー(plugin frame-registration イベント)によってトリガーされます — EventBridge ではなく、generation キューでもありません。内部実行は wf2des 行を持ちません。
消費 / 入力
シンな登録イベント(refs + 事前収集済み URL)。generation メッセージのフィールドはありません(wf2des_id/attempt/nonce なし)。
| フィールド | 型 | 必須 | 検証 | 備考 |
|---|---|---|---|---|
event_type |
string | ✓ | plugin frame-registration カテゴリ | wf_parse へルーティング |
figma_file_key |
string | ✓ | Figma そのまま。英数字、アンダースコアなし | フレームが存在するファイル |
node_id |
string | ✓ | Figma node id(: を使用) |
登録されたフレーム |
| snapshot URL(バックエンドがキャプチャ) | string (S3 URL) | ✓ | バックエンドが事前収集 | parse 対象の WF snapshot |
organization_id, project_id |
integer | ✓ | テナンシースコープ | メッセージが運ぶスコープ。DEFAULT_ORGANIZATION_ID=1 |
wf_parseイベントの JSON 例は定義されていません。上記のフィールドが必須セットです。
読み込み。 S3 からの WF snapshot(バックエンドがキャプチャした登録 snapshot URL)。snapshot スコープ = フレーム + 包含する board section(memo/status フレームは board レベル)。config.json(S3 config ホーム {org}/{proj}/wf2des/config.json、project_figma_file.config_url 経由)を読み込み、curatable な memo-status マーカー(memo_resolved_markers、デフォルト 取込済)、DESIGN エリア矩形(design_area、assemble 時に解決)、およびフレーム名のバリエーション区切り文字(variation_separator、デフォルト |)を取得。config.json が存在しない場合はプラットフォームデフォルトにフォールバック。存在するが壊れている場合(読み込み不能・オブジェクトでない・オーバーライド値が不正)は run を大きく失敗させる — キュレーション済みプロジェクトにデフォルトが黙って適用されることはない。(例外: 壊れた design_area 矩形は flag 付き警告とともに placement.design_area を省略するのみ — 省略は結果で可視であり、黙ったフォールバック矩形は決してない。複数ファイルのプロジェクトは辞書順で最初の config_url を解決。)テナンシー読み込みは organization_id + project_id でフィルタリング。
処理契約
- 決定的 WFNode 抽出(S3 snapshot から): 可視の FRAME / GROUP / INSTANCE / TEXT の各子孫 + 画像塗りの矩形 → WFNode。vector/shape の葉は親に折りたたむ。隠しレイヤーはスキップ。
- memo ステータス — 決定的: 取込済フレーム内部の memo ⇒
status=resolved。resolved の memo はライブ intent として除外。 screen_id— 決定的 な名前文法[A-Z]{2,3}_[A-Z0-9]+、アンカーなし、フレーム名 → board 名 → page 名の順で検索。一致なし ⇒ null + 取り込み issue をstaging/issues.jsonにログ。variation_label— 決定的 なフレーム名分割(会員登録TOP|案1 → 案1)。- LLM(fast tier): role + intent のみ — role は共有の要素タイプ語彙から。intent は OPEN memo + variant ラベルから。ツリー形状は 決して 割り当てない。唯一の LLM ステップ。
- 導出サマリーを計算:
element_type_histogram、node_count。 wireframeキャッシュドキュメントを直接書き込む(LWW フェンス)。結果ドキュメントなし、プレビューなし、Webhook なし —wireframeドキュメントが記録そのものです。
発行 / 出力
DocDB 書き込み — wireframe キャッシュドキュメント(generation-parse と共有する単一ライター。非衝突を意図)。generation-parse の書き込み (A) と同じ形状:
| フィールド | 型 | 入手元 |
|---|---|---|
_id |
string | 複合 {project_id}_{figma_file_key}_{node_id}。各部は split('_', 2) で導出。別個の wireframe_id なし |
organization_id, project_id |
integer | テナンシースコープ |
name |
string | 完全な Figma フレーム名(日本語は PG frame_name の 100 を超える場合あり) |
type |
number | 0 page · 1 component(platform wireframe コレクションの形状) |
based_on |
number | 0 imported · 1 design · 2 wireframe |
screen_id |
string | null | 文法一致。ミス時は null + 取り込み issue を staging/issues.json にログ |
variation_label |
string | 決定的な名前分割 |
structure |
object | WFNode ツリー — generation-parse 書き込み(A)と同じ形状(決定的なレイアウト/スタイル/コンポーネントキャプチャ — component_id/component_variants、auto-layout フィールド、テキストスタイリング、ディバイダーリーフ — を含む)— ルート node_id はドキュメントアイデンティティの node_id と等しい。トップレベルの子 = WF セクション(レビュー採点単位) |
memos |
object[] | { memo_node_id, text, status }(status は決定的: 取込済の包含 ⇒ resolved)— ここに per-run フィールドはなし |
summaries |
object | { element_type_histogram, node_count }(計算値) |
lineage |
object | { source_url (RAW REST snapshot), source_hash (≡ content_hash), processor_version (例: wf_parse@1.2), index_schema_version, processed_at, job_id (wf_parse 実行 id) } |
フェンシング: (source_hash, processed_at) でガードされた LWW — 新しい source hash またはより新しい processed_at の場合のみ書き込みが着地します。
S3。 自身のものはなし。コンテンツアドレス指定キー wf2design/snapshots/{figma_file_key}/{node_id}.{content_hash}.json の snapshot を消費します。クライアント向けの parse.json アーティファクトは generation 実行の結果ドキュメントに属し、wf_parse には属しません。
Webhook。 Webhook フリー。ai-status Webhook なし — wireframe ドキュメントが記録そのものです。
PG 効果。 PG フリー。wf2des 行なし、マニフェストなし。ワーカーは PostgreSQL に決して書き込みません。
rule_process
プロジェクトのデザインルールは、選択された Figma ガイドラインボードから 抽出 されます — 1 回の操作で、1 つのイミュータブルな design_rule リビジョンに帰結します。wf2des-events 取り込みキュー(rule-upload カテゴリ)によってトリガーされます — EventBridge ではなく、generation キューでもありません。デザイナーが plugin でガイドラインボードのフレームを選択し、バックエンドがシンな rule-upload イベントを発行し、rule_process ワーカーが登録時にレンダリングと抽出を行います。この実行はサービスアカウント Figma PAT を 必要とし、LLM フリーでは ありません: フェンス呼び出し 4(ボードごとの vision ルール抽出)を担います。
消費 / 入力
シンな rule-upload イベント(event_type は rule_upload)。旧 file_url フィールド(S3 strict-JSON ルールファイル)は 廃止 され、ボード選択に置き換えられました。
| フィールド | 型 | 必須 | 検証 | 備考 |
|---|---|---|---|---|
event_type |
string | ✓ | rule-upload カテゴリ(rule_upload) |
rule_process へルーティング |
design_rule_id |
string (uuid) | ✓ | プラットフォーム design_rule 行 id、値渡し |
一意なビジネスキーの半分 |
figma_file_key |
string | ✓ | Figma そのまま | ガイドラインボードが存在するファイル |
board_node_ids |
string[] | ✓ | 最低 1 つ | デザイナーが plugin で選択したガイドラインノード — ボード FRAME、または CANVAS/SECTION コンテナ(ワーカーが子のボードフレームに展開する。ネストした SECTION は再帰。他の子タイプはボードではない)。選択の解決であり、キュレーションでは決してない |
organization_id, project_id |
integer | ✓ | テナンシースコープ | メッセージが運ぶスコープ |
spec は rule-upload イベントの JSON 例を示していません。
読み込み。 サービスアカウントの OAuth アプリ許諾(Authorization: Bearer、Admin 内部 token provider から取得。figma_pat はフォールバック)を介した Figma REST: (1)選択されたボードノードの get_file_nodes(タイトル取得)— ボードノードの 欠落 はハードエラー(raise → 再配送)。(2)ファイルのスタイル — 決定的なスタイルトークン名のため(_style_captures_from_file)。(3)Figma images API(GET /v1/images、PNG scale 2)による各ボードのレンダリング — null レンダリングはハードエラー。S3 のルールファイルは読み込みません — ボードがソースそのものです。加えて、この design_rule_id の現在の最大 version を design_rule コレクションから読み込み、次の序数を割り当てます。テナンシー読み込みは organization_id + project_id でフィルタリング。
処理契約
- Figma ノード読み込み — 選択されたボードのタイトルを
get_file_nodesで取得。ボードノードの 欠落 はハードエラー(raise → SQS 再配送)。 - 決定的なスタイルトークン名 をファイルのスタイルから導出(
_style_captures_from_file)。 - 各ボードをレンダリング — Figma images API(
GET /v1/images、PNG scale 2、サービスアカウントの OAuth アプリ許諾(Authorization: Bearer、figma_patはフォールバック))。null レンダリングはハードエラー。各レンダリングは S3 のwf2design/rule-boards/{figma_file_key}/{node_id}.{sha256}.pngに保存。 - LLM — フェンス呼び出し 4: ボードごとに 1 回の vision 抽出(Agent
output_type=RuleSet、RULE_EXTRACTION_INSTRUCTIONS、RULE_EXTRACTION_PROMPT_VERSIONre@0.10、vision tier /config.vision_model)— ボードが示すもの のみ を抽出。slot_constraintsは常に空。 - 決定的なマージ(
merge_rule_fragments): ボードはnode_idでソートして処理。spacing.scaleと color のallowedは UNION してソート。section_gapは最初の非 null。typography はstyle_tokenごとに最初の出現が勝ち。 - マージ済み RuleSet に対する
content_hash(sha256、正規 JSON、キーソート)— アイデンティティのセマンティクスは不変: 同一の抽出は収束し、新しい序数は発行されません。versionを割り当て = (このdesign_rule_idの現在の最大 version)+ 1。 - 1 つのイミュータブルな
design_ruleリビジョンドキュメントを DocDB に直接書き込む(Webhook フリー)— マージ済みルール とboards[]+ 抽出来歴、draft_source = "llm_extracted"。(design_rule_id, content_hash)ごとに 1 つのイミュータブルドキュメント。Webhook なし、PG 効果なし — ドキュメントが記録そのものです。
レビューモデル: 抽出は draft_source="llm_extracted" として着地します。デザイナーは抽出されたルールをレビュー(inspect/preview)し、ガイドライン修正後に再登録します — 同一のマージ済みルールは同じリビジョンに収束し、変更されたルールは次のバージョンを発行します(バージョンの増加は許容されます)。
flowchart TD
A["event: rule upload<br/>(design_rule_id + figma_file_key + board_node_ids)"] --> B["Figma REST: 選択をボードフレームに解決<br/>(CANVAS/SECTION は子フレームに展開)<br/>ノード欠落 / 空コンテナ = HARD error"]
B --> C["deterministic style-token names<br/>from the file styles"]
C --> D["render each board — images API, PNG scale 2<br/>null render = HARD error; store to S3<br/>wf2design/rule-boards/{figma_file_key}/{node_id}.{sha256}.png"]
D --> E["FENCE CALL 4 — one vision extraction per board<br/>output_type=RuleSet · re@0.10 · vision tier<br/>only what the board shows"]
E --> F["DETERMINISTIC merge (merge_rule_fragments)<br/>boards sorted by node_id; unions sorted;<br/>section_gap first-non-null;<br/>typography first-occurrence-wins"]
F --> G["content_hash over the MERGED RuleSet<br/>version = max+1"]
G --> H["write ONE immutable design_rule revision<br/>(webhook-free) — merged rules + boards[] +<br/>extraction provenance, draft_source=llm_extracted;<br/>the collection IS the revision history"]
H --> I["done — NO webhook, NO PG effect;<br/>the design_rule doc IS the record"]
発行 / 出力
DocDB 書き込み — design_rule リビジョンドキュメント(単一ライター、イミュータブル)。
| フィールド | 型 | 入手元 |
|---|---|---|
_id |
string (uuid) | サロゲート uuid |
design_rule_id |
string (uuid) | イベントフィールド — イミュータブルな一意ビジネスキーの半分 |
content_hash |
string (sha256) | マージ済み RuleSet に対する正規 JSON — 一意ビジネスキーのもう半分 |
version |
integer | 序数。新しい content_hash ごとに割り当て |
organization_id, project_id |
integer | テナンシースコープ |
draft_source |
string enum | human | llm_draft_adjusted | llm_extracted — この実行は llm_extracted を書き込む |
rules |
object | マージ済み RuleSet — 各ルールクラスは束縛先の spec フィールドを明示する。spacing {scale[], section_gap}、typography [{style_token, max_lines, usage, size_px, size_min_px, size_max_px, line_height_pct, weight, applies_to[]}]、colors {entries[{token, value, usage}], allowed[]}、usage_rules[]、layout_patterns[]、rich_layouts[]、globals、component_inventory[](component_specs に置き換え済み。旧リビジョン互換のため保持)、component_specs [{component, variants[], states[], seen_on_screens[], slot_rules}]、screen_group_policies [{group, covers, screen_ids[], device, content_width_px, padding_x_px, padding_y_px, section_gap_px, element_gap_px, columns_min, columns_max, section_separator, full_bleed, source_text}]。slot_constraints は削除された — これを生成する抽出パスは存在しなかった |
boards |
object[] | {node_id, title, image_url, content_hash (PNG 画像の sha256), mime} — 選択されたガイドラインボードごとに 1 つ。image_url → S3 レンダリング。assemble 時に vision リファレンスとして再読み込み |
extraction_model_id, extraction_prompt_version, extracted_at |
string / string / timestamp | 抽出来歴。現行はノードパイプライン(comprehend → ロール別 extract → 決定的マージ、pin は bc@0.2 / sys@0.3 / cmp@0.3 / tok@0.2)。vision パス(re@0.10)はオプトアウト用のフォールバックとして残る |
llm_usage |
object | この取り込みのコスト(プロバイダ自身の報告値): {calls, requests, input_tokens, output_tokens, cached_input_tokens, stages[{stage, calls, requests, input_tokens, output_tokens, cached_input_tokens}]} — ステージ別、入力トークンの多い順 |
consolidation |
object | null | 統合ジャッジが実行された場合、その判断内容 |
lineage |
object | { source_url, source_hash (≡ content_hash), processor_version, index_schema_version, processed_at, job_id (rule_process 実行 id) } |
フェンシング: (design_rule_id, content_hash) ごとの冪等なコンテンツアドレス指定 insert — UNIQUE、content_hash は マージ済み RuleSet に対して計算(アイデンティティのセマンティクスは不変)。不変コンテンツ — 同一の抽出 — のリプレイは既存のドキュメント/バージョンにマップされ、新しい序数を発行することは決してありません。変更されたマージ済みルールは次のバージョンを発行します(バージョンの増加は許容)。同じルールの並行アップロードは単一のアップロードイベントによって直列化されます。このコレクションがルールのリビジョン履歴そのものです(各リビジョンが新しいイミュータブルドキュメント)。
S3。 ボードレンダリングを wf2design/rule-boards/{figma_file_key}/{node_id}.{sha256}.png に(Figma images API、PNG scale 2)— boards[].image_url から参照され、assemble 時にセクション選択の vision リファレンスとして再読み込みされます。他の S3 アーティファクトはありません — design_rule ドキュメントが記録です。
Webhook。 Webhook フリー。ai-status Webhook なし — design_rule ドキュメントが記録そのものです。
PG 効果。 PG フリー。wf2des 行なし、マニフェストなし。(プラットフォーム design_rule レジストリ行は、このワーカーではなく、プラットフォーム API を通じてクライアント側で作成されます。)
component_sweep
共有コンポーネントレジストリを真実に保ちます — 決定的な Figma REST フルファイル巡回、どこにも LLM なし。これは EventBridge スケジュールトリガーを持つ 唯一の 内部実行です。project_figma_file ドキュメント上の sweep マーカーによって single-flight でガードされます。
消費 / 入力
2 つのトリガーソース: EventBridge スケジュール(ワーカーを直接呼び出す — AI 所有。メッセージボディなし)または wf2des-events 取り込みキュー上の plugin RESYNC イベント(plugin-events カテゴリ。バックエンドが送信権限を保持し、依然として発行する)— generation キューではありません。
| フィールド | 型 | 必須 | 検証 | 備考 |
|---|---|---|---|---|
event_type |
string | ✓(resync 経路) | plugin-events / resync カテゴリ | component_sweep へルーティング |
organization_id, project_id |
integer | ✓ | テナンシースコープ | 巡回をスコープ |
ワーカーは巡回をスコープするため、プロジェクトの登録済みファイルを
project_figma_fileから読み込みます(wf2des-api ファイルリスト経由)。メッセージ内でコンポーネント URL を受け取ることは なく(自身で Figma REST を巡回)、sweep/job id は存在しません。JSON 例は示されていません。
読み込み。 サービスアカウントの OAuth アプリ許諾(Authorization: Bearer、Admin 内部 token provider から取得。figma_pat はフォールバック)経由の Figma REST。巡回をスコープするため、プロジェクトの登録済みファイルを project_figma_file から読み込み(working + library ロール)。フルファイル巡回: /components は 公開済み コンポーネントのみをリスト。ローカルコンポーネントはフルファイル巡回フォールバックで発見。DocDB + S3 のみ を読み書き(PG は決して触らない)。再開可能/チェックポイント付き: 深さ制限リスト + バッチノード読み込み。フルファイル GET は決して 1 回で行わない。テナンシーは organization_id + project_id でフィルタリング。
処理契約
- スケジュール/resync が発火。single-flight ガードを取得 —
project_figma_fileドキュメント上のsweep_marker {token, acquired_at, expires_at}(クラッシュセーフな stale-lock 回復 —expires_atを過ぎたロックは再取得可能)。 - Figma REST フルファイル巡回(
/componentsは公開済みのみをリスト。ローカルコンポーネントは我々のもので、巡回で発見)— 決定的。 - COMPONENT / COMPONENT_SET ノードごと: コンテンツアドレス指定の S3 snapshot を書き込む。
- コンテキストドキュメントを決定的に導出:
component_key、node_id、variant_properties、text_slots(layer path。曖昧さは登録時にフラグ付け)、image_slots、default_size。どこにも LLM なし — Figma JSON のストレート変換。 - ローカルのスタイル・変数と選択ボードで実際に使用するライブラリ binding をキャプチャ →
project_figma_file.style_captures(導出されたstyle_bindingsマップのソース)。プラグインは同期開始時に固定したボード ID、その子孫、参照先コンポーネント定義・variant を走査し、mixed text run と paint-bound variable を含む正確な variable/style ID を解決します。ライブラリの import や色・名前からの推測は行いません。変数は bare 名と collection 修飾名を保持し、ローカルトークンを優先、衝突する remote alias は除外します。個別参照の読み込み失敗でも読み取り可能な binding は維持します。token → ID 契約は変更せず、既存の空 capture は更新プラグインでコンポーネントライブラリを再同期する必要があります。 - 発見コンポーネント sweep マニフェストを発行 → バックエンドがプラットフォーム
design行type=componentを UPSERT(id · name · type=component · image_url · json_schema_url · status)— 共有レジストリ(des2code のカタログでもある)。バックエンド側 PG。 design_componentコンテキストドキュメントを DocDB に直接書き込む、プラットフォーム design 行 id でキー付け —removed_atなし(削除 = design 行のステータス)。project_figma_fileをフィールドレベルでスタンプ:components_synced_at(最後に 更新、sweep 完了後)、+ 失敗時sweep_error。sweep マーカーをリリース/失効。
flowchart TD
A["schedule / resync event fires<br/>(single-flight guard: a sweep marker<br/>on the project_figma_file doc)"] --> B["Figma REST: full-file walk<br/>(/components lists published only; ours are local)"]
B --> C["per COMPONENT / COMPONENT_SET node:<br/>content-addressed S3 snapshot"]
C --> D["derive context doc: component_key, node_id,<br/>variant_properties, text_slots (layer paths,<br/>ambiguity flagged at registration),<br/>image_slots, default size"]
D --> E["capture local text styles + color variables<br/>(→ project_figma_file.style_captures;<br/>source of the derived style_bindings map)"]
E --> F["emit the DISCOVERED-COMPONENT manifest →<br/>backend UPSERTS platform design rows type=component<br/>(id · name · type=component · image_url · json_schema_url · status) —<br/>the SHARED registry (des2code's catalog too); backend-side PG"]
F --> G["write design_component CONTEXT DIRECTLY to DocDB,<br/>keyed to the platform design row id —<br/>no removed_at (removal = the design row's status)"]
G --> H["stamp project_figma_file field-level:<br/>components_synced_at (+ sweep_error on failure)"]
発行 / 出力
DocDB 書き込み (A) — design_component コンテキストドキュメント(COMPONENT/COMPONENT_SET ごとに 1 つ。単一ライター、_id に対する upsert + single-flight ガード)。INSTANCING コンテキストのみ(レジストリのライフサイクルはプラットフォーム design 行に存在):
| フィールド | 型 | 入手元 |
|---|---|---|
_id |
string | プラットフォーム design 行 id(type=component)= {project_id}_{figma_file_key}_{node_id} — _id がプラットフォーム design id そのもの(サロゲート uuid なし、別個の platform_design_id フィールドなし)。各部は split('_', 2) で導出 |
organization_id, project_id |
integer | テナンシースコープ |
name |
string | 表示名 |
kind |
string enum | published | local(local は publish key を持たず、node_id でインスタンス化) |
component_key |
string | null | Figma publish key。local では null |
variant_properties |
object | 例: {type:[default, logged-in]} |
text_slots |
object[] | {layer_path, default_text, ambiguous} |
image_slots |
object[] | {layer_path, ambiguous} |
default_size |
object | {w, h} |
variants |
object[] | バリアントごとのプロファイル: {variant_props, size, text_slots[], nested_components[], layout_shape, appearance[], semantics[]} — 各バリアントが実際に保持する内容。選択が兄弟バリアントを区別するために使う |
variant_defaults |
object | 軸 → デフォルト値(コンポーネントセットの宣言どおり) |
family |
object | null | {name, node_id, node_type} — デザインシステム自身のグルーピング。スイープがコンポーネントを検出した SECTION から読む |
semantics |
object | null | {kind, also_kinds[], is_placeholder, function[], appearance[], model_id, prompt_version, tagged_at, facts_hash}。kind は閉じた語彙(COMPONENT_KIND_VOCABULARY)の単一カテゴリ。is_placeholder は「内容を提供せず場所だけ確保する」コンポーネントを示す。拡充パスが書き込み、pin は cs@0.3 |
key_provenance, name_provenance |
string | publish key / 名前を最後に書いた生成元(plugin-observed は REST スイープより優先) |
text_props |
string[] | コンポーネントが公開する非バリアントのテキストプロパティ名 |
lineage |
object | {source_url, source_hash (≡ コンポーネントサブツリーのコンテンツハッシュ — 別個の content_hash フィールドなし), processor_version, index_schema_version, processed_at, job_id} |
DocDB 書き込み (B) — project_figma_file フィールドレベル(plugin の role/config_url 書き込みとは分離):
| フィールド | 型 | 入手元 |
|---|---|---|
style_captures |
object | token 名 → Figma style/variable id。figma_file_key ごとに 1 つ — 導出された style_bindings マップのソース |
components_synced_at |
timestamp | 鮮度ウォーターマーク — sweep 完了後に最後に更新 |
sweep_error |
string | null | 最新の sweep 失敗、なければ null |
sweep_marker |
object | null | {token, acquired_at, expires_at} single-flight ロック。sweep 実行中でなければ NULL |
フェンシング: design_component = _id に対する upsert + single-flight ガード(project_figma_file 上の sweep マーカー)。project_figma_file = フィールドレベル更新(plugin フィールドと分離、衝突なし)。コンポーネント削除はプラットフォーム design 行のステータスに記録され、我々のドキュメントには記録されません(removed_at なし)。
S3。 COMPONENT/COMPONENT_SET ノードごとの、コンテンツアドレス指定コンポーネント SNAPSHOT を wf2design/snapshots/{figma_file_key}/{node_id}.{content_hash}.json に。加えて発見コンポーネント SWEEP マニフェスト(S3 コンポーネントマニフェスト)。
Webhook。 Webhook フリーではありませんが、ai-status Webhook は 発行しません(それは generation 専用)。代わりに発見コンポーネント sweep マニフェストをバックエンドに発行し、バックエンド側でプラットフォーム design(type=component)UPSERT として適用されます。マニフェストは別個のレジストリ経路であり、ai-status ペイロードではありません。マニフェストの内容: Figma コンポーネントごとに 1 エントリで id · name · type=component · image_url · json_schema_url · status を運ぶ。
未確定: 正確な sweep マニフェストの形状。これは generation マニフェストのエンベロープに従います — 同じ構造、タイプごとの
row_effects、ここではwf2des行ではなくプラットフォームdesign行を対象とします — ai-status 判別type値も同様です。
PG 効果。 バックエンド側 PG 効果を持つ 唯一の 内部実行: バックエンドが sweep マニフェストからプラットフォーム design 行 type=component(共有コンポーネントレジストリ。des2code でも消費される)を UPSERT — 行: id · name · type=component · image_url · json_schema_url · status。これはレジストリ UPSERT(冪等)であり、ai-status Webhook では なく、wf2des 行の反転でも ありません。ワーカーは依然として PG に直接書き込むことは決してありません — バックエンドがマニフェストを適用します。
エラーハンドリング
すべての実行はプラットフォームの部分バッチ SQS ハンドラ(ReportBatchItemFailures)を使用します。process_record 内でスローされたエラーはそのアイテムのみを失敗としてマークし、SQS がそれを独立して再配送できるようにします。
| シナリオ | 挙動 |
|---|---|
| スローされたエラー、任意の実行 | 部分バッチ ReportBatchItemFailures 経由の SQS 再配送。アイテムは batchItemFailures に追加 |
| Generation Webhook 送信失敗 | 送信が RAISE → SQS が再配送し送信がリトライ。ターミナル S3 マニフェストが永続記録として残る |
| provider credit 枯渇、認証/アクセス無効、設定 model unavailable | 再配信で修復不能な terminal/client-visible failure。…-failed.json + failed manifest を書き、failed webhook を送り、message を consume |
| 通常の provider 429 rate limit または 5xx | terminal surface を作らず throw し、SQS が再配送。retryable |
| parse 途中でのワーカークラッシュ(generation) | ワンショット Lambda が死ぬ → 再配送。フェンシング (wf2des_id, attempt) がゾンビワーカーの DocDB コミットとその遅延 Webhook をブロック |
リトライ枯渇 / 行が '0' でスタック(generation) |
現状、復旧経路は存在しない。 行は非終端のまま残り、部分ユニークインデックスがそのワイヤーフレームへの新規トリガーをブロックし続ける。cancel は awaiting_confirm でのみ適用可能なため、parse / assemble でスタックした実行は API から解消できない |
| awaiting-confirm の忘却 | wf2des 行に対するバックエンド側 sweep がタイムアウト(config、例: 72h)を強制し、fail closed |
| parse 中の generation 失敗(fail-closed) | 解決不能な pin / スコープ不一致(ContractFailure)→ …-failed.json + failed Webhook → 行は status '2' に反転(phase クリア)。transient な parse エラーは代わりに再配送され、terminal サーフェスなし |
| assemble 後の generation 失敗 | …-failed.json アーティファクト + failed Webhook → ハンドラが error を記録し、status '2' に反転(phase クリア)→ デザイナーが理由 + Retry を見る |
| pin 済み入力の欠落 / 再水和時のハッシュ不一致(assemble) | Fail closed — 古いルール/コンポーネントへの静かなフォールバックは決してない |
| 行の読み込みミス(generation) | 実際のエラー(row-first-then-SQS)であり、結果整合性ウィンドウではない |
Materializer diagnostic(name_fallback/ordinal_fallback/build_error/font_fallback/prop_rejected/unmatched/preserved) |
後の plugin concern として placement.materializer_report に記録。font_fallback は degraded render で build failure ではない |
screen_id の文法ミス(wf_parse) |
エラーでは ない: null + オペレーター修正用の取り込み issue を staging/issues.json に生成 |
ボードノードの欠落 / null ボードレンダリング(rule_process) |
ハードエラー — raise → SQS 再配送。部分的な抽出は決してない |
| スローされたエラー(内部実行) | wf2des-events での SQS 再配送。冪等なコンテンツアドレス指定 / LWW 書き込みが再配送時に再収束 |
component_sweep の失敗 |
project_figma_file.sweep_error をフィールドレベルでスタンプ。sweep マーカーはクラッシュセーフ(expires_at を過ぎたロックは再取得可能)。取りこぼしたマニフェスト適用は次の sweep で再収束 |
| スタックした実行の可視性(内部実行) | 出力ドキュメントの鮮度(例: components_synced_at)+ SQS デッドレターキュー(バックエンドは内部実行を決して見ない) |
冪等性とフェンシング
フェンシングはコレクションごとです。wf2des 行を超える PG フェンシングはありません(generation のみ)。
| コレクション / サーフェス | フェンス |
|---|---|
design_generation_result |
(_id, attempt) に対する CAS — ゾンビ/破棄された attempt の書き込みは拒否(generation のみ) |
wireframe |
(source_hash, processed_at) に対する LWW — 新しい source hash またはより新しい processed_at の場合のみ書き込みが着地。generation-parse と wf_parse が同じドキュメントを書き込むのは非衝突を意図(各実行が自身の parse.json を読み込む) |
design_rule |
(design_rule_id, content_hash) UNIQUE ごとの冪等なコンテンツアドレス指定 insert — content_hash はマージ済み RuleSet に対して計算。同一の抽出は既存のドキュメント/バージョンに収束し、新しい序数にはならない |
design_component |
_id に対する upsert + project_figma_file 上の single-flight sweep マーカー |
project_figma_file |
フィールドレベル更新(sweep フィールドは plugin の role/config_url と分離 — 衝突なし) |
design_resolution |
_id(= {org}_{project}_{section_signature})に対する スコアラチェット upsert: voted の書き込みは、保持されているエントリが mined でなく かつ スコアが厳密に低い場合にのみ着地(サーバー側フィルタ。フェンスされた upsert での DuplicateKeyError は 保持 を意味し、そのように返される — エラーではない)。mined の書き込みはフェンスなし。同一エビデンスは同一スコア → 再実行はチャーンしない。より良い判断が勝つ → 単調改善 |
| Generation Webhook | (job_id, attempt, nonce) で重複排除。マニフェストのリプレイは同じ重複排除のもとで安全(1 つの PG トランザクション、job_id/job_type/S3 プレフィックスのマニフェスト↔ペイロード検証)だが、それを実行する sweep は未実装のため、今日リプレイは発生しない |
追加の不変条件:
- parse キャッシュ(コンテンツレベル dedup): トリガー時点の snapshot キャプチャで計算された
wf_content_hashがメッセージに乗り、不変のハッシュは前回ジョブの parse アーティファクトをコピーすることで LLM をショートサーキットします。却下後のリトライではスキップ。 - 再配送(generation)は既存の pin を再利用し、
inputsブロックが不在の場合のみ選択を再実行します — クラッシュリトライが異なるコンポーネント/ルールリビジョンを静かに選択することは決してありません。 - 完了した generation は決して再処理されません — フル置換は主要な人間イベントデータを異なる非決定的な spec で上書きしてしまうためです。ターミナル行は最終であり、ワイヤーフレームの再実行は新しい行を作成します。
- LLM フェンス が全体に適用されます: LLM はステップがそう述べる箇所に のみ 現れ、それ以外はすべて決定的です。Parse/
wf_parse= role + intent(fast tier)。assemble = K サンプルの自己整合投票としてのセクション並列選択(strong tier、vision 対応)+ 画面全体のプランニング。rule_process= ルール取り込み(comprehension/extraction/consolidation、取り込み時)。レジストリ enrichment と render review はそれぞれ設定されたモデルを使用。component_sweep= LLM なし。投票に対する決定論的ガード、design_resolutionの参照/書き戻し、confidence 数式(formula_version)は すべて LLM フリー — モデルの自己申告は禁止。LLM が決定するすべては、そのエビデンス(source、lineage_wf_node_ids、pin 済み入力)と共に保存されます。 - internal event は
wf2desproduct row を持たず、いかなる内部実行にも(_id, attempt)CAS はありません。event_run_idがある場合は TTL-backedwf2des_event_statusに live state を記録し、scheduled resync のみ untracked で、その場合は出力ドキュメントの存在 + 鮮度が実行ステータスそのものです。content write は固有の idempotency/fence を維持します。
ランタイム設定
| 設定 | 値 / 入手元 | 備考 |
|---|---|---|
| DocumentDB データベース | guinness_v2(AWS DocumentDB 5.0) |
共有プラットフォーム DB。6 つの wf2des コレクションはプレフィックスなしの対等な存在(design_resolution を含む。design_resolution_table_name で環境変数上書き可能)。コレクション名は環境変数で上書き可能 |
| コレクションモジュール | packages/models/src/models/documentdb/<name>.py |
COLLECTION_NAME + get_collection + create_indexes。Pydantic ドキュメントスキーマは apps/wf2des/src/wf2des/schemas/ |
DEFAULT_ORGANIZATION_ID |
1 |
現時点でシングルテナント。全ドキュメントに organization_id |
index_schema_version |
新しいWF2Des書き込みは 1.4 |
ドキュメント/エンコーダーの形状変更時にバンプ。追加フィールドにより旧ドキュメントの読み取り互換性を保持 |
| 取り込みキュー | wf2des-events(AI 所有。バックエンド send-only) |
discriminated event: frame_registration、rule_upload、resync、component_upload、component_capture、render_review |
| Generation キュー | バックエンド所有の generation parse / assemble キュー | バックエンドが所有する唯一の 2 つ |
| EventBridge スケジュール | AI 所有。component_sweep の resync sweep のためにワーカーを直接呼び出す |
正確な cron/rate はデプロイ時に設定 |
| ai-status Webhook | POST /v1/webhooks/ai-status、X-API-Key |
GENERATION 専用。internal event はいずれも発行しない — tracked internal event は代わりに wf2des_event_status で報告 |
| Webhook 重複排除キー | (job_id, attempt, nonce)。job_id = wf2des 行 id |
|
| Webhook ペイロード | {job_id, attempt, nonce, status, error?, result_manifest_url?, manifest_schema_version} |
|
| 結果マニフェストスキーマ | manifest_schema_version = 1 |
システムが生成する唯一のマニフェスト |
| S3 結果マニフェストキー | {org}/{proj}/wf2des/{wf2des_id}-{ts}-manifest.json(失敗時 …-failed-manifest.json) |
generation のみ。Webhook ハンドラの行効果入力、result_manifest_url が運ぶ |
| S3 クライアント向けアーティファクトキー | {org}/{proj}/wf2des/{wf2des_id}-{ts}-{result\|failed}.json |
generation のみ。例: 1/3/wf2des/8f14e4…-20260706T093000Z-result.json |
| S3 中間アーティファクトキー | {org}/{proj}/wf2des/{wf2des_id}-{ts}-{spec\|parse\|feedback}.json |
内部 wf2des プレフィックス |
| S3 コンテンツアドレス指定 snapshot キー | wf2design/snapshots/{figma_file_key}/{node_id}.{content_hash}.json |
ワーカーがキャプチャした全ソース用 |
| S3 ルールボードレンダリングキー | wf2design/rule-boards/{figma_file_key}/{node_id}.{sha256}.png |
rule_process のボードごとの PNG レンダリング(Figma images API、scale 2)。design_rule.boards[].image_url から参照 |
| S3 config ホーム | {org}/{proj}/wf2des/config.json |
project_figma_file.config_url から参照 |
| モデルティアリング | FAST_MODEL=openai:gpt-4o-mini(parse の role/intent、mp@0.25)、STRONG_MODEL=openai:gpt-5.4(選択)、VISION_MODEL=openai:gpt-5.4-mini(rule_process はノードパイプライン bc@0.2 / sys@0.3 / cmp@0.3 / tok@0.2 を実行、vision パス re@0.10 はオプトアウト用フォールバック)、SCREEN_REVIEW_MODEL=openai:gpt-5.4。レジストリ拡充はスロットロール(sr@0.2)とコンポーネントセマンティクス(cs@0.4)を追加する |
inputs.llm は matching model、prompt version、review model、reasoning effort、enabled state を pin。具体的なモデル ID とティア割り当てはデプロイごとに設定可能。非 GPT review model は SCREEN_REVIEW_REASONING_EFFORT=none |
| Candidate/review switch | CANDIDATE_KIND_GATE=true、SCREEN_PLANNING_ENABLED=true、SCREEN_REVIEW_REASONING_EFFORT=high |
gate off は controlled evaluation のみ。reasoning none は provider-neutral |
formula_version(confidence) |
cf@0.6 — 実行ごとに pin |
confidence 定数を変更したら必ず bump する(confidence 値はそれを生成した式との対でしか解釈できない) |
| セクションシグネチャバージョン | 例: sig@1 — すべての design_resolution.section_signature の内部に付く |
シグネチャレシピの変更は新しいバージョンプレフィックスを発行。旧エントリは単にヒットしなくなる |
| 選択投票 | セクションごとに K サンプル(自己一貫性、(case, platform_design_id) の多数決) |
K は設定可能。同点は決定的ガードが裁定 |
config.json キュレーションキー |
memo_resolved_markers(デフォルト 取込済)· annotation_section_markers(ボードテンプレートの注釈見出し。出荷デフォルトあり)· variation_separator(デフォルト |)· design_area |
チーム慣習のプロジェクトごとの上書き — パイプライン自体はデザインシステム非依存のまま |
| サービスアカウント Figma 認証 | PRIVATE OAuth アプリ のグラント(Authorization: Bearer)。PAT はフォールバック |
Admin API が暗号化済みグラントを PostgreSQL の figma_oauth_grant に保存し、組織単位の advisory lock のもとでリフレッシュします。IAM で保護された内部 provider は有効なアクセストークン + 有効期限だけを返します。ワーカーは PostgreSQL 認証情報、リフレッシュトークン、クライアントシークレット、ENCRYPTION_KEY を持ちません。Figma には machine-to-machine フローが無いため、組織管理者が専用の Figma サービスアカウントで 一度だけ 認可します。provider 未設定のデプロイ向けに figma_pat はフォールバックとして残ります |
| wf2des-api(データプレーン) | internal-api アプリのルートグループ /internal/wf2des/*(Lambda Function URL。DocDB + S3 のみ を読み書き、PG/SQS は決してない) |
ファイル登録ルート POST/GET /internal/projects/{project_id}/figma-files(UNIQUE(organization_id, project_id, figma_file_key))— component_sweep がこのリストを読んで巡回をスコープ。認証: X-AI-Service-Token(read)/ plugin セッショントークン {org_id, project_id, exp}(read+write) |
| awaiting-confirm タイムアウト | 未実装。 想定: config、例: 72h — バックエンド側 sweep、fail closed | generation のみ。現状、park された実行は永久に待ち続ける |
stuck-'0' sweep 間隔 |
未実装。 想定: 例: ~15 分 | generation のみ。現状、'0' のまま残った行はそのワイヤーフレームを恒久的にブロックする |
| レイテンシソフトターゲット(p50) | trigger→parse プレビュー < 60s · confirm→spec < 2min · spec→materialized < 30s | ソフト、計測値、kill 基準ではない |
| サイズ上限 | spec > ~1MB → その S3 キー(spec.json)にスピル。spec_nodes_flat + summaries はインラインのまま |
generation 結果ドキュメントのみ。他のバイト上限は適用されない |
PROMPT_VERSION |
pin | プラットフォームワーカーレシピに従う |
ロギング
構造化ロギングは初日から期待されます。load-bearing なフィールド:
- Generation timings は結果ドキュメントの
timingsブロックに存在:created_at(実行 START アンカー。ターミナル書き込みタイムスタンプであるlineage.processed_atとは別 — 重複なし)、parse_done_at、assembled_at、加えてフェーズごとの所要時間<phase>_ms(例:snapshot_ms、parse_ms、selection_ms、assemble_ms、validate_ms)。確認レイテンシはtimingsのコピーではなくparse.confirmed_atから読み取られます。 - 選択記録(assemble):
selectionブロックが義務付けられた記録そのものです(セクションごとの候補カウント、composed/unmatched 集計、切り詰め)。validator_reportとconfidenceは計算され再現可能です。LLM が決定したすべては、レビュー/フィードバック帰属のためにエビデンスと共に保存されます。 - 取り込み issue(
wf_parse): screen-ID の文法違反、未知の memo マーカー、非 auto-layout ルートは、オペレーター修正のためstaging/issues.jsonに行きます(generation では該当せず、そこではscreen_idはリクエストから来ます)。 - 内部実行: 構造化ログイベントはここでは列挙されていません。推奨される実践: 各出力ドキュメントに
timings。component_sweepの鮮度はproject_figma_file.components_synced_atから、失敗はproject_figma_file.sweep_errorから読み取る。 - 初日からの可観測性: 推測ではなく計測された p50/p95 から最適化する。
フィールド参照(ルックアップ表)
load-bearing なフィールドを下流の置き場所へクロスウォークします。✓ = 存在/エコー。— = 不在。リテラル名 = リネーム。内部実行は Webhook を持ちません(—)。
| フィールド | Message in | DocDB out | Webhook out | 備考 |
|---|---|---|---|---|
wf2des_id |
✓(generation) | _id(design_generation_result) |
job_id |
実行 id = wf2des PG 行 id |
attempt |
✓(generation) | CAS (_id, attempt) をフェンス |
✓ | 現状は 1 固定。これをバンプするものは存在しない |
nonce |
✓(generation) | — | ✓ | Webhook 重複排除キーメンバー |
organization_id |
✓ | ✓ | — | テナンシースコープ(すべての読み書きがフィルタリング) |
project_id |
✓ | ✓ | — | テナンシースコープ |
screen_id |
✓(generation、リクエストから) | wireframe.screen_id(nullable)+ 結果 screen_id |
— | wf_parse は決定的に導出(文法)。ミス ⇒ null + issue |
wf_content_hash |
✓(parse) | lineage.source_hash ≡ inputs.wireframe.source_hash |
— | parse キャッシュキー |
snapshot_scope |
✓(parse) | 抽出を駆動 | — | フレーム + 包含する board section |
figma_file_key, node_id |
✓(wf_parse) |
複合 _id の内側。split('_', 2) で導出 |
— | 別途保存しない(wireframe、design_component) |
design_rule_id |
✓(rule_process) |
design_rule.design_rule_id |
— | content_hash と共に = 一意ビジネスキー |
figma_file_key, board_node_ids[](rule) |
✓(rule_process) |
design_rule.boards[] {node_id, title, image_url, content_hash, mime} |
— | 選択されたガイドラインボード。レンダリングは wf2design/rule-boards/… に保存 |
flag_count |
— | confidence.flag_count |
マニフェスト row_effects.wf2des 経由 |
wf2des.flag_count にミラー |
llm_usage |
— | design_generation_result.llm_usage |
— | この実行のコスト(ステージ別、プロバイダ自身の報告値)— design_rule.llm_usage と同じ形状 |
result_manifest_url |
— | — | ✓(succeeded/failed) | Webhook 専用。ターミナル S3 マニフェストオブジェクトを指す(結果ドキュメントには保存されない)。その内部の result_url → 結果アーティファクト |
status |
— | — | parse_done | succeeded | failed |
generation のみ |
関連リンク
- 概要 — generation/internal event の processing flow と module breakdown。
- テストケース — この契約を守るテストマトリクス。
apps/wf2des/src/wf2des/schemas/— Pydantic ドキュメントスキーマ(ドメインごとに 1 モジュール)。パッケージが全名称を再エクスポートするため、from wf2des.schemas import Xが契約サーフェスとなる。packages/models/src/models/documentdb/— 6 つの wf2des コレクション用のコレクションモジュール(COLLECTION_NAME+get_collection+create_indexes)。- Des2Code I/O 定義 —
component_sweepが UPSERT するプラットフォームdesign(type=component)レジストリを共有。