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-728x90 at 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 (see docs/director-handoff.md in the repository).