Skip to content

feat: make the conformance gate class-aware; FAMILY.md v2.1 - #85

Merged
chaoz23 merged 1 commit into
mainfrom
family/class-aware-gate
Aug 20, 2026
Merged

chaoz23 merged 1 commit into
mainfrom
family/class-aware-gate

Conversation

@chaoz23

@chaoz23 chaoz23 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Fixes the divergence #83 created the moment it merged.

The problem

v2 introduced member classes but stated them only in FAMILY.md's members table. family_conformance.py (#82) has no way to read prose, so it kept applying v1's flat clause 7 — and immediately began reporting MISSING_PIPE against table-kit, a transport member that v2 explicitly exempts.

The gate contradicted the contract it enforces. That's #84's failure arriving from the other direction: a gate that flags a correct state teaches you to bypass it.

The fix

Members declare their own class in tool.json:

"family_class": "verdict" | "transport" | "adjacent"

The declaration lives in the artifact, not in FAMILY.md's prose. Parsing the members table out of the contract was the alternative, and it would couple the gate to markdown formatting — brittleness that has already been this parser's defect twice (#82 line-vs-paragraph, #84 stream-word matching). It also lets a member own its own classification, matching how the family already pins by link rather than by copy.

Gate changes

  • CLASS_SURFACES gates --pipe (and MCP, still unprobed) to the verdict class
  • CLAUSE_1_CLASSES skips honest-lane and stream checks for the adjacent class, which clause 1 does not bind
  • an undeclared class emits UNDECLARED_CLASS and names the surfaces it therefore did not check, rather than guessing — assuming verdict invents breaches for a transport, assuming transport hides real ones for a verdict tool
  • family_class appears in output and in the human-readable header

tool.json is generated in this repo, so the field is added in gen_tool_json.py and the card regenerated. tests/test_metadata_fresh.py passes.

Verified in all three states

repo declared result
srdcheck verdict PASS
table-kit (none yet) UNDECLARED_CLASS + SCHEMA_NOT_FLAG; --pipe correctly not evaluated
table-kit transport (simulated) MISSING_PIPE gone; SCHEMA_NOT_FLAG + HONEST_LANE_OVERLOAD correctly retained

SCHEMA_NOT_FLAG persisting is right — --schema binds every class (chaoz23/table-kit#23).

FAMILY.md v2.1

Clause 7 now states how class is declared, and that an undeclared class is reported rather than assumed. Per the contract's own versioning rule, that's a heading bump plus a changelog entry.

Follow-ups

Three one-line PRs adding family_class to the remaining members: charactercheck (verdict), dmcheck (verdict), table-kit (transport). Until those land, each reports UNDECLARED_CLASS — visibly and by design, rather than silently mis-grading.

Note: tests/test_event_apply_validation.py::test_deep_json_parse_boundaries_do_not_crash_cli_or_mcp_stdio fails on local Python 3.14 with -32600 == -32700. That is #72, pre-existing — verified identical on untouched main — and CI's 3.10–3.13 matrix is unaffected.

🤖 Generated with Claude Code

v2 introduced member classes but stated them only in FAMILY.md's members
table. The gate had no way to read them, so the moment v2 merged it began
reporting MISSING_PIPE against table-kit -- a transport member that v2
explicitly exempts. The gate contradicted the contract it enforces.

That is the same failure as #84 arriving from the other side: a gate that
flags a correct state teaches you to bypass it.

Members now declare their own class in tool.json:

    "family_class": "verdict" | "transport" | "adjacent"

The declaration lives in the artifact rather than in FAMILY.md's prose. The
alternative -- parsing the members table out of the contract -- would couple
the gate to markdown formatting, and this parser has already been the defect
twice for exactly that kind of brittleness.

Gate changes:

- CLASS_SURFACES gates --pipe (and MCP, still unprobed) to the verdict class
- CLAUSE_1_CLASSES skips honest-lane and stream checks for the adjacent class,
  which clause 1 does not bind
- an undeclared class emits UNDECLARED_CLASS and names the surfaces it did not
  check, rather than guessing: assuming "verdict" invents breaches for a
  transport, assuming "transport" hides real ones for a verdict tool
- family_class is reported in output

tool.json is generated here, so the field is added in gen_tool_json.py and the
card regenerated; tests/test_metadata_fresh.py passes.

Verified in all three states: srdcheck (verdict) PASS; table-kit undeclared ->
UNDECLARED_CLASS + SCHEMA_NOT_FLAG with --pipe correctly unevaluated;
table-kit declared transport -> MISSING_PIPE gone, SCHEMA_NOT_FLAG and
HONEST_LANE_OVERLOAD correctly retained.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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