Dominik Krzemiński

Zapis decyzji 0002Przyjęta

Zod owns the content contract; C# mirrors it under CI enforcement

Ta strona nie została jeszcze przetłumaczona — czytasz angielski oryginał.

Context

ADR 0001 leaves two independent parsers reading one corpus. TypeScript and C# cannot share a type system, so the frontmatter contract exists twice. Without enforcement, a field added to the Zod schema is silently dropped by the .NET parser, and the machine surface starts answering with stale or missing data.

Decision

Zod is the source of the contract. C# mirrors it. Three independent checks make divergence a build failure rather than a runtime surprise.

  1. astro check validates the entire corpus against the Zod schemas, including referential integrity of every reference('stack') — a dangling technology id fails the build.
  2. dotnet test parses the same real corpus (not fixtures) with Portfolio.Content. The YamlDotNet deserializer runs without IgnoreUnmatchedProperties, so a frontmatter field that Zod knows about and C# does not is an error, not a silent drop.
  3. JSON Schema as a committed artefact. content/.schema/*.json is generated from the Zod schemas by pnpm --filter @krzemo/web schema:emit and committed. CI runs schema:verify to prove it is current, and the C# tests validate every parsed record, re-serialized, against it. That catches the opposite direction: a C# record that no longer satisfies the contract.

ContentSchema.Version in C# and CONTENT_SCHEMA_VERSION in TypeScript must be equal; every content file declares schemaVersion and the parser rejects a mismatch. That is the deliberate breaking-change lever.

What C# deliberately does not validate

Length and range constraints (min, max) are declared only in Zod. Re-implementing them in C# would be the exact duplication this ADR exists to prevent. C# validates structure, enums, URL and date formats, and semantic invariants such as period.end >= period.start. Value constraints are enforced by astro check and the JSON Schema contract test, both of which run before anything deploys.

Consequences

  • Editing a Zod schema is a three-step change: edit, regenerate the JSON Schema, mirror the C# record. CI fails if any step is skipped.
  • The JSON Schema artefact is generated with a plain-string stack reference, because Astro’s reference() carries a Zod transform and transforms have no JSON Schema representation. The shared schema modules therefore take the reference validator as a parameter.
  • Zod v4’s native z.toJSONSchema removes the need for zod-to-json-schema.

Alternatives rejected

Generate C# from the Zod schemas. A code generator is a build dependency, a toolchain to maintain, and a debugging surface, for a model of roughly forty fields that changes a few times a year. Enforcement is cheaper than generation at this size.

A shared IDL (Protobuf, TypeSpec) as the source, generating both. Correct at a larger scale. Here it would mean writing content schemas in a third language that neither runtime reads natively, and losing Astro’s inferred types.

Wszystkie notatki