# No `eval` at validation time.

*31 July 2026 · the launch journal*

JSON Schema validators in JavaScript are, under the hood, code generators. Ajv takes a schema and *writes a function* — a string of JavaScript compiled with `new Function()` — because interpreted schema-walking is an order of magnitude slower. That design is fine right up until you ask two questions a security reviewer always asks: **what code is executing, and when was it decided?**

Compile the schema at runtime and the answers are uncomfortable. The code that validates every inbound document is synthesized *inside the production process*, from whatever schema object happens to be in memory at that moment — a dynamic-code-execution surface that CSP policies and hardened runtimes exist to forbid. The Workers runtime this gateway targets forbids it outright: `new Function()` from strings throws `EvalError: Code generation from strings disallowed`. And even where it is allowed, you pay the compiler on every cold start, before the first request can be answered.

This gateway's answer is structural: **validators are generated ahead of time, from the pinned schemas, into standalone eval-free code — and a freshness gate fails the build if the generated artifact drifts from the pins.**

## The pipeline

The generator is one auditable script, `scripts/generate-validators.mjs`, and its header states the policy:

```
Precompile the pinned GS1 schemas into a standalone ESM validator module.
Compiling here — at generate time, with the SAME Ajv engine and options the
tests use — keeps one validation semantics everywhere while shipping
eval-free code.
```

It reads exactly two inputs — `vendor/gs1/epcis-json-schema.json` and `vendor/gs1/query-schema.json`, the artifacts pinned by sha256 in [the pins table](/conformance/) — and emits `src/gs1/validators.generated.js`: plain functions, committed to the repository, containing no `eval`, no `new Function()`, and no `require()` (the script rewrites Ajv's runtime-helper requires into static imports and *throws* if any survive: `"unrewritten require() left in generated validator module"`). The Worker bundles those functions like any other module. At validation time there is no compiler in the building.

Two gates keep the generated artifact honest:

- **Freshness:** a drift test regenerates the module from the pinned schemas and fails if the committed artifact differs — so the code that ships can never silently lag the schema on record. Change a pinned schema without regenerating, and CI stops you.
- **Provenance:** the pins themselves change only by journaled ruling, the discipline described in [the harness entry](/journal/the-harness-that-refuses-a-tampered-spec/). Chain the two together and there is no moment where the schema in memory differs from the schema on record: pinned artifact → generated code → drift gate → the digest every `validate --json` result carries back to the caller.

## The numbers

Measured on this repository, against the golden corpus, 2026-07-31 (Node 22, Apple silicon):

| measurement | result |
|---|---|
| load precompiled validator module | 14 ms, once |
| validate the seven valid-standard corpus documents × 200 (1,400 validations) | 17 ms total |
| per-document validation | **~0.012 ms (12 µs)** |
| Ajv runtime compile of the same pinned EPCIS schema, before the first validation | **73 ms** |

The comparison is the point. The full EPCIS 2.0.1 schema — five event types, the conditional `"then"` towers, the CBV value enumerations — costs 73 milliseconds to compile at runtime *before any document is validated*. Precompiled, that cost was paid at build time, and the marginal cost of a validation is twelve microseconds. In a serverless runtime that cold-starts per isolate, runtime compilation puts the compiler on the latency path of real requests; precompilation removes the compiler from production entirely.

## The questionnaire lines this closes

Security reviews of a validation gateway reliably ask some version of: *Does the service execute dynamically generated code?* No — the runtime forbids it and the shipped validators contain none; the generation step runs at build, from pinned inputs, and its output is committed and diffable. *How do you know the deployed validator matches the claimed schema version?* The drift gate, plus the digest in every result: `validate --json` returns `"schema": { "artefact": "epcis-json-schema.json", "version": "2.0.1", "sha256": "0f46ff69…" }`, which a reviewer can check against GS1's published artifact at `ref.gs1.org` without trusting this origin at all. *What is your schema update process?* A pin change by journaled ruling, then regeneration, then the gate — three artifacts, all in the open repository.

None of this is exotic. It is the same move the estate makes everywhere: decide the normative input once, pin it, derive the runtime artifact mechanically, and gate the derivation — [spec texts](/journal/the-harness-that-refuses-a-tampered-spec/), validator code, and the corpus alike. The unusual part is only that it is written down where a reviewer can check it, which is the difference between a security posture and a security paragraph.

Validate something against it now — `npx epcis.dev validate <file>` runs the same generated functions the gateway runs, and [the corpus is in the tarball](/run-it-yourself/) if you want the twelve-microsecond version of a decade of committee work.

*Sources: `scripts/generate-validators.mjs` and `src/gs1/validators.generated.js` (open repository); timings measured on this repo 2026-07-31; pinned artifact digests at [/conformance/](/conformance/) and ref.gs1.org.*

---

**Proof, not adjectives.** This build could not verify the gateway's test suite green, so no sentence on this page claims a passing run — the last verified run is recorded in the repository's own CI, not here. Live on this origin, no key: POST /translate, /validate, /hash. No conformance attestation has ever been issued. The dated ledger is /what-ships-today/.
