Skip to content

feat(conformance): add an anchor case kind for spec section 11 - #44

Merged
CameronBrooks11 merged 2 commits into
mainfrom
feat/anchor-case-kind
Oct 16, 2026
Merged

CameronBrooks11 merged 2 commits into
mainfrom
feat/anchor-case-kind

Conversation

@CameronBrooks11

@CameronBrooks11 CameronBrooks11 commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

What this adds

A second case kind, anchor, so that the conformance tree can state what
SPEC.md §11 resolves: comments, suggestions, replies and transclusions,
across edits and stock git merges. It adds the runner support, the tree
checks and the documentation. It adds no anchor case: under the standing
rule (org contract §2.1), §11's cases come from an author who has not read an
implementation, and they are to be written from SPEC.md, 0003 and
conformance/cases/README.md, The kind anchor, which this PR adds.

Landing order

The first anchor case turns the required reference check red, because the
reader has no resolve until the resolver (#45). So: after this merges, the
anchor cases are written blind from main, on a branch and pull request of
their own, and reviewed there. #45 is then rebased onto the cases branch, run
against them with no case changed, reviewed, and merged. That merge lands the
cases and the resolver together, and the cases' pull request closes as landed
through #45. conformance/cases/README.md's anchor section states the same.

The case shape

cases/anchor/<group>/<case-name>/
    expect.json
    WHY.md
    tree/<path>         the current snapshot, each file at its path
    blobs/<any>         more readable blobs, such as a record's base
    merge/{base,ours,theirs}/<path>   optional: a merge that makes <path>
    <anything else>     not in the repository (a placeholder's target, a written file)
  • Placeholders. {{blob:PATH}} in any file is replaced by git hash-object's id of the case file at PATH, in a repository of the case's
    object_format (sha1 by default, or sha256). Authors write content,
    never hashes. A file outside tree/, blobs/ and merge/ can be named
    but is not readable, which is how base-unavailable is set up.
  • Merges. "merge": {"policy.md": "clean" | "conflict"} runs kindkit's
    gitmerge.merge (stock git, no configuration from the machine) over the
    three sides; the working-tree result, markers and all, becomes the current
    file, and git's outcome is asserted.
  • records: per record path, status (the six statuses of §11.6 with
    §11.10's reasons, plus REPLY / #REF!(reply-to) for replies), leaf
    (the paired current leaf as a node path), span (code point offsets in
    that leaf's §11 text), and suggestion (null, or the §11.7 gate:
    apply with a case file holding the exact written-back document, or
    stale with null / unmappable-offset).
  • transclusions: per referrer, every transclusion paragraph in document
    order, each with its leaf, status (RESOLVED, REFUSED(<reason>), or
    #REF!(<name>)) and unit (the target document's resolved path and what
    the name binds now).
  • controls: required when any outcome is REFUSED(...), as for a
    parse refusal.

The implementation is handed resolve(repo, queries) with the tree's texts
and the readable blobs by id, so it needs no git (run_cases.py's
docstring).

Where it is checked

  • just conform and the mutation gate dispatch anchor cases through
    run_cases.py; an implementation without resolve fails each one.
  • just tree (check_tree.py, stdlib only) checks each anchor case's right
    answer passes, an empty answer fails, every asserted field is compared
    (one alteration at a time, an index or offset given as a float or a bool
    included), its files are consistent (placeholder targets, merge sides,
    written files, listed records and referrers, and no unreadable placeholder
    target with a readable file's bytes, since ids are content-addressed), and
    its controls.
  • CI's conformance job runs conformance/schema_selftest.py, which feeds
    kindkit's own validator one valid synthetic anchor body and 44 broken
    ones, each in a tree of its own, and requires each broken one to be
    rejected. The empty-implementation step now defines an empty resolve
    too, so it keeps failing every case once anchor cases exist.

tests/test_anchor_kind.py drives the runner and check_tree.py over
synthetic trees in a temporary directory, with a fake implementation that
checks the repository it is handed (ids recomputed with hashlib, merge
results, queries) and echoes a canned answer. Its bodies are not conformance
cases and say nothing about how an anchor resolves; flagging them here in
case they should go.

Gates

$ just check
All checks passed!
$ just test
26 passed in 2.97s
$ just conform
0 failure(s) across 237 case(s) in the fixture tree
$ just tree
237 cases: 160 accept, 77 refuse, 0 anchor
0 problem(s)
$ just mutants
91 killed, 0 survived, 0 equivalent, 0 stale, 0 broken
$ python3 conformance/schema_selftest.py <kindkit checkout at 6ebd80d>
0 broken body(ies) not rejected
$ python3 <kindkit>/tools/validate_case_tree.py conformance/cases
0 invalid case(s) across 237 case(s) in 1 tree(s)

Break-checks

Each new check was broken and seen to fail, by a sweep run locally (not
committed) that hashes the file before and after each mutation and reports
BROKEN when a mutation does not land. Runner and check_tree.py mutants run
pytest tests/test_anchor_kind.py; schema mutants run the self-test.

killed     compare skips span
killed     compare skips status
killed     transclusion length unchecked
killed     written text unchecked
killed     stale reason unchecked
killed     placeholder not substituted
killed     object format ignored
killed     blobs/ not readable
killed     merge sides not readable
killed     merge outcome unchecked
killed     merge sides swapped
killed     nested placeholder allowed
killed     merge over a tree file allowed
killed     merge messages dropped
killed     check_tree: empty answer not tried
killed     check_tree: controls not required
killed     schema: leaf nullable whatever the status
killed     schema: apply on any status
killed     schema: unit allowed when refused
killed     schema: anchor body open
killed     bools taken for integers
killed     no bool alterations
killed     unreadable-blob lint off
killed     schema: transclusion leaf any type
killed     schema: unmappable-offset on any status
25 killed, 0 not

After review

Added in a second commit: the content-addressing lint above; bool
alterations and a test that kills a not_integers checking floats alone;
the schema now requires a transclusion's leaf to be a paragraph and allows
unmappable-offset only on RESOLVED or EDITED, as the README says (the
self-test's good body no longer breaks that); ten more rules pinned in the
self-test; and the README now notes git's CRLF conflict markers and lists
re-anchoring, anchor creation and re-pinning among what a case cannot
express.

Notes for review

  • The parse kind is unchanged: its schema branches are untouched (only
    accept moved from the top-level required into a parse branch), and
    just conform, just tree and just mutants report what they did
    before.
  • SPEC.md §10 says every case has the kind parse. That stays true until
    the first anchor case lands, which is the change that should update it.
  • This changes the runner and the case-tree format, so it is load-bearing
    under the org contract §3 and wants more than one review pass.

A case of kind `anchor` holds a small repository: the current snapshot
under tree/, readable earlier blobs under blobs/, and optionally the
three sides of a merge that the runner performs with stock git. It
lists records and referrers with the outcome expected for each: the
status and reason, the anchored leaf, the span, the suggestion gate
with the written document, and every transclusion with its unit.

Authors write `{{blob:PATH}}` where an object id goes; the runner
replaces it with `git hash-object`'s id in a repository of the case's
object format. check_tree.py checks anchor cases for their files,
their controls, and that every asserted field is compared.

No anchor case is added: the cases come from an author who has not
read an implementation.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
check_tree.py now reports a placeholder target outside tree/, blobs/
and merge/ whose bytes equal a readable file's: ids are
content-addressed, so such a file names a readable blob. Alterations
now also turn a 0 or 1 index into a bool, and a test kills a
not_integers that checks floats alone.

The schema now requires a transclusion's leaf to be a paragraph and
allows unmappable-offset only on RESOLVED or EDITED, as the README
says. The self-test's good body follows that, and pins ten more
rules. The README states the landing order of the anchor cases and
the resolver, warns about content addressing, notes git's CRLF
conflict markers, and lists what a tool writes among what a case
cannot express.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
@CameronBrooks11
CameronBrooks11 merged commit 66ce7f1 into main Oct 16, 2026
6 checks passed
@CameronBrooks11
CameronBrooks11 deleted the feat/anchor-case-kind branch October 16, 2026 04:55
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