コンテンツにスキップ

AI Code2WF — I/O定義

この文書は apps/code2wf/ の計画上の契約を定義します。Code2WFには非同期生成attemptが1回あり、その後にclient側のFigmaマテリアライズがあります。ページ実行とcaptureはPage Importの責務です。


概要

flowchart LR
  PI[("完了済みPage Import")] --> API["Backend"]
  API -->|"解決済み不変result"| Q[("Code2WF SQS")]
  Q --> W["Code2WF worker"]
  W --> OUT[("S3上のCode2WF result artifact")]
  W -->|"ai-status"| API
  API -->|"検証済みresult artifact"| PL["Figmaプラグイン"]
関心事 契約
ソース指定 HTTP pageImportId
ソース適格性 同じorganization/project、未削除、completed
PostgreSQLソース参照 page_import_id のみ
Workerソース参照 dispatch時に解決した正確なPage Import result URL/hash/capture hash
Attempt MVPでは 1 固定
Workerのdatabase access なし
DocumentDB 使用しない
Figma書き込み pluginのみ

共通プリミティブ

生成status

Code2WFはFigma generation rowで使用中の既存 wf2des_status 型を再利用し、次のsubsetを使用します。

値 意味
"0" processing
"1" completed
"2" failed

Enumに既存のrejected/cancelled値はありますが、Code2WFにはconfirm、reject、cancel transitionがないため使用しません。

phase とmaterialization statusはありません。attempt はinteger literal 1 です。

Hashとobjectルール

  • Content hashは sha256: prefix付きlowercase SHA-256です。
  • Worker契約のsource参照はcanonical s3://bucket/key URLを使用します。Terminal manifest/result pointerはWF2Desに合わせ、RESULT_BUCKETから解決するbare keyを使います。
  • Result JSONはUTF-8を使い、hash計算前に決定的なkey order/serializationを使用します。
  • Code2WF SQS messageとouter result envelopeでは未知fieldを拒否します。共有WF2Des Pydantic modelは未知node discriminatorを拒否しますが、現在はextra object propertyを無視するため、Code2WF producerは文書化済みの共有fieldだけを出力し、その正確なshapeをtestします。

Result key

{organization_id}/{project_id}/code2wf/{code2wf_id}/1/result/result.json
{organization_id}/{project_id}/code2wf/{code2wf_id}/1/result/manifest.json
{organization_id}/{project_id}/code2wf/{code2wf_id}/1/result/failed.json
{organization_id}/{project_id}/code2wf/{code2wf_id}/1/result/failed-manifest.json

WF2Desのtimestamp付きmulti-phase artifactと異なり、Code2WFはrowごとに1つの固定attemptだけを持ちます。決定的なattempt単位のkeyにより、SQS redeliveryは同じartifact pairをvalidationして再利用できます。

Successではworkerがclient向けspecを先に、terminal manifestを最後に書きます。Failureではclient向けfailed artifactを先に、failed terminal manifestを最後に書きます。すべてcreate-only writeです。重複deliveryは既存pairを検証して再利用し、どちらも上書きしません。

Terminal manifestはcallback専用です。WF2Des規約に従い、webhookがそのURLを運び、backendが row_effects.code2wf を読み、内側のclient向け result_url をPostgreSQL行に保存します。PostgreSQLにmanifest URLは保存しません。

{
  "manifest_schema_version": 1,
  "job_id": "019ffa75-01c0-75a2-8123-456789abcdf0",
  "attempt": 1,
  "nonce": "8bed02b2-c93d-4854-8f4f-1c91744dc419",
  "job_type": "generation",
  "result_url": "1/7/code2wf/019ffa75-01c0-75a2-8123-456789abcdf0/1/result/result.json",
  "flag_count": 0,
  "row_effects": {
    "code2wf": {
      "status": "1",
      "result_url": "1/7/code2wf/019ffa75-01c0-75a2-8123-456789abcdf0/1/result/result.json",
      "flag_count": 0
    }
  }
}

Failure manifestは result_url に failed.json を使い、row_effects.code2wf.status = "2" とします。Backendは適用前にjob ID、attempt、nonce、job type、許可S3 prefix、row effect、statusを検証します。Successでは flag_count が検証済みspec内の flagged=true outcome数と一致します。


入力1: Backend Trigger

{
  "pageImportId": "0195f16e-6f11-7ea8-b45c-f6712ed9d8a3",
  "figmaFileKey": "aBc1DeFg2HiJ3kLmNoPqRs",
  "screenId": "AUTORACE_DATABASE",
  "placementTarget": "128:9001"
}

Backendが code2wfId をUUIDv7で生成します。同一bodyの2回のtriggerを含め、有効なtriggerごとに新しいrowを作成し、新しいjobをdispatchします。HTTP requestはcaller指定job IDを受け付けません。

BackendはFigma destinationと page_import_id を保存し、SQS messageを組み立てる時点で完了済みPage Importの不変normalized manifestを1回readして検証します。その正確なbytesから一貫したresult URL/hash/capture-hash pin setを得ます。Page Import URL/hashは code2wf 行へcopyせず、PostgreSQL/S3 transactionも主張しません。

正確なHTTP契約はCode2WFトリガーで定義します。


入力2: Conversion SQS Message

{
  "code2wf_id": "019ffa75-01c0-75a2-8123-456789abcdf0",
  "organization_id": 1,
  "project_id": 7,
  "attempt": 1,
  "nonce": "8bed02b2-c93d-4854-8f4f-1c91744dc419",
  "page_import_id": "0195f16e-6f11-7ea8-b45c-f6712ed9d8a3",
  "page_result_url": "s3://bucket/1/7/page-import/0195f16e-6f11-7ea8-b45c-f6712ed9d8a3/normalized/manifest.json",
  "page_result_hash": "sha256:44a7f61a6f90f476c349b264c0cbd824ce45ecad0d9985f0750f8a251aa9592a",
  "capture_hash": "sha256:90e54d8b9cf4ff0fdc2b927b4453d59f4c3e219c236bb398e660aca50a62ae27",
  "screen_id": "AUTORACE_DATABASE"
}
Field 型 検証
code2wf_id UUID row identityとresult prefix
organization_id 正のinteger Page Import resultとS3 prefixに一致
project_id 正のinteger Page Import resultとS3 prefixに一致
attempt integer 1 と一致
nonce UUID transport correlation専用。保存せずPostgreSQL CAS fieldにしない
page_import_id UUID Page Import resultと一致
page_result_url S3 URL backendが解決した正確な不変の完了済みPage Import result
page_result_hash SHA-256 page_result_url の正確なUTF-8 bytes
capture_hash SHA-256 Page Import resultの正規化capture hashと一致
screen_id string 1–255文字。trigger rowからcopy

Figma destination field、credentials、source code、HTML、CSS、presigned URLはSQSに含めません。


Processing契約

各SQS recordについてworkerは次を行います。

  1. messageをstrictに検証し、attempt = 1 を要求する。
  2. 正確なPage Import resultを読み、byte hashを検証する。
  3. organization、project、Page Import ID、capture hash、許可S3 prefixを検証する。
  4. Page Importが宣言した正規化済み可視evidenceのみを読み込む。
  5. grouping、control、annotationを既存 layout_frame / text treeへmapし、表現できない可視placeholderにだけ unmatched を使う。
  6. captured evidenceから決定的なannotation wordingを構築する。
  7. 既存WF2Des DesignSpecModel、node union、固定Code2WF limitを正確に検証する。
  8. 不変な result.json とterminal manifest、または不変な failed.json とfailed terminal manifestを順に書く。
  9. ai-status でterminal success/failureを通知する。

MVPにはCode2WF専用annotation model、node type、asset map、rendering contractを追加しません。Retry可能なprocessing failureは一時的なstorage、queue、webhook dependencyに限定します。


出力1: Code2WF result artifactと既存 DesignSpecModel

{
  "job_id": "019ffa75-01c0-75a2-8123-456789abcdf0",
  "attempt": 1,
  "organization_id": 1,
  "project_id": 7,
  "screen_id": "AUTORACE_DATABASE",
  "status": "success",
  "generated_at": "2026-08-13T09:31:00Z",
  "inputs": {
    "page_import_id": "0195f16e-6f11-7ea8-b45c-f6712ed9d8a3",
    "page_result_url": "s3://bucket/1/7/page-import/0195f16e-6f11-7ea8-b45c-f6712ed9d8a3/normalized/manifest.json",
    "page_result_hash": "sha256:44a7f61a6f90f476c349b264c0cbd824ce45ecad0d9985f0750f8a251aa9592a",
    "capture_hash": "sha256:90e54d8b9cf4ff0fdc2b927b4453d59f4c3e219c236bb398e660aca50a62ae27"
  },
  "spec": {
    "spec_version": "1.0",
    "parse_confirmed": true,
    "style_bindings": {},
    "root": {
      "node": "layout_frame",
      "layer_path": "root",
      "auto_layout": {
        "direction": "horizontal",
        "gap": 32.0,
        "padding": 0.0,
        "sizing": "fixed",
        "gaps": [],
        "primary_align": "",
        "counter_align": "",
        "wrap": "",
        "counter_gap": 0.0
      },
      "fill": "#ffffff",
      "corner_radius": 0.0,
      "bbox": { "x": 0.0, "y": 0.0, "w": 1824.0, "h": 1860.0 },
      "lineage_wf_node_ids": [],
      "children": [
        {
          "node": "layout_frame",
          "layer_path": "root/screen",
          "auto_layout": {
            "direction": "vertical",
            "gap": 16.0,
            "padding": 24.0,
            "sizing": "fixed",
            "gaps": [],
            "primary_align": "",
            "counter_align": "",
            "wrap": "",
            "counter_gap": 0.0
          },
          "fill": "#ffffff",
          "corner_radius": 0.0,
          "bbox": { "x": 0.0, "y": 0.0, "w": 1440.0, "h": 1860.0 },
          "lineage_wf_node_ids": [],
          "children": [
            {
              "node": "layout_frame",
              "layer_path": "root/screen/details-button",
              "auto_layout": {
                "direction": "horizontal",
                "gap": 8.0,
                "padding": [12.0, 16.0, 12.0, 16.0],
                "sizing": "fixed",
                "gaps": [],
                "primary_align": "",
                "counter_align": "CENTER",
                "wrap": "",
                "counter_gap": 0.0
              },
              "fill": "#e5e7eb",
              "corner_radius": 4.0,
              "bbox": { "x": 24.0, "y": 24.0, "w": 240.0, "h": 48.0 },
              "lineage_wf_node_ids": [],
              "children": [
                {
                  "node": "text",
                  "layer_path": "root/screen/details-button/label",
                  "content": "View race details",
                  "style_token": "",
                  "style_refs": {},
                  "font_size": 16.0,
                  "font_weight": 600,
                  "color": "#111111",
                  "bbox": { "x": 16.0, "y": 12.0, "w": 160.0, "h": 24.0 },
                  "confidence": 1.0,
                  "flagged": false,
                  "source": { "kind": "none", "component_key": null },
                  "lineage_wf_node_ids": []
                },
                {
                  "node": "text",
                  "layer_path": "root/screen/details-button/A001",
                  "content": "[A001]",
                  "style_token": "",
                  "style_refs": {},
                  "font_size": 12.0,
                  "font_weight": 600,
                  "color": "#111111",
                  "bbox": { "x": 184.0, "y": 12.0, "w": 40.0, "h": 24.0 },
                  "confidence": 1.0,
                  "flagged": false,
                  "source": { "kind": "none", "component_key": null },
                  "lineage_wf_node_ids": []
                }
              ]
            }
          ]
        },
        {
          "node": "layout_frame",
          "layer_path": "root/annotations",
          "auto_layout": {
            "direction": "vertical",
            "gap": 12.0,
            "padding": 16.0,
            "sizing": "fixed",
            "gaps": [],
            "primary_align": "",
            "counter_align": "",
            "wrap": "",
            "counter_gap": 0.0
          },
          "fill": "#f3f4f6",
          "corner_radius": 4.0,
          "bbox": { "x": 1472.0, "y": 0.0, "w": 352.0, "h": 120.0 },
          "lineage_wf_node_ids": [],
          "children": [
            {
              "node": "text",
              "layer_path": "root/annotations/A001",
              "content": "[A001] Navigates to /proto-pages/auto-race/at_db_rslt03",
              "style_token": "",
              "style_refs": {},
              "font_size": 14.0,
              "font_weight": 400,
              "color": "#111111",
              "bbox": { "x": 16.0, "y": 16.0, "w": 320.0, "h": 48.0 },
              "confidence": 1.0,
              "flagged": false,
              "source": { "kind": "none", "component_key": null },
              "lineage_wf_node_ids": []
            }
          ]
        }
      ]
    }
  }
}

Result envelope契約

Field 契約
job_id WF2Des result artifact規約に従うCode2WF row UUID
attempt literal 1
organization_id, project_id inputと一致する正のinteger
screen_id 1–255文字。trigger値と完全一致
status WF2Des result artifact規約に従うliteral success
generated_at UTC ISO 8601 terminal-write time
inputs SQS messageの正確なPage Import ID、不変result pointer/hash、capture hash
spec 既存WF2Des DesignSpecModel と完全に同じobject

Workerはnested specをWF2Desと同じDesignSpecModel.model_dump(mode="json")規約で永続化します。そのため、lineage_wf_node_ids、style_refs、source.component_key、上記のすべてのAutoLayout defaultなど、共有modelのdefault値fieldもartifactに含まれます。Code2WF専用serializerやdefaultの選択的省略は追加しません。

既存 DesignSpecModel 契約

Field 契約
spec_version Code2WFは現在の共有version 1.0 を要求する。共有model自体ではstring field
parse_confirmed literal true。Code2WFにconfirm phaseがないためのcompatibility field
style_bindings low-fi MVPではempty object。既存text fallback fieldでneutral typographyを保持する
root 既存 SpecNode 1件。Code2WFは既存 layout_frame rootを出力する

Node契約

Code2WFは2つ目のFigma schemaを定義しません。Nested spec は同じAI DesignSpecModel でvalidationされ、pluginの既存 AssemblySpec にそのまま渡せます。既存node union全体は layout_frame、instance、compose、text、unmatched で、Code2WF MVPが出力するのは layout_frame、text、unmatched のみです。

Field 契約
node 既存 layout_frame、text、unmatched
layer_path 一意のslash区切りpath、1–1024文字
bbox finite x/y: -1,000,000…1,000,000、w/h: 0…32,768

layout_frame はWF2Desと同じ auto_layout、fill、corner_radius、bbox、lineage_wf_node_ids、ordered children field名を使います。Directionは horizontal または vertical、paddingは1つのnumberまたは [top,right,bottom,left]、sizingは既存の hug または fixed、uniform gap 使用時の gaps はemptyです。1 frameのdirect childは最大1,000です。

text は既存の必須field content、style_token、confidence、source と、既存fallback fieldの font_size、font_weight、color、bbox を使います。Code2WF生成textは source.kind="none" を使い、既存の wireframe source kindはWF2Des wireframe fallback専用のままです。Low-fi controlは新しいprimitiveではなく、nested layout_frame / text nodeです。Unsupported mediaやshapeは、既存の placeholder {role,text,bbox}、flagged=true、source.kind="none" contractを持つ可視 unmatched nodeにします。

Annotationも通常の既存nodeです。Rootにはscreen frameとannotation-panel frameを置き、[A001] のようなstable markerはoriginal control labelのsiblingとして配置し、annotation text rowにも再表示するため、captureされたcopyは変更しません。Numberingはnormalized DOM orderに従います。Wordingは Navigates to {href}、Submits {METHOD} to {action}、Input: {type}; required、Capture warning: {code} のようなevidence-only固定templateを使い、captureされていないJavaScript behaviorは推測しません。

Geometry変換

  • Screen frameはPage Importのdocument_widthとbelow-the-foldを含むfull document_heightを使用します。Viewport heightでcropしません。
  • Normal flow、flex、grid evidenceは、既存のdirection、padding、gap、alignment、wrap、bounding-box fieldを使うordered nested layout_frame nodeへ変換します。
  • Childのbbox.x/yはsource evidenceおよびvalidation用であり、新しいabsolute-positioning instructionではありません。Frame内のplacementは既存auto-layout treeで決まります。Code2WFはabsolute-position fieldやnode caseを追加しません。
  • Overlap、transform、fixed/sticky positioning、その他のrelationshipをvisible contentの非表示や並べ替えなしで表現できない場合、workerはunsupported fidelityを主張せず、そのlocationに既存の可視unmatched placeholderを出力し、flagged=trueにします。

未知node discriminator値は共有 DesignSpecModel validationで失敗します。Extra propertyは現在の共有Pydantic modelでは無視されるため、Code2WFはそれらへ依存せず、文書化済み共有fieldだけを出力します。

既存planner、node case、materializer、wf2des stamp/index、rebuild-and-swap pathを再利用し、Code2WF専用rendererやnode schemaは追加しません。Current builderはすでにこれらのnode caseを作成し、frame fill、padding/gap/alignment、width pin、text content、unmatched placeholderを処理します。Code2WF release前に、同じ共有builderが共有schemaにすでに存在するfieldのサポートを完了する必要があります。sizing="fixed"はbbox.wとbbox.hの両方をpinし、sizing="hug"は現在のbbox.w pinとauto heightを維持します。wrap="WRAP"はauthoritativeで、empty wrapでは現在のoverflow-safety wrapだけを維持し、wrapがactiveな場合はcounter_gapをFigma counter-axis spacingへmapします。Text fallbackはstyle bindingより先にfont_size、font_weight、color、bbox.wを適用し、clipせずtext heightの拡張を許可し、解決に成功したbindingがそのfieldをoverrideします。Requested weightをloadできない場合は既存の共有font fallbackとdiagnosticを使います。Shared plugin typeも既存Pydantic serializationに合わせ、source.component_key: nullを受け付ける必要があります。これらは共有contract/materializerのalignmentであり、並行するCode2WF pathではありません。

計画中のplugin wiringはrowの任意placementTargetを解決し、targetのcurrent page上のabsolute rectangleを既存placeRootへ渡します。Shared functionはgenerated rootをそのrectangleのx/yに配置し、targetの置換、resize、deleteは行いません。Targetが未指定、stale、またはother-pageなら既存のviewport-center fallbackを使います。MVPでは既存root label wf2des · 1.0 と既存plugin-data namespaceもそのまま使用します。

固定V1 limit

Limit 値
UTF-8 result.json size 10 MiB
Total node 5,000
Tree depth 64
Visible annotation row 500

構造limit超過はterminal result_limit_exceeded failureです。workerはinteractive contentを黙って削除しません。既存specにはCode2WF専用assets、annotations、warnings collectionはありません。


出力2: Terminal Webhook

Successは不変terminal-manifest pointerを通知します。

{
  "type": "code2wf",
  "job_id": "019ffa75-01c0-75a2-8123-456789abcdf0",
  "attempt": 1,
  "nonce": "8bed02b2-c93d-4854-8f4f-1c91744dc419",
  "manifest_schema_version": 1,
  "status": "succeeded",
  "result_manifest_url": "1/7/code2wf/019ffa75-01c0-75a2-8123-456789abcdf0/1/result/manifest.json"
}

Stableなworker failureは同じenvelopeで status: "failed"、failed-manifest.json を指す result_manifest_url、安全な error: {message} を使い、WF2Des producer contractに合わせます。Backendはmanifestを検証し、内側のfailed-artifact result_url と安全なerrorを保存し、(id, attempt, status="0") が一致する間はrowの flag_count をnullのままにします。不変artifactとrow CASにより、row terminal後の重複eventはaccepted no-opです。


エラーハンドリング

Code Retry可能 意味
invalid_message No SQS bodyがclosed contractに違反
source_not_found No 解決済みPage Import result/objectが存在しない
source_hash_mismatch No source bytesがdispatch hashと不一致
source_scope_mismatch No tenant、import ID、capture hash、prefixが不一致
unsupported_source_schema No Page Import result versionが未対応
invalid_result No 生成specがclosed schemaに違反
result_limit_exceeded No 生成specが固定V1 limitを超過
storage_unavailable Yes 一時的なS3 dependency failure
webhook_unavailable Yes terminal eventがacceptされない

Annotation model errorは含めません。MVPはannotation textを決定的に生成し、annotation専用modelを呼びません。


冪等性とフェンシング

  • Acceptされたtriggerごとにbackendが新しいUUIDv7を生成します。同一request bodyでも別row/jobを作成し、callerはjob IDを指定または再利用できません。
  • MVPでは attempt を 1 に固定します。Backendのterminal updateは (id, attempt=1, status="0") をPostgreSQL compare-and-set fenceとして使います。
  • Workerはclient向けresult/failure artifactを先に、terminal manifestを最後に書きます。どちらもcreate-onlyです。Duplicate deliveryは既存pairを正確に検証して再利用し、identityまたはPage Import pinが異なる場合はfail closedして上書きしません。
  • Backendはterminal manifestを検証し、processing-row fenceが一致する場合だけ row_effects.code2wf を適用します。Terminal transition後のduplicate/conflicting callbackはaccepted no-opです。
  • nonce はwebhook transport correlation専用です。PostgreSQLには保存せず、row fenceにも使いません。
  • Placementは既存のrow-level materialized_at 規約で冪等です。MVPにはcross-session materialization claim/leaseを追加しません。

Runtime設定

Config 値 / Source 備考
RESULT_BUCKET 共有result bucket 許可済みPage Import s3:// inputを解決し、不変Code2WF result/manifest keyを保持
WEBHOOK_BASE_URL 非公開backend origin Code2WFが既存 ai-status endpointへterminal statusをPOST
WEBHOOK_API_KEY Deployment secret 既存 X-API-Key webhook headerとしてのみ送信

Code2WF queueはLambda event-source mappingで接続するため、workerにqueue URLは不要です。Workerはmodel、DocumentDB、PostgreSQL、Figma token、browserのconfigurationを持ちません。


ロギング

Structured logには code2wf_id、organization_id、project_id、attempt、screen_id、terminal outcome、安全なerror code、source/result hash、node/flag count、read/convert/validate/write/webhook durationを含めます。Logとfailure payloadにはraw source code、DOM/page copy、object bytes、Cookie、credential、authorization header、browser storage、presigned URLを含めません。


フィールド参照(ルックアップ表)

— はそのsurfaceにfieldが存在しないことを示します。

HTTP field PostgreSQL code2wf SQS message Result artifact 備考
code2wfId id code2wf_id job_id backend生成UUIDv7
pageImportId page_import_id page_import_id inputs.page_import_id completed Page Import rowを参照
pathのorganization/project ID organization_id, project_id organization_id, project_id organization_id, project_id 全accessをtenant scopeで制限
— attempt attempt attempt MVPではliteral 1
— — nonce — webhook transport correlation専用
— — page_result_url, page_result_hash, capture_hash inputs 配下の同名field 検証済みの不変Page Import pin set 1件。PostgreSQLへcopyしない
figmaFileKey figma_file_key — — backend/plugin discovery専用
screenId screen_id screen_id screen_id trigger値と完全一致
placementTarget placement_target — — 任意のplugin placement reference専用
— result_url — 決定的result keyへwrite backendはmanifest row effect内側のclient向けartifact URLを保存
— flag_count — spec から算出 検証済み共有specの flagged=true outcome数
placement response materialized_at — — plugin placement成功後のみ設定

関連リンク