@setsquare/schema
v0.11.0
Published
The Setsquare wire contract. Zod definitions that generate every other representation — JSON Schema, OpenAPI, typed clients. Published because @setsquare/review-sdk depends on it at runtime (P1-17.8).
Downloads
1,047
Readme
@setsquare/schema
The wire contract between Setsquare's SDK and its platform, and the definition of the finding format itself.
Apache License 2.0 — free to use, modify, vendor, mirror and redistribute, provided
you retain the notices, state your changes and pass on the NOTICE shipped
alongside the LICENSE. OSI-approved, so it should clear a dependency policy
without a conversation; not an open-source project, because the repository is
private and the package ships built output. The accurate phrase is "Apache-2.0
licensed". (Revised 2026-09-08 from the Elastic License 2.0, whose hosted-service
restriction no longer applies.)
You probably do not need to install this directly. It arrives as a dependency of
@setsquare/review-sdk,
and that is the package to start with.
What it is for
Install it directly when you are on the receiving end: writing something that consumes a Setsquare run, validating an envelope in CI, or building a sink that must agree with ours about what a finding is.
import { runIngestRequestSchema, FORMAT_VERSION } from '@setsquare/schema';
const parsed = runIngestRequestSchema.parse(JSON.parse(body));formatVersion accepts every supported version, 1.0 to the current one, while
the shape it validates is always the current one. That works because every
change since 1.0 was published has only added optional fields and values, so
an older minor's envelope parses as it is, with nothing migrated. So a field a newer minor added
may be absent from what you parsed: read formatVersion before relying on one.
Also exported: @setsquare/schema/limits (the caps and character classes the SDK
sanitises against), /wcag22 and /conformance (the rule-to-criterion
projection), /automation-limits (why sixteen criteria have no rule that can
decide them, and what forty-four can and cannot be checked for),
/component-scope (what a component scan does not apply or withholds, and
why), /questions (below), /axe-rules, /format-version and /client.
Counting and answering questions, as the report does
A row's wording can state a measurement, such as "a requirement of 470px²", and
a font metric changes it between themes. So grouping rows by their text counts
one element several times. @setsquare/schema/questions is the definition the
run summary and report.html use: one question per rule, surface, state and
persona, across themes, viewports and engines, and a page-wide listener once
for the run. It also applies a person's answers file the same way the reporter
does. Answers are never in run.json, because they are statements, not
measurements.
import {
answerQuestions,
elementsOf,
judgementQuestions,
parseAnswers,
} from '@setsquare/schema/questions';
const questions = [...judgementQuestions(run).values()];
const total = questions.reduce((sum, question) => sum + elementsOf(question), 0);
const answers = parseAnswers(JSON.parse(answersFileText), 'setsquare-answers.json');
const { answered, passes, fails, unasked } = answerQuestions(run, answers);
// Every answered question is "not re-checked against this run".Why it is a package at all
Zod is the single source of truth. The JSON Schema, the OpenAPI document and the typed client are all generated from it, and a shared fixture corpus is validated by both runtimes that read the wire — the TypeScript SDK and the PHP platform. That corpus includes invalid fixtures pinned to the exact path they must fail at, so a schema change that quietly relaxes a constraint fails loudly instead.
Publishing it separately is what lets the SDK depend on one definition rather than duplicating wire shapes, which is the whole point: two definitions of a contract are two contracts.
What you can rely on
FORMAT_VERSION is the contract's version, and it is separate from this package's
version. A change to the wire format moves the former; a change to the packaging
or the exports moves only the latter.
Fields are .strict(), so a payload carrying a field we removed is refused rather
than silently tolerated. Optional means absent, not zero — a field that can be
genuinely unknown is optional so that a zero stays readable as a zero.
Some fields are marked "Contract-only for now — validated on ingest, not yet persisted" in their docblock. That is exactly what it says: the platform accepts them and does not yet store them, so nothing may imply it is trending something it never kept. Check the docblock before building on a field.
Node
Node 22 or later. That is the oldest active LTS line, and it is the floor our
compatibility matrix tests. @playwright/test itself allows Node 20, so we are
never the reason an install fails on a supported runtime — we simply do not claim
a floor we have decided not to verify.
Reporting a problem
Bugs, questions and security reports all go to https://setsquare.dev/report, which files a tracked issue directly. There is no public issue tracker — the repository is private — so the form is the whole route in, and it is watched.
Not open to outside contributions yet. The source is not published pre-1.0, so there is nowhere to send a pull request. That is a deliberate position rather than an oversight, and it will be revisited.
Licence
Apache License 2.0 — see LICENSE and NOTICE.
The wire contract shares the SDKs' licence rather than the platform's because it has to be shippable inside them. That used to need explaining at length: under the two-licence split an Apache-2.0 package could not depend on a source-available one, and a hand-maintained duplicate of the contract inside each SDK — exactly what this package exists to prevent — was the alternative. With one licence across everything published, the constraint is gone and the reason is a footnote.
(The LICENSE file itself carries no such note any more: it is the verbatim
Apache-2.0 text, byte-identical to licenses/Apache-2.0.txt and asserted so by
scripts/verify-packages.mjs. The copyright line and trademark reservation live
in NOTICE, which is where Apache-2.0 §4(d) expects them.)
