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;
edgeis 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.