Skip to content

AI Design Import โ€” I/O Definition

This page is the official I/O contract for apps/design-import/. Any changes to fields, shapes, status values, or error categories constitute a contract change. Always update this page and test-case-design.md before writing code. The shared strict storage model in packages/models is authoritative for the persisted shape.

Reading order: Overview โ†’ Input โ†’ Processing contract โ†’ Output โ†’ Error handling โ†’ Idempotency. The final Field reference section is for lookups.


Overview

flowchart LR
  api[guinness-backend] -->|INSERT design status=0| RDB[(PostgreSQL)]
  api -->|PUT image.png| S3
  api -->|PUT schema.json| S3
  api -->|SendMessage| Q[(SQS design-import)]
  Q --> L[AI Design Import worker]
  L -->|GET image.png| S3
  L -->|GET schema.json| S3
  L -->|UPSERT _id=design_id| Doc[(DocumentDB design)]
  L -->|POST /v1/webhooks/ai-status| api
  api -->|UPDATE design status=1 or 2| RDB
Item Value
Trigger SQS record from the design-import queue
Input 1 SQS message + 1 S3 image + 1 S3 Figma JSON
Output 1 DocumentDB upsert + 1 Webhook POST
RDB writes None โ€” the worker does not access PostgreSQL / MySQL (database isolation)
External calls S3 GET (image + Figma JSON), current code-index read, Vision LLM via PydanticAI, provider-selected embeddings (1 variable-size batch), Webhook POST
AI framework PydanticAI structured output; embeddings use the configured OpenAI/OpenRouter client

Input

1. SQS Message (Trigger)

Lambda receives a standard AWS SQS event. The fields below are included as a JSON string in Records[*].body.

{
  "Records": [{
    "messageId": "0192a3b4-c5d6-7e8f-9a0b-1c2d3e4f5a6b",
    "body": "{\"organization_id\":1,\"project_id\":42,\"img_url\":\"s3://gnss-prod-inputs/designs/42_hDDA9B..._40002029:37033.png\",\"node_id\":\"40002029:37033\",\"file_id\":\"hDDA9BNori9OTXSClduXqR\",\"design_id\":\"42_hDDA9BNori9OTXSClduXqR_40002029:37033\",\"design_name\":\"Login Screen\",\"json_schema_url\":\"s3://gnss-prod-inputs/figma_json_schema/42_hDDA9BNori9OTXSClduXqR_40002029:37033.json\"}",
    "attributes": { "ApproximateReceiveCount": "1" },
    "eventSource": "aws:sqs"
  }]
}

Body fields (parsed from Records[*].body, validated by the SqsBody Pydantic model):

Field Type Required Convention Notes
organization_id integer โœ“ > 0 Tenant scope. Also copied to the DocumentDB document and echoed as organizationId in Webhook payloads
project_id integer โœ“ > 0 Project scope. Also copied to the DocumentDB document
img_url string โœ“ S3 URL (s3://... or HTTPS S3) Screenshot of the design
node_id string โœ“ Figma node id (typically <int>:<int>) Part of design_id
file_id string โœ“ Figma file id Part of design_id
design_id string โœ“ Must match "{project_id}_{file_id}_{node_id}" โ€” enforced by a Pydantic model_validator Becomes the _id in DocumentDB
design_name string โœ“ Figma frame name Stored as name and visual.metadata.name
json_schema_url string โœ“ S3 URL of the Figma node JSON export Required matching evidence. Downloaded, parsed, and persisted as structural/node metadata
figma_url string | null โœ— Canonical Figma URL Derived from file_id/node_id when omitted

Body unwrapping. The unwrap_sqs_body utility accepts the following 3 nested shapes (in priority order):

  1. Raw payload โ€” body is the JSON described above.
  2. SQS-in-SQS โ€” re-parses body.Records[0].body.
  3. API Gateway proxy โ€” body.body (optionally isBase64Encoded).

2. S3: Design Image

Property Value
Source img_url from the SQS body
Size limit 10 MB hard limit (MAX_IMAGE_BYTES in service.py)
MIME detection Shared media-type inference from the canonical URL; fallback is image/png

3. S3: Figma JSON

Property Value
Source json_schema_url from the SQS body
SQS field Required โ€” null or omitted is a validation error
Does the worker fetch it? Yes โ€” downloads and parses it for every successful import
On failure Sends failed Webhook and re-raises so SQS can retry

Expected Figma JSON shape: {"nodes": {"<node_id>": {"document": <node>, ...}}}

4. RDB Row State (Read Elsewhere)

The worker does not read the PostgreSQL design row directly. Before sending the SQS message, the backend creates the row with status = 0. The worker only reports completion via Webhook; the backend manages the row's state machine.


Processing Contract

The worker executes the following 8 steps in order for each record. Messages that fail at any step proceed to the Error handling path.

  1. Parse + validate: Validates the SQS body with SqsBody. Verifies that organization_id > 0, project_id > 0, and design_id == f"{project_id}_{file_id}_{node_id}"; raises ValueError if they do not match.

  2. Download the design image: Fetches from S3. Raises an exception if the image exceeds 10 MB.

  3. Download + parse Figma JSON with the shared deterministic evidence extractor to obtain:

  4. screen-level visible/generic terms and canonical structure text;
  5. bounded regions with node IDs, roles, query terms, bounds, composition counts, and typography evidence;
  6. concrete component instances with Figma component/component-set identities, properties, bounds, paths, and visible text.

If this step fails (missing keys, network error, etc.), the record fails. A successful design document always contains Figma-derived structural evidence.

  1. Run the Vision Agent (PydanticAI, run_sync): Obtains a DesignDescription from the image:

    class DesignDescription(BaseModel):
        layout: str                  # one of 11 archetypes
        component_types: list[str]   # 3โ€“15 normalized snake_case names
        color_palette: list[str]     # 2โ€“5 dominant hex colors (#RRGGBB)
        typography_style: str        # one of 8 archetypes
        semantic_words: list[str]    # 5โ€“10 domain keywords
        component_functions: list[str] # reusable component roles/functions
    
    Model used: desc_model (configured via environment variable, e.g. openai:gpt-5.4-nano).

  2. Require current code index and build embedding input texts:

  3. read the exact organization/project code_index and require status=ready;
  4. align index terms only when deterministic Figma evidence supports them;
  5. build one semantic text per vision sample, one screen structure text, and one text per bounded region.

  6. Generate semantic, structure, and region embeddings in a single batch call:

    client.embeddings.create(
        model=config.embedding_model,
        input=[*semantic_samples, structure_text, *region_texts],
        dimensions=config.embedding_dimensions,     # default 512
    )
    
    Semantic samples are averaged and normalized when VISION_SAMPLES > 1; the remaining vectors map to the screen structure and each region in stable order.

  7. Replace a scoped DesignDocument in DocumentDB (_id == design_id). A stable processing.hash skips repeated paid work when all required vectors already exist.

  8. POST the success Webhook (to webhook_base_url).

The document is written only after all required embeddings are available โ€” no partial documents exist.


Output

Output 1: DocumentDB design Document (upsert)

{
  "_id": "42_hDDA9BNori9OTXSClduXqR_40002029:37033",
  "organization_id": 1,
  "project_id": 42,
  "name": "Login Screen",
  "type": 0,
  "based_on": 0,
  "file_id": "hDDA9BNori9OTXSClduXqR",
  "node_id": "40002029:37033",
  "figma_url": "https://www.figma.com/design/hDDA9BNori9OTXSClduXqR?node-id=40002029-37033",
  "json_schema_url": "s3://bucket/1/42/design/hDDA9BNori9OTXSClduXqR/40002029-37033.json",
  "visual": {
    "metadata": {
      "name": "Login Screen",
      "image_url": "s3://bucket/1/42/design/hDDA9BNori9OTXSClduXqR/40002029-37033.png",
      "layout": "stacked_form",
      "component_types": ["input_email", "input_password", "button_primary", "heading"],
      "color_palette": ["#1A1A2E", "#FFFFFF", "#4A90E2"],
      "typography_style": "modern_sans"
    }
  },
  "semantics": {
    "metadata": {"words": ["authentication", "button", "email", "form", "login", "password", "submit"]},
    "vector_embedding": [/* 512 floats */]
  },
  "structure": {
    "metadata": {
      "visible_text": ["Email", "Password", "Login"],
      "generic_terms": ["form", "button", "input"],
      "text": "screen login structure with email password and submit regions",
      "regions": [{
        "metadata": {
          "node_id": "40002029:37101", "role": "action", "query_terms": ["login", "button"],
          "text": "login button", "bounds": {"x": 24, "y": 520, "width": 327, "height": 48},
          "depth": 1, "direct_child_count": 1, "descendant_text_count": 1,
          "descendant_instance_count": 1, "direct_child_type_counts": {"TEXT": 1},
          "repeated_child_signatures": [], "font_weights": [600], "font_sizes": [16]
        },
        "vector_embedding": [/* 512 floats */]
      }],
      "component_instances": [{
        "node_id": "40002029:37101", "name": "Button / Primary", "component_id": "comp_button_primary",
        "component_key": "button-key", "component_name": "Button", "component_description": null,
        "component_set_id": "button-set", "component_set_key": "button-set-key", "component_set_name": "Button",
        "properties": {"State": "Default"}, "bounds": {"x": 24, "y": 520, "width": 327, "height": 48},
        "depth": 1, "parent_node_id": "40002029:37033", "path": ["Login Screen", "Button / Primary"],
        "visible_text": ["Login"], "summary": "Button / Primary State=Default text=Login"
      }]
    },
    "vector_embedding": [/* 512 floats */]
  },
  "processing": {"hash": "<64-character SHA-256 fingerprint>"}
}

Field reference:

Field Type Source
_id string SQS design_id
organization_id integer SQS organization_id
project_id integer SQS project_id
name string SQS design_name
type integer enum Default 0 (DesignType.SCREEN)
based_on integer enum Default 0 (DesignOrigin.FIGMA_IMPORT)
file_id string SQS file_id; Figma file key
node_id string SQS node_id; Figma node id
json_schema_url string SQS json_schema_url; downloaded and parsed
figma_url string SQS value or canonical URL derived from file/node IDs
visual.metadata.layout string Vision LLM output
visual.metadata.component_types string[] Vision LLM output (3โ€“15 items)
visual.metadata.color_palette string[] Vision LLM output, normalized #RRGGBB (2โ€“5 items)
visual.metadata.typography_style string Vision LLM output
semantics.metadata.words string[] Canonical vision terms plus evidence-aligned current-index terms
semantics.vector_embedding float[512] Averaged semantic vector when multiple vision samples are configured
structure.metadata object Screen text, bounded regions, and concrete Figma component occurrences
structure.vector_embedding float[512] Embedding of screen-level structure text
structure.metadata.regions[].vector_embedding float[512] Per-region structural query vector
processing.hash string Stable SHA-256 fingerprint used for unchanged-import reuse

Write operation:

collection.replace_one(
    {"_id": design_id, "organization_id": organization_id, "project_id": project_id},
    doc.model_dump(by_alias=True, mode="json"),
    upsert=True,
)

Indexes (created idempotently by packages/models/documentdb/design.py):

Index name Path Dimensions Similarity m efConstruction
design_scope_catalog organization_id, project_id, type, name โ€” โ€” โ€” โ€”

Design vectors are query vectors used against code/code-variation indexes; no design vector index is provisioned.

Output 2: Webhook โ€” success

POST {WEBHOOK_BASE_URL}
Content-Type: application/json

{
  "type":      "design-import",
  "recordId":  "42_hDDA9BNori9OTXSClduXqR_40002029:37033",
  "projectId": 42,
  "organizationId": 1,
  "status":    "success",
  "result": {
    "keywords":             ["login", "authentication", "form", "submit", "credentials"],
    "textVectorDims":       512,
    "vectorsProduced":      ["semantic", "structure", "regions"]
  }
}
Field Type Notes
type string Always "design-import"
recordId string design_id from the SQS body
projectId integer From SQS project_id
organizationId integer From SQS organization_id
status string "success"
result.keywords string[] Same values as semantics.metadata.words
result.textVectorDims integer Number of dimensions for semantic, structure, and region vectors
result.vectorsProduced string[] Always ["semantic", "structure", "regions"] on success

Output 3: Webhook โ€” failure

POST {WEBHOOK_BASE_URL}
Content-Type: application/json

{
  "type":      "design-import",
  "recordId":  "42_hDDA9BNori9OTXSClduXqR_40002029:37033",
  "projectId": 42,
  "organizationId": 1,
  "status":    "failed"
}

The result block is absent on failure. A failure webhook is sent only after a valid body provides the record and scope IDs. Backend updates the design import status.


Error Handling

Partial batch failure support. The handler returns {"batchItemFailures": [...]}, so a single failure does not contaminate the rest of the batch. ReportBatchItemFailures must be enabled on the Lambda event source mapping. The worker emits a failure Webhook before marking an item as failed. SQS then retries only that failed item independently.

def lambda_handler(event, context):
    failures = []
    for record in event["Records"]:
        try:
            process_record(record, ...)
        except Exception:
            failures.append({"itemIdentifier": record["messageId"]})
    return {"batchItemFailures": failures}
Scenario Behavior
Invalid SQS event envelope (no Records) Raises ValueError. No Webhook. Entire invocation fails
design_id does not match composite key Pydantic validation error. Failure Webhook. Added to batchItemFailures
S3 GET for img_url fails Failure Webhook. Added to batchItemFailures
Image exceeds 10 MB ValueError. Failure Webhook. Added to batchItemFailures
Missing json_schema_url Validation error. Failure Webhook. Added to batchItemFailures
S3 GET for json_schema_url fails Failure Webhook. Added to batchItemFailures
Figma JSON parse error Failure Webhook. Added to batchItemFailures
Vision LLM exception Failure Webhook. Added to batchItemFailures
Embedding API exception Failure Webhook. Added to batchItemFailures
DocumentDB write exception Failure Webhook. Added to batchItemFailures. Up to 2 retries for transient failures
Webhook POST itself fails WARN log. Added to batchItemFailures so SQS retries
SQS retry count exceeded Message is moved to DLQ (configured outside the worker)

Idempotency

Three layers are applied in order.

  1. Fingerprint-aware scoped replacement: identical evidence reuses the valid stored vectors; changed evidence replaces the exact scoped document.
  2. Stateless worker: No writes to PostgreSQL and no local checkpoints.
  3. At-least-once Webhook: A failure Webhook followed by a success Webhook may arrive twice. The backend must be idempotent for (type, recordId).

Locked Parameters

Parameter Value / Source Notes
Embedding model EMBEDDING_MODEL environment variable e.g. text-embedding-3-small
Embedding dimensions EMBEDDING_DIMENSIONS environment variable (default 512) Must match HNSW index configuration
Vision model DESC_MODEL environment variable e.g. openai:gpt-5.4-nano
Agent framework PydanticAI Not changeable via environment variable
Agent execution mode run_sync (synchronous) Once per record
Embedding API calls per changed record 1 variable-size batch semantic sample(s) + structure + every region
Image size limit 10 MB Hard-coded as MAX_IMAGE_BYTES in service.py
Vision timeout 60 seconds VISION_TIMEOUT_SECONDS in service.py
Vision samples VISION_SAMPLES, default 1 When greater than one, semantic sample vectors are averaged and normalized

Logging

Structured fields bound per record: app=ai-design-import, message_id, prompt_version, design_id, organization_id, project_id.

Event Level Timing
design_import.received INFO First line of process_record
design_import.parsed INFO After Pydantic validation
design_import.s3.image.downloaded INFO After image GET (size_bytes, media_type)
design_import.figma_evidence.extracted INFO After Figma JSON parse (visible text, region, component-instance counts)
design_import.vision.completed INFO After Vision LLM (sample/component/semantic-word counts and layout)
design_import.embeddings.completed INFO After embedding call (dimensions and sample count)
design_import.documentdb.upserted INFO After upsert (matched, modified, upserted_id)
design_import.webhook.sent INFO After Webhook POST (http_status)
design_import.failed ERROR Terminal failure inside process_record
design_import.webhook.failed WARN Webhook POST itself failed

Field Reference (Lookup Table)

Field SQS in Doc out Webhook out Notes
design_id โœ“ _id recordId Composite id (enforced validation)
organization_id โœ“ โœ“ organizationId tenant scope
project_id โœ“ โœ“ projectId project scope
design_name โœ“ name, visual.metadata.name โ€”
type โ€” โœ“ (default 0) โ€” screen
based_on โ€” โœ“ (default 0) โ€” Figma import
img_url โœ“ visual.metadata.image_url โ€”
json_schema_url โœ“ โœ“ โ€” Required Figma node JSON; downloaded and parsed
node_id, file_id โœ“ โœ“ โ€” Components of design_id; stored for downstream evidence
figma_url optional โœ“ โ€” Supplied or canonically derived
visual.metadata.* โ€” โœ“ โ€” name, image, layout, component types, palette, typography
semantics.metadata.words โ€” โœ“ result.keywords
semantics.vector_embedding โ€” โœ“ โ€” 512 floats
structure.metadata.text โ€” โœ“ โ€” screen-level structure text
structure.metadata.regions โ€” โœ“ โ€” bounded region metadata + 512-vector each
structure.metadata.component_instances โ€” โœ“ โ€” concrete Figma occurrence evidence
structure.vector_embedding โ€” โœ“ โ€” screen structure vector, 512 floats
processing.hash โ€” โœ“ โ€” unchanged-import fingerprint
status โ€” โ€” "success" \| "failed"
result.textVectorDims โ€” โ€” โœ“
result.vectorsProduced โ€” โ€” โœ“

  • Overview โ€” Processing flow diagram and module breakdown.
  • Test cases โ€” Test matrix that enforces this contract.
  • packages/models/documentdb/design.py โ€” scoped catalog index definition.
  • packages/agentic/vision.py โ€” DesignDescription structured output schema.
  • packages/des2code-core โ€” deterministic Figma JSON evidence extraction.