Skip to content

forms: the DSL's key vocabulary has no table-completeness gate, unlike wire.md #554

Description

@Yaraslaut

morph::forms has become a domain-specific language: a renderer that is not
morph's own must implement 26 x-* schema keys, 5 document keys (w-steps,
w-title, app-menu, app-screens, app-title) and 13 rule/condition kind
strings, and an application author must reach a C++ declaration surface of 64
public types and 44 public free functions across 10 headers (5,526 lines).

docs/spec/forms/ documents it — 6 files, 4,388 lines — and today it does so
almost completely. The problem is not the prose. It is that nothing can
notice when the prose stops being complete
, and this is precisely the
situation morph#233 established a gate for on the other spec that third parties
implement against.

Measured coverage today

Every "x-*" / "w-*" / "app-*" string literal in include/morph/forms/**,
and every public type and free function outside a detail namespace, checked
against the concatenated text of docs/spec/forms/*.md:

Surface Documented
Wire keys (26 x-* + w-steps, w-title, app-menu, app-screens, app-title) 31 / 31
Public types, concepts, aliases 62 / 64
Public free functions 40 / 44

The six gaps are small and worth closing on the way past:

  • concepts HasExplicitSubmit and RuleLiteral are never named in the spec
    (the explicitSubmit member is documented; the concept that detects it is
    not).
  • fieldKeyStem, fieldSlotName and withSlot (forms/i18n.hpp) and
    groupKindName (forms/layout.hpp). For the i18n three the spec documents
    the key format<actionTypeId>.<wireField>.<slot> — but never names the
    functions that build it, so the seam is described and its API is not.

There is also a systematic asymmetry rather than an omission: the rule
constructs AtLeastOneOf, ExactlyOneOf, MutuallyExclusive, ReadonlyWhen,
NotEngaged, Equals, Greater, GreaterOrEqual, Less, LessOrEqual and
Plain appear in the spec only under their JSON-key spellings
(atLeastOneOf, …). A reader emitting JSON is fully served; a reader writing
C++ has to infer the capitalisation of every one of them.

Why the prose being fine today does not settle it

Two gates touch this area and neither can see completeness:

  1. .github/workflows/spec-sync.yml requires only that some file under
    docs/spec/forms/ changed when include/morph/forms/** changed. A commit
    that adds x-newKey to forms.hpp and a typo fix to choice.md passes.
  2. scripts/check_spec_citations.sh checks 1–3 work outward from
    docs/spec/pinned_facts.toml, a hand-maintained list. A key nobody pinned
    is a key nobody checks.

Check 4 of that same script is the shape this needs. It exists because
docs/spec/core/wire.md presented itself as exhaustive and was not — the
Envelope table omitted primary and shared, the discriminator table omitted
attach/assign/instances, and four make* factories were missing
(morph#233). Its reasoning applies here verbatim:

Nothing could notice, because morph's own client and server both read
wire.hpp — so neither can observe that the spec disagrees with it. Only a
check that reads both can.

morph's own Qt/QML renderer reads the same headers the schema generator does.
It cannot observe the spec disagreeing with them either. forms.md likewise
presents its key table as the renderer contract ("Renderer contract: the schema
key vocabulary"), and the renderer conformance kit is written against that
claim.

Proposal

Add a check 6 to scripts/check_spec_citations.sh, mirroring check 4:

  • every "x-*" literal in include/morph/forms/** is a row in
    docs/spec/forms/*.md;
  • every "w-*" / "app-*" document key likewise;
  • every rule/condition kind string (atLeastOneOf, engaged, equals,
    exactlyOneOf, greater, greaterOrEqual, less, lessOrEqual,
    mutuallyExclusive, notEngaged, readonlyWhen, requiredWhen,
    visibleWhen) is documented as a kind, and its C++ constructor name is named
    beside it — which closes the capitalisation asymmetry above by construction.

Inherit check 4's per-category floors, not a lumped total. That check
carries a comment recording why: a lumped count let uint64_t Envelope fields
parse to nothing while the other two categories carried the total over the
line, and the check reported green while ignoring a whole category. A forms
version has the same failure mode across keys / document keys / rule kinds.

Extend scripts/test_check_spec_citations.sh with the matching self-test, as
the other drift-guard checkers have.

Acceptance

  • Deleting any documented x-* row from docs/spec/forms/forms.md fails the
    check locally.
  • Adding a new "x-…" literal to a forms header without documenting it fails
    the check locally.
  • A category that parses to zero fails rather than passing quietly.
  • The six gaps listed above are closed and the check passes on master.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions