Record the second merge gate: my redundancy ruling shipped a regression

R-AK was mine. I called a guard duplicate authority and ordered it deleted;
Hands verified, got it wrong, and left a comment claiming an alias uniqueness
property that the source contradicts. A legal document shape now takes every
hop path into an untyped exception, the peer gets no typed refusal, and the
batch's own new dispatch backstop swallows it without a log. Restoring the
guard leaves the suite green, which is the other half of the problem.

That is the second redundancy ruling of mine to be wrong. The first was caught
by Hands; this one was not. A deletion argued from a property has to be probed
at the shape that would break it.

R-AF is genuinely settled and Soul confirmed it harder than the probe did: the
reference type has nowhere to put a schema id, so no formatter or version can
introduce one. Both runtimes now agree on every shape that split them,
including the role-filter reordering no ruling covered.

R-AH has slipped five passes and cannot be built as I ruled it, because the
ruling never fixed map encoding, field order, or whether an absent optional is
nil or omitted, and because C# has no page type at the layer the vectors
test. This batch decides all four. Until it lands, the map now claims semantic
parity over a named field subset rather than byte parity, because byte parity
is not true today and the smaller honest claim is worth more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-23 06:20:15 +02:00
commit 69d736def5

View file

@ -2969,3 +2969,189 @@ count.** At `-j1`, 150 mutants is a several-hour commitment.
pass on batch 5 first, and the selection cut cannot merge on a mutation sweep
of an adjacent file. Recorded as a follow-up with the partial signal attached
so the next pass starts from it rather than rediscovering it.
## Soul, the merge gate again, 2026-09-23
Opus, against `fc51e9a`. Counts re-measured: C# Networking 274 / 2 skipped,
Mesh 255. **Rust is 254 tests across 11 targets**, not the 50 reported —
that figure was `tests/selection.rs` alone, one target of eleven.
**Verdict: Cut 1 may not merge.** Two reasons, either sufficient.
### SM-6, CONFIRMED, high. R-AK deleted a load-bearing guard, and wrote a false premise into the source to justify it.
This one is mine. I ruled the `!member.IsReference` guard was duplicate
authority and ordered it deleted. Hands verified and got it wrong, then left
a comment claiming "index aliases are unique per descriptor, so a data
member's role string can never coincide with a reference member's."
**Source contradicts that.** D10 (`CultCache.cs:1085-1097`) groups only
members whose `ResolveIndexAlias` is non-null — alias against alias. A plain
data member with no alias contributes its `MemberName` to the same role
namespace and is checked against nobody.
Probed: a document with a data member `Owner` and a reference member aliased
`Owner` **registers fine**, and then every hop path throws
`ArgumentNullException: Value cannot be null (Parameter 'targetType')`.
`ReferenceMembers` now yields the data member, `ReferencesOf` matches it to
the *reference* member's edges by role string, `member.TargetType!` is null,
and `ResolveTargetLeaves` throws. Before the deletion the guard skipped that
member and the selection returned one edge.
**Restoring the guard leaves 274/274 green — the suite has no opinion either
way.** Any schema shaped that way makes every `cites`/`cited` selection over
it fault, the peer gets no typed refusal, and **the batch's own new dispatch
backstop swallows it with no log.**
### CONFIRMED, medium. The dispatch backstop is untested and answers nothing.
`catch (Exception) when (false)` — so the backstop never matches — survives
274/274. `HandleSnapshotRequestV1Async` catches only the three typed selection
exceptions, unlike its siblings `HandleShardLogRequestAsync` and
`HandlePutAsync`, which catch `Exception` and answer a `CultNetErrorMessage`.
**The snapshot path is the one handler where an untyped fault becomes
silence.** R-AG's invariant was that malformed input reaches the wire typed
and nothing else escapes; a silently dropped dispatch is not that. It is a
compensator, and it is already hiding SM-6.
### SM-4, CONFIRMED, medium. R-AJ closed about half of it, and Rust got the thin half.
| mutation | C# | Rust |
|---|---|---|
| drop edge-sort `To.SchemaId` | survives | survives |
| drop `To.Key` | survives | survives |
| drop `From.SchemaId` | killed | **survives** |
| drop `Role` | killed | **survives** |
| drop row-order `recordKey` | killed | **survives** |
| swap row-order schema/key *(control)* | killed | killed |
**The controls dying means the rewritten tests are not degenerate in the old
way** — R-AJ's headline fix is real. But the new Rust edge test puts both
edges on the same `from.schema_id` and never reads `to`, so it pins exactly
one thing: `from.record_key` before `role`. **Dropping `role` entirely — the
component it was written for — survives.**
### CONFIRMED, medium. Shared-key resolution is order-dependent and unpinned in both runtimes.
Iterating candidates reversed (C#) and preferring the last in-target candidate
(Rust) both survive. Nothing pins "the first row in the caller's order wins"
when **two in-target** rows share a key; R-AF's new vector covers one-in and
one-out only. Both runtimes agree in source today, and that agreement assumes
both are handed rows in the same order, which nothing between a C# cache and a
Rust row owner guarantees.
### PLAUSIBLE. `RowSet::target_leaves(role)` takes no schema
(`selection.rs:1333`), where C# resolves the target set from the declaring
member's own `TargetType`. Two schemas legitimately declaring one role name
with different targets **cannot be expressed in Rust at all**. Low-medium.
### PLAUSIBLE, low. A `+` prefix in a cursor length field
parses in Rust (`str::parse::<usize>`) and is refused in C#
(`NumberStyles.None`). Gated behind the HMAC, so unreachable without forging
it. Read from source, not probed.
### Promises that held
**R-AF's wire fact is confirmed, and harder than the probe claimed.** It is
not one serialization — **the type has nowhere to put a schema id.**
`CultRecordRef<T>` has exactly one field, `ICultRecordRef` exposes only `Key`,
the formatter writes only the key, and all three declared reference shapes
yield `CultRecordKey`. A document carrying scalar, many-list and
many-dictionary references at once encodes with no schema id anywhere. **No
other formatter, version or nesting can introduce one without changing the
type.** C#'s rule was right, Rust adopting it is correct, and it is not wrong
in the other direction.
**Both runtimes now agree at the evaluator on every shape the previous verdict
split them on** — seven matched probes built independently in each runtime,
identical answers, including the shared-key matrix in either row order. **The
role-filter reordering, which no ruling covered and only Hands had checked,
agrees with C#.**
**R-AG is behaviourally pinned**: moving the cast inside the parens — still
wrapping, still looking like a fix — kills a test. Dropping the redundant
`length < 0` survives and is genuinely equivalent.
### R-AH: right scope, under-specified, still unbuilt
**Comparing the payload rather than the envelope is the right call** — the C#
message carries a non-deterministic `messageId` and three shard fields Rust
has no concept of, while the shared six are in the same declaration order on
both sides with camelCase keys already.
**But "the canonical MessagePack bytes" is not a specification.** The same
page encodes as 285 bytes compact and 402 named, and **the compact form emits
a 4-element array for a 6-field struct** because `skip_serializing_if` drops
absent optionals positionally — it is not decodable. The ruling must also fix
map encoding with camelCase keys, declared field order, and whether an absent
optional is **omitted** (Rust today) or **nil** (C# today). Those differ right
now, and the same rule has to reach `Edge`, `RawDocumentHeader` and
`SelectionDocumentRecord`.
**The structural reason it keeps slipping: C# has no page type at the
evaluator layer.** `Select` returns rows, and `Projection` appears once in the
evaluator, inside the cursor digest. Either `Select` gains projection, or the
harness builds the page outside it and the vectors start testing
`CultNetDocumentRegistry` too. **Nobody has chosen, and my ruling did not.**
At `fc51e9a`: `pageBytes|payloadBytes|canonicalBytes` is **0 hits** in both
vector files, `rs-written.json` still carries no projection vector, and door
refusals still compare a field and never a code.
**Can parity be claimed without it? No — not byte parity.** What exists is
semantic parity over an agreed field subset. **Even the selection bytes differ
between the two written files for the same vector** — C# writes nine fields
with nils, Rust omits absent ones — so there is no byte-level agreement
anywhere in this cut.
### What Soul could not run
No Stryker run; the 47 survivors stay untriaged, and **two of Soul's own
confirmed survivors are on lines batch 5 changed**, so that list is not empty
of real signal. The `--since`/`GitInfoProvider` crash is still not
root-caused. `contracts.rs` and `schema_discovery.rs` unmeasured. The R-AH
harness unbuilt, pending the shape decision. Caching's three failures still
unproven. **One run was lost to CRLF and still exited 0** — fixed in the
stopgap at Eureka `b12016a`, with a verdict sentinel so a job that never ran
cannot report success.
## Self's rulings for fix batch 6, 2026-09-23
- **R-AL (SM-6). Restore the guard, and fix the owner.** The guard is not
duplicate authority — filtering members by kind is the evaluator's own job,
and I was wrong to call it a duplicate. Restore it, delete the false comment,
and add the probed shape as a test that fails without it. **Separately, D10
is the owner of role-namespace uniqueness and currently admits the
collision**: make registration refuse a data member whose name collides with
a reference member's alias. Both, with tests. **Standing: a "redundant"
deletion must be probed at the shape that would break it, not argued from a
property nobody checked.** This is the second time a redundancy ruling of
mine was wrong; the first was caught by Hands, this one was not.
- **R-AM. The snapshot handler answers like its siblings** — catch
`Exception`, return a `CultNetErrorMessage`. **The dispatch backstop logs**,
always. A backstop that swallows silently is a compensator, and this one hid
a high-severity regression for a whole batch.
- **R-AN. Finish R-AJ in Rust.** Its edge test pins one component of a
four-component rule. Every component of both orderings dies under its own
mutation, in both runtimes.
- **R-AO. Pin shared-key resolution order** — two **in-target** rows sharing a
key — in both runtimes and in a vector. "The first row in the caller's order
wins" is currently a coincidence of source, not a rule.
- **R-AP (R-AH). Decide the shape, then build it.** The payload, as a
**map with camelCase keys**, fields in declared order, and **absent
optionals written as nil** rather than omitted — C#'s current behaviour,
because a decodable fixed-shape encoding is worth more than brevity, and
because omission is what makes the compact form undecodable. The same rule
reaches `Edge`, `RawDocumentHeader` and `SelectionDocumentRecord`.
**`Select` gains projection**, so the thing the vectors test is the thing
that decides; the harness must not assemble a page the evaluator never
produces.
- **R-AQ. Until R-AP lands, this cut claims semantic parity over a named
field subset, not byte parity** — and the map says so in those words.
Soul is right that byte parity cannot be claimed today, and it is better to
name the smaller claim than to let the larger one stand unearned.
**Flagged for the operator**: wire parity is this campaign's stated
invariant, so if semantic parity is not enough for Cut 1 to merge, R-AP
becomes a blocker rather than a follow-up.