fix(okf-v0.2): V-A7 counted every naming field but nullable

The gate refuses a schema that names both `timestamp` and `generated`,
because OKF §13.1 grants the legacy fallback only while `generated` is
ABSENT. `__post_init__` built the named set from `order | required |
allowed` and left `nullable` out, so

    FrontmatterSchema(order=("timestamp", "title"),
                      nullable=frozenset({"generated"}))

constructed, and `violations()` on a document carrying both returned ().
The namespace is open by default, so a key named only by `nullable` is
admitted exactly as surely as an emitted one — the omission was a hole in
the gate, not a narrower gate.

Found by the proving consumer the same day the gate was written; their
construction is the red test case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
This commit is contained in:
Kjell Tore Guttormsen 2026-07-26 21:28:13 +02:00
commit a00355533e
2 changed files with 14 additions and 1 deletions

View file

@ -35,6 +35,10 @@ RESERVED_OKF_TYPE = "verdict"
# schema can express today is a scalar (`_is_legal_value`) and therefore
# malformed as a v0.2 mapping — naming both IS the hazard here. When a value
# model can express a well-formed `generated`, this narrows with it.
# "Naming" spans EVERY field that puts a key in the schema's namespace — `order`,
# `required`, `allowed` and `nullable` alike. A field left out of that union is a
# hole in the gate, not a narrower gate: the namespace is open by default, so a
# key named only by `nullable` is admitted just as surely as an emitted one.
_TIMESTAMP_FALLBACK_PAIR = frozenset({"timestamp", "generated"})
@ -144,7 +148,7 @@ class FrontmatterSchema:
key_pattern: re.Pattern[str] | None = None
def __post_init__(self) -> None:
named = set(self.order) | set(self.required)
named = set(self.order) | set(self.required) | set(self.nullable)
if self.allowed is not None:
named |= set(self.allowed)
if _TIMESTAMP_FALLBACK_PAIR <= named: