Commit Graph

2 Commits

Author SHA1 Message Date
Levi Neuwirth 694d135bfc Packet 2B apparatus: the neutral candidatekit and the check-5 oracle
Pin 13 requires an oracle committed and reviewed before any candidate is
scored against it. Both halves land here, before C1 or C2 exists, so
neither candidate can shape what it will be measured by.

round2-candidatekit is the only code the two candidates share. It loads
and validates Packet 2A's fixtures, probe table, reference rasters and
regions; it renders nothing, resolves no hit test, and builds no
accessibility node -- a dependency deny-list test reads its own manifest
at test time to keep it that way, including the dotted-subtable and
target-cfg forms a line scanner would otherwise miss. Check 3 is pinned
NOT RUN by the recipe's 1.2 ruling: a report claiming Pass or Fail for it
is refused, and the supplementary F-D field is one the scoring function
never reads, so it cannot reach the criterion cell by construction rather
than by convention. A check-5 NOT RUN now requires typed evidence that the
platform bus was unreachable, because Round 0 already proved it reachable
for both candidates -- "we did not build it" is scope, and scope is not
environmental.

round2-a11y-oracle derives every byte string a live AT-SPI readback must
compare against, from the digest-validated fixtures alone: the expected
name three ways, the at-spi2 role sets, a precommitted alternative form
per prohibited outcome, the visual-order form, and the per-segment source
atoms. The fixtures digest is unchanged at acc13c0d, so Packet 2A stays
frozen.

verify.py gains a check-5 mode whose classifier is a pure function, unit
tested without a bus, and which fails closed on platform, digest, fixture
completeness, the name/hex/length triple, atom concatenation, and any
string that would classify as two different outcomes.

The scoring policy, settled over eight review rounds and worth stating
because most of it was won by finding the opposite in the code first:

  - An accepted-role exact match wins from anywhere in the forest. It used
    to be first-match, so tree order decided a disqualifying check.
  - Composition is scoped to one run subtree and outranks any per-node
    diagnosis. Application-wide concatenation both passed a run half-drawn
    as a canvas and failed a correct split run because the window carried
    a label.
  - Contributor order stays significant, since 8.1 requires logical order;
    only non-contributor order is required not to matter.
  - name-empty means an attempted static-text exposure without a name.
    absent-from-tree means a drawing-only or unrelated tree, and nothing
    else -- it is the outcome 8.3 says this check will most likely catch,
    and it was nearly unreachable while gated on role presence.
  - A byte-exact PASS outranks an alternative-form match. F-C's
    "drops unresolved codepoints" form is its own first segment, so a
    legitimate per-run split was being failed on a check that eliminates.
    That collision is structural for any two-segment fixture with one
    unresolved segment, not an F-C accident.

Recorded as a finding, not fixed: the recipe's claim that visual-order
assembly diverges for F-D "and only there" is false -- F-B diverges too,
and any non-palindromic RTL run of two or more graphemes will. F-D's real
distinguishing property is an interior RTL run. The same stale wording is
compiled into fixtures.json and covered by its frozen digest, so
correcting it would move acc13c0d out from under Packet 2A.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-30 15:49:07 -04:00
Levi Neuwirth 496dfd5640 T4 round 0: iced is eliminated, and the probe that cleared it proved nothing
Round 0 is the ladder's cheapest hard gate: for each candidate, a desk survey
and a demonstrated accessibility route -- a window exposing one node with a
role and a name, read back through the platform adapter. It is first precisely
so that a candidate with no accessibility story costs one round instead of
four.

C1 (egui/eframe 0.35.0) passes on a first-party route: egui-winit's accesskit
feature is literally dep:accesskit_winit, so the chain runs eframe ->
egui-winit -> accesskit_winit -> accesskit_unix and inherits that crate's
window-lifecycle handling. C2 (vello 0.9.0 + winit 0.30.13) passes on the
manual accesskit_winit route the contract names, wired into the same
ApplicationHandler that owns the vello renderer.

C3 (iced 0.14.0) FAILS, and the interesting part is that it first reported as
a pass. The probe registered a hand-built two-node tree through
accesskit_unix::Adapter, which takes no window handle at all -- only handlers
-- and registers with AT-SPI from process identity. It read back cleanly. It
also happened to label its button exactly as iced's own view() labelled a
button, so the transcript looked as though iced had produced it. Deleting iced
from that probe would produce the identical readback. Round 0 asks whether the
CANDIDATE exposes a route; a process-level side channel answers a different
question.

The verdict is recorded with dual attribution, because two distinct things
went wrong. The probe-design defect is the false positive above. The candidate
limitation -- which alone fails the round -- is that iced 0.14 ships no
accessibility integration anywhere (accesskit appears in no iced crate
manifest) and its stock runner hands application code neither the winit
ActiveEventLoop nor a pre-visibility Window, both of which every
accesskit_winit::Adapter constructor requires. That scoping matters and is
deliberate: iced_winit documents a conversion module for users implementing a
custom event loop, so a hand-built shell remains conceivable but unproven, and
would mean owning the shell. Upstream iced #552 is still open.

The evidence file keeps the verifier's factual READBACK: PASS beneath a
ROUND-0 RESULT: FAIL annotation rather than being rewritten. A corrected record
that erases the false positive teaches nobody why it was false.

Two findings carried forward. C1's frame node is unnamed -- its readback path
is application:'probe-egui' / frame:'' / button:'...' where C2 names its frame
-- which is non-disqualifying here but means a screen-reader user hears an
unnamed window, and round 3 must check it. And AT-SPI application registration
is gated behind two settings that are off by default; without both, probes
connect to the bus and enumerate zero applications, which would read as a
candidate failure rather than the environment absence it is.

The spike workspace lives outside the root workspace with its own lockfile;
the root gains one line, exclude = ["spikes"], and nothing else. Round 0's
probes depend on no epiphany crate, which is what makes this round independent
of the pinned source baseline the later rounds need.

Root gate unchanged: fmt clean, clippy -D warnings clean, 1371 tests passed 0
failed. Spike workspace gated separately: fmt clean, all probes build --locked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 19:01:51 -04:00