AI Des2WF — Stores & DB Definition
Des2WF shares platform stores with WF2Des and owns its kit, result and graphic-override data. This page is the
des2wf-scoped contract; canonical per-table pages under development/database/ follow the frozen
shapes.
Ownership rule, non-negotiable: the backend owns PostgreSQL and the worker never writes
it. Workers hold no PG credentials; every PG effect is applied by the ai-status handler or a product
endpoint.
PostgreSQL — the shared wf2des generation row
Des2WF does not get its own table. It extends the existing one, because one table keeps job ids globally unique across both edges, which is what keeps the plugin's single idempotency index safe by construction.
| Column | Type | Notes |
|---|---|---|
edge |
enum | '0' wf2des, '1' des2wf, default '0'. Named edge, not direction: that name is already taken in the worker schemas for auto-layout axis. Dispatch routes on it as a tagged union, never on which optional fields happen to be populated |
wf_node_id |
string | the input node — a wireframe node on the wf2des edge, the design frame on the des2wf edge. One column, read together with the edge beside it |
screen_id |
string | NOT NULL on the shared row, and never read as a screen-grammar match on this edge, so a des2wf run stores the source node id in it |
score, score_version |
numeric, text | the sortable scalar + the formula version, the same role flag_count plays. Written only when the worker reports one, so the wf2des edge — which reports none — is not stamped 0 on a sortable column. The BAND is deliberately not in PG: it lives in the result document, where the ratchet reads it |
| other columns | — | status, phase, attempt, result refs, timestamps, audit fields — one shape for both edges |
The open-generation fence. The partial unique index is
(project_id, figma_file_key, wf_node_id, edge) WHERE status = '0' — at most one live run per node,
per project, per edge. Two consequences:
edgeis part of the key, so an in-flight run blocks a second run on the same node for the same edge only: a design frame may run through des2wf while its own wireframe runs through wf2des;- the fence has no sweep. A row stranded at
status = '0'keeps its node key occupied until the run is cancelled, and des2wf's cancel accepts a run that is still processing in any phase — wf2des's own cancel additionally requires the confirm checkpoint, which a one-shot edge never reaches.
DocumentDB
| Collection | Shared? | Des2WF's use |
|---|---|---|
wireframe |
shared with WF2Des | the output. The emitted WireframeDoc: based_on = 1 (design), type = 0 (page), memos = [], summaries computed |
wireframe_component |
des2wf-owned | the wireframe kit — the output's building blocks. Written by the shared component sweep under its wireframe target, read at run time by selection |
design_component |
shared, owned by WF2Des | read-only here. Selection consults it for the name, slots, size and axes of the design component it must express, and never writes back |
project_figma_file |
shared | per-file style captures and sweep watermark; scopes the sweep walk |
des2wf_generation_result |
des2wf-owned | the run's own document, _id = the shared row id, committed under an (_id, attempt) fence so a redelivered older attempt cannot overwrite a newer one. The render review amends it in place, merging a render_review block and keeping any previous one under render_review_previous. Separate from the wf2des result collection because the two edges answer different shapes — a wireframe spec + score against a design spec + confidence — and one collection holding both would make every reader branch on the edge |
des2wf_verdict |
des2wf-owned | Explicit human overrides only: record_type=graphic_override_v1, filtered by organization, project and file. Legacy verdict records are ignored by generation; no automatically cached AI judgments |
Explicit graphic overrides
The storage name remains des2wf_verdict (config: DES2WF_VERDICT_TABLE_NAME). Its current runtime
records are graphic_override_v1, not the legacy metadata-only verdict schema.
| Field | Contract |
|---|---|
_id |
graphic: + SHA256 of JSON [organization_id, project_id, file_key, target, identity, variants], serialized with sorted keys |
record_type |
graphic_override_v1 |
organization_id, project_id, file_key |
Required scope on lookup and operator changes; no cross-tenant or cross-file reuse |
target |
node or component |
identity |
Exact source node ID, or actual master component ID |
variants |
Exact component variant mapping; default {}, not a wildcard |
action |
preserve or simplify |
reason |
Required explanation, 1–600 characters |
Node overrides take precedence over component overrides. Conflicting duplicates prefer preserve.
Component overrides apply to every matching appearance in this file; prefer node targets for
context-specific exceptions. An explicit simplify produces an ink placeholder, not decoration
removal. The classifier itself never writes an override.
Run the operator CLI from guinness-ai-v2, using the worker's DOCUMENTDB_* environment.
The set and clear commands below are intentional database writes; substitute the intended scope
and review it first. clear removes only that exact scoped override, leaving other variants and
legacy records untouched.
uv run python apps/des2wf/scripts/graphic_override.py --org 1 --project 7 --file FIGMA_FILE_KEY list
uv run python apps/des2wf/scripts/graphic_override.py --org 1 --project 7 --file FIGMA_FILE_KEY set --node '123:456' --action preserve --reason 'Navigation meaning'
uv run python apps/des2wf/scripts/graphic_override.py --org 1 --project 7 --file FIGMA_FILE_KEY set --component '123:400' --variants '{"State":"Default"}' --action simplify --reason 'Redundant artwork beside its label'
uv run python apps/des2wf/scripts/graphic_override.py --org 1 --project 7 --file FIGMA_FILE_KEY clear --node '123:456'
Legacy verdict.py / propose_verdicts.py records are not consumed by generation.
If override loading fails, classification remains best-effort and does not reactivate legacy demotions.
The result's graphic_classification stores per-appearance evidence and decisions separately from
these human rules. Inspect both the proposal and post-kit rendered_kind / covered_by before
concluding a source glyph was visibly replaced. See I/O Definition.
Why wireframe_component is a separate collection
The WF component library is a different library from the design system — not a subset of it — so it does not belong in the design registry. Two further reasons, both mechanical:
- WF2Des's candidate generator is offered the whole registry for every section, with the kind gate config-defaulted off. Putting a second vocabulary in that collection makes WF2Des's behaviour depend on a filter being correct at every read site, against a pipeline that is already the weaker of the two.
- Leakage in that direction is already observed in the plugin's own code, which names it self-reinforcing corruption. A separate collection makes the failure impossible rather than filtered.
One sweep serves both libraries. It is parameterised by a registry target carrying the three facts
that differ: the collection, the identity rule ({project_id}_{component_key} for the kit, the node
composite for the design system), and whether the semantic enrichment pass applies — it does not for a
kit, whose atoms are already named by their own shape, and running a design vocabulary over them would
invent semantics the kit never claimed. Registry reads stay bound to the design accessor, so a read
cannot drift onto the kit and quietly make its atoms design candidates.
wireframe_component — fields
The document shape is the sweep's, shared with design_component; what selection reads of it:
| Field | Purpose |
|---|---|
_id |
{project_id}_{component_key} — composite on the componentSet publish key, never a node id (per-file-copy), never a name (measured collisions covered 56% of instances by use). A master with no publish key yet falls back to the node composite rather than going unregistered. Shared by every variant of the set, so it is not what selection votes for |
name |
display only, explicitly not identity |
family |
the board the entry was swept from — the kit's own grouping. Used to collapse interchangeable stand-ins, never matched against a design layer name |
component_key, component_node_id |
how the plugin reaches the master. The kit's pages are copied into the working file, so its masters are local: importComponentByKeyAsync has nothing to import, and the node id is the only way to instance them. An entry carrying neither is dropped at load as unreachable |
variant_properties, variant_defaults |
the set's axes and the value each falls back to. A variant value the set does not enumerate is never accepted from the model — Figma would refuse it at materialize time |
variants |
per-variant variant_props, size, text_slots, nested_components — the selectable rows |
text_slots, default_size |
the set's own; read only for a document that declares no variants |
lineage |
source file, hash, processor version, index_schema_version — identifies which kit (and which version of it) an entry came from: the kit is designer-chosen and will change, so a swap is a re-registration and stale entries must be tellable from current ones |
The selectable unit is a variant, not a component. A component set cannot be instanced; one of
its variants can. Selection therefore expands each document into one row per entry in variants —
falling back to the top level only for a document that declares none — and votes for, and resolves by,
a key unique to that row. Keying by _id instead hands the guards an arbitrary sibling of the variant
that was shortlisted, and it measures the wrong object: the top level states the default variant's
slots and size, while 44 of 415 registry documents hold a variant with more text slots than the
top level reports.
Slot capacity is the number of distinct slot layer paths. The materializer addresses a slot by its path and writes it, so two slots declaring one path are one destination — the second write overwrites the first, and that design string is gone. The guards count paths, never declarations: on the live kit 52 variants declare a repeated path, and one declares five slots that all address a single layer.
The kit's pages are copied into the working file, so its masters are local — the same installation the design system has on the wf2des side. The setup view reads the registered kit back as a summary: how many entries carry a node id (without one nothing can be instanced at all), how many carry a publish key, and how many declare variant axes and text slots.
The wireframe collection — two hazards
_idcollision — impossible by construction. Des2WF writes the edge-scoped{project_id}_{figma_file_key}_{node_id}~des2wf;wf_parsekeeps the existing unscoped id. The silent last-write-wins overwrite between the two edges is impossible by construction, and the wf2des parse-cache lookup is untouched.based_onis written and never read. The output carriesbased_on = 1(design), which is the only thing that tells a machine-made wireframe from one a designer drew. Nothing on the consuming side refuses it: a des2wf wireframe can reach WF2Des as if a designer had drawn it, and only a consumer-side check on this field can stop that.
Kit volatility
The kit is per-project data and will change. Two store-level rules follow:
- selection reads the registry at run time rather than a precomputed table, so a swapped kit takes effect on the next run: an entry absent from the currently registered kit is simply not among the atoms a run loads, and the design component it expresses is decided again;
- nothing kit-specific is ever encoded in code or in a schema default; a kit swap must be expressible
entirely as a data change in
wireframe_component.
S3
| Path | Content |
|---|---|
{org}/{proj}/des2wf/snapshots/{file}/{node}.{source_hash}.json |
Frame-only REST envelope with pruned components, componentSets and styles maps |
{org}/{proj}/des2wf/snapshots/{file}/{node}.{source_hash}.{render_hash}.png |
Optional immutable source PNG from the snapshot's Figma version, with absolute bounds; generation's design_png_url |
{org}/{proj}/des2wf/{job_id}/parse.json |
Parsed tree relied on by the run |
{org}/{proj}/des2wf/{job_id}/result.json |
Spec, score fields and graphic_classification audit |
{org}/{proj}/des2wf/{job_id}/manifest.json |
Artifact index and row_effects.wf2des, referenced by the webhook |
{org}/{proj}/des2wf/renders/{job_id}/design.png |
Plugin-provided design image for post-terminal review |
{org}/{proj}/des2wf/renders/{job_id}/wireframe.png |
Plugin-provided generated wireframe image for that review |
Source-evidence keys include content hashes. The review pair uses per-job paths, not immutable
content-addressed keys, and can be replaced on re-review. Do not confuse these two image lifecycles.
An unsuccessful source export/upload leaves design_png_url absent rather than pointing to a
missing object. The worker also caps fetched source PNG bytes at 20 MiB and decoded pixels at
32,000,000; failure preserves artwork.
The retained snapshot maps are part of the identity contract: publish keys and named style identity cannot be recovered after they are discarded.