CANVAS schema
A CANVAS is the design comp drawn from the manuscript before coding: for web the top page in PC and SP, for other media every page or face (NeoFactory workflow, phase 6). Magic Asset Manager stores each CANVAS image and its metadata document in the production's canvas/ folder. Magic Designer owns the schemas and the checks (php artisan canvas:check, POST /api/v1/canvas/check). The media list they check against (media types, formats, presets and their CANVAS sizes, viewports) is media-contract's (yutoseta/media-contract, media.json), which Magic Designer reads at one pinned version.
| Schema | File | Role |
|---|---|---|
magic://schemas/canvas/v2 |
canvas/v2.schema.json | The metadata of one delivered CANVAS. |
magic://schemas/canvas-order/v1 |
canvas-order/v1.schema.json | The CANVASes the director orders for one production. |
Both are JSON Schema draft 2020-12. magic://schemas/canvas/v1 was removed with the switch to v2: the director never called it.
What the check decides
Magic Designer accepts a CANVAS when it is the deliverable it was ordered as: the image of the ordered preset (and, on the web, viewport) at the size media-contract gives the preset, and, with an order, one CANVAS for every slot. It does not decide what a production consists of or how the director draws it:
| Rule | Owner |
|---|---|
| Media type, format, preset, media list version, CANVAS size, image file | Magic Designer, against media-contract |
| How the CANVAS is drawn (model, generation ratio and resolution, crop) | The director; Magic Designer checks only the delivered size |
| Which pages, faces and viewports a production has (required pages, page counts, a web page in both PC and SP) | The director's order; Magic Designer checks that a delivery fills it |
| Page roles and positions | magic-contract's media profiles and the manuscript (Magic Asset Manager); a CANVAS does not carry them |
| Drawing order (PC then SP in the top flow, SP then PC on a landing page, a back face from the front) | The director; source.derived_from_sha256 records it for provenance and is not checked |
canvas/v1 held the second to fourth rows (page rules and flows copied from NeoFactory's ProjectContractValidator::contractFor and CanvasTrial::FLOWS); they left Magic Designer with it.
Names
Every name, the CANVAS key, the order's slot key, the preset, the media type and the format, follows the naming rule all Magic products share. Its source is media-contract (magic://schemas/names/v1#/$defs/name, which both schemas reference): ^[a-z][a-z0-9]*(-[a-z0-9]+)*$, 1 to 64 characters, and not a Windows device name (con, prn, aux, nul, com1–com9, lpt1–lpt9). Names can become file and folder names, URLs, HTML ids and CSS names, so they are lowercase ASCII words joined by single hyphens and start with a letter: top, front, page-1, business-card-vertical, business-card-91x55-mm. The codes before media-contract (business_card, business_card_91x55_mm) fail the schema.
Fields
Each field comes from what NeoFactory records for a CANVAS: the AiRequestLog of a media_canvas or first_view request (execution_state, image_* columns), CanvasImage and local production artifacts.
| Field | Meaning | NeoFactory origin |
|---|---|---|
$schema |
Always magic://schemas/canvas/v2. |
The schema reference every structured asset carries (PRODUCT-SPLIT.md). |
media_contract |
The version of the media list (media-contract's media.json version, for example 0.1.0) the specification was taken from. It must be the version Magic Designer checks against. The same name as in Magic Asset Manager's production.json. |
Replaces catalog_revision (execution_state.catalog_revision). |
media_type |
Media type from media-contract (web, business-card, banner …). |
The medium's type in media.json; project.type in the page contract. |
format |
Format from media-contract (flow, business-card-91x55-mm, canvas-1200x630-px …). |
The medium's format in media.json; execution_state.format.format_code. |
preset |
The medium the person chose: a media-contract id (web, landing, ogp, business-card …) that has a CANVAS. Fixes media type, format and CANVAS size, so each sheet size of a medium is a preset of its own: business-card is 91 × 55 mm and business-card-vertical 55 × 91 mm; flyer (A4 portrait), flyer-a4-landscape, flyer-a3, flyer-a3-landscape, flyer-b5 and flyer-b5-landscape; slide (16:9) and slide-4x3; banner (300 × 250 px) and one banner-<width>x<height> preset for each other display-ad slot (banner-728x90 …). |
execution_state.variant; GenerationRun state media; LocalProduction.media. |
viewport |
Web only, required there: desktop (PC) or mobile (SP). It fixes the CANVAS size. |
Variant suffix (web-desktop, landing-mobile); MediaGenerationPrompt::canvasSettings viewport. |
key |
Key of the order slot the CANVAS fills. The director names slots after the manuscript pages (top, front, back, page-1 …); Magic Designer only matches the key against the order. |
execution_state.content_page_key, page_context.key. |
image.sha256, image.media_type, image.width, image.height |
The image asset: SHA-256, image/png, image/jpeg or image/webp, pixel size. |
AiRequestLog.image_*, CanvasImage.sha256/width/height; image API output_format: png. |
source.production_revision |
The revision of the production in Magic Asset Manager the CANVAS was drawn from (its brief, manuscript and references), written <asset ID>@<revision number> (for example 8f9a1cae-da68-5654-9f70-d1c51ec77234@3; 1 to 255 characters). Provenance only: the form is not checked. |
Replaces the input bundle hash: Asset Manager hands each stage a revision of the production instead of a bundle. |
source.prompt_sha256 |
Hash of the prompt asset, or null when none was recorded (a CANVAS uploaded by a person or an external agent). Provenance only. |
The saved prompt.md / prompt_text. |
source.derived_from_sha256 |
Hash of the CANVAS image this one was drawn from (SP from PC, a back face with the front as a reference), or null when it was drawn on its own. Provenance only. |
execution_state.previous_log_id. |
Not carried: the model and its generation settings (the director's, see "CANVAS sizes" below), cost, request bodies and AI request log IDs (generation history stays with the director), and disk paths (storage belongs to Magic Asset Manager).
Orders
A magic://schemas/canvas-order/v1 document lists what the director orders for one production: the media list version, one preset and its slots. A slot is a key and, for a web preset (media type web), a viewport. Keys and preset names follow the naming rule (see "Names").
{"$schema": "magic://schemas/canvas-order/v1", "media_contract": "0.2.0", "preset": "web", "slots": [{"key": "top", "viewport": "desktop"}, {"key": "top", "viewport": "mobile"}]}
{"$schema": "magic://schemas/canvas-order/v1", "media_contract": "0.2.0", "preset": "business-card", "slots": [{"key": "front"}, {"key": "back"}]}
The director decides the slots from the manuscript and the page contract: a one-sided card orders only front, a carousel orders as many slots as it has pages. canvas:check --order order.json canvas.json and the API's order check the CANVAS metadata (one document, or an array) as the delivery of that order.
Checks
MagicDesigner\Core\Services\CanvasCheck returns violations with code, path (JSON pointer), target (the slot key, plus :viewport on the web), expected, actual and message, the ContractViolation shape used in NeoFactory.
Each canvas/v2 document:
| Code | Rule |
|---|---|
schema.<keyword> |
The document does not match the schema. Other checks run only on schema-valid documents. |
catalog.media_contract_mismatch |
media_contract differs from the version Magic Designer checks against. |
catalog.unknown_media_type, catalog.no_canvas |
The media type is not in media-contract, or has no medium with a CANVAS (document, logo). |
catalog.unknown_format |
The format is not in media-contract. |
catalog.unknown_preset, catalog.preset_mismatch |
The preset is not a medium with a CANVAS, or its media type or format differ from the document. |
canvas.size_mismatch |
The declared size differs from the preset's CANVAS size in media-contract (the viewport's on the web). |
image.unreadable, image.media_type_mismatch, image.size_mismatch, image.sha256_mismatch |
The image file (optional) cannot be decoded, or does not match the document. getimagesize reads the type and size from the header only, so the whole file is decoded with GD as well: a truncated or corrupt PNG or WebP fails, and a JPEG must also end with its EOI marker because libjpeg pads a truncated file. |
An array of canvas/v2 documents without an order is checked document by document (an empty array is set.empty); nothing decides which of them a production needs. With an order, the order is checked first; its violations have target order or the slot, and their path points into the order:
| Code | Rule |
|---|---|
order.invalid_json, order.unreadable |
The order is not JSON, or its file cannot be read (canvas:check --order). |
schema.<keyword> (target order) |
The order does not match its schema. |
order.media_contract_mismatch |
The order names another version of the media list. |
order.unknown_preset |
The preset does not exist or has no CANVAS. |
order.viewport_missing, order.viewport_unexpected |
A slot of a web preset has no viewport, or a slot of another preset has one. |
order.duplicate_slot |
Two slots have the same key and viewport. |
When the order and every document are valid, the delivery is matched against it; path points into the CANVAS metadata (/<index>/… for an array, /… for one document):
| Code | Rule |
|---|---|
delivery.preset_mismatch |
A CANVAS is drawn for another preset than the order. |
delivery.unordered |
The order has no slot for a CANVAS's key and viewport. |
delivery.duplicate |
Two CANVASes fill the same slot. |
delivery.missing |
No CANVAS fills a slot (expected is the slot). |
CANVAS sizes
Each medium with a CANVAS has its final CANVAS size in media-contract (canvas in media.json); web and landing are drawn per viewport (desktop 1536×1024, mobile 1024×1536). The size has the format's exact ratio. Magic Designer checks that the delivered image has exactly that size, nothing else about how it was made.
How to draw it is the director's (owner decision 2026-10-09): which model, at which generation ratio and resolution, and how to crop and downscale the result to the final size. A CANVAS is never upscaled to reach it. Magic Designer's catalog no longer publishes canvas_model or canvas_generation.
Faces
A two-sided business card or flyer has two CANVAS images: the director orders a slot per face and draws each face as its own image, and Magic Coder reproduces each face faithfully from its own image. Whether a card has a back is the order's. A back drawn with the front as a reference image records the front in source.derived_from_sha256.
Fixtures
packages/magic-designer-core/schemas/canvas/fixtures/v2/valid/: web PC and SP, OGP (a single-face medium), the 728×90 display-ad slot (banner-728x90at its CANVAS size 1820×225), business card front and back in both orientations.invalid/fails the schema (Magic Asset Manager rejects it too), including keys outside the naming rule;rejected/is schema-valid but misses the catalog (size, format, a medium without CANVAS, another media list version).packages/magic-designer-core/schemas/canvas-order/fixtures/valid/: orders the v2 fixtures fill (web PC and SP, a two-sided card, OGP).invalid/fails the order schema;rejected/breaks an order rule (unknown preset, missing or unexpected viewport, duplicate slot).docs/director-samples/: whole deliveries as the director sends them, with the response (seedocs/director-handoff.mdin the repository).