CONCEPT Cited by 1 source
Schema linter enforcement¶
Definition¶
Schema linter enforcement is the discipline of running a build-time static-analysis tool over an API schema and failing the build when the schema violates the organisation's governance rules. The linter is a CI gate: rules live as executable checks in the build pipeline, and the PR cannot merge until the schema passes.
The mechanism is the enforcement arm that turns
declarative schema annotations (directives, naming
conventions, structural conventions) from documentation
of intent into gates on merge. Without a linter, a
policy like "fields with a sensitive-keyword name must
carry @sensitive" is an aspiration that new team
members forget; with a linter, the rule is a property of
the repository that cannot be bypassed without explicit
opt-out.
Contrast with runtime enforcement¶
A policy can be enforced at three points along the development lifecycle, each with different cost / coverage / blast-radius trade-offs:
| Enforcement point | Cost | Coverage | Failure mode |
|---|---|---|---|
Documentation ("developers should add @sensitive") |
Zero engineering | Depends on developer recall | Silent leak when forgotten |
| Linter (build-time) | Moderate engineering (write rules, wire CI) | 100% of merged schema changes | Build fails before merge |
| Runtime check (e.g. deny at execute) | Higher engineering (runtime code path) | 100% of runtime traffic | Request fails; customer impact |
Schema linter enforcement sits in the sweet spot for schema-shaped policy: schemas change infrequently enough that build-time check is a fast feedback loop, and the linter catches problems before customer-facing failure modes can happen.
The Zalando UBFF worked example¶
Zalando uses multiple linter rules that interact with schema directives:
-
PII keyword detection →
@sensitiveforcing function. The linter fails any PR whose schema contains a field or argument name matching a bootstrapped keyword list —password,email,phone,bank,bic,account,owner,order,token,voucher,customer— unless the field has@sensitiveapplied. This prevents the policy-by-discipline failure mode where a new engineer adds a sensitive field without realising redaction is a requirement. -
@finalenum value protection. The post explicitly notes: "It is only used in our GraphQL linter that executes during the build time and prevents additions of new values to enums which are marked as final." The linter compares the current PR schema against the base branch; any new value on a@final-marked enum fails the build. To make a dangerous change, the engineer must first open a PR that removes@final, and only then open a second PR adding the value (concepts/final-enum). -
Persisted-query compatibility (implicit). The persist-time validator is a linter-shaped gate on the client side: a client that references a
@draft-marked field or an unauthorised@allowedFor- scoped field fails persist registration, preventing the query from reaching production (patterns/directive-based-field-lifecycle).
(Source: sources/2023-10-18-zalando-understanding-graphql-directives-practical-use-cases-at-zalando).
Why linters succeed where documentation fails¶
- Documentation decays. A convention written in a wiki loses fidelity as the team grows; new members don't read every wiki page before adding a field.
- Policy without enforcement biases toward forgetting.
If the policy is "please add
@sensitiveto fields containing PII", the default in any PR is no directive, and forgetting is silent. - Linters invert the default. The default in any PR is the linter passes; violating the policy requires an explicit opt-out (lint-disable comment, justification in PR description, or reviewer sign-off).
- Audit trail. Every lint failure and every exception-documented PR is visible in the build history, giving the policy team a feedback loop on which rules need tuning.
Keyword-based linting is a floor, not a proof¶
The keyword approach has a known false-negative profile:
oddly-named fields escape detection (contactInfo.primary
for an email doesn't match email). Zalando's post is
aware of this — the directive must still be applied
manually in blind spots; the keyword linter catches the
obvious cases and backstops "I forgot" failures without
attempting to be a complete PII classifier. The keyword
list is bootstrapped from the organisation's own field-
naming patterns and extended as new blind spots surface.
For higher-precision PII detection, organisations can layer
semantic classification (ML-based PII detectors, regex on
sample values, or the @sensitive directive at
FIELD_DEFINITION with a central taxonomy) on top — the
linter remains the first-line enforcement mechanism.
Other schema-linter instances on the wiki¶
Non-directive-shaped schema linting has broader precedent:
- systems/zally — Zalando's open-source linter for OpenAPI specs; enforces the RESTful API Guidelines at the spec level as a CI gate (patterns/template-project-nudges-consistency).
- Language-ecosystem analogues — eslint rules, protobuf-lint, buf-lint, kotlin-detekt — all instantiate the same idea at different points in the stack.
Seen in¶
- Zalando UBFF — GraphQL schema linter with keyword-
based PII detection,
@finalenum-stability enforcement, persisted-queries validation (sources/2023-10-18-zalando-understanding-graphql-directives-practical-use-cases-at-zalando, systems/zalando-graphql-ubff). - Zalando API platform broadly —
zallylints OpenAPI specs against the RESTful API Guidelines; same enforcement-via-linter discipline at a different layer.
Related¶
- concepts/graphql-schema-directive — the markers the linter inspects.
- concepts/sensitive-field-logging-redaction — canonical keyword-based PII lint use case.
- concepts/final-enum — canonical enum-stability lint use case.
- concepts/draft-schema-field · concepts/component-scoped-field-access — adjacent lifecycle gates.
- concepts/tech-radar-language-governance — Zalando's adjacent language-governance discipline using Tech Radar as a human lint.
- patterns/directive-based-pii-redaction · patterns/directive-based-field-lifecycle — pattern- level instances that rely on linter enforcement.
- systems/zally · systems/zalando-graphql-ubff
- systems/graphql