docir — design documents Graph

Documents / Issues / issue-e3c4dfad4f7b

A type may declare a required field no document can carry, and the schema loads anyway

'required:' accepts any name but is checked with getattr on the entity, so an unsatisfiable name loads fine and then rejects every add of that type, naming the write rather than the schema.

issue-e3c4dfad4f7bissueresolved#material#schema
View as Markdown◉ View in graph

Class: incorrect · Severity: material Source: ref-a6db21f52427 re-verification, 2026-08-06 (found while writing issue-90aea6d1b891) Step: authoring docs-schema.yaml · Frequency: the first time anyone uses required:

Finding

A type may declare required: [anything] and the schema loads. Every add of that type then fails, permanently, with no way to satisfy the field.

_parse_type checks only that required is a list (schema_loader.py:290-292) — unlike default_status, transition targets and inactive_statuses, which are all checked against the declared statuses. Tier0Validator.validate_required_fields then reads each name with getattr(document, name, None) (validation.py:33-42), so a name that is not a Document attribute is None for every document ever written.

Reproduced in a throwaway store: a type declaring required: [code] rejects every add with required field 'code' is missing or empty for type 'probe'. No CLI flag can supply it, and the markdown store would drop the key on the next write anyway (markdown_store.py:118-160).

What happens today

The doc comment shipped in every generated schema invites exactly this: "extra frontmatter fields this type must carry, on top of the always-required id/title/description/type/status/created/ updated" (default_schema.py:80-81). It reads as an extension point and is one only for the handful of names that are already entity attributes (owner, verified, tags, related, body, path). The failure surfaces at the first write, names a field the author believes they declared correctly, and does not mention the schema.

Impact

Same class as the status-name defect the loader already guards: a typo or a wrong assumption in the schema is reported much later, by a message that points at the write. The schema is the one file in the store that cannot be rebuilt from the documents, so an author edits it rarely and with little feedback.

Proposed default

Validate required at load against the set of fields a document can actually carry, and raise SchemaError naming them — the same shape as the existing status check. A schema that cannot be satisfied should not load.

Deliberately not proposed: making required accept arbitrary frontmatter keys. That is a different feature (arbitrary per-type metadata), it is what issue-90aea6d1b891 needs for code: specifically, and it should be decided there rather than smuggled in as a loosened validation.

Resolution

FIXED 2026-08-06, as proposed. _parse_type now checks every required: entry against REQUIRABLE_FIELDS and raises SchemaError naming the fields that would have worked — the same shape as the undeclared-status check beside it, and for the same reason: a schema that cannot be satisfied should not load.

The allowed set is derived from the Document dataclass rather than written out, so it cannot fall behind a new field — code landed the same day and needed no edit here. path is excluded: it is a real field, and the file store assigns it after Tier 0 runs, so requiring it would reject every create.

The fix surfaced its own second half. With real field names now expressible, required: [tags] was accepted and enforced nothing: validate_required_fields treated only None and a blank string as missing, and an empty tuple is neither. A rule that loads, reads as enforced and enforces nothing is worse than one that is refused — so emptiness now covers collections, and required: [tags] means "at least one tag". False stays a value rather than an absence: archived: false is the normal state of a document, and plain falsiness would have rejected every unarchived one.

Also rewritten: the comment in the shipped docs-schema.yaml that described required as "extra frontmatter fields this type must carry". That phrasing is what invited a name no document could carry; it now says the entry names an existing document field and lists them.

Both halves were confirmed by injecting the bug each claims to catch.

Actors affected

  • ACT-002 repository maintainer / developer

Evidence

  • src/docir/modules/documents/infra/schema_loader.py:290-292
  • src/docir/modules/documents/domain/services/validation.py:33-42
  • src/docir/modules/documents/infra/default_schema.py:80-81
  • src/docir/platform/filesystem/markdown_store.py:118-160

To amend: Re-verify: