Skip to content

`metadata.conventions` Carries Only the Producing Platform

Summary
MetadataConventions narrows a spec's recorded conventions to the one platform entry that produced it, not every configured platform.
Status
ACCEPTED · 2026-08-30
Deciders
Nathan Curtis (author)

Context

Every emitted spec records the configuration it was produced with (types/Metadata.ts):

conventions: ResolvedConventions;
settings: ResolvedSettings;

Under ADR-071 that was unambiguous, because Conventions held one thing — facts about the Figma library — and the doc comment says exactly that:

conventions — Facts about the Figma library this spec was generated from.

ADR-073 made Conventions a platform-keyed map, and ADR-078 spread it across a directory. The type in Metadata did not change, so a spec generated from Figma in a four-implementation workspace now embeds this:

metadata:
conventions:
platforms:
figma: # produced this spec
naming: SENTENCE
glyphs: {...}
states: {...}
react: # had nothing to do with it
primitives: {...}
web-components: # nor this
primitives: {...}
swiftui: # nor this
primitives: {...}
compose: # nor this
primitives: {...}

Four platforms’ component vocabulary rides along in every spec, and the doc comment is now false.

This is not only noise. Three consequences follow, and the second is a defect:

  • Leakage. conventions.yaml is the artifact ADR-071 designed to be publishable — handed to another team, adopted verbatim. A spec is published far more widely, and it now carries a team’s internal component names for platforms the reader has nothing to do with
  • The drift check breaks. ADR-071 Decision 9 states the purpose plainly: “the drift check the linter wants compares metadata.conventions directly.” Comparing the whole map means a change to the Compose vocabulary marks every Figma-generated spec as drifted. The check reports conventions changed when nothing that produced the spec changed at all
  • Churn. Every spec’s metadata diffs whenever any platform’s conventions change, so a SwiftUI prop rename dirties the entire spec corpus

The producing platform is knowable and singular. A spec is generated from exactly one platform’s artifacts — Figma today, a code library when specs are produced by reading code. Everything else in the map was never consulted.


Decision Drivers

  • Metadata records what produced this artifact. A value that had no bearing on the output does not belong in its provenance
  • The drift check must compare like with like (ADR-071 Decision 9). Whatever metadata carries has to be directly comparable against the current conventions for the same platform
  • A spec is published more widely than a conventions file. What rides in metadata should be the minimum that serves provenance
  • Absence keeps its meaning (ADR-071). A platform absent from metadata must not read as “this platform declares no conventions” — it means it did not produce this spec
  • No logic in the schema package (Constitution II). The package states the shape and the constraint; the producer decides what to put there
  • Metadata.conventions is unreleased. It is @since 0.31.0; npm’s latest is 0.30.0. Narrowing it now breaks no published contract

Options Considered

Four decisions: what “relevant” means, the shape metadata carries, whether the constraint is enforced, and whether settings has the same problem.


Decision 1 — Which platform is relevant

Option 1A: The platform whose artifacts produced this spec (Selected)

Exactly one. Today that is figma, because specs are generated by reading a Figma file. When specs are produced by reading code, it is that code platform.

Pros:

  • Matches what metadata is for. metadata.source already records the Figma page and node; metadata.conventions should record the conventions that governed the reading of that node
  • Always singular, so there is no set to reason about and no ordering question
  • Makes the drift check correct rather than merely quieter: the comparison is against the one platform entry that actually shaped the output
  • Survives the bidirectional pipeline. A spec produced by reading React carries react, by the same rule and with no special case

Cons / Trade-offs:

  • A reader of a spec cannot see the target platforms’ vocabulary. Correct — those did not shape the spec, and a transform output is a different artifact with its own provenance
  • The rule depends on the producer knowing which platform it read. Every producer does; it is the entry it consulted

Option 1B: Every platform named in the run’s pipeline (Rejected)

Rejected because: it conflates provenance with intent. A pipeline declaring a React transform does not mean React conventions shaped the spec — they shape a different output, produced later, from this spec. It also makes a spec’s metadata depend on what else the workspace happened to run, so the same Figma node yields different metadata in two workspaces, breaking determinism.


Option 1C: Every platform, as today (Rejected)

Rejected because: it is the defect. Values that had no bearing on the output are recorded as its provenance, the drift check fires on unrelated changes, and internal vocabulary leaks into a widely published artifact.


Option 1D: None — drop conventions from metadata (Rejected)

Rejected because: it deletes the drift check ADR-071 built the member for. Knowing which state pattern or naming convention produced a spec is exactly what lets a consumer detect that the library moved underneath it.


Decision 2 — The shape metadata carries

Option 2A: The same shape as Conventions, with exactly one platform key (Selected)

metadata:
conventions:
platforms:
figma:
naming: SENTENCE
glyphs:
match: "DS Icon Glyph / {i}"
states: {...}

Pros:

  • Directly comparable. ADR-071 Decision 9’s stated purpose was that the drift check compares metadata.conventions against the artifact without unwrapping. Keeping the shape identical preserves that: the check narrows the loaded conventions to the one platform and compares objects
  • The platform id is carried by the key, so no separate discriminator member is needed and nothing can disagree with it
  • One type, one mental model. A reader who knows conventions.yaml reads this without translation
  • Extends without a shape change if a spec is ever produced from more than one platform’s artifacts

Cons / Trade-offs:

  • A map that always holds one entry looks like it could hold more. Decision 3 makes the constraint explicit rather than implied
  • One level of nesting for a single value

Option 2B: Flatten to the entry body plus a platform discriminator (Rejected)

metadata:
conventions:
platform: figma
naming: SENTENCE

Rejected because: it introduces a third shape for one concept — the file form, the resolved form, and now a metadata form — and the drift check would have to reshape before comparing, which is precisely the unwrapping ADR-071 Decision 9 chose Option 9A to avoid. It also mixes an identifier into a body of conventions, where every other member is a convention.


Option 2C: Keep ResolvedConventions unchanged and make it a producer convention (Rejected)

Rejected because: nothing would enforce it, and the type would keep saying “a map of every platform” while the intent is one. A constraint worth stating is worth stating in the contract.


Decision 3 — Whether the constraint is enforced

Option 3A: The schema constrains metadata’s platforms to a single entry (Selected)

A distinct definition — MetadataConventions — references the same PlatformConventions as the artifact form, with maxProperties: 1 on platforms.

Pros:

  • A spec carrying four platforms is invalid rather than merely wrong, so the defect cannot recur silently
  • The constraint lives beside the shape, where a reader of the schema will find it
  • Referencing the same PlatformConventions definition means the two forms cannot drift (Constitution I)
  • maxProperties is declarative; the package still contains no logic (Constitution II)

Cons / Trade-offs:

  • A second definition for one shape with one constraint. The alternative is a rule that lives only in prose
  • If a spec ever legitimately has two producing platforms, this is a MAJOR relaxation. No such case exists, and relaxing a bound is a smaller break than tightening one

Option 3B: Documentation only (Rejected)

Rejected because: the current defect exists precisely because the type permitted something the intent did not. Repeating that pattern with a comment is not a fix.


Decision 4 — Does metadata.settings have the same problem?

Option 4A: No — settings is unchanged (Selected)

ResolvedSettings describes the run: output format, colour format, directories, inclusion choices. Every member of it shaped the output, and none of it is platform-keyed.

Pros:

  • Keeps the change scoped to the member that actually has the defect
  • The asymmetry is not arbitrary: Conventions became platform-keyed in ADR-073 and Settings did not, so only one of them can carry entries that had no bearing on the output

Cons / Trade-offs:

  • Metadata’s two configuration members are now shaped by different rules. Documented on both

Decision

Type changes (types/)

FileChangeBump
Metadata.tsconventions retyped from ResolvedConventions to MetadataConventions — the same shape with exactly one platform entryMINOR
Metadata.tsDoc comment corrected from “Facts about the Figma library this spec was generated from” to name the producing platform, whichever it isPATCH
Conventions.tsAdded MetadataConventions — { platforms: Record<string, ResolvedPlatformConventions> } documented and constrained as a single entryMINOR

Example — new shape (types/Metadata.ts):

# Before (unreleased)
metadata:
conventions:
platforms:
figma: {...}
react: {...}
swiftui: {...}
compose: {...}
# After
metadata:
conventions:
platforms:
figma: {...} # the platform this spec was produced from — the only one

Schema changes (schema/)

FileChangeBump
component.schema.jsonmetadata.conventions references #/definitions/MetadataConventions rather than the whole-artifact conventions definitionMINOR
conventions.schema.jsonAdded #/definitions/MetadataConventions — platforms with maxProperties: 1, values $ref-ing the same PlatformConventionsMINOR

Notes

MetadataConventions references the same PlatformConventions definition as the artifact form. The two cannot drift, and a platform entry validates identically wherever it appears — in config/conventions.yaml, in a per-platform file (ADR-078), or in a spec’s metadata.

Absence has a different meaning here than in the artifact. In conventions.yaml, a missing platform means that platform declares no conventions. In metadata, a missing platform means it did not produce this spec. Both doc comments must say so, because the shapes are identical and the meanings are not.

metadata.settings is deliberately untouched (Decision 4).


Type ↔ Schema Impact

  • Symmetric: Yes
  • Parity check: MetadataConventions ↔ #/definitions/MetadataConventions; its platforms values ↔ the same PlatformConventions the artifact form references. Metadata.conventions ↔ component.schema.json’s metadata.conventions

Downstream Impact

ConsumerImpactAction required
specs-from-figmaEmit only the figma entry into metadata rather than the whole mapImplement
specs-cliSame, wherever it assembles metadata; narrow the drift check to the producing platformImplement
specs-plugin-2SameImplement
figma-from-specsReads metadata; a narrower object is still validRecompile
react-from-specsReads conventions from the resolved artifact, not from metadataNone
webcomponents-from-specsSameNone
Existing specsSpecs emitted before this change carry extra platforms and would now fail validation. Only specs generated since ADR-073 are affected, and none has been releasedRegenerate

Semver Decision

Version: 0.32.0 (release branch release/schema-0.32.0+cli-0.29.0) — MINOR

Justification: Metadata.conventions is @since 0.31.0 and unpublished — npm’s latest is 0.30.0. Narrowing a member within the release that introduces it breaks no published contract, so the release stays MINOR against 0.30.0. As with ADR-073, this holds only until 0.31.0 ships.


Consequences

  • A spec records the conventions that produced it and nothing else. Provenance means provenance
  • The drift check becomes correct: a Compose vocabulary change no longer marks every Figma-generated spec as drifted
  • Specs stop churning on changes to platforms that had no bearing on them
  • A widely published spec no longer carries internal component vocabulary for platforms its reader has nothing to do with
  • The single-entry constraint is enforced in the schema, so the defect cannot silently return
  • Metadata and the conventions artifact share one shape with two meanings for absence. Both are documented, and the shared definition keeps them from drifting
  • This must land in 0.31.0. After release, narrowing the member is MAJOR