Today — a plan is checked value by value and not at all key by key. Against 0.6.3, with a background plan the tool saved itself:
$ mossaic-art VYNCINT --year 2026 --start-week 6 --background 2 --save --plan ./bg.json --no-colour >/dev/null
$ mossaic-art --track --plan ./bg.json --merge … --format json | jq -c '{field_level}'
{"field_level":2}
$ # the same file with `background` renamed `backgruond`
$ mossaic-art --track --plan ./bgtypo.json --merge … --format json | jq -c '{field_level}'
{"field_level":0}
exit=0 (stderr empty)
$ # by contrast, a bad VALUE:
$ mossaic-art --track --plan ./bgbad.json --merge … --no-colour
mossaic-art: ./bgbad.json is not a plan these tools can … background is 99, which is not between 0 and 4
A key a build simply does not have behaves the same way. Dropping art, or renaming it Art, turns a 146-day picture into a 79-day text at exit 0:
plan.json {"text":"Dragon","columns":53,"letters":146,"owing_commits":6902}
noart.json {"text":"DRAGON","columns":35,"letters":79,"owing_commits":4602}
and --backfill then asks for a different date range and a different total (65 days / 4,294 contributions for the picture, 42 days / 4,382 commits from 2026-01-04 to 2026-08-18 for the text).
Worth saying what this is not, so it is not re-argued in the thread: a plan carrying keys this build has never heard of behaves identically to one without them (schema + outline added: byte-identical output), and the old-reader direction is already written down as deliberate and unfixable in CHANGELOG 0.6.0:302-308, with art's #[serde(default)] documented as intentional at src/plan.rs:583-589.
Why it is worth fixing — src/plan.rs:592-601 already argues the case: "A plan is input: --plan PATH names a file, and a file may have come from somewhere other than your own --save. Without this it was the way around every bound the command line enforces." validate() then bounds year, top, commits, start_week, background and the stored picture's width — every value, and no part of the shape. So the loader refuses background: 99 by name and accepts backgruond: 2 in silence, applying the default instead: about 290 background days become keep-dark days, the one kind the Action's README calls "do not commit today".
The plan is the input to --backfill --write, and contributions cannot be unlit. A hand-edit typo produces permanent commits with exit 0 and nothing on stderr to look back at, from a loader that is loud about every other kind of wrong.
Fix — #[serde(deny_unknown_fields)] on plan::Spec is the mechanical part, and it turns a typo into the same loud refusal a bad value already gets. It is not free: it also refuses a plan written by a newer mossaic, which is the forward direction CHANGELOG 0.6.0 deliberately left open, so this is a policy call rather than a cleanup. Two ways to have both:
- write a
version (or schema) field on save and refuse only a version higher than the build understands, keeping unknown keys tolerated within a known version; or
- take
deny_unknown_fields and accept that a plan is version-locked to the tool that wrote it, which is closer to what the file already is in practice.
Either way the one-sentence caveat missing from docs/ART.md's "Saving the plan" section is not optional: that section teaches --save and never says a picture plan needs a mossaic that understands art.
Done when — a plan carrying a key Spec does not recognise is refused by name and non-zero rather than applied at its default, and the message says which key; the chosen forward-compatibility rule is written down in docs/ART.md's "Saving the plan" section alongside the picture-plan caveat; and tests/art_cli.rs covers a mistyped key, a bad value, and — if a version field lands — a plan from a future version.
Today — a plan is checked value by value and not at all key by key. Against 0.6.3, with a background plan the tool saved itself:
A key a build simply does not have behaves the same way. Dropping
art, or renaming itArt, turns a 146-day picture into a 79-day text at exit 0:and
--backfillthen asks for a different date range and a different total (65 days / 4,294 contributions for the picture, 42 days / 4,382 commits from 2026-01-04 to 2026-08-18 for the text).Worth saying what this is not, so it is not re-argued in the thread: a plan carrying keys this build has never heard of behaves identically to one without them (
schema+outlineadded: byte-identical output), and the old-reader direction is already written down as deliberate and unfixable in CHANGELOG 0.6.0:302-308, withart's#[serde(default)]documented as intentional at src/plan.rs:583-589.Why it is worth fixing — src/plan.rs:592-601 already argues the case: "A plan is input:
--plan PATHnames a file, and a file may have come from somewhere other than your own--save. Without this it was the way around every bound the command line enforces."validate()then bounds year, top, commits, start_week, background and the stored picture's width — every value, and no part of the shape. So the loader refusesbackground: 99by name and acceptsbackgruond: 2in silence, applying the default instead: about 290 background days become keep-dark days, the one kind the Action's README calls "do not commit today".The plan is the input to
--backfill --write, and contributions cannot be unlit. A hand-edit typo produces permanent commits with exit 0 and nothing on stderr to look back at, from a loader that is loud about every other kind of wrong.Fix —
#[serde(deny_unknown_fields)]onplan::Specis the mechanical part, and it turns a typo into the same loud refusal a bad value already gets. It is not free: it also refuses a plan written by a newer mossaic, which is the forward direction CHANGELOG 0.6.0 deliberately left open, so this is a policy call rather than a cleanup. Two ways to have both:version(orschema) field on save and refuse only a version higher than the build understands, keeping unknown keys tolerated within a known version; ordeny_unknown_fieldsand accept that a plan is version-locked to the tool that wrote it, which is closer to what the file already is in practice.Either way the one-sentence caveat missing from docs/ART.md's "Saving the plan" section is not optional: that section teaches
--saveand never says a picture plan needs a mossaic that understandsart.Done when — a plan carrying a key
Specdoes not recognise is refused by name and non-zero rather than applied at its default, and the message says which key; the chosen forward-compatibility rule is written down in docs/ART.md's "Saving the plan" section alongside the picture-plan caveat; and tests/art_cli.rs covers a mistyped key, a bad value, and — if a version field lands — a plan from a future version.