Put the campaign maps on main, where the agents reading them actually look
A Hands agent working the selection cut reported that the map had no fix batch 4 section. It did not, on the branch it could see: the live copy was on my own working branch, main was a day behind, and the selection work branch carried a copy from 2026-09-17. It worked from its brief instead and said so, which is the only reason this surfaced rather than producing a cut built on five-day-old design. That is my failure, not the agent's. Self commits the maps and I had been committing them wherever I happened to be standing. A map is only current if it is current on the branch the reader has. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
1bcc19298f
commit
7fd16ec45a
2 changed files with 281 additions and 3 deletions
|
|
@ -671,8 +671,183 @@ edit `src/` at the same time.
|
|||
five-field bodies with an HMAC-SHA256 digest, and they differ in base64
|
||||
flavour and separators. That is correct and stays. Rust's own doc said so
|
||||
first.
|
||||
- **To check:** pass B reports three Caching failures as pre-existing, where
|
||||
pass A reported one. Soul settles it against `main`.
|
||||
- **Settled by Soul:** three Caching failures, identical at `b3d9cf7` and
|
||||
`52082ec`. All three expect a write to a read-only directory to fail, and
|
||||
the container is root. **Environmental.** Pass B's count was right.
|
||||
|
||||
**Soul's whole-cut pass, 2026-09-22** (Opus, at `52082ec`). **Cut 1 may not
|
||||
merge.**
|
||||
|
||||
- **Held:** Networking 238 and 2 skips, Mesh 255, cultnet-rs 213. R-Q holds
|
||||
(both replica applies, the startup rebuild, and a shard-spanning refusal).
|
||||
R-O holds against a stranger and across a restart, and the astral key
|
||||
round-trips. R-H's digest covers every selection term. R-R holds and is
|
||||
honest. R-A holds. R-F's door is inside `EvaluateAll`, which every path
|
||||
reaches. The canonical-number door is anchored `\A..\z`.
|
||||
- **S-1, high: the hop indexes rows by record key alone.** `byKey`
|
||||
(`CultNetSelectionEvaluator.cs:176-178`) and Rust's `HashMap<&str, &R>`
|
||||
(`selection.rs:1457`) both keep the last row at a key. CultCache keys are
|
||||
unique **per schema**, and the Mesh tests already put two schemas at one
|
||||
key. With rows in one order the selection refuses valid data as
|
||||
`reference_outside_target`; in the other order it succeeds. Under `cited`
|
||||
the index is a set of bare keys, so a row of one schema counts as cited
|
||||
because something cites another schema at the same key.
|
||||
- **S-2, high: R-T's `cites` half did not land in C#.** C# filters edges by
|
||||
the citation's key (`:355`) **before** `EnsureWithinDeclaredTarget` (`:359`);
|
||||
Rust checks first. A many-reference declared to one leaf but holding an edge
|
||||
into another is refused by Rust and accepted by C#.
|
||||
- **S-3, high: a selection with both hops loses its `cites` edges in Rust.**
|
||||
`selection.rs:1541` computes one direction for every edge; C# anchors each
|
||||
edge by its own hop. No vector covers both hops.
|
||||
- **S-4, high: the error message fails its own schema.** Nothing decodes real
|
||||
`CultNetErrorMessage` bytes against `cultnet.error.schema.json`.
|
||||
`routingHint` is undeclared under `additionalProperties: false`, and nil
|
||||
`details.asOf`, `details.current`, `code` and `details` all fail their
|
||||
declared types. The header schema shows the nullable pattern was understood
|
||||
and simply not applied here.
|
||||
- **S-5, medium: Stryker leaves 85 survivors** over the cut's two core files:
|
||||
396 mutants, 310 killed, 85 survived, **75 with no coverage at all**, score
|
||||
66%. The old "every entry killed" was an artifact of a hand-picked list.
|
||||
- **S-6, medium: the cursor is malleable in its own position.** The digest
|
||||
covers the selection but not the body's `asOf`, `ordinal`, `schemaId` or
|
||||
`recordKey`. A caller holding one valid cursor can rewrite its position and
|
||||
reuse the digest.
|
||||
- **S-7, medium: R-I's claim is false.** The vectors compare ids, `matched`,
|
||||
`hasNext` and edges. They never compare page bytes, `asOf`, `next`, or a
|
||||
refusal's code, and `rs-written.json` carries no projection vector at all.
|
||||
- **S-8, low: the out-of-target null branch is dead.** D11 refuses a bare
|
||||
`ICultRecordRef` in both scalar and many form, so every surviving shape has
|
||||
a non-null target.
|
||||
- **S-9, low: `asOf` is the maximum across all shards**, so a single-shard
|
||||
page advertises another shard's number, and an unrelated write invalidates
|
||||
a live cursor.
|
||||
- **S-10, low: `MiniJsonSchemaValidator` silently ignores** `minimum`,
|
||||
`maximum`, `maxLength`, `maxItems`, `anyOf`, `allOf`, `if/then/else`,
|
||||
`uniqueItems` and `format`. The committed schemas use `minimum` 34 times,
|
||||
including `limit`'s bound. Its `pattern` also runs through .NET `Regex`,
|
||||
whose `$` accepts a trailing newline.
|
||||
- **S-11, low:** the v1 subscribe refuses a hop before running the door and
|
||||
always names field `cites`.
|
||||
- **S-12, low:** the fixture's prose still calls the alias vectors a
|
||||
cross-runtime defect, which the alias port fixed. Rust's port also drops
|
||||
`SchemaVersion` and `CompatibleSchemaIds`, so R-E is two rules agreeing on a
|
||||
subset.
|
||||
- **Where the evidence lives.** The pass ran against `52082ec`. Its nine hand
|
||||
probes — eight C# and the Rust both-hops probe that proved S-3 on a matched
|
||||
pair — are on branch `soul-probe` in the scratch clone `C:\ss5`
|
||||
(`e0fd672..107ff5f`), **unpushed**. Nothing reached origin and the pinned
|
||||
checkout at `F:\Projects\CultLib` was never touched. Notes are at
|
||||
`scratchpad/soul-sel-final-notes.md`. Probes are scratch by doctrine and are
|
||||
meant to be thrown away; this pointer exists because S-1 through S-3 were
|
||||
proven there and fix batch 4's tests have to reproduce them independently. If
|
||||
`C:\ss5` is gone, the probes are gone with it, and that is acceptable — the
|
||||
findings are recorded here, which is the durable copy.
|
||||
- **Soul's own corrections, same pass.** It first reported a wedged
|
||||
`cargo mutants` job on Yggdrasil and asked for it to be killed. Checking the
|
||||
box rather than its log, the work directory was already gone: what it had
|
||||
lost was its detached ssh's output. Self had by then killed the process,
|
||||
which was Soul's own run, so nothing else was affected. **The shape is worth
|
||||
keeping: a log is not the machine, and Self acted before confirming the
|
||||
process was actually stalled.**
|
||||
- **The Stryker triage is owed, after four attempts, none of them Stryker's
|
||||
fault.** The `dotnet/sdk:10.0` image has no `python3`; the JSON reporter threw
|
||||
on `CreateDirectory`; two runs with a different output path ran about fifty
|
||||
minutes without finishing, against three minutes for the mutation phase that
|
||||
did complete. The completed run stands, and the command is in Soul's notes:
|
||||
`dotnet-stryker --project GameCult.Networking.csproj --mutate "**/CultNetSelection*.cs" --reporter json --reporter progress --concurrency 4`.
|
||||
**Read the HTML report Stryker writes beside the JSON**, rather than parsing
|
||||
JSON in the container.
|
||||
- **The triage landed on the fifth attempt, 2026-09-22, and it sharpens S-5
|
||||
rather than softening it.** Most of the 85 are message-string mutations and
|
||||
are not worth chasing; roughly a dozen are real, and they sit on the rules
|
||||
this cut exists to enforce:
|
||||
- `CultNetSelection.cs:439`, `Any()` → `All()` survived. §2's "a comparison
|
||||
on an alias any reachable schema declares non-numeric is refused" degrades
|
||||
to "refused only if every reachable schema declares it non-numeric". No
|
||||
fixture has an alias numeric on one reachable leaf and a string on
|
||||
another — the exact case the rule exists for.
|
||||
- `CultNetSelection.cs:387`, `||` → `&&` survived. A `cites.target` with a
|
||||
schemaId and a blank recordKey, or the reverse, stops being refused.
|
||||
- `member.IndexAlias ?? member.MemberName` → `member.MemberName` survived at
|
||||
all four sites (`CultNetSelectionEvaluator.cs:352,384,404` and
|
||||
`CultNetSelection.cs:445`). §2's role-naming rule for the hop is untested
|
||||
everywhere it is implemented.
|
||||
- `CultNetSelectionEvaluator.cs:203,204`, `ThenBy` ↔ `ThenByDescending`
|
||||
survived on both paths: the `(ordinal, schemaId, recordKey)` tiebreak is
|
||||
pinned by nothing.
|
||||
- `CultNetSelectionEvaluator.cs:290-297`, five order flips in `EdgesFor`, all
|
||||
survived. **R-B's "page-row order, then `(from, role, to)` in code-point
|
||||
order" has no test pinning a single component**, though R-B exists because
|
||||
that ordering was wrong once already.
|
||||
- `CultNetSelectionEvaluator.cs:590-605` has no coverage at all:
|
||||
`ComputeDigest`'s `cites` and `cited` branches, plus the number-presence
|
||||
flag at `:584-585`. R-H's collision-freedom rests on a reading, not a test.
|
||||
- `:673,678` survived: `Cursor.Parse`'s length-prefix bounds checks, on
|
||||
attacker-supplied base64. `:307,322` survived: cursor positioning under
|
||||
`descending`, and `ComparePosition`'s schema-vs-key tiebreak. `:710,715,729`
|
||||
survived: the code-point comparator's surrogate path, despite R-C having a
|
||||
vector for it.
|
||||
- **Equivalent, not to be chased:** `Selection.cs:103` `<=`, `:118` `>=`,
|
||||
`:121` `dot <= 0` are guarded upstream and unreachable for canonical input;
|
||||
`:103,107` turn `sign * x` into `sign / x` with `x` in {-1,1}; `:109`
|
||||
Max→Min is a no-op because `PadRight` to a shorter length does nothing;
|
||||
`:80` is a `RegexOptions` flag; `Evaluator:272` is equivalent because
|
||||
`full.Edges` is empty without a hop.
|
||||
- **Soul withdrew a finding of its own in the same pass.** It had reported that
|
||||
C#'s cursor digest does not sort its lists while Rust's does, offered as a
|
||||
low-severity intra-runtime difference. `CultNetSelectionEvaluator.cs:637`
|
||||
sorts by code point, same as Rust. The remark belongs in no fix list.
|
||||
- **cargo-mutants cannot run on `cultnet-rs` at all.** `schema_discovery.rs`
|
||||
uses `include_str!("../../../contracts/…")`, which escapes the package, so
|
||||
the tool's copied tree fails its baseline build. **There is no mutation
|
||||
evidence for the Rust half**, and the package's own source shape is why.
|
||||
|
||||
**Self's rulings for fix batch 4, 2026-09-22:**
|
||||
|
||||
- **R-V (S-1). A row's identity is `(schemaId, recordKey)`, never the key
|
||||
alone.** Both runtimes index rows and incoming edges by the pair. `RecordRef`
|
||||
already carries both. Tests: two schemas at one key, in both row orders,
|
||||
give the same answer; `cited` does not match a row of another schema at the
|
||||
same key.
|
||||
- **R-W (S-2 and S-3). The hop behaves identically in both runtimes.** The
|
||||
target check runs **before** any key filtering, and each edge is anchored by
|
||||
its own hop, so a selection carrying both `cites` and `cited` returns both
|
||||
sets. C# adopts Rust's ordering for the first; Rust adopts C#'s per-edge
|
||||
anchoring for the second. Vectors cover both.
|
||||
- **R-X (S-4 and S-10). A message that fails its own schema is a defect.**
|
||||
`cultnet.error.schema.json` declares `routingHint` and every nullable field
|
||||
the way the header schema does. A test decodes real bytes for every code.
|
||||
`MiniJsonSchemaValidator` **refuses a schema containing a keyword it does
|
||||
not implement**, rather than ignoring it, and implements `minimum`,
|
||||
`maximum`, `maxLength` and `maxItems`. Its `pattern` anchors so a trailing
|
||||
newline cannot pass.
|
||||
- **R-Y (S-6).** The cursor's HMAC covers the body as well as the selection,
|
||||
so the position cannot be rewritten. Test: a rewritten ordinal or key is
|
||||
refused.
|
||||
- **R-Z (S-7).** The vectors compare **page bytes**, plus `asOf`, `next` and a
|
||||
refusal's code, and carry a header-projection and a document-projection
|
||||
vector on both sides. Where a field is legitimately per-server, such as a
|
||||
cursor, the vector states that and compares the rest.
|
||||
- **R-AA (S-5).** Triage all 85 survivors by name. The 75 uncovered mutants
|
||||
mean whole regions have no test; write behavioural tests for them. A
|
||||
survivor that is genuinely equivalent gets a one-line reason in the report.
|
||||
**The triage half is done** (see the list under S-5, 2026-09-22). What is
|
||||
owed is a behavioural test for each named live survivor: the alias
|
||||
disagreement across reachable schemas, the half-blank `cites.target`, the
|
||||
four index-alias role sites, the row tiebreak, `EdgesFor`'s ordering,
|
||||
`ComputeDigest`'s hop branches, `Cursor.Parse`'s bounds, descending cursor
|
||||
positioning, and the surrogate comparator. The equivalent mutants are
|
||||
recorded and are not to be chased.
|
||||
- **R-AB. Make `cultnet-rs` self-contained enough to mutate.** Replace the
|
||||
escaping `include_str!` with a build script that stages the contracts into
|
||||
`OUT_DIR`, or another route that keeps every source path inside the package.
|
||||
Prove it by running cargo-mutants on the crate.
|
||||
- **S-8:** delete the dead branch.
|
||||
- **S-9:** `asOf` is the watermark of the shards the selection actually
|
||||
covers.
|
||||
- **S-11:** run the door first, and name the field that is actually set.
|
||||
- **S-12:** correct the fixture's prose, and state Rust's alias reduction
|
||||
where R-E is described.
|
||||
|
||||
**Ledger correction.** Commits 0 and 1 came in at about twice the §14
|
||||
estimate:
|
||||
|
|
|
|||
|
|
@ -773,7 +773,110 @@ and P3. **Cut 3 still does not close.** The method moved the gaps outward.
|
|||
checks. Prove it with 5 rounds under 16 burners on Windows.
|
||||
6. **F6.** Fix the prose.
|
||||
|
||||
To Hands (Sonnet) as the eighth fix batch.
|
||||
**Superseded the same day, 2026-09-22, by the operator's retirement of the
|
||||
mutation harness.** The operator's words: *"The manual mutation testing turned
|
||||
out to be a terrible idea, lots of guarding the shape of the code instead of
|
||||
the behavior. Better to have nothing than a harness that punishes
|
||||
refactoring."* `scripts/mutate-cultmesh.mjs` and every committed suite are
|
||||
deleted, with no fallback. Four of the six rulings above named harness entries
|
||||
(R1, W1–W3, P1, Z1, Z2); those clauses are dead text. What each ruling
|
||||
protected is not:
|
||||
|
||||
1. **F1 stands as behaviour.** The scenarios gain a **release** configuration
|
||||
on both targets, run against the release library. The release build is the
|
||||
thing that ships, so a rule the release build never runs is not pinned.
|
||||
Without the harness there is no R1 entry; the release path is proven by the
|
||||
scenarios themselves going red when the seam's `#else` shortens the wait,
|
||||
which Soul checks by hand on a scratch copy.
|
||||
2. **F2 stands.** The seam wraps the whole argument to `wait_for` — the
|
||||
duration expression — and `waitseam` asserts it equals
|
||||
`milliseconds(host argument)`. The assertion is the pin; W1–W3 are Soul's
|
||||
hand probes, not committed entries.
|
||||
3. **F3 stands.** `pollhammer` also runs against a runtime holding live
|
||||
objects: an open connection to an unroutable address, plus a listener and a
|
||||
stream where the fixture can make them.
|
||||
4. **F4 stands.** Any `timeout_ms <= 0` returns within a small bound, timed
|
||||
with a generous margin. A negative timeout is probed. The seam's sentinel
|
||||
moves to a value that cannot collide, or becomes a separate recorded flag.
|
||||
5. **F5 stands**, and its 5 rounds under 16 burners run **on Yggdrasil**. The
|
||||
burners that motivated this ruling are what froze the operator's
|
||||
workstation on 2026-09-22; no CPU burner runs on Starfire again.
|
||||
6. **F6 widens.** The prose fix now includes removing every reference to the
|
||||
deleted harness, not only the stale 1300 ms history and the scenario list.
|
||||
|
||||
**There is no mutation evidence for the bridge and there will not be one
|
||||
committed.** It is C++, and no ecosystem mutation tool is wired here. The
|
||||
bridge's rules are pinned by behavioural scenarios at the layer that decides
|
||||
them; a mutant is a thing Soul tries by hand on a scratch copy and throws away.
|
||||
|
||||
**The eighth fix batch landed** (Sonnet) at `5646ff7`, before the retirement,
|
||||
and the harness deletion followed at `796433f`. Both are on
|
||||
`hands/cultmath-erf`, not yet on `main`. What landed:
|
||||
|
||||
- the seam wraps the whole duration handed to `wait_for`, and its "unset"
|
||||
sentinel became a separate `debug_wait_recorded` flag, so a recorded `-1`
|
||||
cannot read as nothing recorded;
|
||||
- `pollhammer` runs against a runtime holding a live connection to an
|
||||
unroutable address (RFC 5737 TEST-NET-1), a stream on it, and a real
|
||||
never-connected listener;
|
||||
- a `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms,
|
||||
probing 0, -1 and `INT32_MIN`;
|
||||
- `closerace`'s settle waits for `debug_peak_calls` to reach the poller count
|
||||
instead of sleeping a guessed duration;
|
||||
- `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms, so the wall-clock
|
||||
layer catches arithmetic applied outside the seam's own argument —
|
||||
`waitseam`'s equality check structurally cannot see that.
|
||||
|
||||
The release configuration and the seven mutation entries (R1, W1–W3, P1,
|
||||
Z1–Z2) landed **inside the harness**, and went out with it two commits later.
|
||||
The scenario runner and every seam survived untouched: the README and the dev
|
||||
Dockerfile now document building with `-DCULTMESH_QUIC_BUILD_TESTS=ON
|
||||
-DCULTMESH_QUIC_DEBUG_ASSERTS=ON` and running each scenario as a direct
|
||||
argument to the binary. **The release configuration is therefore owed again as
|
||||
a plain build, not as a harness target**, and R1's substance — that the
|
||||
release macro is unexercised — is a Soul hand probe now.
|
||||
|
||||
**The Linux leg ran on Yggdrasil, 2026-09-22** (Sonnet), against
|
||||
`hands/cultmath-erf` HEAD, in an image built from the committed
|
||||
`scripts/quic-native-linux-dev.Dockerfile` (pinned `debian:13`, tagged by
|
||||
content hash `cultlib-quic-native-dev:13d7fbd109fc`). No repo change was
|
||||
needed and none was made.
|
||||
|
||||
- **Asserts build, all ten scenarios pass**, 20 rounds each: `closerace`,
|
||||
`holdclose`, `holdtimeout`, `polltimeout`, `pollbusy`, `pollhammer`,
|
||||
`zerotimeout`, `payloadfit`, `latecall`, `waitseam` (11 probes, 40 repeated
|
||||
polls on one runtime).
|
||||
- **Release build, asserts off, all four required scenarios pass**:
|
||||
`polltimeout`, `pollbusy`, `pollhammer`, `zerotimeout`.
|
||||
- Worst overshoot anywhere was 4 ms, against `kGenerousLateToleranceMs` of
|
||||
400 ms. **F1 is closed on Linux**: the build that ships now runs the timeout
|
||||
rule.
|
||||
|
||||
**A defect in the README's own worked example, found by running it.** The
|
||||
example builds the test binary and runs it directly, and every scenario dies
|
||||
with `libmsquic.so.2: cannot open shared object file`. Both the bridge and the
|
||||
test executable carry `INSTALL_RPATH "$ORIGIN"` and expect the MsQuic runtime
|
||||
beside them in `bin/`, but nothing puts it there for a test build — only
|
||||
`build-quic-native.sh`'s packaged output directory gets that copy. Hands
|
||||
worked around it with a `cp` after each build and correctly did not treat it
|
||||
as a bridge defect. **This is the documented path, written in `796433f` when
|
||||
the harness was deleted, and it has never worked.** A documented path that
|
||||
fails on first use is worse than no documentation: it was published in the
|
||||
same commit that removed the working one.
|
||||
|
||||
**Still outstanding for Cut 3:**
|
||||
|
||||
- The **Windows release configuration**. It lived inside the harness and left
|
||||
with it, exactly as Linux's did; Linux has now been re-run without the
|
||||
harness and Windows has not.
|
||||
- **F5's stress under load cannot be run on Windows at all.** It wants five
|
||||
rounds with sixteen CPU burners, and those burners are what froze the
|
||||
operator's workstation on 2026-09-22. `closerace` is Windows-sensitive (it
|
||||
went red under load there in four of five rounds), so the check matters most
|
||||
on the platform where it is now forbidden. **This is an operator fork, not
|
||||
something to route around:** either Windows stress finds a host that is not
|
||||
Starfire, or the check is accepted as unproven on Windows and recorded that
|
||||
way.
|
||||
|
||||
**Cut 1 Soul findings, 2026-09-16.** Held: every test count, both negative
|
||||
greps, all twelve mutations rerun and killed, the control catching a
|
||||
|
|
|
|||
Loading…
Reference in a new issue