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/keyURLを使用します。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は次を行います。
- messageをstrictに検証し、
attempt = 1を要求する。 - 正確なPage Import resultを読み、byte hashを検証する。
- organization、project、Page Import ID、capture hash、許可S3 prefixを検証する。
- Page Importが宣言した正規化済み可視evidenceのみを読み込む。
- grouping、control、annotationを既存
layout_frame/texttreeへmapし、表現できない可視placeholderにだけunmatchedを使う。 - captured evidenceから決定的なannotation wordingを構築する。
- 既存WF2Des
DesignSpecModel、node union、固定Code2WF limitを正確に検証する。 - 不変な
result.jsonとterminal manifest、または不変なfailed.jsonとfailed terminal manifestを順に書く。 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を含むfulldocument_heightを使用します。Viewport heightでcropしません。 - Normal flow、flex、grid evidenceは、既存のdirection、padding、gap、alignment、wrap、bounding-box fieldを使うordered nested
layout_framenodeへ変換します。 - 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に既存の可視
unmatchedplaceholderを出力し、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成功後のみ設定 |