Skip to content

docs(adjudication): decide 0003's input-problem order and gaps - #47

Merged
CameronBrooks11 merged 5 commits into
mainfrom
docs/0003-addendum-2
Oct 16, 2026
Merged

CameronBrooks11 merged 5 commits into
mainfrom
docs/0003-addendum-2

Conversation

@CameronBrooks11

@CameronBrooks11 CameronBrooks11 commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Adds a second addendum to docs/adjudications/0003-anchors.md, recording
the owner's decisions of 2026-10-16 on nine gaps §11 left open, and
carries each into SPEC.md §11. No implementation and no conformance case
changes; the anchor case kind gains one shape (below).

The owner's decisions

decided SPEC
1 a prefix or suffix longer than 32 code points is record-inconsistent §11.1
2 several input problems report the first in data-dependency order: record grammar, document-missing, document not-in-profile, base-unavailable, base not-in-profile, leaf at nth (record-inconsistent); several pinned-<reason>s the same way (the leaves taken in base order is this write-up's precision, below) §11.10
3 unresolvable-path before a broken pins block §11.9
4 a \n in replace that cannot be re-emitted (a list item's first line, a lazy line, a tab in the container prefix, the last line of a file with no final line ending) is stale unmappable-offset §11.7
5 a notes-directory file not named <ULID>.md, a subfolder, or a malformed reply-to is record-inconsistent §11.1
6 a name that only a heading slug matches in the current target is #REF!(name), checked before the pinned blob's binding; "binds" means marker binding throughout §11.9 §11.9
7 a referrer with conflict markers refuses its transclusions as not-in-profile; a referrer the profile refuses is refused as a whole and its transclusions are not enumerated; a conflict-marker line anywhere counts, frontmatter and fences included, whatever readings 4 and 12 say; §11.10's row names the referrer §11.9, §11.10
8 a record whose body the profile refuses is record-inconsistent §11.1
9 two clauses that can never apply are removed (proofs below) §11.4, §11.9, §11.10

Precision the write-up adds, marked as not the owner's

Each is marked in the addendum with its reason.

  • Which checks go in item 2's two record-inconsistent steps. Step 1
    covers everything that reads only the record, leaf.text included: file
    name, frontmatter, body, an empty span, an over-long context, the
    exact-once occurrence of prefix + span + suffix, and the short-context
    edge check. Step 6 is only the leaf at nth in base. So a triple that
    occurs twice in leaf.text, with an unreadable base, is
    record-inconsistent. Reason: each check sits at the first step whose
    data it reads.
  • The full order for a transclusion, now §11.9's numbered steps:
    referrer not-in-profile, unresolvable-path, broken pins,
    document-missing, current-target not-in-profile, slug-only name
    #REF!(name), pinned blob base-unavailable, pinned blob
    not-in-profile, name unbound in the pinned blob, then the name, leaf and
    rebinding steps. Reason: the path and the pins are read from the
    referrer, and the rest follows the record's order with the pin standing
    in for base. This supersedes the first addendum's detail 7, which put
    the path before everything else.
  • Pinned leaves in base order. Among several pinned-<reason>s, the
    first leaf's in the pinned blob's document order is reported. Reason:
    that is the order the leaf step visits them.
  • The ULID form. 26 upper-case Crockford base-32 characters, the first
    0 to 7. Reason: one spelling per id, so ids, file names and
    reply-to compare byte for byte.

This closes the §12 item about conflicts among pin lines, which is removed.

The two proofs (item 9)

  • #REF!(name) for "more than one" match. §11.9 requires the current
    target to pass the profile before the name resolves, the profile refuses
    a duplicate name, and the name is matched against markers only.
  • "The pair must lie in the gap" under EXACT. The rule fires only
    for a repeated key. A repeated key is never on the chain, so the pair
    comes from step 2's alignment inside the anchored leaf's own gap.

0003's body keeps both clauses as first written; the addendum removes them.

Checks

$ just check
All checks passed!
$ just test
27 passed
$ python3 conformance/schema_selftest.py <kindkit at 6ebd80d>
0 broken body(ies) not rejected
$ just conform
0 failure(s) across 237 case(s) in the fixture tree
$ just tree
0 problem(s)

The anchor case kind

  • conformance/cases/README.md: the ordering note, now Cases that set up
    several reasons
    , lets a case set up several input reasons and expect the
    first by §11.10's order, and keeps a single-reason case as the clearest
    way to pin one reason. A referrer refused as a whole is written as the
    one string "REFUSED(not-in-profile)" instead of a list. The limits list
    now says a subfolder in a notes directory cannot be named.
  • case-body.schema.json accepts that string as a referrer's entry.
    schema_selftest.py's good body has both shapes, and two new broken
    bodies are rejected: another referrer-level status, and the status inside
    a list. Run against kindkit at the pinned 6ebd80d, 0 broken bodies are
    accepted.
  • anchor_cases.py compares the string, builds alterations for it, and
    counts it as a refusal when deciding whether a case needs a control.
    tests/test_anchor_kind.py covers this, and the test fails when the
    comparison is disabled.

Not merged: this awaits review.

Add a second addendum to 0003 with the owner's decisions on nine gaps
and carry them into SPEC.md §11:

- a context longer than 32 code points is record-inconsistent
- input problems report in data-dependency order, records and
  transclusions alike, pinned leaves in base order
- unresolvable-path before a broken pins block
- a replace line break that cannot be re-emitted is unmappable-offset
- stray notes-directory entries and malformed reply-to are
  record-inconsistent
- a heading slug as a transclusion name is #REF!(name)
- a referrer with conflict markers refuses its transclusions as
  not-in-profile
- a record whose body the profile refuses is record-inconsistent
- two clauses that can never apply are removed, each with its proof

Precision the write-up adds is marked as not the owner's, with its
reason. The §12 item on conflicts among pin lines is now decided.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
SPEC.md §11.10 now fixes which input problem a record or a
transclusion reports, so an anchor case may set up several reasons
and expect the first by that order. A case where only one reason
applies stays the clearest way to pin that reason.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
A name that only a heading slug matches is now #REF!(name), checked
before the pinned blob's binding, and "binds" means marker binding
throughout §11.9. A referrer the profile refuses is refused as a whole,
with conflict markers counted anywhere in it. The checks that read only
a record's leaf.text move to the first reporting step, the leaf order
among pinned reasons is marked as the write-up's precision, and §11.9's
steps are renumbered so the prose and §11.10's order agree.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
A referrer the profile refuses is refused as a whole and its
transclusions are not enumerated, so an anchor case's referrer entry
may now be the string "REFUSED(not-in-profile)" in place of a list.
The schema, the runner's comparison, alterations and control check,
and the README accept that shape, and the schema self-test and the
kind's tests cover both shapes. The README's ordering note gets a new
heading, and its limits list a subfolder in a notes directory.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
…nd 12 say

Both readings already count such a line, so the rule agrees with them;
"overriding" said the opposite.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
@CameronBrooks11
CameronBrooks11 merged commit ed2e50e into main Oct 16, 2026
6 checks passed
@CameronBrooks11
CameronBrooks11 deleted the docs/0003-addendum-2 branch October 16, 2026 15:08
CameronBrooks11 added a commit that referenced this pull request Oct 16, 2026
The reference resolver for `SPEC.md` §11, together with §11's
conformance
cases. The cases are #46's, `test/anchor-cases` at `d389dee` (215 anchor
cases, 452 in all), written from `SPEC.md` by an author who had not read
the
resolver, and merged here with no case changed. Merging this lands both,
and
#46 closes as landed through it. `main`, with 0003's three addenda (#43,
#47,
#48), is merged in too.

**Landing order** (decided). The first anchor case would turn the
required
`reference` check red on `main`, since the reader had no `resolve` until
this PR. So the cases were written blind from `main` on their own branch
and
reviewed there, then merged into this branch and run against it.
`README.md`, `AGENTS.md` and `SPEC.md` now say that the 0001 and 0002
cases
came before the reader, and the 0003 cases after the resolver.

## Against the cases

$ just conform # after merging d389dee, before any change made for it
    0 failure(s) across 452 case(s) in the fixture tree
    $ just conform    # at the head of this branch
    0 failure(s) across 452 case(s) in the fixture tree

No case failed, here or at the earlier merges of `e0f5525` (422 cases)
and
`7375c2c` (446). So no
case is wrong per SPEC, no SPEC question came out of a failure, and the
resolver needed no fix for the cases. `run_cases.MIN_CASES` is 452.

## What this adds

`reference/blockspec/anchor.py`, standard library only, implements §11
on
top of the existing reader:

- **records** (§11.1): the exact frontmatter lines, the canonical JSON
anchor
(key order, spacing, escape set, `B` as 40 or 64 lower-case hex digits
by
object format, `N` as plain decimal), `replace` tied to `kind`, ULID
file
  names and `reply-to`, a body the profile refuses, and the consistency
  rules against `base`, with occurrences counted by start position;
- **correspondence** (§11.3): the leftmost longest chain, the §11.2 LCS
alignment inside each gap with MOVED leaves left out, and EDITED pairing
  with the integer comparisons of the first addendum (`22/30 − 42/72` is
  tested as exactly 0.15);
- **the anchored leaf** (§11.4): every rule and reason, in first-applies
  order;
- **the span** (§11.5): hits counted by start position, the edge rule
and
  the alignment check;
- **input problems** in §11.10's order, replies included (third
addendum);
- **statuses, the suggestion gate and write-back** (§11.6, §11.7):
NFC-to-raw mapping through container prefixes, lost indentation, CRLF
and
  bare CR; a `\n` in `replace` written as the line's ending plus its
container prefixes; and `unmappable-offset` for a span that does not
map,
for a line break that cannot be re-emitted, for a span crossing or
ending
right before a tab's leftover spaces, and for a span ending with a code
  block's final `\n`;
- **transclusion** (§11.9): the pins grammar, path resolution, and steps
0
  to 4, with a referrer the profile refuses reported as
`"REFUSED(not-in-profile)"` and the first pinned leaf in the pinned
blob's
  document order deciding among several `pinned-<reason>`s.

`document.py` gains `resolve(repo, queries)`, the interface the runner
calls. It loads `anchor.py` from its own directory by path and hands it
this
file's reader, so a mutated copy of the reader is the one the resolver
runs.
The reader's own output is unchanged: a script run locally compared
`conform()` from `origin/main` and from this branch, messages included,
for
every `parse` case input, and got `237 inputs, 0 differ`. A copy with
one
line changed gives `237 inputs, 35 differ`.

## Fixed after review

- A JSON array where the grammar wants an object (`anchor: [1]`, a leaf
or
a pin that is an array) raised out of `resolve()`. Objects are now told
from arrays, and every malformed record or pin is `record-inconsistent`.
The review's fuzz (6,000 random records, pins and referrers) now raises
  nothing.
- `[a [b](c.md) d](policy.md#scram "transclude")` was read as a
  transclusion. Link text cannot hold a link (CommonMark 0.31.2, example
  518), so it is no link and no transclusion.
- The self-transclusion walk skipped a whole document when one of its
paragraphs was undecided, so A to B to A went unseen. It now skips only
  that paragraph.
- The claim that "moved leaves stay in gaps" is equivalent was false:
with
  twins beside a move, the mutant swaps `repeat` and `unaccounted-copy`.
  It is an ordinary mutant now, a unit test kills it, and
  `anchor/repeat/twins-beside-a-move` kills it in the gate.
- Shapes CommonMark decides, which were `UNDECIDED`: an image titled
  `transclude` is no link; a paragraph with no inline link is no
  transclusion; a title may be followed by a line ending before `)`; an
entity reference is decoded only with its `;`, so `&#116ransclude` is
not
  `transclude`.

## The mutation gate

`just mutants` gates the resolver as well as the reader: a second table
in
`conformance/mutants.py` over `reference/blockspec/anchor.py`, probed by
the
whole tree. Its probe writes each mutant as `anchor.py` beside a copy of
the reader. Mutants that only turn an `UNDECIDED` into a guess are left
out, since no case can expect one, as are the duplicate-id warning and a
notes-directory subfolder, which no case field can observe. The
resolver's table, as the gate printed it:

    --- the resolver, reference/blockspec/anchor.py
killed escape set misses U+2028 (1 case(s):
anchor/record/accepts-every-escape)
killed escape set misses C1 controls (2 case(s):
anchor/record/accepts-an-escaped-c1-control,
anchor/record/refuses-a-raw-c1-control)
killed negative nth read (1 case(s):
anchor/record/refuses-a-negative-nth)
killed replace not tied to kind (2 case(s):
anchor/record/refuses-a-suggestion-without-replace,
anchor/record/refuses-replace-on-a-comment)
killed canonical bytes not compared (7 case(s):
anchor/order/grammar-before-document-missing,
anchor/record/refuses-a-raw-c1-control,
anchor/record/refuses-a-short-tab-escape)
killed table pipe not escaped (1 case(s):
anchor/leaf/an-escaped-pipe-keeps-two-tables-apart)
killed heading level dropped (2 case(s):
anchor/status/edited-when-a-heading-changes-level,
anchor/suggestion/applies-when-a-heading-changes-level)
killed text not normalised (3 case(s):
anchor/status/resolved-when-the-normalisation-form-changes,
anchor/suggestion/stale-when-the-span-does-not-map,
anchor/suggestion/writes-back-without-normalising)
killed half compares strictly (2 case(s):
anchor/correspondence/a-similarity-of-exactly-one-half-pairs,
anchor/near-copy/exact-with-an-unmatched-leaf-at-one-half)
killed below one allows one (1 case(s):
anchor/near-copy/an-exact-copy-elsewhere-is-not-a-near-copy)
killed margin compares strictly (1 case(s):
anchor/correspondence/a-margin-of-exactly-0-15-pairs)
killed margin in floating point (1 case(s):
anchor/correspondence/a-margin-of-exactly-0-15-pairs)
killed alignment skips the second first (1 case(s):
anchor/correspondence/the-gap-alignment-skips-the-base-leaf-first)
killed chain is the rightmost (1 case(s):
anchor/correspondence/a-swap-keeps-the-leaf-now-first-on-the-chain)
killed chain is not the longest (55 case(s):
anchor/correspondence/a-swap-keeps-the-leaf-now-first-on-the-chain,
anchor/correspondence/the-gap-alignment-skips-the-base-leaf-first,
anchor/exact-copy/a-paired-twin-does-not-count)
killed moved pairs dropped (10 case(s):
anchor/correspondence/a-swap-keeps-the-leaf-now-first-on-the-chain,
anchor/near-copy/moved-with-its-reworded-text-in-place,
anchor/refilled-gap/moved-with-a-new-leaf-in-its-place)
killed moved leaves stay in gaps (1 case(s):
anchor/repeat/twins-beside-a-move)
killed no lcs in gaps (7 case(s):
anchor/exact-copy/a-paired-twin-does-not-count,
anchor/near-copy/an-exact-copy-elsewhere-is-not-a-near-copy,
anchor/no-pair/a-twin-deleted-between-unique-neighbours)
killed edit needs no half (2 case(s):
anchor/correspondence/a-similarity-just-below-one-half-does-not-pair,
anchor/status/orphaned-when-rewritten-beyond-recognition)
killed edit needs no lead over other current (1 case(s):
anchor/correspondence/the-margin-must-hold-on-the-base-leaf-side)
killed edit needs no lead over other base (1 case(s):
anchor/correspondence/a-margin-just-below-0-15-does-not-pair)
killed no unaccounted-copy (3 case(s):
anchor/no-pair/a-twin-deleted-between-unique-neighbours,
anchor/repeat/one-of-two-adjacent-copies-deleted,
anchor/repeat/twins-beside-a-move)
killed repeat only when base repeats (1 case(s):
anchor/repeat/a-copy-added-beside-the-leaf)
killed repeat only when current repeats (2 case(s):
anchor/repeat/one-of-two-adjacent-copies-deleted,
anchor/repeat/twins-beside-a-move)
killed exact near-copy off (3 case(s):
anchor/near-copy/exact-with-an-unmatched-leaf-at-one-half,
anchor/near-copy/exact-with-its-reworded-text-beside-it,
anchor/suggestion/stale-on-a-refusal)
killed near-copy below half (8 case(s):
anchor/near-copy/exact-with-an-unmatched-leaf-just-below-one-half,
anchor/pins/a-pin-matches-its-raw-path,
anchor/refilled-gap/moved-with-a-new-leaf-in-its-place)
killed exact near-copy counts exact copies (1 case(s):
anchor/near-copy/an-exact-copy-elsewhere-is-not-a-near-copy)
killed moved near-copy off (3 case(s):
anchor/near-copy/moved-with-its-reworded-text-in-place,
anchor/transclusion-order/a-pinned-reason-before-name-rebound,
anchor/transclusion/pinned-near-copy)
killed refilled gap off (1 case(s):
anchor/refilled-gap/moved-with-a-new-leaf-in-its-place)
killed exact-copy off (1 case(s):
anchor/exact-copy/edited-with-its-old-text-unmatched)
killed exact-copy counts paired copies (2 case(s):
anchor/correspondence/the-gap-alignment-skips-the-base-leaf-first,
anchor/exact-copy/a-paired-twin-does-not-count)
killed crowded gap off (2 case(s):
anchor/crowded-gap/edited-with-a-new-leaf-beside-it,
anchor/transclusion/pinned-crowded-gap)
killed edited near-copy off (1 case(s):
anchor/near-copy/edited-with-a-variant-elsewhere)
killed empty span allowed (1 case(s):
anchor/record/refuses-an-empty-span)
killed triple may occur twice (3 case(s):
anchor/order/a-quote-twice-before-base-unavailable,
anchor/record/refuses-a-quote-that-occurs-twice,
anchor/record/refuses-a-quote-that-overlaps-itself)
killed short prefix anywhere (1 case(s):
anchor/record/refuses-a-short-prefix-away-from-the-edge)
killed short suffix anywhere (2 case(s):
anchor/order/a-short-context-before-base-unavailable,
anchor/record/refuses-a-short-suffix-away-from-the-edge)
killed occurrences skip overlaps (2 case(s):
anchor/record/refuses-a-quote-that-overlaps-itself,
anchor/span/overlapping-hits-are-ambiguous)
killed no prefix edge rule (1 case(s):
anchor/context/a-short-prefix-must-sit-at-the-leaf-start)
killed no suffix edge rule (1 case(s):
anchor/context/a-short-suffix-must-sit-at-the-leaf-end)
killed two hits not ambiguous (2 case(s):
anchor/span/overlapping-hits-are-ambiguous,
anchor/span/two-hits-are-ambiguous)
killed no alignment check (1 case(s):
anchor/span/the-alignment-detaches-a-rewritten-span)
killed gate ignores status (3 case(s):
anchor/suggestion/stale-when-accepted,
anchor/suggestion/stale-when-rejected,
anchor/suggestion/stale-when-resolved)
killed gate applies on moved (2 case(s):
anchor/residual/r1-a-deleted-leaf-quoted-elsewhere,
anchor/suggestion/stale-on-moved)
killed break written as LF (2 case(s):
anchor/suggestion/writes-a-line-break-as-a-bare-cr,
anchor/suggestion/writes-a-line-break-as-crlf)
killed break written with no prefix (2 case(s):
anchor/suggestion/writes-back-in-a-block-quote,
anchor/suggestion/writes-back-in-a-list-item-continuation-line)
killed synthetic spaces mapped (1 case(s):
anchor/suggestion/writes-back-a-span-beginning-after-leftover-tab-spaces)
killed absolute path allowed (1 case(s):
anchor/transclusion/unresolvable-path-with-a-scheme)
killed scheme path allowed (2 case(s):
anchor/transclusion-order/unresolvable-path-before-broken-pins,
anchor/transclusion/unresolvable-absolute-path)
killed above the root allowed (1 case(s):
anchor/transclusion/unresolvable-path-above-the-root)
killed pins order unchecked (4 case(s):
anchor/pins/refuses-a-path-pinned-twice,
anchor/pins/refuses-pins-out-of-order,
anchor/transclusion-order/broken-pins-before-document-missing)
killed pins after a blank line read (2 case(s):
anchor/pins/refuses-a-pin-after-a-blank-line,
anchor/pins/refuses-a-pin-after-a-comment-line)
killed name ref skipped (1 case(s):
anchor/transclusion/a-name-that-binds-nothing-now)
killed rebound unchecked (3 case(s):
anchor/pins/a-pin-matches-its-raw-path,
anchor/transclusion/name-rebound-by-a-leaf-inserted-after-the-marker,
anchor/transclusion/name-rebound-when-the-marker-moves)
killed reply-to not canonical (1 case(s):
anchor/reply/refuses-a-reply-to-with-extra-spacing)
killed pin not canonical (1 case(s):
anchor/pins/refuses-a-pin-not-in-canonical-form)
killed pins line with trailing text (1 case(s):
anchor/pins/refuses-a-pins-line-with-more-after-it)
killed dot segment kept (2 case(s):
anchor/pins/a-pin-matches-its-raw-path,
anchor/transclusion/resolves-relative-paths-from-a-subdirectory)
killed synthetic char inside span mapped (1 case(s):
anchor/suggestion/unmappable-span-across-a-tab-left-by-fence-indentation)
killed repeat ignores counts (3 case(s):
anchor/repeat/a-copy-added-beside-the-leaf,
anchor/repeat/one-of-two-adjacent-copies-deleted,
anchor/repeat/twins-beside-a-move)
killed over-long context read (2 case(s):
anchor/record/refuses-a-prefix-longer-than-32,
anchor/record/refuses-a-suffix-longer-than-32)
killed reply-to any string (3 case(s):
anchor/reply/a-malformed-reply-to-before-document-missing,
anchor/reply/refuses-a-lower-case-reply-to,
anchor/reply/refuses-a-reply-to-that-is-not-a-ulid)
killed lowercase ulid read (1 case(s):
anchor/record/refuses-a-lower-case-ulid-file-name)
killed reply parent never found (3 case(s):
anchor/reply/a-reply-to-a-record-renders,
anchor/reply/a-reply-to-a-reply-renders,
anchor/reply/a-reply-to-an-orphaned-record-renders)
killed base checked before record-only checks (2 case(s):
anchor/order/a-quote-twice-before-base-unavailable,
anchor/order/a-short-context-before-base-unavailable)
killed record body not checked (1 case(s):
anchor/record/refuses-a-body-the-profile-refuses)
killed document missing ignored for records (3 case(s):
anchor/input/document-missing,
anchor/input/document-renamed-without-its-notes,
anchor/order/document-missing-before-base-unavailable)
killed base read before the document (1 case(s):
anchor/order/current-not-in-profile-before-base-unavailable)
killed lazy bq line repeats a shorter prefix (1 case(s):
anchor/suggestion/unmappable-line-break-on-a-lazy-line)
killed lazy list line repeats a shorter prefix (1 case(s):
anchor/suggestion/unmappable-line-break-on-a-list-item-s-lazy-line)
killed tab after a quote marker repeated (1 case(s):
anchor/suggestion/unmappable-line-break-under-a-quote-marker-and-tab)
equiv item first line repeated (a list item's first line holds its
marker in the prefix region, so the lazy-line check refuses the break
anyway)
killed break with no ending written (1 case(s):
anchor/suggestion/unmappable-line-break-on-a-last-line-with-no-ending)
killed refused referrer enumerated (3 case(s):
anchor/referrer/refused-for-a-conflict-line-in-a-fence,
anchor/referrer/refused-for-conflicting-adjacent-re-pins,
anchor/referrer/refused-for-raw-html)
killed pins before the path (1 case(s):
anchor/transclusion-order/unresolvable-path-before-broken-pins)
killed broken pins ignored (9 case(s):
anchor/pins/refuses-a-path-pinned-twice,
anchor/pins/refuses-a-pin-after-a-blank-line,
anchor/pins/refuses-a-pin-after-a-comment-line)
killed missing target after the pin (1 case(s):
anchor/transclusion-order/document-missing-before-base-unavailable)
killed slug-only name not checked early (2 case(s):
anchor/transclusion-order/a-slug-only-name-before-base-unavailable,
anchor/transclusion-order/a-slug-only-name-before-the-pinned-binding)
killed slug match ignored (2 case(s):
anchor/transclusion-order/a-slug-only-name-before-base-unavailable,
anchor/transclusion-order/a-slug-only-name-before-the-pinned-binding)
killed marker name taken for slug-only (2 case(s):
anchor/merge/re-pins-of-two-targets-merge-cleanly,
anchor/transclusion/a-marker-name-that-is-also-a-slug-resolves)
killed pinned blob not required (2 case(s):
anchor/transclusion-order/an-unbound-name-after-base-unavailable,
anchor/transclusion/base-unavailable)
killed pinned name unchecked (1 case(s):
anchor/transclusion/a-pinned-name-that-binds-nothing)
killed orphaned pinned leaf passes (2 case(s):
anchor/transclusion-order/several-pinned-reasons-report-the-first-leaf,
anchor/transclusion/pinned-orphaned)
killed last pinned leaf decides (1 case(s):
anchor/transclusion-order/several-pinned-reasons-report-the-first-leaf)
killed anchor array taken for an object (1 case(s):
anchor/record/refuses-an-anchor-that-is-an-array)
killed inner link ignored (1 case(s):
anchor/transclusion/a-link-inside-link-text-is-not-one)
killed entity without semicolon decoded (1 case(s):
anchor/transclusion/an-entity-without-a-semicolon-is-not-decoded)
killed image read as a link (1 case(s):
anchor/transclusion/an-image-is-not-one)
killed prose with a title read as a link (1 case(s):
anchor/transclusion/prose-with-the-title-s-characters-is-not-one)
killed line ending after the title refused (1 case(s):
anchor/transclusion/a-line-ending-before-the-closing-parenthesis)
killed code block's final line ending written (1 case(s):
anchor/suggestion/unmappable-span-ending-in-a-code-block-s-final-line-ending)
killed reply's document unchecked (3 case(s):
anchor/reply/document-missing-before-a-missing-parent,
anchor/reply/refused-when-its-document-is-missing,
anchor/reply/refused-when-its-document-is-not-in-profile)
    
    91 killed, 0 survived, 1 equivalent, 0 stale, 0 broken

**No survivors.** The six holes the last run reported, one per review
fix,
are now killed by the cases #46 added for them.

**One equivalence claim**, "item first line repeated": a list item's
first
line holds its marker in the prefix region, so the lazy-line check
refuses a
break there anyway. kindkit flags a claim only when the suite kills the
mutant; it cannot show that one the suite does not kill is equivalent,
so
this rests on the argument.

## Where SPEC.md is still open: refused, not guessed

Each of these returns `{"status": "UNDECIDED", "question": ...}` for
that
record or transclusion, which no case can expect.

**Left open by §12:**

1. Transclusion shapes: a destination with no path, angle brackets,
another
title quoting form or a title with backslash escapes or entity
references
(one that decodes to `transclude` but is not written `"transclude"`), a
`transclude` link in a paragraph that holds other text, a transclusion
in
   a list item or block quote, no pin, and a document that transcludes
   itself, directly or through another.
2. Writing a suggestion back into a table.
3. An empty path segment, and a path naming a file that is not markdown
   (read here as not ending in `.md`).

**Not in §12, and not decided either:**

4. A destination with no `#name`.
5. Link text holding a code span or HTML, either of which can move where
the
   link text ends under CommonMark.
6. A line break written over a span whose first and last lines end or
are
prefixed differently: §11.7 writes each `\n` as the ending of "the line
   it breaks", and such a span has two candidates.
7. Where the open transclusion questions sit in step 1's order: the
empty
segment and self-transclusion after the path resolves, the non-markdown
   target before the pins, and no pin at step 1.6.
8. Inputs no case should produce: a record or referrer that is not in
the
   tree, and a record path in no notes directory.

**Cheap to decide but left `UNDECIDED`** (CommonMark settles them, at
the
cost of a full inline parser): prose that holds `"transclude")`
alongside a
real inline link elsewhere in the same paragraph.

**Readings taken, not refused**, worth confirming:

- When the span ends at a line break, write-back maps its end past the
next
line's prefixes, so `> a\n> b` with `a\n` replaced by `x` gives `> xb`.
- A paragraph holding `"transclude")` in a link-like shape is treated as
a
possible transclusion and refused as undecided where it is not exactly
one
  link. That refuses more than needed, and never resolves wrongly.

## Gates

    $ just check
    All checks passed!
    $ just test
    181 passed in 13.02s
    $ just conform
    0 failure(s) across 452 case(s) in the fixture tree
    $ just tree
    452 cases: 160 accept, 77 refuse, 215 anchor
    0 problem(s)
    $ just mutants
    --- the reader, reference/blockspec/document.py
    91 killed, 0 survived, 0 equivalent, 0 stale, 0 broken
    --- the resolver, reference/blockspec/anchor.py
    91 killed, 0 survived, 1 equivalent, 0 stale, 0 broken

## Break-checks of the unit tests

`tests/test_anchor.py` was break-checked by a sweep run locally (not
committed). It hashes the file before and after each mutation, reports
BROKEN when one does not land, runs the tests, and restores the file.
The
latest run gave `105 killed, 1 not`, the one being the equivalent mutant
above. The hook and the gate's scratch location: `3 killed, 0 not`.

## Not covered

- The thresholds 0.5 and 0.15 are as §11 states them; nothing here
  measures them on real history (0003 §12).
- Re-anchoring (§11.1) and the tool behaviours of §11.11 are not
  implemented: the format does not require them.

---------

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.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