Print Lab Software · production-side press and print
Model what the press can make. Refuse what it cannot. Output print-ready.
A print job has no business reaching a plant the printer cannot run it on. Print Lab Software is the production-side stack that enforces that: every vendor has a capability profile declaring exactly what it produces — bindings, stocks, finishes, sizes, publication types. Before a job is offered, priced, or submitted, the fail-closed gate checks the assigned printer’s profile and refuses anything undeclared. An absent capability is a denial, never a silent yes. Preflight validates to print-ready against the same profile. The single render facade produces the artifact. The plant gets a file it can actually run, or it gets nothing.
The capability profiles, producibility gate, preflight, and print-ready render are built and run today. The live plant-send path and any pricing rail are in early access — named plainly below. This is production-side press software: distinct from photolab.software (photographic/package-lab order intake and vendor routing) and from mailing.software (postal operations).
What is built and what is in early access
The pillars below make up the production print stack. Capability profiles, producibility gating, the cover/spine paper-bulk estimate, and preflight/print-ready render are built and running today. The per-printer-profile cover/spine/finishing derivation is a library layer with test coverage but has no production callers yet. The plant-send path and pricing rail are in early access: the infrastructure is wired and money movement is switched off until enabled. Each is labeled honestly.
Capability profiles per vendor — the single source of producibility truth
The @homeroom/print-delivery CapabilityProfile is the central model of what a given printer or supplier declares producible. Each profile carries the vendor’s complete producibility picture: bindings, paper stocks, finishing options, cover materials, lamination, endsheets, tip-ins, supplements, personalization, variable-data printing, and the publication types the vendor will run. Every dimension is a closed world — only what the vendor explicitly declares is available; everything else is denied. resolveCapabilityProfile fail-closes to the EMPTY profile when no capability data exists for a vendor, so a printer with no record denies everything rather than permitting everything. Absence of data is never a pass. Shipped
Producibility gating — fail-closed, fail-loud
assertProducible(spec, profile) is the single gate every builder calls before offering, pricing, or submitting a job. It checks the publication type first (a newspaper sent to a vendor that does not print newspapers is denied immediately), then every requested physical attribute against the profile’s declared capability sets. On any unsupported dimension or value it throws ProducibilityDeniedError with a 422 status code and a plain-language reason the UI can show directly. There is no permissive fallback. A job that fails the gate is not offered, not priced, and not submitted. Every downstream consumer — branded products, cover builder, manifest builder — defers its yes/no to this one gate rather than re-implementing the check. cap-default-deny: an undeclared product is denied; a disabled tile carries a whyUnavailable reason. Shipped
Cover and spine sizing — a documented paper-bulk estimate
Cover and spine sizing that ships today (coverSpineInches / coverSpineInchesFor in @homeroom/pdf) is computed from a documented per-page-count paper-bulk estimate: a single coefficient in inches per page for a standard interior stock (60# uncoated text), plus a board/hinge allowance for case binding. The result is always labeled an estimate, with guidance to confirm the final spine against the print vendor’s own spine calculator before submitting. It is a global coefficient, not a per-printer-profile or per-stock derivation. Shipped
Per-printer-profile cover, spine, and finishing derivation
The cover-capability, cover-spine-from-profile, cover-finish-separations, finishing-configurator, and cover-assigned-printer-profile-gate modules in @homeroom/print-delivery are built as a library layer with test coverage, intended to derive the buildable cover, spine, and finishing set from the assigned printer’s capability profile rather than the single global estimate above. This profile-driven path has zero production callers today — it is not yet wired into the live cover pipeline. We list it as available/roadmap, not running. Early access
Preflight and print-ready output
The print manifest, manifest-validation, ssy-manifest-validator, and print-profile modules in @homeroom/print-delivery validate a completed job to print-ready against the same capability profile the gate checked. The single server-side render facade (packages/composite/src/render-facade.ts, the check:render-facade invariant) is the one place that maps a scene to the raw @homeroom/pdf render primitive — no other composite module calls it directly. The facade re-runs the render gate before it renders, so preflight and artifact agree on one source of truth. A job that passes preflight produces a file the printer can run. A job that does not pass produces nothing. Shipped
Live plant submit and pricing rail
The plant-send path — the mechanism that submits a print-ready manifest to a live production plant — and any pricing or checkout rail are built and wired but switched off pending enablement. No job is submitted to a plant today and no transaction is processed. The capability modeling, gating, preflight, and print-ready render described above are built and run. The live send is the early-access leg. We name this plainly rather than presenting the full pipeline as live. Early access
How the fail-closed pipeline works
A print job moves through the pipeline in a fixed order. Each step is a gate; the job cannot advance until the current gate passes. The pipeline is fail-closed: any failure stops the job at the gate that caught it and returns a plain-language reason. There is no silent degradation.
- Printer assignment. A publication is assigned to a printer or supplier. The assignment triggers a profile resolution: resolveCapabilityProfile looks up the vendor’s CapabilityProfile. If the vendor has no profile on record, resolveCapabilityProfile returns the EMPTY profile — hasData=false, every capability set empty, every publication type unsupported. The pipeline then runs against that empty profile, which denies everything. A printer with no declared capabilities offers nothing.
- Capability gate before offering. Before a cover option, a finishing treatment, a paper stock, or any other physical attribute is presented to the adviser as available, capabilityFor checks the profile. An undeclared attribute returns supported=false with a reason the UI surfaces as a disabled tile with a whyUnavailable label. The adviser sees only what the assigned printer can actually produce. There is no option presented that the printer cannot run.
- Publication-type gate before submit. assertProducible checks the publication type first. A book whose type — newspaper, literary magazine, yearbook, class storybook, event program — is not in the profile’s supportedPublicationTypes list is denied immediately with a 422 and a plain reason. A printer that does not produce newspapers cannot receive a newspaper. A printer with no capability data cannot receive any job.
- Full producibility gate before manifest build. assertProducible then iterates every requested physical attribute and checks each against the profile. The first unsupported dimension or out-of-limit value throws ProducibilityDeniedError with a machine code, a human-facing reason, and a FERPA-clean detail bag of physical-spec tokens only. The job does not proceed to manifest build until every attribute clears.
- Preflight against the profile. The manifest and manifest-validation layer preflight the complete job against the same profile used at the gate. Preflight confirms that the assembled artifact — page count, trim, bleed, color system, font embedding, TAC cap, PDF dialect — matches what the profile expects. A job that fails preflight does not proceed.
- Print-ready render through the single facade. The render facade (check:render-facade invariant, one facade over @homeroom/pdf) re-runs the render gate server-side before it renders. The gate checks the scene a second time against the concrete render facts — the same profile, the same constraints — so there is no divergence between what preflight passed and what the renderer produces. The facade returns a render job reference; the worker produces the print-ready artifact and lands it as a Photo record.
- Plant send (early access). The plant-send path takes the print-ready artifact and the manifest and submits them to the production plant. This step is wired and switched off. When it is enabled, the plant receives only jobs that have cleared every prior gate. A plant is never handed a file it cannot run — the gate is upstream of the send, not a note attached to the submission.
What a capability profile models
A CapabilityProfile is a complete, per-printer declaration of producibility across every dimension a print job can ask about. It extends the vendor-neutral PrintProfile (color system, PDF dialect, bleed, TAC cap, font embedding, base bindings, and paper stocks) with the full catalog and treatment dimensions. The closed-world rule governs every dimension: only what is explicitly declared is available.
Publication types
Each profile declares which publication types the vendor will print: yearbook, newspaper, news magazine, literary magazine, magazine, class storybook, event program. A non-print medium — a news site, a broadcast, a podcast — is denied by definition regardless of the profile. A vendor that does not print newspapers cannot be assigned a newspaper, and a vendor with no capability data cannot be assigned anything. The publication-type gate runs first; a job of an unsupported type never reaches the physical-attribute checks.
Finishing and materials
The profile declares each supported finishing treatment (spot UV, foil, emboss, deboss) and each supported cover material (leatherette, linen, poly, and others) as a CapabilityEntry with a supported flag, an optional upcharge in exact integer cents, an optional lead-time in days, and optional numeric limits. A finish not in the declared set is denied. An explicit supported=false in the entry is treated identically to an absent entry: denied. A tile for a denied finish is shown as unavailable with a plain reason, not hidden silently.
Lamination, board, endsheets, interior stock
Cover lamination (gloss, matte, soft-touch), cover board weight and type, endsheet stock and printing, and interior paper stock are each their own closed-world dimension. A lamination option is available only when the profile declares it. A specific interior paper stock is available only when the profile declares it. The absence of a declaration in any dimension is a denial across the whole dimension — no implicit default that lets a job slide through on an undeclared stock.
Per-copy and variable-data capabilities
Personalization (per-book name stamping, per-book cover photo), variable-data printing, and per-copy unique page composition each require a specific production capability that not every plant has. Each is a separate dimension in the profile. A vendor that cannot run variable-data printing declares nothing in that dimension; any job requesting variable-data on that vendor is denied at the gate. A vendor that cannot handle per-copy unique pages cannot be assigned a publication that requires them. These are not features a caller can assume unless the profile declares them.
Tip-ins and supplements
Tipped-in inserts (vellum, photo tip-in, signature tip-ins) and bound or loose supplements (supplement signatures, loose inserts) are declared as separate dimensions. Each declared entry carries the supported flag and optional limit fields: a tip-in dimension may declare a maximum count or a minimum order quantity. A job that requests a tip-in above the declared maximum count fails the out-of-limit check in assertProducible with a plain reason naming the limit. Limits are enforced by the gate, not noted as advisory text.
The fail-closed default
Every dimension and every value not explicitly declared in the profile is denied. There is no permissive default, no "probably fine," and no fallback that lets a job through when the profile is silent. resolveCapabilityProfile returns the EMPTY profile — hasData=false, every set empty — for a vendor with no data. capabilityFor on an empty profile returns supported=false for every query. assertProducible on an empty profile throws at the publication-type check before it ever reaches the physical-attribute loop. Absence of data is structurally identical to a vendor that produces nothing.
Refusal is the feature
The most important behavior this pipeline has is what it does not do. It does not submit a job to a plant that cannot run it. It does not offer a finishing option a printer has never declared. It does not present an interior stock as available when the profile is silent on it. It does not pass a newspaper to a vendor whose declared publication types are yearbooks only. It does not accept a tip-in quantity above the declared maximum. And when a vendor has no capability data at all, it offers nothing — it does not fall back to a generic template of what a “typical” printer can do.
This matters because the alternative — offering options optimistically and catching errors at the plant — produces real production problems. A job that reaches a plant with a finishing treatment the plant cannot run requires manual intervention: a call, a correction, a resubmission, a delay. A job that reaches a plant with the wrong stock requires a reprint. A job that reaches a plant with a publication type it does not handle requires re-routing after the fact. These are not edge cases; they are what happens when the system does not enforce capability at the point of offer.
The fail-closed gate runs at offer time, before the adviser makes a selection. By the time a job is assembled, every attribute in it has already been checked against the assigned printer. The manifest builder, the preflight step, and the render facade check again — authoritatively, server-side, at the gate before submission. A plant that receives a file through this pipeline receives a file it can run, because a file that cannot be run does not leave the pipeline.
The why-unavailable reason system is the UI-facing side of the same principle. When a tile is disabled because the assigned printer does not support a finishing option, the UI shows a plain-language reason from the profile: “The assigned printer does not offer the finish [value]. It is not on this printer’s list of options.” The adviser understands why, can assign a different printer, or can contact us to confirm the printer’s actual capabilities. The system is transparent about what it knows and what it does not.
Preflight and the single render facade
Print-ready output requires two things to agree: the preflight check that validates a job, and the renderer that produces the artifact. When those two are separate systems, they can drift — preflight passes a job with properties the renderer handles differently, and the output does not match the approved specification. This pipeline uses one source of truth for both.
The print manifest and preflight
The print manifest is the complete description of a job: trim, bleed, color system, PDF dialect, TAC cap, font embedding, page count, binding, stock, finishing. The manifest-validation and ssy-manifest-validator layers check the assembled manifest against the same CapabilityProfile the gate checked at offer time. A job whose manifest declares a stock the profile does not support fails preflight with a plain reason. A job whose page count is outside the binding’s declared limits fails preflight. Preflight is not a separate check that might have different rules — it uses the same profile, so the rules are identical.
The check:render-facade invariant
The render facade in packages/composite/src/render-facade.ts is the single place that maps a composite scene to the @homeroom/pdf render primitive. No other composite module calls renderPdf directly. The facade re-runs the render gate server-side, authoritatively, before it renders — so a scene that passes the preflight check and then fails the server-side render gate is caught before any bytes are produced. The invariant means there is one path from a scene to a print-ready artifact, and that path passes through one gate twice: once at preflight, once at the facade. A file that the facade produces is a file the gate approved.
Print profiles and color system
The PrintProfile (the vendor-neutral print contract the CapabilityProfile extends) carries the color system, PDF dialect, bleed rule, TAC cap, and font-embedding requirement for a vendor. The render facade resolves the printer target and color profile from the size variant’s declared ICC: a GRACoL/CMYK ICC routes to the offset press target; an sRGB ICC routes to the photographic studio target. The color system the profile declares and the color profile the renderer applies are the same fact, read from the same model. There is no manual step where a print operator must remember to set the color profile correctly — the model does it.
Cover spine sizing today, and a profile-driven layer in progress
The spine width that ships today comes from coverSpineInches / coverSpineInchesFor in @homeroom/pdf: a documented per-page-count paper-bulk estimate using a single coefficient (in inches per page) for a standard interior stock, plus a board/hinge allowance for case binding. It is always labeled an estimate, paired with a note to verify against the print vendor’s own spine calculator before submitting. Separately, cover-spine-from-profile, cover-finish-separations, cover-capability, the finishing-configurator, and the cover-assigned-printer-profile-gate are built in @homeroom/print-delivery as a library layer with test coverage, intended to derive the spine and finishing set from the assigned printer’s declared profile instead of the single global coefficient. That profile-driven path has zero production callers today — it is not yet wired into the live cover pipeline.
How this differs from photolab.software and mailing.software
These three products share a platform but are distinct operations. Confusing them leads to looking for production-press capability modeling in the wrong place.
printlab.software is the production-side press and print stack: capability profiles per vendor, fail-closed producibility gating, preflight, and print-ready render. It is the layer a printed publication passes through on its way to a plant. It answers the question: can the assigned printer actually produce this job, and if so, is the output print-ready?
photolab.software is the photographic and package-lab side: order intake for school portrait packages, the PSPA/pony-sort routing logic that assigns a lab order to a vendor, and the lab-routing module (@homeroom/lab-routing) that manages the vendor dispatch. Its concern is the fulfillment side of a school portrait order — which lab receives which order, how the packages are sorted, how the tracking flows back. It is not the press production layer; it is the portrait-fulfillment and vendor-routing layer.
mailing.software is the postal/print-and-mail operations home: the layer that manages the outbound mail operations for a printed piece after it leaves the press. Its concern is addressing, presort, USPS compatibility, and the mailing side of a print-and-mail job. It is not the capability-modeling or preflight layer; it is the mailing-operations layer.
A yearbook that is designed in the production editor, preflighted through printlab.software, sent to a plant, and then mailed to a school touches all three layers at different points. The design and preflight live here. The portrait-package order that feeds the yearbook portraits lives at photolab.software. The outbound mailing of the finished book lives at mailing.software. They are separate operations on the same platform, not overlapping products.
The yearbook connection
The fail-closed print pipeline described here is the production layer that yearbooks pass through on their way to a plant. A yearbook built in the production editor arrives at the manifest builder with a set of physical choices — binding, stock, trim, finishing, lamination, cover material — that were presented to the adviser only because the assigned printer’s capability profile declared them. The manifest is built, the preflight checks the manifest against the profile, the render facade produces the print-ready artifact, and the plant receives a file it can run.
The publication-type gate in assertProducible closes the gap that exists when a print pipeline treats every publication as interchangeable. A vendor that runs yearbooks may not run newspapers; a vendor that runs newsprint papers may not run the coated stocks a literary magazine requires. The CapabilityProfile’s supportedPublicationTypes list enforces the distinction: a yearbook assigned to a newspaper-only vendor fails the gate before preflight runs, rather than reaching a plant that does not know what to do with it.
yearbooklab.software is the yearbook-production toolchain front door. Its books preflight and print through this same capability/print-ready layer. The production toolchain and the print pipeline are not separate systems that need to be kept in sync — the toolchain produces the scene; this pipeline validates, preflights, and renders it. One source of truth for what the press can make, applied at every point from offer to submit.
What is in early access and what is planned
These are not presented as available today. They are named so a production operation considering this platform can see the honest state of each item rather than discovering gaps after the fact.
Live plant submit
The plant-send path — the mechanism that submits a print-ready manifest and artifact to a live production plant — is wired and switched off. When it is enabled, jobs that have cleared every gate (capability gate, preflight, render gate) will be submitted to the plant programmatically. No job reaches a plant today; the submit path is the early-access leg. We name this plainly. Early access
Pricing rail
The capability entries in a CapabilityProfile carry integer-cents upcharges as producibility facts — the supplier’s declared upcharge for a finishing option or a material, in exact integer cents. The pricing rail that processes these into a live quote or a checkout transaction is switched off. An unpriced capability entry (priceCents=null) is a custom-quote capability: the press can run it, but the price is not yet firm. No transaction is processed today and no checkout is live. Money is honest-off. Early access
Additional vendor capability profiles
The profile resolver is built and fail-closed. The completeness of the capability model depends on the profiles that have been entered for each vendor. As additional vendor profiles are authored and validated against real plant specifications, the range of producible options the gate can confirm expands. This is ongoing data work, not a feature build. In progress
Automated plant communication
After a job is submitted to a plant, the production workflow involves status updates, proof approvals, and delivery confirmations. An automated plant-communication layer — where status events from the plant flow back into the job record — is planned alongside the live plant-send path. Planned
Common questions
What is the difference between this and photolab.software?
printlab.software is the production-side press and print stack: capability profiles per vendor, fail-closed producibility gating, preflight, and print-ready render. It is the layer a printed publication passes through on its way to a plant. photolab.software is the photographic and package-lab side: school portrait order intake, PSPA/pony-sort routing, and the lab-routing module that assigns a lab order to a vendor. They are different operations on the same platform — distinct products, cross-linked, not overlapping.
What is the difference between this and mailing.software?
mailing.software is the postal/print-and-mail operations home: addressing, presort, USPS compatibility, and the mailing-operations side of a print-and-mail job after it leaves the press. printlab.software is the capability-modeling and preflight layer that runs before the press produces the piece. They serve different points in the lifecycle of a printed piece.
What does “fail-closed” mean for the capability gate?
It means the default posture is denial. A capability not explicitly declared in the profile is denied, not permitted. A vendor with no profile data resolves to the EMPTY profile, which denies everything. There is no permissive branch, no implicit "the vendor probably supports this," and no fallback that lets a job through on the assumption that most printers are similar. An absent entry is a no.
What does “fail-loud” mean for assertProducible?
Fail-loud means the gate throws a typed error with a status code, a machine code, and a plain-language reason when a job fails the producibility check. The error is ProducibilityDeniedError with a 422 status. The reason is human-facing plain text the UI can show directly. The detail bag carries physical-spec tokens only — dimension, value, vendor, publication type — never any student-identifying text. A failure is loud so the caller knows exactly what failed and why, not just that something went wrong.
Can a job reach a plant if the printer has no capability profile?
No. resolveCapabilityProfile returns the EMPTY profile for a vendor with no data. The EMPTY profile has hasData=false and every capability set empty and every publication type unsupported. assertProducible checks the publication type first; the EMPTY profile fails that check and throws ProducibilityDeniedError with the code vendor_no_capability_data and a reason asking for a printer with a published profile. The job does not proceed.
What is the check:render-facade invariant?
It is an enforced architectural rule: there is exactly one place in the codebase that maps a composite scene to the @homeroom/pdf render primitive (renderPdf). That place is the render facade in packages/composite/src/render-facade.ts. No other composite module calls renderPdf. The facade re-runs the render gate server-side before it renders, so the gate that preflight passed and the gate that the renderer checks are the same gate. There is no divergence between what preflight approved and what the renderer produces.
What is a “publication type” in the capability model?
The publication type is the kind of printed artifact: yearbook, newspaper, news magazine, literary magazine, magazine, class storybook, or event program. Non-print mediums (news site, broadcast, podcast) are not printable and are denied by any profile. Each CapabilityProfile declares which publication types the vendor will print. A book whose type is not in the declared set is denied at the first gate in assertProducible, before any physical attribute is checked.
Are pricing details available today?
No. The capability entries in a profile carry integer-cents upcharges as producibility facts — what the supplier declares as the upcharge for a specific option, in exact integer cents, or null for a custom-quote capability. The pricing rail that processes these into a live quote or a checkout transaction is switched off. No transaction is processed today. Money is honest-off.
Can I configure which printers and suppliers have capability profiles?
The profile data is entered and validated against real plant specifications by the platform team. As additional vendor profiles are authored, the range of options the gate can confirm expands. The resolver and the gate are built; the completeness of the model grows with the profiles that have been entered.
How does the spine width get calculated?
The spine width that ships today is a documented paper-bulk estimate, not a per-printer-profile computation: coverSpineInches / coverSpineInchesFor (in @homeroom/pdf) multiply the page count by a single coefficient — 0.002252 inches per page, based on a standard 60# uncoated text stock — and add a board/hinge allowance for case binding. The result is always labeled an estimate, with guidance to verify it against the print vendor’s own spine calculator before submitting. A per-printer-profile, per-stock spine derivation (cover-spine-from-profile, in @homeroom/print-delivery) is built as a library layer with test coverage but has zero production callers today; it is available/roadmap, not the live calculation.
What happens when a finishing option is not available for the assigned printer?
The option is shown as a disabled tile with a whyUnavailable reason: a plain-language string from the profile that names the printer and the denied option. The adviser sees why it is unavailable, can assign a different printer with the right capability, or can contact us to confirm the printer’s actual capabilities. The option is not hidden; it is shown as unavailable so the adviser understands the constraint.
Related products
These are the properties that share a platform with the production print stack or sit adjacent to it in the job lifecycle.
photolab.software
The photographic and package-lab side: school portrait order intake, PSPA/pony-sort routing, and the lab-routing module that assigns a lab order to a vendor. The portrait-fulfillment and vendor-routing layer — distinct from press capability and preflight.
yearbooklab.software
The yearbook-production toolchain whose books preflight and print through this same fail-closed capability and print-ready layer. The production toolchain front door for yearbook design, layout, and adviser workflow.
mailing.software
The postal/print-and-mail operations home: addressing, presort, USPS compatibility, and mailing operations for a printed piece after it leaves the press. Distinct from press production capability and preflight.
homeroom.software
The flagship K–12 platform brand home: yearbook, picture day, student records, family communication, scheduling, and the full school program suite that this production print stack is part of.
schoolyearbook.software
A consumer and school front door for the yearbook program whose print jobs land on this production layer: the capability gate, the preflight, and the print-ready render that sends a book to a plant.
What is built and what is honest-off
Capability profiles per vendor (CapabilityProfile, resolveCapabilityProfile, the EMPTY fail-closed default) are built and running today. The fail-closed, fail-loud producibility gate (assertProducible, ProducibilityDeniedError 422, capabilityFor, whyUnavailable) is built and running today. Cover and spine sizing that ships today uses a documented per-page-count paper-bulk estimate (coverSpineInches / coverSpineInchesFor in @homeroom/pdf: a single coefficient for a standard interior stock, plus a board/hinge allowance for case binding) — built and running today, always labeled an estimate, paired with a note to verify against your print vendor’s own spine calculator before submitting. Preflight and print-ready output (the print manifest, manifest-validation, ssy-manifest-validator, the single render facade check:render-facade invariant over @homeroom/pdf) are built and running today. A per-printer-profile, per-stock spine and finishing derivation (cover-capability, cover-spine-from-profile, cover-finish-separations, finishing-configurator, cover-assigned-printer-profile-gate) is built as a library layer with test coverage but has zero production callers today — it is not yet wired into the live cover pipeline. We list it as available/roadmap, not running. The live plant-send path and the pricing/checkout rail are in early access: wired and switched off pending enablement. No job is submitted to a plant today and no transaction is processed — we say so plainly rather than presenting the pipeline as fully live. Money is honest-off throughout. No competitor names appear on this page.
Print Lab Software is a Stanley Studios product — the production-side press and print stack for the K–12 platform. The same stack handles yearbooks from the production toolchain at yearbooklab.software and printed publications across the platform fleet. Distinct from photolab.software (photographic/package-lab order intake and vendor routing) and from mailing.software (postal operations).