Skip to content

AI Des2WF — Backend Contract

The product control plane for Des2WF, in guinness-backend. This page is the surface contract; per-endpoint pages under development/apps/backend/user/api-definition/des2wf/ follow the frozen shapes.

The split is unchanged from WF2Des and is the system's main invariant: the backend owns the product contract and PostgreSQL; the AI side owns the runs. The worker never writes PostgreSQL and holds no PG credentials.


Endpoints

Release 1 is deliberately narrower than WF2Des's twenty-endpoint surface, because a one-shot flow with no confirm step does not need most of it.

Endpoint Purpose Release 1
POST /des2wf create a run: insert the row (edge=des2wf), capture the design snapshot, enqueue. The body carries the wireframe's mode and layout, plus an optional client-resolved sectionNodeId to scope the capture (see below) yes
GET /des2wf/{id} run status, phase, attempt, result refs, flag count, score yes
POST /des2wf/{id}/cancel cancel an open run and release the fence yes
GET /des2wf/{id}/result the result document — the emitted spec, the score and the artifact refs, the worker's own shape passed through yes
POST /des2wf/{id}/render plugin posts the design and wireframe PNGs after materializing; the pair is stored and the render review queued yes
POST /des2wf/component-uploads register the wireframe kit from a plugin selection of kit boards yes
POST /des2wf/component-captures submit the plugin's per-variant captures for that kit yes
GET /des2wf/kit wireframe-kit summary for the setup view: how much is registered, and how much of it can be instanced yes
POST /des2wf/{id}/confirm — no: one-shot, nothing to confirm
POST /des2wf/{id}/feedback — no: no human-correction loop exists in this product
rule endpoints — no: Des2WF ingests no rule set

There is no confirm checkpoint, feedback endpoint or rule-ingestion flow. Explicit human graphic overrides are an operator CLI facility, scoped to organization/project/file, not a plugin feedback API. See Stores.


The trigger body

POST /organizations/{organization_id}/projects/{project_id}/des2wf

Field Required Values Meaning
figmaFileKey yes Figma verbatim (alphanumeric, no underscore) the file the design frame lives in
sourceNodeId yes Figma node id the design frame — the input node
sectionNodeId no Figma node id the frame's enclosing board/SECTION, which scopes the snapshot fetch (see below)
mode no primitives (default) | kit what the wireframe is drawn from
layout no absolute (default) | auto what kind of wireframe it is

mode. primitives preserves copy and source geometry with neutral primitive surfaces. Both modes classify graphics from contextual source-image evidence when available; primitives does not mean “no model calls.” kit adds sampled kit-variant selection and deterministic guards. Unusable kit selections fall back to primitives, while uncertain graphics preserve source artwork.

layout. Independent of mode. absolute keeps source coordinates without reflow. auto preserves source container ownership, including one-child wrappers, native itemSpacing, padding, alignment, wrapping, per-axis sizing and absolute overlays.

The API defaults remain mode=primitives and layout=absolute; the endpoint resolves them into the queue message. The current plugin explicitly sends layout=auto and has no layout selector. Neither mode guarantees byte-identical generation because graphic classification can use a model.

{
  "figmaFileKey": "9kQzR4TmVb2NxPy7LcWd1s",
  "sourceNodeId": "2199:40144",
  "sectionNodeId": "2199:39877",
  "mode": "primitives",
  "layout": "absolute"
}

Scoped snapshot capture — sectionNodeId

sectionNodeId scopes the REST fetch to the enclosing subtree. A missing, deleted or incorrect scope falls back to the full-file lookup; only the selected frame is stored. Component, component-set and style maps are pruned to references used by that frame. source_hash hashes the frame subtree, not its enclosing board or sibling annotations.

The backend retains the top-level Figma version from the response that actually supplied the frame. Using the requesting user's PAT, it best-effort exports a PNG at that same version, with absolute bounds, and stores it at a source-hash + render-hash key. No version means no unpinned export. Export/upload failure still returns a valid JSON snapshot without a dangling image URL.

The optional design_png_url is populated by capture in the generation queue message, not supplied in the public trigger body. This source evidence is distinct from the later plugin render-review pair.


The generation row

One shared table, one discriminator. See Stores for the column-level definition. What matters at the API boundary:

  • the row is written before the message is enqueued, so a job can never reference a row that does not exist;
  • edge is set by the endpoint, never inferred from the payload;
  • the open-generation fence is a partial unique index including edge, so an in-flight run blocks a second run on the same node for the same edge — and does not block the other edge;
  • the fence is released by a terminal status or an explicit cancel — a capture failure fails the row rather than leaving it open — and a trigger colliding with a live run answers 409 with the id of the run already in flight.

Webhook effects

POST /v1/webhooks/ai-status with X-API-Key, carrying type: "des2wf" as the tagged-union discriminator. It is the sole completion-time PG writer.

Event Effect, applied in one transaction, attempt-guarded
parse_done phase advance — a no-op while release 1 is one-shot, kept so the two-phase flow can be enabled without a contract change
succeeded status → completed, result refs, score
failed status → failed with the typed error code

A stale-attempt webhook is ignored rather than applied, and a repeated terminal event is a no-op.


Data plane

The plugin never talks to the AI side directly. The backend proxies server-to-server (X-AI-Service-Token) to the des2wf-api route group hosted in the internal-api app, which touches DocumentDB and S3 only — never PostgreSQL. It serves the result document the plugin materializes from and the wireframe-kit summary the setup view reads.


Render-review validation

POST /des2wf/{id}/render requires project write access and a tenant/project-scoped DES2WF row. The body jobId must equal the route's loaded row id (mismatch: validation error, HTTP 400), and the row must already be COMPLETED (otherwise HTTP 409). Validation happens before PNG upload or queue submission.

The plugin binds the pair to the source frame and backend client/project captured when generation starts, and checks that the materialized result belongs to that job. Selecting the output or changing projects while a job runs must not redirect its review.

The worker loads and validates the existing result's organization/project before downloading images, calling the model or merging the review. Unknown or wrong-scope results cannot create a new result. A skipped review has a null finding count; a review failure does not reverse a completed generation.


Deploy order

Migration → worker → backend → plugin. The route acks on enqueue, so a backend deployed ahead of a worker that cannot consume edge=des2wf will DLQ envelopes while the plugin reports success. The ordering is the mitigation.