Lolly
App openen

Production checks

Lolly can check a finished artifact against explicit requirements. The lolly/production-still-v1 and lolly/production-motion-v1 profiles measure the delivered bytes. They do not approve the design, establish factual truth or identify AI authorship.

Ordinary exports keep their existing behaviour. A render that requests this profile returns its artifact only when every required check passes. Missing collectors, stale references, incomplete coverage and exhausted budgets prevent that verified result. The report remains available to explain the refusal.

Requirements

A contract records its own revision and the expected output. Dimensions are pixels; PDF page dimensions are converted from points at 96 pixels per inch. All pages must have the requested size. alpha is any, opaque or transparent, where transparent means that at least one decoded pixel has alpha below full opacity.

{
  "profile": "lolly/production-still-v1",
  "id": "campaign-card",
  "revision": "1",
  "format": "svg",
  "width": 200,
  "height": 100,
  "pages": 1,
  "alpha": "any",
  "requirements": [
    { "id": "legal", "kind": "text", "location": "legal", "expected": "Legal 125" }
  ]
}

For SVG, text addresses a text element by its exact id, and compares its text content without rewriting or whitespace normalization. A missing element fails; a duplicate id is undetermined. link compares an element's href. node compares the SHA-256 of the parsed element's serialized XML. That identity is parser-specific; use the same collector for a reference and its candidate. None of these checks establishes visibility, clipping, font identity or glyph coverage. Keep Design's existing text-layout receipts and export checks enabled.

For PDF, text locations are page:1, page:2 and so on. The expected string is the complete trimmed page text from Poppler's layout extraction, including its internal whitespace. Extraction does not establish visible placement. Images and outlined text have no extractable text claim in this profile.

resource requirements compare the expected digest with an observed resource read, using its asset id or worker resource URL. Work supplies those facts from actual reads. An unobserved font or asset stays undetermined. Multiple different observed digests under one id do not resolve to whichever value arrived last. Optional sourceSha256 and contextSha256 require the corresponding execution snapshot. Standalone file inspection cannot reconstruct those facts.

Reusable Design and Chart requirements

An input requirement identifies a declared runtime input in location. Its expected value is the SHA-256 of canonical JSON, computed by the engine's productionDigest(value). Object keys are sorted; array order, JSON types, whitespace inside strings, numbers and units are preserved. Observations contain only digests of requested values. Missing, duplicate, non-JSON or over-budget values remain undetermined. The combined observed JSON budget is 1 MiB.

For example, build a reusable Chart contract from reviewed source values:

import { productionDigest, parseProductionContract } from '@lolly/engine';

const protectedValues = {
  data: 'Category,Revenue (GBP)\nA,125\nB,250',
  yTitle: 'Revenue (GBP)',
  yScaleType: 'linear',
  yZero: true,
};
const requirements = await Promise.all(Object.entries(protectedValues).map(
  async ([id, value]) => ({
    id, kind: 'input', location: id, expected: await productionDigest(value),
  }),
));
const contract = parseProductionContract({
  profile: 'lolly/production-still-v1', id: 'chart-revenue', revision: '1',
  format: 'svg', width: 1280, height: 800, pages: 1, alpha: 'any', requirements,
});

Protect the fields that determine meaning: Chart's data, hasHeader, delimiter, transpose, labelColumn, seriesColumns, yScaleType, yZero, yMax, numberFormat and axis titles when those are part of the requirement. Keep numeric inputs as numbers and boolean inputs as booleans when hashing. CSV is preserved as its exact input string. Changing data, units or scale to make a layout check pass is refused, even if a repair plan permits that input.

For reusable Design tools, name their declared copy, number or data inputs. Keep layout alternatives in the tool's existing Share with rules choices and in the explicit repair plan. Portable .lolly renders retain the tool's trust, input and format policy checks; optional sourceSha256 identifies the exact package bytes. The requirements file can be reused with each approved variant.

These observations come from the runtime at its export boundary after export hooks. Chromium reports are matched to the exact downloaded bytes; Work signs and binds them to the worker request and SVG. They establish source identity, not whether every value is visible or correctly plotted. Add independent text or reference-region requirements for output conformance. File-only inspection, older web shells, the desktop renderer and tool-owned export paths that bypass host.export.render cannot supply these observations and stay undetermined. Use LOLLY_RENDERER=chromium when a CLI job requires browser input evidence. The observers describe the cooperating shell; they are not a sandbox against hostile tool code.

Native comparison

An optional comparison adds whole-image and declared-region checks. Supply reference bytes separately; referenceSha256 must identify those exact bytes.

{
  "referenceSha256": "<64 lowercase hexadecimal characters>",
  "channelTolerance": 2,
  "maxChangedFraction": 0.001,
  "regions": [
    {
      "id": "legal",
      "x": 0, "y": 50, "width": 150, "height": 40,
      "minSsim": 0.99,
      "maxInkDelta": 0.001
    }
  ]
}

These example tolerances are authored requirements, not universal quality thresholds. Choose and review tolerances against your reference material. The comparator never resizes or aligns an image. It checks decoded RGBA channel differences at native size, then region luminance SSIM and the change in the fraction of pixels below luminance 250. Whole-image RGBA retains alpha changes; region luminance is composited on white. Critical regions matter because a small missing label can fall below a whole-image changed-pixel allowance.

The profile compares decoded 8-bit sRGB pixels, not HDR values, separation plates, every device's rendering or compressed byte equality. The existing 768-pixel preview comparator remains a separate review feature. This profile is not the broader draft still-2d/1 print suite.

Capability matrix

FactNode CLI/MCPBrowser VerifyWork
SVG namespace, absolute viewport, exact text/id/hrefYes, passive XMLYes, passive XMLYes, passive XML
PNG/JPEG decode, dimensions, alpha, native comparisonsharpBrowser decoder and canvassharp
PDF pages, uniform size, extracted page textOptional PopplerUndeterminedOptional Poppler
Single PDF page pixels at 96 dpiOptional Poppler and sharpUndeterminedOptional Poppler and sharp
MP4/WebM decoded timing, audio and declared frame samplesOptional FFmpeg and ffprobeUndeterminedProfile unavailable
SVG pixels and linked resources from file inspectionUndeterminedUndeterminedUndetermined
Protected runtime input identityIn-process and Chromium rendersUndetermined for uploaded filesIn-process and signed Chromium observations
Source/context and asset-read identityUndetermined in file inspectionUndeterminedObserved runtime facts; worker coverage is partial
Local appearance exceptionEngine decision APIExplicit local review downloadDoes not grant Work authority
Required production checks before returning render bytesYesVerify inspects existing bytesYes, repeated at durable publication

Install pdfinfo, pdftotext and pdftoppm to enable Node PDF readback. Missing programs remain visible in the report. Input is capped at 32 MiB, SVG at 2 MiB, decoded images at 16 million pixels and PDF structure at 100 pages. Animated images and multipage pixel comparison are outside this profile. Parser, pixel, resource and time budgets never turn a missing measurement into a pass.

Encoded motion requirements

lolly/production-motion-v1 checks existing MP4 and WebM deliveries in CLI and MCP, including their existing video render paths. Work's governed renderer still accepts only the still profile. Browser Verify can load a motion contract but reports that independent full-media readback is unavailable.

{
  "profile": "lolly/production-motion-v1",
  "id": "short-explainer",
  "revision": "1",
  "format": "webm",
  "width": 320,
  "height": 320,
  "requirements": [],
  "motion": {
    "seconds": 2,
    "secondsTolerance": 0.05,
    "fps": 24,
    "fpsTolerance": 0.1,
    "frameCount": 48,
    "timestampTolerance": 0.01,
    "audio": false
  }
}

Every decoded video frame contributes to the frame count and timestamp check. Timestamps must increase and remain within timestampTolerance seconds of frameIndex / fps, starting at zero. Duration and measured frame rate have separate authored tolerances, so quantized container timestamps need not match an exact rational clock. A wrong frame count, missing frame or discontinuity cannot be excused as an appearance difference.

audio explicitly requires or forbids a track. With audio: true, also supply audioSecondsTolerance: it bounds both decoded sample duration against the requested duration and timestamp error against contiguous samples starting at zero. Optional loudness: { min, max } is integrated LUFS; truePeakMax is dBTP. Absent or unreadable measurements remain undetermined. Intentional silence may pass when no loudness minimum was authored. Black, frozen and silent spans are retained as review candidates from the existing inspector, not automatic faults.

Optional motion.comparison uses the same reference digest, pixel tolerances and regions described above, with an additional times array. Each time selects the nearest frame by the authored frame rate; its actual timestamp must be within the authored tolerance. Supply a reference video with --production-reference. Both files are independently decoded at native size. Between one and sixteen strictly increasing times are allowed. Appearance between those samples remains explicitly unmeasured. Text visibility, chart semantics, transition quality and spatial or fabrication conformance are not inferred from video pixels.

The Node collector requires ffmpeg and ffprobe, records their versions, and uses their decoded frame and audio filters. It admits one video track and at most one audio track, 8-bit SDR, square pixels and no rotation. Limits are 32 MiB, 120 seconds, 120 fps, 14,400 frames, four million pixels per frame, one billion decoded video pixels and sixteen million sample pixels combined. Probes have a 30-second timeout and each decode has a 60-second timeout. Missing programs, exhausted budgets, unsupported layouts, HDR and higher bit depths cannot produce a verified result.

lolly inspect clip.webm --production=motion-checks.json
lolly run design --export=webm --width=320 --height=320 --fps=24 --seconds=2 --production=motion-checks.json --output=clip.webm

SDK callers use parseProductionSpec for either profile. The existing ProductionContract type and parseProductionContract parser retain their still-only contract; ProductionSpec adds the motion alternative.

CLI and MCP

lolly inspect card.svg --production=checks.json
lolly run my-tool --export=svg --production=checks.json --output=card.svg
lolly run my-tool --export=png --production=checks.json --production-reference=reference.png --output=card.png

Rendering writes a report to <output>.production.json, including on a check failure. --production-report=path overrides that location; stdout renders send the report to stderr. Failed verification writes no artifact. Portable tools use the same flags with their existing trust requirement:

lolly run card.lolly --trust-tool --export=svg --production=checks.json --production-repairs=repairs.json --output=card.svg

On-device file transforms refuse this render option; inspect their finished files explicitly.

MCP lolly_inspect accepts file, production and optional base64 productionReference. lolly_render accepts the same contract and reference; its shared render core enforces the requirement even for direct callers. Failure responses retain the report. The browser's Verify view has a Production checks panel for a requirements file and optional reference. SVG inspection never mounts document code or fetches linked resources.

Permitted repairs

An authored repair plan lists protected inputs, an ordered finite list of permitted values, triggering failed findings and an attempt limit:

{
  "protected": ["legalCopy"],
  "permitted": { "layout": ["short", "long"] },
  "maxAttempts": 2,
  "when": [{ "findingId": "requirement.legal", "input": "layout" }]
}

Use --production-repairs=repairs.json in CLI, productionRepair in MCP, or production.repair in Work. Only declared tool inputs can change. Every patch binds the input snapshot, report digest, finding fingerprint and prior value. The next alternative must be listed in the plan. The loop rerenders and checks the artifact, stops when any passing check regresses or the number of unresolved requirements does not fall, and permits at most eight repair attempts. A required input is protected independently of the repair plan. A refused protected edit retains the failed attempt report. The loop cannot change its contract. Protected copy, numbers and logos should never be listed as permitted edits.

CLI keeps each report in <report>.history.json; MCP and Work retain attempt reports alongside the final report. An unresolved attempt does not publish a verified artifact. Work's ordinary policy checks also apply to every candidate. Cancellation and publication fencing remain the existing durable runner's responsibility.

Decisions and reproducibility

A measurement report binds its checks to artifact and contract digests, with source/context digests where observed. A local review record is separate and binds the exact report and finding fingerprints. Only failed appearance checks can carry a reasoned local exception. Unknown coverage and structural or protected-content failures cannot be excepted. Saving and loading a decision does not change a failed measurement into a pass.

Work decisions require its authority adapter and existing approval policy; client-supplied local reviews do not grant governed delivery rights. A C2PA signature also does not supply design approval.

Exact artifact and reference files plus the contract support offline rechecking. A Work execution receipt remains partial, even with signed worker network observations. It is not a complete dependency lock or a promise to reproduce a render against a changed catalogue. Required old source/resource identities fail or remain unknown when unavailable. Existing portable tools remain the route for packaging editable source and dependencies; this feature adds no competing document or archive format.

The maintained fixtures exercise native comparison, missing critical content, alpha changes, byte and rule substitution, PDF metadata-only differences, permitted layout repair, stale patches, local exceptions and browser/Node parity. They establish this profile's implementation behaviour. They do not provide a comparative product benchmark or human approval of creative output.