Skip to content

conformance: every object position declares its own closure - #9

Merged
coderoast-dev merged 1 commit into
mainfrom
spec/schema-closure-walk
Aug 24, 2026
Merged

coderoast-dev merged 1 commit into
mainfrom
spec/schema-closure-walk

Conversation

@coderoast-dev

Copy link
Copy Markdown
Collaborator

Change class: Editorial + conformance tooling (GOVERNANCE.md §2). No conformant document changes validity. {"description": ...} and true are the same schema in Draft 2020-12, and an absent additionalProperties already meant open — so nothing here closes anything, and a 0.9.0 producer stays legal.

What it adds

--selftest walks every object position in both shipped schemas — after the $defs mirror check, before any fixture runs — and requires each to declare one of three dispositions:

disposition meaning
additionalProperties: false closed — the members are named
a constraining value schema a map: closed over its VALUES, never over its key set
{"description": "<why it is open>"} open, with its reason attached

An absent additionalProperties is a defect, because absence is not a disposition — it is the lack of one. provenance[].source and attribution.sketch_params are byte-identical {"type": "object"} in the shipped metalog schema and mean opposite things: the first is a standard object whose members §12.4 names, the second a map whose keys are data. Nothing in the schema text separates them; only the prose does. So the census of what is open cannot be maintained by reading the schema, and a hand-kept list of exemptions beside it would rot on the next release. The position set is derived from the artifacts — a position added tomorrow is checked on arrival.

Bare true is refused because it is the one spelling with nowhere to put the why. Accepting a node-level description instead would have passed both document roots vacuously, on sentences that describe the document type ("Pair-wise difference between two MetaLog documents") and say nothing about openness — a check going green on the two positions it exists to interrogate.

Exit 2, like a $defs drift: the standard's own artifacts fail to say what they mean, and no verdict about anyone's documents is honest underneath that.

First run on the shipped tree: RED at 7 of 49 positions

position shipped state
metalog # true, no reason
metalog #/properties/provenance/items/properties/window true, no reason
metalog #/properties/attribution/properties/sketch_params absent
metalog #/properties/provenance/items/properties/source absent
diff # true, no reason
diff #/properties/current/properties/window true, no reason
diff #/properties/previous/properties/window true, no reason

All seven now declare, and nothing closes. sketch_params declares that it is a map: §6 makes it required and makes its parameter set depend on sketch_type, so additionalProperties: false there would admit only the empty object and invalidate every document carrying attribution. provenance[].source declares that it is a standard object whose members are not declared at that position, so closing it bare would forbid the service and host that §12.4's own example carries.

Two coupled repairs, because landing the walk alone would have shipped defects with it

The undescribed walker read the spelling, not the meaning. It decided "unconstrained by design" from the absence of every object keyword, so writing an openness down instead of leaving it absent armed the walker against the author. Measured: spelling those two positions open produced six invented findings on a fully conformant document, while additionalProperties: {} — the same schema — produced none.

The mirror image was live in the other direction: any non-empty value schema was treated as describing the extras, so moving the two roots from true to {"description": ...} would have silenced the legal-but-undescribed species at both of them.

Both halves are mutation-measured:

mutation result
revert the meaning-based silence test valid/rich.metalog.jsonl reds with the six invented findings
revert the constraining-fallback guard undescribed/open_containers.metalog.jsonl + invalid/unprefixed_extension_key.diff.json red by going quiet
delete additionalProperties from one position exit 2, names #/$defs/cube_cell
spell one open position bare true exit 2, names diff #
census in-place applicators too 58 positions instead of 49, reds on all 9 fragments

In-place applicators are not positions, and the exclusion is load-bearing. additionalProperties: false inside an if changes the condition; inside a then it closes the object. Demanding a declaration there would order an author to break his own schema.

The control that should have caught this was blind

valid/rich.metalog.jsonl claimed to exercise "sketch-shaped free objects" while carrying no attribution block at all, and its provenance[] entry carried no source. The two positions this change had to rule on were the two the undescribed-false-positive control could not see. It now carries §6's three sketch parameters and §12.4's service/host beside a fleet.

Also repaired

Six live sites claimed the roots are spelled additionalProperties: true; they now say the roots are open, which is what was ever meant and stays true. 0.8.0's CHANGELOG entry is deliberately left alone — it is a past-tense record of a state that really was that.

Verification

  • python3 conformance/metalog_validate.py --selftest → 16/16, 5 controls armed, exit 0
  • python3 conformance/metalog_validate.py --expect-documents 1 schema/metalog.v0.example.json → CONFORMANT, exit 0
  • Both schemas still pass Draft202012Validator.check_schema
  • Baseline before this branch: 16/16 with the same 5 controls — the fixture count and control set are unchanged

… censused

Absence is not a disposition. `provenance[].source` and
`attribution.sketch_params` are byte-identical `{"type": "object"}` in the
shipped metalog schema and mean opposite things: the first is a standard object
whose members §12.4 names, the second a map whose keys are data. No property of
the schema text separates them — only the prose does. So a census of what is
open cannot be maintained by reading `additionalProperties`, and a hand-kept
list of exemptions beside the schemas would rot on the next release. Enforce the
rule instead and derive the position set from the artifacts.

--selftest now walks every object position in both schemas, after the $defs
mirror check and before any fixture runs, and requires each to declare one of
three dispositions: `false`, a CONSTRAINING value schema (a map — closed over
its values, never over its key set), or `{"description": "<why it is open>"}`.
Bare `true` is refused because it is the one spelling with nowhere to put the
reason; accepting a node-level `description` in its place would have passed both
document roots vacuously, on sentences describing the document TYPE ("Pair-wise
difference between two MetaLog documents") that say nothing about openness — a
check going green on the two positions it exists to interrogate. Exit 2, like a
$defs drift: the standard's own artifacts fail to say what they mean, and no
verdict about anyone's documents is honest underneath that.

In-place applicators are not positions and the exclusion is load-bearing:
`additionalProperties: false` inside an `if` changes the condition and inside a
`then` closes the object, so demanding a declaration there would order an author
to break his own schema. Nine such fragments carry `required`/`properties` and no
`type` here; a walk blind to the distinction censuses 58 instead of 49 and reds
on all nine.

Run against the shipped v0.9.0 pair it reds at 7 of 49 — the two absences above,
and five positions open with no reason attached (both roots, `provenance[].window`,
the diff's `current.window` and `previous.window`). All seven now declare, and
NOTHING closes: `{"description": ...}` and `true` are the same schema in Draft
2020-12 and an absent `additionalProperties` already meant open, so no conformant
document changes validity. `sketch_params` declares that it is a map — §6 makes it
REQUIRED and its parameter set depend on `sketch_type`, so `false` there would
admit only the empty object and invalidate every document carrying `attribution`.

Coupled, because landing the walk alone would have shipped two defects with it.
The undescribed walker decided "unconstrained by design" from the ABSENCE of
every object keyword, so writing an openness down instead of leaving it absent
armed the walker against the author: measured, spelling those two positions open
produced six invented findings on a fully conformant document while
`additionalProperties: {}` — the same schema — produced none. The mirror image was
live too: any non-empty value schema was read as DESCRIBING the extras, so moving
the roots from `true` to `{"description": ...}` would have silenced the
legal-but-undescribed species at both of them. The walker now reads what a
declaration means rather than whether a keyword is spelled out. Both halves are
mutation-measured: reverting the first reds valid/rich.metalog.jsonl with the six
findings; reverting the second reds undescribed/open_containers.metalog.jsonl and
invalid/unprefixed_extension_key.diff.json by going QUIET, which is the worse
direction.

And the control that should have caught this was blind. valid/rich.metalog.jsonl
claimed to exercise "sketch-shaped free objects" while carrying no `attribution`
block at all, and its `provenance[]` entry carried no `source` — so the two
positions this change had to rule on were the two the undescribed-false-positive
control could not see. It now carries §6's three sketch parameters and §12.4's
service/host beside a fleet.

Six live sites claimed the roots are spelled `additionalProperties: true` and are
repaired to say they are OPEN, which is what was ever meant and stays true;
0.8.0's CHANGELOG entry is left alone because it is a past-tense record of a state
that really was that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderoast-dev
coderoast-dev merged commit 73b5600 into main Aug 24, 2026
1 check passed
@coderoast-dev
coderoast-dev deleted the spec/schema-closure-walk branch September 19, 2026 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant