BOARD — next validation candidates, each rooted

Measured 2026-07-27 by running the -tests pipeline over every unbanked candidate the shared-fixture fix structurally unblocked, plus the packages a prior scout left build-blocked. Every entry carries the first and most informative diagnostic, so the next arc starts from a root cause rather than an exploration. Revised 2026-07-27 (later) by the reference-closure arc: the closure family is closed, internal/zstd is banked, and two claims in the original revision are retracted as measurement errors — see the sections below. Corpus state after that arc, plus the 2026-07-29 hash/maphash bank and the 2026-07-31 image/draw, image/gif, crypto/md5, compress/flate, image/jpeg, image/png and index/suffixarray banks: 69 validated / 215 (32.1%).

Revised again 2026-07-31 by the build-blocker arc: path/filepath and net — the last two unrooted build blockers — are both fixed at the converter, and both rows moved down into their own sections with what stood behind them. Neither package banks, and the roster is unchanged at 66; a build blocker closing is worth recording precisely because the next wall is now measurable.

Revised again 2026-07-31 (r28-net): six of the seven semantic roots the previous revision bucketed for net are fixed — net is down to 2 errors from one root, and that root is a ruling (testing.T.Deadline needs a type the one-testing-package cannot name), not a defect. See the net section. The roster is unchanged at 69 — the “66” in the paragraph above was already stale when it was written, and no package banks from this arc.

Revised again 2026-08-02 (r35-context): context gets its own section below — five converter roots closed, T.Deadline un-blocked, 36 of 38 verdicts match, and two rooted failures left, one owned by the reflection-bridge arc and one a measured disclosure. The roster is unchanged at 71: nothing banks from that arc. Its most valuable measurement is a negative — the sharpest select/cancellation suite in the standard library finds no channel defect at all, which is independent confirmation of the wave3 landing.

Revised again 2026-08-02 (r35-os): os gets its own section below — it builds with 0 errors and reaches 158 of 178 top-level tests matching + 1 disclosed, up from 48 at the start of the arc. Two converter roots and one host-killer closed; the residual is rooted row by row, and the largest single item (12 unreached) is heap corruption whose crash SITE moves between runs, not a defect at any of the three sites it has been credited to. os does not bank and the roster is unchanged at 71.

Revised again 2026-08-02 (r37-gob): encoding/gob gets its own section below — the build blocker that made all 106 of its verdicts read empty is closed at the test-project-model record-anchoring root, and gob is measured for the first time: 86 of 106 match, every mismatch bucketed to one of seven roots. A second converter defect found through it — a dead deref alias that took down unique’s and net/netip’s package initializers corpus-wide — is fixed in the same arc, though it moves TestNetIP’s site rather than greening it. The roster is unchanged at 71: gob does not bank.

Revised again 2026-08-03 (r38-gob-fin): gob moves 86 → 88 of 106 on a converter fix, unique BUILDS for the first time and gets its first census, and four of r37’s seven gob roots turn out to be mis-attributed — see the rewritten gob section. The roster is still 71: neither package banks.

Revised again 2026-08-03 (r39-nilcomplex): both converter items r38 handed on ROOTED — the typed-nil BOUNDARY and the complex() element-width pin — land, and gob moves 88 → 91 of 106. Two of r38’s seven roots close outright; the third typed-nil row does NOT, and its residual is now rooted one layer down in the reflection bridge (see r39-nilcomplex at the end of the gob section). The roster is still 71/72: gob does not bank.

Revised again 2026-08-07 (r41c-cloneseq): unique’s makeCloneSeq root closes and the package moves 1 → 4 of 19, on an internal/abi hand-own plus a converter fix — but the row’s own DESCRIPTION below was wrong in both halves (it is not a slice<T> enumerator edge, and it is reflection-bridge territory), so read The makeCloneSeq root, CLOSED rather than the table cell. The roster is unchanged at 73: unique does not bank.

Revised again 2026-08-07 (r43c-candidates): the first pure measure-first breadth pass — 47 never-measured candidates run back to back through the pipeline. Twenty-three validated on the first run with no converter change of any kind, taking the roster 73 → 96 (44.7%); every one of the twenty-four that did not is rooted in the new section at the end of this file. The finding worth carrying forward is the negative one: the corpus had already grown past those packages and nothing was watching, so the roster’s denominator is limited by who has looked, not by what is broken. Re-scout the tail after any capability lands, not only the packages that capability names.

Revised again 2026-08-07 (r44a-rescout): r43c’s own instruction executed — 108 pipeline runs over BOTH r43c’s rooted non-validators and the 76 never-measured tail packages. Twelve bank, taking the roster 97 → 109 (50.7%) and past the campaign’s 50 % mark; eleven needed nothing, and the twelfth (internal/cpu) took a one-declaration hand-own. The re-scout of r43c’s OWN roots yielded exactly one package (expvar) and every other rooting re-measured verbatim, which sharpens the instruction rather than repealing it: a rooted non-validator has been looked at; the yield is in what nobody has run. Eighteen packages are now ONE OR TWO ROWS from banking, and the tail’s build blockers are named with their verdict counts — see the r44a section at the end of this file.

A note the arc earned: a first diagnostic is a starting point, not a diagnosis. io’s first error is CS0012 and reads as a missing reference; it is not one. Two of the three claims below that were stated as “measured” did not survive re-measurement on a freshly built converter. r41c is the same lesson at one more remove: the exception TYPE and the frame it is thrown in (IndexOutOfRangeException in go.slice<T>.Enumerator) named a component that had nothing wrong with it, because a garbage slice HEADER two frames up makes a correct enumerator throw.

Re-validate everything after any change here with ./src/run-validated-sweep.ps1 — it reads the roster and the expected counts from ValidatedTestPackages.md and fails on a count mismatch, so a package that still passes but asserts something different is caught rather than assumed.

✅ BANKED (2026-08-17, lane claude/connraw-hang) — database/sql validates 137 of 139 as roster row 148; TestConnRaw was never a lock, and the guilty layer was GoFrame.Run

The hypothesis this lane was handed was wrong in every particular, and one dotnet-stack report against the hung host said so before any code was read — which is the sixth consecutive brief this week to name the wrong layer, and the reason the instruction to measure first is worth its cost.

The brief’s leading suspicion was a deadlock or lost wakeup in golib’s hand-owned sync.RWMutex shim, from release(ErrBadConn)closemuRUnlockCondReleaseConnConn.close taking closemu.Lock() against the test’s own recover handler. No thread was blocked on closemu, or on any lock. The only test thread was in time.Sleep, inside waitCondition, inside closeDB — the package’s ordinary teardown poll. sync’s shim is innocent and unchanged; so is the Conn.Raw emission the previous entry had already cleared.

The hang was never a hang — it is waitCondition sized by t.Deadline()

closeDB ends with waitCondition(t, func() bool { return db.numOpenConns() == 0 }), and waitCondition does not poll for five seconds when the test has a deadline: it polls for time.Until(deadline) minus 10 % headroom. So an assertion failure that leaves ONE connection open does not fail fast — it consumes 90 % of whatever -test-timeout was passed, then reports. That is the whole of the 3,418 s: at -test-timeout 60m, 0.9 × 60 min. The two previous entries’ 1,712 s and 3,423 s package figures are the same arithmetic at their own deadlines, not machine load and not lane contention.

Consequence for the board’s reading habits: a Phase-4 row whose C# elapsed lands suspiciously near 0.9 × -test-timeout is a failing assertion inside a deadline-sized wait, not a deadlock. Run it again with a small -test-timeout (20 s was enough here) and it prints its real failure in seconds. Do not reach for dotnet-stack first — but if you do, it answers immediately too.

The root: GoFrame.Run re-raised a panic the frame never caught

Probes on the converted sql.cs caught the panic being re-thrown from a frame that had nothing to do with it. Conn.Raw’s deferred cleanup reaches Conn.close, which sets c.dc = nil only AFTER dc.releaseConndb.putConndc.ClosefinalClosewithLock. withLock is two lines, it holds one defer, and it panics nothing — but its finally’s ᒐ.Run() ended with

if (GoFuncRoot.CapturedPanicValue is not null)
    throw GoFuncRoot.CapturedPanicValue;

and that slot is the THREAD’s, non-empty for the whole of the panicking frame’s deferred sequence. So withLock threw Conn.Raw’s panic on the way out, close never reached c.dc = nil, conn.dc stayed non-nil (the failure the previous entry recorded), the later deferred conn.Close() found done already true and returned ErrConnDone without releasing (the “1 connections still open”), and waitCondition sat on it for the rest of the deadline. One root, both recorded symptoms, and the hang.

The rule is now stated instead of inferred: the re-raise belongs to the frame whose own catch caught the panic. GoFrame.Capture arms a claim, the next Run on the thread claims it — always that same frame’s, because nothing runs between an emitted catch body and its finally — and a frame that caught nothing claims null. The emission is untouched; the change is 33 lines inside golib/GoFrame.cs + GoFuncRoot.cs, and no converted file moved.

This is a general class, not a database/sql fix: EVERY converted function with a defer was a spurious re-raise site whenever it was called during another frame’s deferred sequence. Cleanup paths that call helpers are the common shape, so expect other Phase-4 rows in the “cleanup didn’t finish” family to move without being touched.

Doctrine: ConversionStrategies-Reference.mdThe re-raise of an unrecovered panic belongs to the frame that CAUGHT it, not to the thread, which also records the one adjacent hole left deliberately open (a nested frame’s recover() clears the outer frame’s parked panic — same shared slot, predates this change, no measured consumer).

database/sql — 137 of 139, and the owed $longTimeouts floor is repriced away

139 rows, 137 agree, 2 disclosed, 0 skipped, 27 excluded (the standard Benchmark/Example deferrals). The two are TestGrabConnAllocs and TestRawBytesAllocs, the standing alloc-profile class, pinned by signature in a hand-owned go2cs_test_disclosures.json.

No $longTimeouts entry is owed, and the two previous entries’ “'database/sql' = '60m' if it ever banks” is formally repriced to zero. The converted suite now runs in 3.5 s (Go: 46 s); the whole -test-action all round trip, both builds included, is 33 s. The sweep’s default deadline clears it by three orders of magnitude.

Guards, and the corpus footprint

✅ CLOSED (2026-08-16, lane claude/assignableto-arc) — the AssignableTo deferral is retired, with the struct/func/interface identity arms and the chan direction fixed in the same change

The reflect-bridge lane recorded the retirement SEQUENCE rather than the fix, and the sequence was right in every particular: HasName() had to become truthful first, and the struct and chan arms of haveIdenticalUnderlyingType had to be fixed with the hand-own’s retirement rather than after it. Following it turned up one arm the sequence did not name — func — and one it did name as undecidable turned out to be decidable after all, once the question was asked about the right object.

database/sql moves 135 → 136 of 139. It does not bank: TestConnRaw is still open, and this lane measured something about it that changes what “open” costs (below).

What each of the four pieces actually was

Piece Before Now
abi.Type.HasName() false for EVERY synthesized descriptor — TFlagNamed was never carried synthesizeDescriptor stamps the bit from GoReflect.HasGoName, the SAME gate reflect’s own rtype.Name() already stood on
implements (the free function) reinterprets the descriptor as an interfaceType and reads .Methods off a default promoted-embed box — throws for any non-empty interface bridged over GoReflect.GoImplements, the probe rtype.Implements and the emitted _<T> asserts already share
haveIdenticalUnderlyingType struct / func / interface arms prefix-downcast; read ZERO fields / ZERO in-out counts / ZERO methods and returned true Go’s own clauses over GoReflect.GoFields + TryFuncShape, at the reflect level
ChanDir() (both abi.Type and rtype) reads a direction out of the memory FOLLOWING the descriptor’s value slot — non-deterministically BothDir, the real direction of the only channel type the bridge can describe
rtype.AssignableTo hand-owned as identity-on-the-managed-type + implements RETIRED — Go’s directlyAssignable(uu.t, t.t) \|\| implements(uu.t, t.t) runs

The struct arm was worse than recorded, and the recorded shape understated it. The board said “any two structs of EQUAL FIELD COUNT compare identical”. Measured: the count check itself reads len(Fields) off the same dead downcast, so it is 0 == 0 for both operands and even a differing field COUNT compares identical. struct{B []byte; M map[string]int} was reported convertible to the same struct with M map[string]int64, to one whose second field is merely RENAMED, and to struct{B []byte}.

The FUNC arm is the one the sequence did not name, and it had to be fixed here. It fails the same way through InCount/OutCount, so any two func types compared identical. It was already live through ConvertibleTo, and it would have widened to assignment the moment AssignableTo started routing through the walk — the exact trade the recorded sequence exists to prevent. It is answered from GoReflect.TryFuncShape, the same shape rtype.NumIn/In/NumOut/Out read.

The CHAN ruling — decidable, because the question was being asked about the wrong object

The scout recorded ChanDir as “the only one of the family with no synthesis waiting for it: <-chan int and chan int are both channel<nint>, so a direction is not recoverable from the managed type for an unnamed directional channel. This one needs a ruling, not a fix.” Both halves of that are true, and the conclusion still does not follow — because a directional channel type is never an OPERAND here. The bridge cannot build a descriptor for <-chan int at all; it builds one for channel<T>, whose direction genuinely is BothDir, and whose Type.String() has said chan T since the beginning. Answering BothDir is therefore not a guess about something unknown — it is the correct answer about the descriptor actually being asked, and it makes the descriptor’s kind, name and direction agree where the downcast made one of the three disagree at random.

The limit is real but it lives one layer up, in the converter’s channel emission: reflect.TypeOf over a <-chan int reports chan int. That is stated in ConversionStrategies-Reference.md rather than hidden, and it is not disclosable and needs no gate, for a specific reason worth recording: no package on the validated roster observes it. The one corpus consumer that branches on direction is text/template’s walkRange (rejecting a range over a send-only channel), and text/template is not on the roster — only text/template/parse is. Recovering the direction would mean carrying it as descriptor cargo the way array dims are carried, which no measured consumer asks for (the r39d rule). If text/template is ever taken up, this is the row to expect, and the remedy is cargo, not a disclosure.

Two residuals stated the same way: the interface arm proves “methodless” only for object (Go’s any), so a defined empty interface with a managed type of its own answers not identical — the conservative direction, since a false negative degrades a caller to “needs a conversion” while a false positive is a silent wrong assignment; and a defined methodless func type has no managed identity at all (the converter renders it inline as its base delegate), so the named/unnamed pairs every other kind asserts cannot be produced for funcs.

database/sql — 136 of 139, and TestConnRaw is a HANG, which reprices the owed floor

TestUserDefinedBytes flips exactly as predicted, and TestConversions stays passed. 139 rows, 136 agree, 3 disagree, 0 skipped, 0 disclosed, 27 excluded (the standard Benchmark/Example deferrals). The three: TestGrabConnAllocs and TestRawBytesAllocs, the standing alloc-profile class, and TestConnRaw.

The brief for this lane predicted 137 of 139; the arithmetic was one high (135 + the one flip = 136). Recording it so the next planner starts from the measured number.

The new measurement. TestConnRaw does not merely assert wrong — it hangs, and it is the entire runtime of the package:

  C# Go
whole-package terminal elapsed 3,423.3 s 46 s
TestConnRaw alone 3,418.2 s 0.005 s
every other test COMBINED 5.1 s

So the 'database/sql' = '60m' $longTimeouts floor two previous entries record as owed is not “this package is slow” — it is this one test. Fix TestConnRaw and database/sql runs in about five seconds and needs no floor entry at all. Nothing is added to $longTimeouts today (the package still has no roster row, and a floor for a package the sweep never visits is dead configuration) — but the entry that banks it should re-measure before assuming it owes one. It also retro-explains the previous two lanes’ 1,712 s and 3,423 s figures, which were read as machine load and lane contention: both were this test blocking until whatever bound applied.

Where the root is NOT. The converted Conn.Raw emission is structurally correct: fPanic and the named result err are captured by REFERENCE while dc/release are snapshotted, the deferred lambda runs from finally { ᒐ.Run(); }, and ᒐdone: return err sits after the try/finally so the deferred write to err is observed. The suspect path is what the defer CALLS — release(driver.ErrBadConn)closemuRUnlockCondReleaseConn (closemu.RUnlock(), then errors.Is, then c.close(err)) → Conn.close, which takes closemu.Lock(), a WRITE lock, before setting c.dc = nil, while the test’s own recover handler independently takes conn.closemu.Lock(). A deadlock or lost wakeup in the hand-owned sync.RWMutex shim under that ordering is the leading hypothesis; errors.Is answering false for an identity comparison is the cheap one to falsify first. Not chased further here — it is a sync/defer-during-panic question, not an assignability one.

Guards, and the corpus footprint

Doctrine: ConversionStrategies-Reference.mdGo’s ASSIGNABILITY rule, and the identity walk underneath it (new), which also CLOSES the follow-on recorded at the end of reflect.Type.Name() — a DEFINED type HAS a name even when its underlying type is a composite: reflectlite.rtype.Name() becomes truthful for free, since it gates on the bit this lane started carrying.

OPEN — -recurse emission is covered by NO standing gate, and issue #35 proves what that costs (2026-08-08)

Every standing gate measures the behavioral corpus or the standard library. Neither can see a -recurse-only defect, so an end-user conversion is guarded solely by hand-authored synthetic fixtures — one per past issue. Issue #35 is the demonstration: a truncated project name put 175 duplicate .csproj names into a user’s 1,727-project solution (Visual Studio then refuses to open it and says nothing), and every gate stayed green through it. check-no-regression reported byte-identical C# and .csproj across all 574 behavioral packages, and -stdlib cannot reach the code at all — it returns on the GOROOT branch before the module walk. See ConversionStrategies-Reference.md, A project name is the package’s FULL import path.

That is structural, not bad luck. -recurse is one of the two end-user use cases (DESIGN-recursive-enduser-conversion.md), and the only thing exercising it is the nine fixtures in moduleConverter_integration_test.go — three that cover a mode (TestRecurseSyntheticModule, …NuGetReferences, …ModuleOnly) and six written after the defect they cover (TestModuleCachePoisonedGoWorkLoad, TestModuleCacheVestigialReplaceLoad, TestRecurseQuotedModulePath, …KeywordNamespaceSegment, …ChannelOfHyphenatedModulePath, …GoFileFreeContainerDirsKeepDistinctProjectNames). That is precisely the “enumerate the shapes we have SEEN rather than state the property we need” tell this file already names as the recurring signature of a point repair: the six shapes are the ones issues #32, #33 (×3) and #35 happened to hit.

Two increments, the first nearly free:

  1. State the property. After any -recurse run the emitted project names must be distinct and each must equal its package’s import path, dotted. That is one assertion over convertedCsproj, it closes the whole class rather than one shape, and it costs nothing to add to the existing fixtures. (Deliberately as a TEST assertion, not a converter runtime check — post-fix the name IS the import path by construction, so a runtime guard would be machinery for an unreachable state. The value is in pinning the invariant, which is a test’s job.)
  2. One adversarial fixture instead of seven incidental ones. A checked-in, network-free module whose layout is the union of every shape that has bitten: go-file-free container directories, internal/, a /vN submodule, a quoted module directive, a C#-keyword path element, a hyphenated path, same-named leaf packages, a co-located replace, a go.work. Convert it and golden-compare the emitted .csproj/.slnx/.cs the way the behavioral corpus is compared — which gives -recurse the drift detection it has never had, and makes the next shape a few lines of fixture rather than a new test.

Worth doing before the next end-user report rather than after it: the class has now produced four issues (#33 ×2, #35, and #32’s loader shape), and each arrived from a user rather than from a gate. The derivation’s own recurring-defect row is in the Recurring classes section below.

CLOSED — the ARGUMENT-path exponential is fixed, and the corpus paid its 29-file debt in the same change (2026-08-07, r43a-argexp)

Same bug class as the chained-call exponential closed directly below, one code path over, and closed the same way: stop paying for a traversal whose answer the type system already holds. Nesting depth 22 went from 13.7s to 0.54s, and the whole 302-package standard library still compiles.

What it was. After rendering a call, convCallExpr re-walked every argument for its recording side effects — the loop at the end of convCallExpr, whose own comment said it “re-converts each arg purely for its side-effects (recording implicit conversions); the result is discarded” — and checkForImplicitConversion opened with a full expr := v.convExpr(arg, nil). So every argument subtree was converted twice: once by convExprList for the emitted text, once again here for the recording. On NESTED calls — f(f(f(…))), where each argument IS the next call — that compounds to 2^depth.

Fix (convCallExpr.go), exactly the split the rooting designed, because the premise held on inspection: expr is pure text that flows only to the return value (two pointer cases wrap it), while every recording decision comes from funcType, argType, targetTypeName/argTypeName and packageTypeSpecRHS.

Removing the traversal also retires the hoistedDecls save/restore that bracketed the loop: its only job was to stop a func-literal argument’s capture decls being written into the hoist buffer a second time by the very conversion that is now gone.

Measured A/B on the DESKTOP (Windows, this repo’s box — the rooting’s table was a laptop, so both columns are re-measured here). Paired runs, same seeded scratch module, single-package conversion, best of two:

argument nesting depth before after
10 0.56s 0.53s
14 0.59s 0.55s
18 1.22s 0.56s
22 13.66s 0.54s
26 killed at 416s, unfinished 0.54s
30 (not attempted — extrapolates past half an hour) 0.55s

After is FLAT at the ~0.55s go/packages load floor through depth 30, i.e. the conversion component is gone, not merely reduced. Before, subtracting that floor leaves a conversion component that doubles per level: 0.67s at depth 18 → 13.11s at depth 22, a factor of 19.6 over four levels ≈ 2.1× per level. The excess over a clean 2× is GC of what the doubled traversal allocates, and it compounds — which is what put depth 26 past 416s of wall at ~1.9 cores without finishing.

Full-stdlib conversion wall. go2cs -stdlib -comments over all 302 packages: 378.9s before → 221.2s after. Read that as directional only — the two runs saw different sibling-lane load on a shared box (the same before-converter measured 251.2s on an earlier, quieter run), and CLAUDE.md’s own baseline for this command is ~195–225s, which the after run sits inside. The honest claim is that the argument tax is real but small against go/packages load time on ordinary code; the fixture is where it is dramatic.

Gates — all green, and the arc is NOT emission-neutral in the way that mattered.

  1. CNR: 4 of 571 changed (550s) — FuncLitCaptureInCondition, NilPointerPanic, NilPointerParamMethods, NilReceiverMethods, all main.cs, all a pure capture-variable RENUMBERING (lookupʗ3/5/7lookupʗ2/3/4), declaration and every use renamed together. Same mechanism the sibling arc hit: the discarded conversion had been consuming values from getCapturedVarName’s monotonic per-prefix counter, so removing it closes the gaps. Verified collision-free (every generated name declared exactly once per file) and then verified where it counts — all four Compile pass and Output pass against go run — before re-baselining. Goldens updated with the runner’s --update-targets; only those four .cs.target moved, no test-method churn.
  2. Full behavioral suite: 546/546 Transpile, 546/546 Target, C# Compile 0 failed — but its Output phase was never reached, and that is a machine story, not a result. Three consecutive full runs were killed externally, each truncated mid-run with no diagnostic (the signature CLAUDE.md documents for a sibling lane’s name-matched cleanup); one of them also hit CS8104/CS0016 “not enough space on the disk” in the one-shot batch while C: sat at 2.67 GB free, which the runner’s own per-project re-attribution then cleared to 0 failed. What the completed phases DO establish is the part that matters here: all 546 goldens byte-match, so emission is stable across two independent full re-transpiles (CNR’s and the suite’s). Output is covered where it can differ, by filtered runs that completed: the 27 projects spanning all four changed goldens — --filter Nil (25) and --filter FuncLitCapture (2) — pass all four phases, 25 Output-compared against go run, 0 failed. For the other 542 the generated C# is byte-identical to HEAD, and byte-identical generated C# ⟹ identical compile+run ⟹ identical results — the same reasoning that makes CNR the authoritative drift instrument.
  3. go test ./...: ok, exit 0 (44.9s), including the new guard and the projitems gate.
  4. Full go2cs-stdlib.slnx build: 0 errors (302 projects, 199s) on the overlaid corpus.

The one thing the rooting said to prove, PROVEN — and CNR alone could not have proven it. The recorded SET is identical: a paired seeded full-stdlib A/B (both roots seeded per the measurement-loop rules, single run each, seed gate clean at 39 marked files) puts 8,356 of 8,386 files byte-identical, and normalizing away the numeric suffix of the counter-driven generated identifiers makes the other 30 identical too — zero residual differences. Not one package_info.cs, .csproj or README.md moved anywhere in the corpus, which is exactly where a divergent recording would have surfaced (recorded conversions land in package_info.cs and drive ImplicitConvGenerator).

The 30 split two ways, both counter renumbering: 19 capture (ʗN, getCapturedVarName’s per-prefix counter), 10 type-switch temp (ᴛN, getGlobalTempVarName("switch")), one file both. The ᴛN counter is package-global, which is why all ten of its files are in go/types: ONE extra hoist in the discarded traversal shifts every later switchᴛN in the whole package. That half is a family CNR structurally cannot see — no behavioral project puts a side-effecting type-switch tag inside an argument subtree — so the stdlib A/B, not CNR, is the instrument that closes this class. Worth remembering for the next converter change that touches a traversal: CNR’s 571 small packages and the corpus’s 302 real ones fail in different places.

Corpus levelled in the same change: 29 files overlaid into src/core (+116/−116 lines, mechanically verified to be counter renumbering and nothing else), and the full stdlib solution rebuilt clean on top of them. runtime/mfinal.cs.auto also renumbered but is deliberately NOT overlaid — the standard overlay rule excludes *.cs.auto, and those siblings are levelled together as CleanupBacklog item 18.

Guard: TestNestedArgumentConversionIsNotExponential (nestedArgScaling_test.go) converts a 30-deep nested call under a 90s budget in a CHILD PROCESS — same plumbing as the chained-call guard, and for the same reason (the conversion cannot be cancelled, so an in-process regression would keep go test alive until the harness killed it) — then asserts every nesting level survived into the emitted C#, so it cannot pass by dropping the expression. Negative control against the pre-fix source: FAIL at 90.02s; with the fix, PASS at 0.85s. runWithinBudget’s timeout message is now generic, with each guard naming its own defect in the Fatalf that wraps it.

Reproduction fixture (depth N nested calls; N=22 is the row above):

package main

func f(x int) int { return x + 1 }

func main() {
	y := f(f(f(/* … N deep … */ 1)))
	println(y)
}

How to profile the converter — the recipe, and three traps that cost the sibling arc an hour

The chained-call arc was diagnosed by profiling a live, still-spinning converter. That is now a one-liner, and this section exists so the next session does not rebuild it:

GO2CS_PPROF=localhost:6060 go2cs -recurse ./app ./out       # off unless the var is set
go tool pprof -top -nodecount=35 http://localhost:6060/debug/pprof/profile?seconds=20
curl http://localhost:6060/debug/pprof/goroutine?debug=2    # every goroutine's stack

The endpoint is loopback-only by design (it serves goroutine stacks and heap contents); a bare :6060 is read as localhost and an explicitly non-loopback host is refused. See diagnosticProfiling.go.

Trap 1 — a -cpuprofile-style flag cannot see this class of bug at all. Those write on exit, and the failure mode is a run that never exits. Same blind spot for -memprofile. The live endpoint (or a stack dump on a timer) is the only thing that observes it.

Trap 2 — dlv attach KILLS its target. On a non-terminal stdin it exits with Stdin is not a terminal, use '-r' … and takes the process down with it, destroying a reproduction that took minutes to reach. If a debugger is genuinely wanted, pass --allow-non-terminal-interactive=true or run headless (--headless --listen) and connect separately — but the pprof endpoint above is the cheaper answer.

Trap 3 — Ctrl+Break does NOT dump goroutines on Windows. The Go runtime’s ctrlHandler maps CTRL_BREAK_EVENT to SIGINT and calls exit(2) when nothing is handling it; there is no SIGQUIT-style traceback the way there is on Unix. Do not plan a diagnosis around it.

Read the process before the source. The single most useful step in the sibling arc took seconds and no tooling: sample CPU and working set. CPU pegged + heap flat ⇒ exhaustive re-work (this family). 0% CPU ⇒ deadlock. Heap climbing ⇒ runaway allocation/recursion. Stack depth stable while CPU burns is the signature of re-walking a bounded tree, which is what separates an exponential from runaway recursion — and it rules out most of the field before a profiler is even attached.

Per-package wall time is now printed at the end of every -recurse run (Slowest N of M packages), so a superlinearity that has not yet become fatal is visible as an outlier rather than needing a reproduction to find.

CLOSED — the issue-#33 follow-up: the bsoncodec “hang” is an EXPONENTIAL, and it is fixed (2026-08-07)

The reporter re-ran with the three fixes in, cleared the crash, and hit a different wall: a -recurse run “hanging indefinitely” at [1440/1726] Converting go.mongodb.org/mongo-driver/bson/bsoncodec, over half an hour on one package. It is not a hang. It is (p+1)^N work, and the whole arc is measured.

Diagnosed from the process, not from the source. Reproduced locally in a 7-package closure (a scratch module importing bson/bsoncodec from go.mongodb.org/mongo-driver@v1.17.9): the other six packages convert in seconds, bsoncodec never finishes. The process is CPU-bound at ~1.5 cores with a FLAT 345 MB working set — which is what rules the field down to one answer before any code is read: not a deadlock (that is 0% CPU), not a leak (that grows). A CPU profile puts convCallExpr/convExpr at 66% cumulative, mutually recursive, the balance being GC of what they allocate; goroutine dumps show a stable ~40-deep convCallExpr → convExpr → convSelectorExpr → convExpr cycle that does not grow. Bounded depth with unbounded work is re-walking, not runaway recursion.

Root cause. A fluent chain nests LEFT, so each link’s callee IS the rest of the chain. convCallExpr’s argument classifier ran funcName := v.convExpr(callExpr.Fun, nil) inside for i := range params.Len() — a full conversion of the entire callee subtree on every iteration — purely to test whether the callee TEXT spelled print/println, and Phase 7 then converted it once more for real. A call with p parameters walked its callee p+1 times, which on a chain compounds to (p+1)^N. bsoncodec registers its default codecs as 42-link (encoders) and 63-link (decoders) rb.RegisterTypeEncoder(t, codec).… chains over a 2-parameter method: 3^42 ≈ 1.2e19 callee walks for one function.

The fix is to stop asking the question in text. callFunIsUniversePrint reads the name from the AST and is O(1). It agrees with the old form by construction: identIsUniverseBuiltin (ObjectOf(ident).(*types.Builtin)) already required a bare identifier resolving to Universe, and such an identifier’s name IS the built-in’s name — a shadowing declaration makes both forms false.

Rule this establishes, and it generalizes past this bug: never derive a predicate from CONVERTED TEXT when the AST or the type system answers it. Conversion is not a pure function of a node — it is a full subtree walk with side effects — so a text probe inside a loop is a hidden complexity multiplier, and on any LEFT-NESTING construct it is exponential rather than merely quadratic.

Paired A/B, idle machine, single-package conversion of a synthetic chain over a 2-parameter method (the bsoncodec shape); after is flat at the go/packages load floor:

links before after
12 4,375ms 1,902ms
16 >120s (killed) 2,014ms
20 >120s (killed) 2,342ms
24 >120s (killed) 1,912ms
42 >120s (killed) 1,974ms

And the reporter’s real shape: the bsoncodec closure converts 7/7 in 36.7s, the package’s 42-link chain emitted faithfully (all links, interface adapters and ж<T> boxes intact).

Gates — all green, and the arc is NOT emission-neutral, which CNR caught rather than argued.

  1. CNR: 2 of 569 changedDeferArgEnclosingCapture/main.cs and GoStmtValueReturn/main.cs, both a pure capture-variable RENUMBERING (doneʗ3doneʗ2, oʗ2oʗ1), declaration and uses renamed together. The discarded callee conversion had been bumping the capture counter as a side effect, so removing it closes a gap in the sequence. Verified collision-free (every …ʗN occurs exactly twice, properly nested) and then verified where it counts: both projects Compile pass and Output pass against go run. Goldens re-baselined with UpdateTestTargets --createTargetFiles; only those two .cs.target moved, no test-method churn.
  2. Full behavioral suite: 544/544 Transpile+Compile+Target, 514/514 Output, 0 failed (1,792.5s).
  3. go test ./...: ok, exit 0 (106s), including the new guard and the sibling lane’s projitems gate (the new source file is registered in go2cs-src.projitems, BOM and CRLF preserved).

Guard: TestChainedCallConversionIsNotExponential (chainedCallScaling_test.go) converts a 40-link chain over a 2-parameter method under a 90s budget in a CHILD PROCESS — the conversion cannot be cancelled, so a regression would otherwise leave a goroutine spinning and keep go test alive until the harness killed it minutes later — then asserts every link survived into the emitted C#, so it cannot pass by dropping the chain. Negative control against the pre-fix source: FAIL at 90.05s; with the fix, PASS at 1.6s.

Finding handed on — a SECOND exponential of the same class, on the ARGUMENT path

Not owned by this arc and not what the reporter hit. After rendering a call, convCallExpr re-walks every argument through checkForImplicitConversion — its own comment says it “re-converts each arg purely for its side-effects (recording implicit conversions); the result is discarded” — which is a second full conversion of each argument subtree, compounding to 2^depth on NESTED calls (f(f(f(…)))). Measured with the callee fix already in: nesting depth 18 → 3.4s, depth 22 → 24.9s. It did not block the reporter (argument nesting that deep is rare where 42-link fluent chains are not), and the recording is entirely type-drivenexpr flows only to the return value — so the durable fix is to split the recording from the rendering and let the discard-the-result call site skip convExpr entirely. Deliberately NOT folded in here: it is an independent change with its own emission-regression surface (this arc already moved two goldens), and entangling it with a one-line fix would cost the clean A/B. Banked as its own arc by user ruling (2026-08-07), and CLOSED the same day by r43a-argexp — see the section at the top of this board for the fix, the desktop A/B and the gates. Holding it back was the right call: it moved four behavioral goldens and 29 corpus files, none of which would have been separable inside the one-line callee fix.

OWED DISCHARGED — the issue-#33 arc is measured on Windows (2026-08-06, same day)

Every owed gate ran green, the 3a probe validated findings (b) and (c) end to end, and the probe paid for itself with a new finding — (d), below, FIXED and CLOSED the same day.

  1. CNR: NO REGRESSION — byte-identical across all 569, exit 0 (1,088s). go2cs.exe was rebuilt immediately before the run per this entry’s own route-#2 warning. This also discharges item 5 corpus-wide: no bare-LF line boundary surfaced anywhere under F3’s normalized split.
  2. Full behavioral suite: 544/544 Transpile+Compile+Target, 514/514 Output, 0 failed (3,508.5s under machine load). Honesty proven, not assumed: the exe was rebuilt after CNR’s transpile (20:17:47 > every CNR-refreshed .cs at 20:02:56), the suite re-transpiled all 544 (DeepEqual/main.cs → 20:21:13), and the tree was CLEAN after — emission unchanged, in agreement with CNR.
  3. go test ./...: ok, exit 0 (84.3s). The three new guards’ first Windows run: 3/3 PASS — TestModuleCacheVestigialReplaceLoad (1.17s, both sides of its fixture, so the control still reproduces on Windows), TestUntypedPackageConvertsWithoutPanic (1.73s), TestEscapeAnalysisPanicReachesCaller. The container’s nine Linux failures are absent here, as predicted. 3a. The otel probe ran, with finding (c) honored first: this box’s native toolchain is go1.23.2 — below (c)’s floor — so the probe converter was built with GOTOOLCHAIN=go1.25.0 into a scratch location (the repo’s binary stays the native build the gates measured). A module importing go.opentelemetry.io/otel@v1.44.0 (vestigial ./trace/./metric replaces confirmed present in the cached go.mod): closure 209 discovered, 25/25 converted (1 app + 24 third-party, 48.2s), zero invalid package name, zero newer Go version — (b)’s remedy and (c)’s guidance both hold on Windows, where the reporter hit them.
  4. Sweep waived by this entry’s own condition — 1–3 clean and byte-identical emission leaves no path into the banked suites.

The stray remote branch claude/recurse-option-diagnosis-cb1ins (fully contained in master) is deleted. The container’s original record follows.

(d) CLOSED 2026-08-06 — the build-constraint evaluator could not parse a Go release tag, and the !go1.21 “asymmetry” was never an asymmetry. Observed in the same otel probe: five github.com/go-logr/logr@v1.4.3 files gated //go:build go1.21 each warned failed to parse build constraint: 1:4: expected 'EOF', found .21, while the paired context_noslog.go (!go1.21) warned not at all and was correctly excluded — with an identical dual-line header, which made the two look like they were handled by different code paths.

Root cause. EvaluateConstraint ran the constraint through parser.ParseExpr, a Go expression parser, for which go1.21 is the identifier go1 followed by an illegal .21 selector. It fails on !go1.21 too — at 1:5 — so nothing diverged here. context_noslog.go produced no warning because go/packages had already excluded it upstream: it is absent from pkg.GoFiles and never reaches this code at all. Verified directly, both halves: ParseExpr errors on both forms, and a two-file probe module loaded through packages.Load returns only the go1.21 file.

Why it was not cosmetic. conversionDriver.go warns on a constraint error and falls THROUGH to including the file, so on those five files the wrong machinery reached the right answer. It is wrong the moment a constraint mixes a release tag with a platform: //go:build go1.21 && windows converted for linux lost its platform half along with the rest of the expression and was included. Two further defects fell out of the same layer — the regex scanner matched only //go:-prefixed lines, so a legacy // +build-only file (the norm in pre-1.17 third-party modules, which is exactly what -recurse meets) converted as unconstrained; and it scanned the WHOLE file, so a //go:build quoted in documentation below the package clause gated the file.

Fix. The hand-rolled parse/eval layer is gone, replaced by go/build/constraint — the package the toolchain itself uses. constraint.IsGoBuild/IsPlusBuild recognize the lines (column zero, header only, //go:build winning over +build as go/build orders them), constraint.Parse parses both syntaxes, and Expr.Eval drives a single matchTag callback that owns every tag class. Tag matching is now case-sensitive, as the toolchain matches; the old evaluator lowercased the whole expression, which quietly made a mixed-case -tags MyTag unsatisfiable.

One hazard this fix creates and closes in the same change, and it is finding (c) wearing a different hat. Release-tag evaluation was previously inert — it always errored — so activating it puts the compiled-in build.Default.ReleaseTags in charge of go1.N. Under GOTOOLCHAIN=auto that list is not the loader’s: go2cs.exe built with Go 1.23 converting a module that declares go 1.25 would call go1.24 false while go/packages called it true, dropping every file gated between the two along with the !go1.24 sibling the loader had already excluded — leaving the package with neither half. That configuration is not hypothetical; it is what this machine had (converter built go1.23.2, otel probe loading under go1.25.0). Over-exclusion is this evaluator’s recurring failure mode — the purego seeding and the goexperiment ToolTags branch both exist to undo one — and it is the dangerous direction, because the loader has already applied the full constraint for the target platform, so anything this pass subtracts is real code. Release tags are therefore resolved by asking the go command (go env GOVERSION from the same directory packages.Load uses), cached per module root so a -stdlib run pays one ~300 ms lookup rather than 302. Note this does not retire (c): the linked-in type checker is still whatever release compiled go2cs, and no toolchain switch reaches it. Build go2cs with a toolchain at least as new as the closure’s newest go directive regardless.

Guarded by src/go2cs/buildConstraints_test.go — release tags bare/negated/compound, the legacy +build grammar, extraction precedence, and the loader-toolchain resolution. Verified against the pre-fix converter rather than assumed: every new assertion fails on it, including the two the fix was not looking for (the legacy-only file and the documentation-gated file).

For the next local (Windows) session: master carries the issue-#33 arc in three commits — fe9bec0 (the package_info.cs EOL-agnostic read-back, Linux finding F3), 6ca9565 (the panic fix itself), and the main-module load shape for module-cache packages that closes finding (b) below — posted directly to master under the same standing ruling as the issue-#32 entry, and for the same reason: a remote Linux container where the .NET/PowerShell gates cannot run, so the arc ships with converter-level evidence only. All three are emission-neutral for the corpus, and that is measured rather than argued: 569/569 behavioral packages transpile byte-identically to the converter that predates each change.

What the reporter hit. A -recurse conversion of renart died at [736/1726] on a nil dereference at escapeAnalysisOperations.go:739, discarding ~1,000 packages of queued work; reported again under -recurse=module, where it lands at [33/44] on the app’s own packages.

Root cause, two independent halves — the second is the one that mattered.

  1. The dereference. go/types records no type at all for an expression whose operand went invalid (Checker.record returns early for mode == invalid), so types.Info.TypeOf returns a nil interface — not Typ[Invalid]. The reported crash is TypeOf(call.Fun).Underlying() for an address-taken argument of a call to an undefined function. The addr=0x20 in the pasted trace is the itab’s fun[1] slot, which is what distinguishes nil-interface from typed-nil. Reproduced in six lines of Go, same file, same line, same fault address.
  2. The containment hole. ModuleConverter.convertAll and StdLibConverter.convertPackage each already wrap a conversion in recover so one unconvertible package fails alone — and performEscapeAnalysis runs its files in goroutines, where a panic unwinds only its own stack. Every fault raised on that side of the go statement was unrecoverable by anyone. That is what turned a one-package defect into a dead run. Workers now capture the first panic with debug.Stack() (before the frame is lost, so the report still names the faulting converter line rather than the re-raise site) and re-panic after Wait.

Rule this establishes, and it generalizes past this bug: any pass that spawns goroutines must re-raise a worker panic on the caller’s goroutine, or the per-package containment both batch drivers depend on is silently void. Written up under Packages That Do Not Type-Check in ConversionStrategies-Reference.md, with the underlyingOf() convention for any type reached through TypeOf/getType on an arbitrary source expression.

What the container DID establish. All 569 behavioral packages re-transpiled twice — once with the converter that predates the arc, once with the fix — and the output is byte-identical everywhere except two Windows-only packages, UnsafeStringEmpty and FindFirstFileData, which do not type-check on Linux (syscall.UTF16ToString). Those are the in-repo proof rather than an exception: the old converter dropped UnsafeStringEmpty/main.go entirely through the per-file recover, and the fixed converter emits a main.cs matching the committed Windows golden byte-for-byte modulo CRLF. The converter’s own go test ./... failure set is identical with and without the arc (isolated by re-running with only the F3 commit applied) — nine failures, all pre-existing Linux path-separator/CRLF findings, none in these paths.

Owed, in order (budgets from the CLAUDE.md table):

  1. ./src/tests/Behavioral/check-no-regression.ps1 — timeout 700s. Expect byte-identical. Both commits are no-ops on Windows by construction: F3’s read path only differs on an LF file (autocrlf gives CRLF working trees), and the #33 guards only fire on a package that does not type-check — the behavioral corpus has none on Windows. ⚠ Re-run go build -o bin\go2cs.exe first: a git checkout restore refreshes every .cs mtime and re-arms false-green route #2, exactly as the issue-#32 entry records.
  2. ./src/tests/Behavioral/run-behavioral.ps1 (full) — timeout 2100s. Expect 544/544 + 514/514.
  3. go test ./... from src/go2cs — expect ok, exit 0, including the three new guards (TestUntypedPackageConvertsWithoutPanic, TestEscapeAnalysisPanicReachesCaller, TestModuleCacheVestigialReplaceLoad) and the seven pre-existing recurse tests that the Linux container cannot pass. The third guard asserts both sides from one fixture, so a Windows pass also confirms the control still reproduces there. 3a. Worth doing once, and it is not a gate: a real -recurse run against a module that depends on go.opentelemetry.io/otel@v1.44.0 (or any monorepo-layout module with relative replaces) — the container measured 2 → 0 invalid package name failures, and Windows is where the reporter hit it. Build the converter with Go 1.25 or newer first, per finding (c).
  4. ./src/run-validated-sweep.ps1 only if 1–3 surface anything — byte-identical emission leaves no path into the banked suites otherwise.
  5. One Windows-specific risk worth a look, not a gate: F3 now splits a read-back package_info.cs on normalized \n. A file containing a bare LF inside a line was previously kept as part of that line and is now a line boundary. Converter-written files are CRLF throughout and autocrlf normalizes on checkout, so this should be unreachable — CNR clean in step 1 confirms it across all 569.

Findings for follow-up, neither owned by this arc

(a) F3 was masking the Linux F5 failures. With the read-back seam fixed, the converter’s go test ./... on Linux runs to completion for the first time and surfaces nine failures. That is not a regression: the old binary log.Fataled inside the first processConversion and ended the whole test binary, so most of the suite never ran and the truncated output read as two failures. Seven of the nine are F5 (Linux filepath.Join does not normalize the \ the code injects — $(go2csPath)core\fmt/\fmt.csproj) and two are the CRLF-template tests. All nine are unchanged with the #33 arc removed. Recorded here because the count of Linux failures moved for a benign reason, and the next Linux session should not read it as drift. F5 remains Arc 2 of PLAN-linux-operation.md, untouched.

(b) ROOTED, REPRODUCED, and FIXED — the reporter’s invalid package name: "" was the issue-#32 family, one directive over. Reproduced end-to-end the same session against the reporter’s own dependency (go.opentelemetry.io/otel@v1.44.0), so this is measured, not argued. The hypothesis first written here — “the standalone module-cache load is a weaker context” — is confirmed in mechanism and wrong in detail: it has nothing to do with MVS version selection or the app’s own replace directives.

The mechanism. otel@v1.44.0/go.mod carries the monorepo’s own relative replaces:

replace go.opentelemetry.io/otel/trace  => ./trace
replace go.opentelemetry.io/otel/metric => ./metric

Valid in the otel source repo, where those are sibling directories. The published module zip excludes themtrace and metric are separate modules — so in the cache ./trace does not exist. A replace is honored only in the main module, and processConversion loading a package with Dir inside the cache is exactly what promotes that dependency’s go.mod to main-module status. The go command then says replacement directory ./trace does not exist, otel/trace never loads, its types.Package stays empty-named, and go/types reports could not import go.opentelemetry.io/otel/trace (invalid package name: "") at every use site. Same root as issue #32 — a module-cache directory is not a main module — and GOWORK=off cannot reach it, because replace is not a workspace feature.

The three-way probe (packages.Load, LoadAllSyntax, run under go1.25 so the language-version noise below is out of the picture):

Load shape Result
ADir = the cache dir, pattern = that dir (what processConversion does) could not import go.opentelemetry.io/otel/trace (invalid package name: "") — the reporter’s error verbatim
BDir = the app module, pattern = the import path 0 errors. The dependency’s replaces are ignored, as a non-main module’s must be
CDir = otel/trace@v1.44.0’s own cache dir 3 further failures from its vestigial replace go.opentelemetry.io/otel => ../

Blast radius, measured: 189 of the 244 packages in the otel module zip import otel/trace or otel/metric, so all 189 lose their types under load shape A. This is not an otel quirk — it is every monorepo-layout module that carries relative replaces, which is the common shape for a multi-module Go repo.

The remedy is validated, not sketched: for a package under GOMODCACHE, load it from the main module’s directory by import path (shape B) instead of standalone by directory. That also makes the issue-#32 GOWORK=off gate redundant for third-party packages — the go command never enters the dependency’s directory, so a vestigial go.work is not read either — though the gate should stay for the non-recurse paths. ModuleConverter has both inputs already (pkgPath and the main module dir); processConversion takes a directory, so the import path needs plumbing through. Worth weighing at the same time: this is also 1,726 separate packages.Load invocations, the dominant cost of a recurse run, against a closure loadClosure already type-checked correctly in one pass.

LANDED 2026-08-07 (commit on master; design detail in DESIGN-recursive-enduser-conversion.md, The same seam, one directive over). A module-cache package is loaded from the main module’s directory by import path whenever the run is -recurse and both inputs are known; every other load keeps the directory shape and the GOWORK=off gate with it. Measured: the otel -recurse run goes 2 → 0 invalid package name failures, and with the converter rebuilt under Go 1.25 the closure converts 14/14 with no warning of any kind. All 569 behavioral packages transpile byte-identically to the converter that predates the change — expected, since no behavioral package is under GOMODCACHE, and measured rather than assumed. Guarded by TestModuleCacheVestigialReplaceLoad, which asserts BOTH sides from one fixture so the guard cannot pass vacuously. The converter’s own suite has the same nine pre-existing Linux failures, unchanged.

Deliberately LEFT UNDONE — the one item this arc hands forward. Closure reuse: loadClosure already type-checks the whole graph in one pass in the main module’s context, and now that each package is loaded from that same context anyway, the per-package reload is re-deriving in 1,726 separate packages.Load invocations what one pass already had. That is the dominant cost of a recurse run. It was not folded into this fix because it is a pipeline-shape decision rather than a bug fix, and because it has to respect -recurse=module, which deliberately skips the full-closure type-check precisely so an unconvertible dependency graph cannot block the app’s own code (issue #32’s mode). Rooted and ready; wants a measurement of the real saving before it is worth the risk.

(c) A second, independent finding from the same reproduction — the converter cannot type-check a module whose go directive exceeds the Go release go2cs was BUILT with. otel@v1.44.0 declares go 1.25.0; a go2cs built with go1.24 reports package requires newer Go version go1.25 (application built with go1.24) and every downstream expression goes untyped. The go command switches toolchains automatically (GOTOOLCHAIN=auto), but the type checker go2cs links in is whatever release compiled it, and no toolchain switch reaches that. This is invisible until a dependency adopts a new language version, then it silently degrades whole packages. Two things follow: build go2cs with a toolchain at least as new as the newest go directive in any closure it is asked to convert, and consider making the converter say so by name rather than letting it read as an ordinary type error. Independent of (b) — it reproduced on both load shapes and disappeared on both when the probe was re-run under go1.25, and confirmed a third time end to end: rebuilding the converter itself with Go 1.25 is what took the otel -recurse run from “14/14 with 13 best-effort warnings” to “14/14, silent”. Left as guidance, not code — the honest fix is a build requirement, and the optional refinement (naming this condition in the diagnostic instead of letting it read as an ordinary type error) is a small, separate change nobody is blocked on.

OWED DISCHARGED — the issue-#32 go.work fix is measured on Windows (2026-08-06, same day)

Every owed gate ran; the change is clean, and its emission-neutrality is proved against the converter that predates it rather than argued from the diff.

  1. CNR — the gate could not use the committed corpus as its reference, so it was run in a stronger form. A plain CNR reported drift under both candidate roots, in opposite directions and on disjoint file sets (4 files vs 12) — a pre-existing condition of the committed package_info.cs corpus that has nothing to do with this change; it gets its own entry below. The gate’s actual question was therefore answered converter-vs-converter: every one of the 569 behavioral packages transpiled twice in one environment, once with master’s converter and once with a converter built from c57f1a878 (the commit before this arc), hashing all 1,176 generated .cs. Manifests byte-identical (A8E0B75B…C15EC80 both sides), 0 transpile failures. The change is emission-neutral across the whole corpus, which is what “expect byte-identical” was for.
  2. Full behavioral suite: 544/544 Transpile+Compile+Target, 514/514 output comparisons, 0 failed, 30 skipped (2,124.3s). ⚠ The FIRST attempt was a false green by documented route #2 and is recorded because the trap is easy to re-enter: restoring the tree with git checkout refreshes every .cs mtime, so UpToDate’s csTime <= exe guard sees fresh output, Transpile is skipped for all 544, and the suite validates the committed .cs instead of the converter’s. The guard is sound — a checkout defeats it, not a converter rebuild. Re-running go build -o bin\go2cs.exe before the suite makes the exe newest again and forces the real pass; confirm it ran by checking that the transpile left the tree dirty.
  3. The recurse guards’ first Windows run: all 7 PASS (14.7s) — TestModuleCachePoisonedGoWorkLoad 0.65s, TestRecurseModuleOnly, TestRecurseSyntheticModule, TestRecurseNuGetReferences, TestRecurseNuGetResolvesForeignImplements, TestRecurseLinknameForwarder, TestRecurseModeFlag. Full go test ./... is ok, exit 0 — the container’s 7 “failures” were Linux-path artifacts, as it predicted. Additionally measured, because the committed guard pins goModCache directly and so never exercises the real Windows resolution: goModCacheDir() resolves through go env to C:\Users\rcarroll\go\pkg\mod (the GOMODCACHE env var is unset here, so the second fallback is the live path), and isPathUnder classifies correctly against a real cache path — case-insensitive in both directions, separator-agnostic, root-inclusive, and not fooled by the sibling-prefix trap …\pkg\mod-notthecache. The gate fires on Windows.
  4. Sweep waived by this entry’s own condition. Items 1–3 surfaced nothing attributable to the change, and byte-identical emission leaves no path into the banked suites. The corpus finding below is confined to behavioral package_info.cs files and touches neither src/core nor any banked suite.
  5. The eol=crlf pin is invisible on this clone, positively. git check-attr eol reports crlf for all three templates, all three are fully CRLF on disk, and git status stayed clean across the pull — the expected outcome, verified rather than assumed.

The container’s original record follows, kept for its diagnosis.

For the next local (Windows) session: master carries the second issue-#32 arc — commit 121c61d (the GOWORK=off fix + its guard) and 0267629 (the template eol=crlf pin), the diagnosis of the reporter’s pasted -recurse failure log (the Renart project) and its fix, posted directly to master per user ruling 2026-08-06. Same posture as the d00cac5 entry below, same reason: a remote Linux container where the standing gates cannot run, so the change ships with unit-level evidence only.

What was found (full write-up: DESIGN-recursive-enduser-conversion.md, Module-cache loads and the vestigial go.work): the reporter’s abort was their pre-d00cac5 binary (the fatal load path this board’s discharged entry below already measured), but underneath it sits a real, still-current loss — cloud.google.com/go’s module zip ships the monorepo’s go.work, and processConversion’s reload, running the go command from inside the module cache, enters workspace mode and fails every package of that root module (“cannot load module ../accessapproval listed in go.work file”). The fix appends GOWORK=off to the loader env only when the input dir is under GOMODCACHE; ambient workspace behavior is preserved everywhere else. A second commit pins the three embedded converter templates eol=crlf in .gitattributes — the checkout-level discharge of the CRLF seam the entry below recorded as recorded-not-owed (an LF checkout’s converter log.Fataled on every conversion; the "\r\n"-splitting code seam itself is unchanged).

What the container DID establish: TestModuleCachePoisonedGoWorkLoad (new, network-free, both sides of the gate) passes; the full go test ./... failure set is identical to baseline (the same 7 pre-existing Windows-path tests, nothing new — measured with-fix vs. master on the same box); an end-to-end repro (a module importing cloud.google.com/go/civil@v0.123.0) goes from 1/2 converted (civil failed) to 2/2 converted with the emitted civil.cs/csproj/slnx spot-checked.

Owed, in order (budgets from the CLAUDE.md table) — the d00cac5 pattern verbatim:

  1. ./src/tests/Behavioral/check-no-regression.ps1 — timeout 700s. Expect byte-identical: the change is an env-var gate on a -recurse-only load path plus a checkout attribute; no emission logic moved.
  2. ./src/tests/Behavioral/run-behavioral.ps1 (full) — timeout 2100s. Expect 544/544 + 514/514.
  3. go test -run 'TestRecurse|TestModuleCachePoisonedGoWorkLoad' ./ from src/go2cs — the new guard’s first Windows run.
  4. ./src/run-validated-sweep.ps1 only if 1–3 surface anything (no path into the banked suites otherwise).
  5. ⚠ The eol=crlf pin takes effect on checkout — existing Windows clones already have CRLF working trees via autocrlf, so expect no visible change there; a git status after pulling the attribute commit should stay clean for the three templates. If it does not, that is a finding.

Open CLOSED — CNR’s verdict no longer moves with an ambient variable, and the split package_info.cs corpus is normalized (2026-08-06, same day)

All three steps landed, in the order this entry insisted on — pin the root, make an unusable one loud, then normalize — because normalizing against a root no gate enforces would only have re-split the corpus on the next machine. Commits 826b7e486 (the mechanism) and 9859dd993 (the data), kept apart so each is reviewable on its own.

  1. Every seam names its root, computed from its own location. Five invocation sites, from a tree-wide sweep for go2cs.exe: check-no-regression.ps1 (a $PSScriptRoot walk), BehavioralRunner and PerformanceRunner (an AppContext.BaseDirectory walk, s_srcRoot), MSTest BehavioralTestBase (a new Go2csRoot, resolved in Init before its up-to-date early return and with the trailing separator trimmed — a backslash before a closing quote escapes it on a Windows command line), and run-validated-sweep.ps1. The sweep was not on the list above and is pinned deliberately: a -tests run self-locates only when the ambient root is invalid, so a GO2CSPATH aimed at some other real go2cs tree — a deploy-core staging root — would still have been honored, building a suite against one tree’s metadata while compiling the other’s sources. Confirmed first that -go2cspath cannot move WHERE single-package output is written (it feeds only getImportPackageInfo’s TargetDir substitution; outputFilePath is untouched): DeepEqual transpiled with the flag lands in place and leaves the tree clean, without it the same command drops its five reflect aliases.
  2. The converter recovers, and says so when it cannot. resolveGo2CSPath (commandLineOptions.go) now runs for every single-package conversion, not just -tests: when the configured root is not a go2cs root (no core\golib\golib.csproj) it walks the ancestors of the conversion’s OUTPUT path for one. Output, not input, is the anchor — the emitted package_info.cs/.csproj and their $(go2csPath)core references live there, so the tree that must satisfy them is the tree the output is written into; where the two differ (converting GOROOT sources into a repository tree) the input walks the wrong chain entirely, and where they are the same directory — the bare go2cs <pkg-dir> — it is exactly what makes an unconfigured run inside a clone resolve against that clone. An explicitly configured working root still wins. Found nothing, the run proceeds (standalone conversion with no deployed runtime is legitimate) but emits ONE prominent stderr warning naming the resolved path and both consequences. -recurse warns but never self-locates (its root doubles as the output root without a second positional, so moving it would move the generated tree); -recurse=nuget does neither; -stdlib does neither, because there the root is the output root the run itself populates and an absent golib is the normal state of a first conversion. Guarded by TestResolveGo2CSPathSelfLocation / TestResolveGo2CSPathUnusableRootWarns — network-free, both sides, the real stderr captured through an os.Stderr swap rather than a stand-in, with the precedence rule and both suppressions pinned; the once-per-run warning latch is package-level and test-pinnable in the goModCache manner.
  3. Exactly the twelve, exactly the predicted direction. The now-deterministic CNR reported precisely the twelve files this entry named, all pure additions — 46 lines, 0 removals: 28 time, 10 os (the io/fs re-exports FileInfo/FileMode/DirEntry/PathError plus os.Signal), 5 syscall, 2 encoding/json, 1 runtime. Banked alone in 9859dd993.

Gates, all green. go test ./... from src/go2cs: ok, exit 0 (103.3s cold), including the two new guards. check-no-regression after the normalization commit: NO REGRESSION — byte-identical across all 569 behavioral packages, exit 0 (917.5s; the pre-normalization run was 864.2s and reported the twelve). Full behavioral suite: 544/544 Transpile, Compile and Target, 514/514 output comparisons, 0 failed, 30 skipped — PASS in 2,453.3s. Both long runs sat above the CLAUDE.md budgets because a sibling worktree was active; forward progress was confirmed by watching the transpile advance alphabetically, not assumed. The MSTest seam was spot-checked separately (--filter DeepEqual, 4/4).

The false-green trap was avoided by construction, and the avoidance was measured. CNR’s own transpile leaves every .cs newer than go2cs.exe, which is precisely the state that makes UpToDate’s csTime <= exe guard skip Transpile for all 544. go2cs.exe was rebuilt immediately before the suite and the skip was disproved with mtimes, not assumed: DeepEqual/main.cs moved 15:14:26Z → 15:27:19Z, strictly newer than the exe at 15:24:43Z. A clean tree after a CONFIRMED transpile is the pass condition, and that is what this run produced.

DECISION (the coordinator’s, recorded and not relitigated): package_info.cs gets NO .cs.target golden. CNR is deterministic now and is the standing converter gate; 569 new golden files is disproportionate footprint for a line CNR already holds.

Left alone, recorded rather than fixed: deploy-core.ps1 still stages to %GOPATH%\src\go2cs while the converter’s -go2cspath defaults to ~/go2cs, so running the documented deploy does not populate the root a flagless converter run reads. That divergence no longer costs anything — every gate names its root, and a bare run self-locates or says why it cannot — so the two roots stay as they are rather than being unified in this arc.

OWED DISCHARGED — the issue-#32 -recurse change is now measured on Windows (2026-08-05, same day)

All four gates ran or were legitimately waived; the change is clean. (1) check-no-regression: byte-identical across all 569 behavioral packages — the entry’s highest-stakes expectation held exactly. (2) Full behavioral suite: 544/544 Transpile+Compile+Target, 514/514 output comparisons, 0 failed (1,092.5s). (3) The recurse tests’ first real Windows run: TestRecurseModuleOnly PASS (0.81s — the Windows-path assertion that could only fail-on-Linux now actually exercises), TestRecurseSyntheticModule PASS, TestModuleConverterPartitionScope both scopes PASS; full go test ./... ok with nothing new failing. (4) The sweep was waived by this entry’s own condition — 1–3 clean and byte-identical emission leaves no path into the banked suites. The stray remote branch claude/go2cs-issue-32-5osg4q is deleted. The container’s original record follows, kept for its observations (the CRLF-coupled packageInfoWriter seam remains recorded-not-owed).

For the next local (Windows) session: commit d00cac5issue #32, -recurse=module plus the load-failure fix — was authored and pushed from a remote Linux container, where the standing gates cannot run. It is on master with unit-level evidence only. Nothing about it is suspected; it is simply unmeasured against the corpus, and that is the whole point of this entry.

What the container could not do, and why it is not a converter defect:

What WAS established, so the re-check knows what to expect:

Owed, in order (budgets from the CLAUDE.md table):

  1. ./src/tests/Behavioral/check-no-regression.ps1 — timeout 700s. Expect byte-identical: the change touches only error paths and -recurse-scoped branches, and no emission logic. A non-empty git status here is a real finding and outranks everything else in this entry.
  2. ./src/tests/Behavioral/run-behavioral.ps1 (full, 4 phases) — timeout 2100s. Expect 544/544 + 514/514 output comparisons.
  3. The three recurse integration tests on Windows — go test -run 'TestRecurse' ./ from src/go2cs — which is the FIRST real run TestRecurseModuleOnly will get.
  4. ./src/run-validated-sweep.ps1 (backgrounded, 46–53 min) only if 1–3 surface anything; a converter change confined to the recurse driver has no path to the banked suites, so a clean 1–2 discharges this item without it.

Also owed, trivially: delete the remote branch claude/go2cs-issue-32-5osg4q. It is fully contained in master (both point at d00cac5) and the local copy is gone, but the remote one could not be deleted from the container — the session’s git proxy rejects ref-deletion pushes (send-pack: unexpected disconnect, twice, for both --delete and :branch forms), and the GitHub MCP surface here has no delete-branch tool. One git push origin --delete claude/go2cs-issue-32-5osg4q locally, or the button on GitHub.

LANDED — the GoFrame arc (2026-08-05), and what it leaves behind

The frame is built. DESIGN-closure-emission.md §4 is now the AS-BUILT record; §4.10 carries the findings and §4.11 the bang verdict. Landed in five gated checkpoints along §4.8’s path — golib frame, declarations with unnamed results, recover + named results, function literals, then the rename and the deletion of the machinery it replaced — each with its own full behavioral gate.

Verdicts and findings, in one place:

The measured result. os.File.WriteString — the row that named the 440 B term in the first place — goes 2,736 → 2,368 B/call, the same 368 B coming off os.File.Write and off the wrapper band that contains internal/poll.FD.Write’s two defers. Per-shape: the execution context cost 160 B with no defers and 248 B with one or two non-capturing ones; the frame costs 0.

The arc’s original commission (user rulings 2026-08-05), and two tasks it queues

The closure-emission frame design is APPROVED (DESIGN-closure-emission.md §4–§5): the execution-context lambda gives way to the ref struct frame with the body emitted inline in try/catch/finally. The user’s context, recorded because it shapes the work: the lambda form was chosen for visual parity and was long suspected of a capture-semantics divergence class (the lambda captures variables the original Go never captured); the frame form removes that class by construction and the allocation cost was never weighed. One ruling amends the design:

Arc mechanics: lands with its OWN corpus regen (post-r40 doctrine — the corpus stays level with its converter; no new standing-drift era), full gate battery including the sweep (the banked alloc rows are the design’s own motivation), and per-stage checkpoint commits along §4.8’s migration path.

Queued task 1 — the documentation-reality pass (dedicated sub-agent, AFTER the arc lands). The frame changes every deferred function’s emitted shape: ConversionStrategies.md and ConversionStrategies-Reference.md examples, and any doc quoting the lambda form, must be brought to match reality. Style ruling: present tense, educating a new reader — no history in the teaching docs; posterity lives in the design doc.

Queued task 2 — the [GoTestMatchingConsoleOutput] audit — CLOSED (r41b-outputattr, 2026-08-07). Before core/fmt was real, some behavioral tests skipped output-matching because the stub could not format their output. Measured 2026-08-05: 14 projects had package main but no attribute. Each was run via go run . (5+ repetitions per project, comparing stdout/exit-code across runs) to classify as GRADUATE (deterministic stdout, exit 0), DELIBERATE-SKIP (nondeterministic or panic/deadlock by design), or FIXABLE-MISMATCH (deterministic Go output, but the transpiled C# currently diverges). 4 of 14 graduated and are now output-compared (run-behavioral.ps1 --filter <Name>, all four phases green); one attempted graduate uncovered a genuine converter bug and was left un-annotated, reported below as a new board candidate:

Project Verdict Reason
ChannelReceiveFromNil DELIBERATE-SKIP <- on a nil channel — Go’s deadlock detector fires (fatal error: all goroutines are asleep - deadlock!), zero stdout, exit code 2. The message carries a goroutine stack trace (addresses/line offsets); a managed re-implementation cannot be expected to reproduce it byte-for-byte, and there is nothing on stdout to compare regardless.
ChannelSendToClosed DELIBERATE-SKIP Ten goroutines race to send on / close the same buffered channel with no synchronization — a deliberately racy program. Repeated go run showed both the count of values printed before the panic (0 vs 10 observed) and which goroutine panics vary between runs; output is provably nondeterministic.
ChannelSendToNil DELIBERATE-SKIP c <- v on a nil channel — same deadlock-detector shape as ChannelReceiveFromNil (zero stdout, exit code 2, non-reproducible stack trace).
DeferSimple GRADUATED Deterministic 3-line stdout (Open file / Write data to file / Close file), exit 0, confirmed across 5 runs. run-behavioral.ps1 --filter DeferSimple — 4/4 phases PASS (48.2s).
ForVariants DELIBERATE-SKIP Spawns unsynchronized goroutines (go fmt.Println(...)) whose print ordering interleaves with the main goroutine’s loop output. Two consecutive go run invocations produced different line orderings/content, confirming scheduler-dependent nondeterminism.
GoCallVariations DELIBERATE-SKIP Exercises ~8 different go-statement call shapes (bare func, closure, method value, function-returning-function, etc.) with no synchronization between them; two consecutive runs printed the same lines in different relative order — nondeterministic by design (that’s the point of the test).
InferredForeignTypeNoImport GRADUATED Deterministic 2-line stdout (true / 5), exit 0, confirmed across 5 runs. run-behavioral.ps1 --filter InferredForeignTypeNoImport — 4/4 phases PASS (19.6s).
InterfaceInheritance GRADUATED Deterministic 2-line stdout (two map[:N :M] lines — Go’s fmt sorts map keys since 1.12, so the single-key-per-map output is stable), exit 0, confirmed across 5 runs. run-behavioral.ps1 --filter InterfaceInheritance — 4/4 phases PASS (15.5s), proving the transpiled map-print ordering matches too.
PointerCastSliceRange GRADUATED Deterministic single-line stdout (6 100 11), exit 0, confirmed across 5 runs. run-behavioral.ps1 --filter PointerCastSliceRange — 4/4 phases PASS (17.9s).
RangePointerArrayConversion FIXABLE-MISMATCH (new board candidate) Go’s stdout is deterministic (63, exit 0, confirmed across 5 runs) — a graduate by the audit’s own criterion — but the transpiled C# prints 0. Root cause is visible in the emitted code: for i, x := range (*[3]int)(p) (p := unsafe.Pointer(&a)) converts to foreach (var (i, x) in ((ж<array<nint>>)(uintptr)(p)).Value) — the round-trip through uintptr cannot recover the original managed box Ꮡa, so the cast yields a fresh/default array<nint> and the loop sums over zero elements instead of {10,20,30}. This is the same “unsafe.Pointer reinterpret via raw address” limitation already load-bearing in the neighboring UnsafePointerReinterpret test’s own design comment (that test deliberately stays Compile+Target-only for exactly this reason). The attribute was NOT added — adding it would redden the Output phase — so this project is left exactly as measured (no diff). Candidate fix belongs with whichever arc next touches unsafe.Pointer reinterpret-cast codegen (see ж<T>/ boxing notes); until then this stays a known, deliberate non-graduate for a different reason than the other nine (a real bug, not an inherent nondeterminism).
SelectStatement DELIBERATE-SKIP Go’s select deliberately pseudo-randomizes among multiple ready cases. Two consecutive runs showed different orderings/values (OK: true -- got: 12 at a different line position; final tuple 17 -5 12 3 vs 3 17 20 -5) — confirmed nondeterministic.
StructWithPointer DELIBERATE-SKIP stdout embeds a raw pointer address (Value of red = {2 red 0xc0...}); 5 repeated runs showed two distinct addresses (0xc000028180, 0xc00010a150) recurring at random. A memory address can never be expected to match between the Go runtime’s allocator and the CLR’s, so this can never be a stable golden regardless of transpile correctness.
TypeConversionReturnType DELIBERATE-SKIP Same shape as StructWithPointer — stdout embeds two raw pointer addresses ({Go 0xc0... 0xc0... map[]}) that varied across all 5 runs. Not stable in Go itself, so not a candidate for a byte-exact golden.
UnsafePointerReinterpret DELIBERATE-SKIP Explicitly documented in its own source comment as “a Compile + Target (golden byte-comparison) test, NOT an output-comparison test” — it uses println (Go builtin, writes to stderr) rather than fmt.Println, and exercises the same raw-address unsafe.Pointer reinterpret limitation that RangePointerArrayConversion’s mismatch surfaces at runtime.

Net: 4 graduated (DeferSimple, InferredForeignTypeNoImport, InterfaceInheritance, PointerCastSliceRange), 9 deliberate-skips (documented above, each for a distinct concrete reason — deadlock detection, goroutine-scheduling nondeterminism, select randomization, or raw pointer-address non-reproducibility), 1 new FIXABLE-MISMATCH board candidate (RangePointerArrayConversion — unsafe.Pointer reinterpret-cast through a raw uintptr round-trip loses the original managed box). Change footprint: 4 one-line [GoTestMatchingConsoleOutput] additions to package_info.cs + the UpdateTestTargets-generated OutputComparisonTests.cs block (4 new Check<Name>() methods) — no golden re-baselining needed (no emission changed), no go2cs.slnx registration changes (all 14 projects were already registered).

The -tests reference-closure family — CLOSED (2026-07-27)

DisableTransitiveProjectReferences=true means the generated test project lists only the imports the converter computed, so any package named by a type the test code merely touches is missing and the build fails with CS0012. crypto/hmac was the first case solved (interface embedding); the closure is now generalized to the declaration edges of the types the compilation names (declarationClosureImports), covering both an interface’s bases and a struct’s field types. Full rule, minimality gates and guards: docs/ConversionStrategies-Reference.md, Reference closure (the declaration-edge rule).

Package Missing type Outcome
image/draw rand_package.Rand build unblocked — a struct field of quick.Config reached at an element-bearing composite literal. Now validated 9/9 (2026-07-31), once the two runtime defects below were fixed.
io io_package.Writer NOT a closure defect — see the next section. Adding the reference cannot fix it.

Minimality is the hard part, and it is measured, not asserted. Regenerating every banked package’s .tests.csproj and diffing is the instrument, and it rejected three looser rules before the landed one. Seeding from every file rather than the compiled ones drifted compress/gzip (context, crypto/tls, mime/multipart, net/http, net/url — reached through http.Request’s fields, from a Phase-4D-excluded example_test.go that is never compiled) and go/token (go/ast); firing the struct edge on any value use drifted eleven more (sync.Once, sync.Map, reflect.Value); firing it on an unscoped empty literal still drifted three (mime, testing/quick, encoding/binary), because an empty Go literal converts to new Δsync.Once(nil) — go2cs-gen’s nil constructor, which names no field, and whose FIELDWISE overload is internal and so not even a candidate outside the declaring assembly. Each of those gates drifts zero banked packages. The one edge that is deliberately not zero is the root-scoped empty literal, re-measured at the 63-package roster on 2026-07-31: it changes exactly one project by exactly one line (math/rand/v2 gains internal.chacha8rand.csproj) — the root set itself, with all three foreign-struct negatives byte-identical.

Run that probe with the converter’s exit status checked. A conversion that fails writes no csproj, so an ignored failure reads exactly like “no drift” — a false-clean of the same family as charter §9’s false-green traps. That is how a real defect in the first cut hid through three measurement rounds: a struct literal declared in the EXTERNAL test variant reached reach(<pkg>_test), a synthetic path that resolves to no importable package, and every affected package died with F14b’s resolve test project dependency "bytes_test": package bytes_test is not in std — silently, until the validated sweep failed on bytes at the second package.

io — duplicate-type build blocker CLOSED (landed on master 2026-07-31); runtime blockers remain

The diagnosis was correct: recompiling io into its mixed internal/external test assembly created a second io_package.Writer, distinct from the one named by hash.Hash, bytes, fmt, and the rest of the referenced closure. The general fix is the new whitebox-reference test-project model. A production package with build-selected same-package tests conditionally grants friend access to <assembly>.tests; internal _test.go declarations emit into <name>_internal_test_package; external references to those declarations route to the bridge by go/types.Object identity; and test-contributed adapters live in the test metadata anchor. Production remains the only identity for its types. Records that truly require a production-type mutation still fall back to recompile.

Fresh io conversion now emits testProjectModel: whitebox-reference, references io.csproj, compiles no production .cs into io.tests, and builds with 0 errors. The host runs all 54 included test functions.

2026-07-31 (reflection chip): the 0-errors claim had silently regressed on landed master — a fresh conversion produced CS1503 ×20: emittedAdapterPair’s bare-cast fallback resolved io_test’s own Buffer to the first same-simple-name record in order, the FOREIGN bytes_package.Buffer (bytes_BufferжReader(rb)), while the generator names the anchor-local record’s adapter bare (BufferжReader). Both A/B binaries (8d55344cc landing, f73d62d71) emit the same broken pairing, so the recorded 45/54 was measured with an intermediate, not the final, binary — the §9 mixed-vintage lesson in the wild. Fixed in the chip’s landing (anchor-local records win the dotless fallback; exact-key matching is a full first pass; anchoredAdapterMemberName composes bare for anchor-local records — guard TestBareCastPrefersAnchorLocalRecordOverForeignSimpleNameMatch).

With that repaired and the chip’s runtime.Callers/Frames.Next managed traceback landed, the host reached 47 pass / 54 (superseded 2026-08-01 — see the closing paragraph of this section); the remaining seven top-level verdicts were separate runtime/semantic roots:

With the two above settled, the host reaches 48 pass / 54 · 2 disclosed · 4 os-blocked, and the os runtime_rand row is the whole of what stands between io and a bank. Every remaining verdict has a named owner and must be handled by that arc rather than folded into this item.

BANKED 2026-08-01 (r32 train): io validates — 59 matching · 2 disclosed (alloc-count-semantics). The os.runtime_rand hand-own landed with the os-roots lane and the four OffsetWriter tests pass; the probe fix and the disclosures above did the rest. One standing footprint note: the satisfies-but-never- witnessed recorder (r32’s converter increment) adds 2 GoImplement records to io’s production package_info.cs on every -tests regeneration; the committed file predates the recorder and is deliberately NOT rebanked (charter: no partial rebanks), so sweeps show that +2 as expected drift — restore, don’t chase — until the whole-corpus regen levels it, along with the rest of the increment’s measured 34-file footprint.

context — five converter roots closed; 36 of 38 match; two rooted failures remain (2026-08-02)

Attempted after the wave3 channel semantics were ground-truthed. The channels are not the problem and never appear in this censuscontext’s suite is the stdlib’s sharpest select/cancellation exerciser (100-node cancellation trees, interlocked cancels, closed-channel Done() broadcast, AfterFunc registration races) and every one of those tests passes. That is a strong independent confirmation of the wave3 landing, and the single most useful thing this arc measured.

Five converter roots stood between the package and a run; all five are fixed and documented in ConversionStrategies-Reference.md:

# First diagnostic Root Layer
1 CS1003/CS1026/CS1513 ×195 in x_test.cs a func literal inside a for … range composite literal emits its capture snapshot — a STATEMENT — into the element position; visitRangeStmt provided no pre-statement hoist sink (the fourth statement kind to need one) converter
2 CS0051 ×4 — testingT less accessible than XTestParentFinishesChild visitTypeSpec asked the testInlineTypeAccess arm FIRST, so it decided the modifier’s VALUE from the name and discarded the publicization signal converter
3 CS8030 — anonymous function converted to a void-returning delegate a returned FUNC LITERAL is typeless in C#, which allExecWrapperReturnsAreTypeless (written for nil/constants) did not count converter
4 CS1929timerCtx has no Done, best overload wants ж<afterFuncContext> the internal bridge re-recorded a production↔production pointer pair production already implements, minting a DUPLICATE adapter whose members resolved in the test class’s scope (and whose cancel was an EMPTY body) converter
5 CS8917 + CS8130 in example_test.cs a func literal returned inside another literal has no natural type, so the enclosing lambda has none either — the sibling of lambdaConstReturnCastType converter

Root 1 is guarded by the RangeExprFuncLitCapture behavioral test (its A/B reproduces the cascade); roots 2–5 are -tests-only shapes with no behavioral-corpus expression, so context’s own banked suite is their guard when it banks.

T.Deadline was ALSO still capability-blocked, and that was pure staleness. The member landed with the one-tree consolidation (core/testing/testing.cs Deadline + TestHost.PackageDeadlineUtc) but supportedTestCapabilities() was never widened, so six of context’s tests — TestDeadline, TestTimeout, TestSimultaneousCancels, TestInterlockedCancels, TestLayersCancel, TestLayersTimeout, i.e. the whole tree-cancellation family — were excluded rather than run. Widened, with the charter §9 roster scan done first (positive control context/x_test.go:50 + net/net_test.go:78 both fire): the only validated package whose _test.go calls it is os/signal, and both of its call sites are in //go:build unix files this platform never builds. All six now run and pass.

Census after all six changes: 38 top-level verdicts, 36 pass, 2 fail. The two failures are rooted and owned elsewhere:

Test Root Owner
TestValues internal/reflectlite’s rtype.String() is the literal Go conversion — t.nameOff(t.Str).Name() over a type-descriptor name offset the managed bridge never populates — so it returns "". reflect’s equivalent is hand-owned over GoReflect.GoTypeName (type.cs:517 placeholder); reflectlite’s mini-bridge only ever landed Len/Swapper. Symptom: context.Background.WithValue(, c1k1) where Go prints WithValue(context_test.key1, c1k1) — the stringify fallback arm for a key with no String() method. reflection-bridge arc
TestAllocs testing.AllocsPerRun unit mismatch, the established alloc-count-semantics class (io, strings, bytes). MEASURED before ruling: Background() allocs = 128.000000 want 0, WithValue = 754 want 3, WithTimeout(1ns) = 3744 want 12, WithCancel = 2104 want 5, WithTimeout(5ms) = 4876 want 8 — bytes in every case, so no allocation behavior can satisfy a count assert. A signature-pinned disclosure is warranted; it is deliberately NOT written here, since a disclosure manifest belongs with the banking commit that verifies it end to end. context’s banking arc

So context is one reflectlite member plus one disclosure away from banking, with nothing context-local left. Note the reflectlite gap is not context-specific: any package whose code path reaches reflectlite.TypeOf(x).String() gets an empty string today, silently.

Build-blocked, each its own root

Package First diagnostic Note
image/jpeg CS0111: … already defines a member called 'init' DONE 2026-07-31 — 14/14, banked. NO converter change was needed — the diagnostic was stale by the time the row was written. The converter has always uniquified multiple package inits from a package-scoped counter (init, initΔ1, … in visitFuncDecl.go), and jpeg’s production pair (reader.go + writer.go) emits correctly. The collision was between PRODUCTION’s init and INTERNAL test file dct_test.go’s, which the recompile model put in the same jpeg_package; the whitebox-reference model emits internal test declarations into <pkg>_internal_test_package, so it cannot form. A corpus scan finds 12 packages with both a production and a test init (flag, net, os, runtime, sync, testing, time, crypto/x509, image/jpeg, net/http, os/signal, os/user); every one takes a reference model. The recompile FALLBACK (recordsRequireProductionMutation) would still collide — latent, reachable by no package today, deliberately not fixed speculatively. Cross-file multi-init is now guarded by the MultiFileInitOrder behavioral test (five inits across three files, order-compared vs go run); Solitaire already covered two in one file.
index/suffixarray CS0206: A non ref-returning property or indexer may not be used as an out or ref value DONE 2026-07-31 — 12/12, banked. TWO go2cs-gen defects, stacked, both general. suffixarray_test.go declares type index Index — a defined type over the production struct — and Go gives it Index’s field set. (1) GetStructDeclaration resolves an underlying struct only from SOURCE, and a real MSBuild <ProjectReference> arrives as compiled METADATA, so under the white-box model NO members were forwarded and every x.sa/x.data was CS1061; a symbol-based fallback now resolves it, forwarding what IsSymbolAccessibleWithin permits — Go’s exported/unexported rule projected into C#. (2) The forward was a get/set property, i.e. a VALUE, so x.sa.len() (a this ref receiver) and &x.sa could not bind — this row’s original CS0206. It is now an [UnscopedRef] REF-returning property, a strict superset. Fixing (1) alone collapsed the CS1061 wall onto exactly the CS0206 recorded here: root-cause layering, the first diagnostic moving rather than clearing. Full rule: docs/ConversionStrategies-Reference.md, The forwarded member must be a VARIABLE, and the underlying may be METADATA-ONLY; guarded by the DefinedTypeOverForeignStruct behavioral test (whose A/B reproduces CS1061 and CS0206 separately). ⚠ TestNew{32,64}/exhaustive3 run ~35 min in C# vs 12.4 s in Go — a performance gap, not a correctness one; run-validated-sweep.ps1 gives the package a 60m deadline.
internal/zstd CS1929: 'testing_package.B' … 'Cleanup' DONE 2026-07-27 — 534/534, banked. The common members are on core/testing’s B; see the retraction below.
crypto/md5 CS0030: Cannot convert type 'System.Type' to 'uint' DONE 2026-07-31 — 11/11 (1 alloc-profile disclosure), banked. TWO defects, both general. unsafe.Alignof/Offsetof built their System.Type argument by splitting the CONVERTED C# text on . as though it were a Go field selector, so unsafe.Alignof(uint32(0)) emitted (uint32)0.GetType() — which C# parses as (uint32)(0.GetType()). Both now resolve the operand through go/types and emit typeof(T). Behind it stood a second: buf := buf in benchmarkSize reads a package-level buf declared in md5_test.go, and the shadowed-global qualifier named the PRODUCTION class (md5_package.buf, CS0117) rather than the white-box bridge class that actually declares it.
path/filepath CS0103: The name 'ßÅælstat' does not exist Build blocker CLOSED 2026-07-31; FindFirstFile host-killer CLOSED 2026-08-01; BANKED 2026-08-01 (r32 train) at 61 matching — see below.
net CS1031: Type expected Syntax cascade CLOSED 2026-07-31 — see below. Still does not compile: 94 SEMANTIC errors stood behind it.

path/filepath — build blocker closed; the FindFirstFile root closed; 46 of 61 match; two runtime roots remain

The name was never mangled. ßÅæ is the bytes E1 8F 91 rendered in cp437 — the UTF-8 encoding of U+13D1 Ꮡ, the AddressPrefix marker. The missing symbol is Ꮡlstat, the heap box for path.go’s var lstat = os.Lstat // for testing, whose address export_test.go takes (var LstatP = &lstat, the hook that lets a test swap the implementation Walk calls). go/packages excludes _test.go from a production package, so the production emission never saw the address-taking and left lstat a plain field, while the test variant emitted Ꮡlstat. Fixed generally: the converter now scans the build-selected in-package _test.go half for addressed globals and folds them into packageAddressedGlobals — in ordinary and -tests conversion alike, so production storage shape stays mode-stable. Rule, the three properties that keep it safe, and the SiblingTestAddressedGlobal guard: ConversionStrategies-Reference.md, A global addressed only by the package’s own _test.go is still heap-boxed.

Its reach is wider than filepath. A whole-stdlib A/B put the footprint at 13 globals in 13 files, every one a Go “for testing” hook and no false positives: os’s lstat / testingForceReadDirLstat / allowReadDirFileID, runtime’s readRandomFailed / useAeshash / doubleCheckReadMemStats / casgstatusAlwaysTrack / forcegcperiod / timeBeginPeriodRetValue, reflect’s callGC, internal/poll’s logInitFD, net/http’s maxWriteWaitBeforeConnReuse and testHookEnterRoundTrip, and time’s usPacific. Those are exactly the hooks os, runtime, reflect, net/http, internal/poll and time need aliasing real storage before their own suites can pass — so this is prerequisite work already banked for six future arcs, not filepath-local cost.

filepath now builds with 0 errors and the host runs. Root 2 below is closed (2026-08-01), and closing it is what lets the host survive a whole-suite run — so the numbers no longer have to be gathered per test. Measured in ONE -tests -test-action all -test-timeout 10m run: 46 of 61 match (C# 40 pass + 6 skip against Go’s 41 pass + 20 skip), with zero empty verdicts. Every one of the 15 remaining mismatches reaches one of the two roots that are left — 14 the symlink-privilege one, 1 the gogetenv one — and none is a marshalling failure:

Root Reached via Note
os.runtime_rand unimplemented os.MkdirTempnextRandomtestenv.MustHaveSymlink / initWinHasSymlink The same root the io row names — owned by the os operational arc. Go skips these tests for want of symlink privilege; C# infrastructure-errors before testenv can decide, so clearing this likely converts most of them to matching skips rather than passes.
Win32 FindFirstFile struct marshalling EvalSymlinkstoNormnormBasesyscall.FindFirstFile CLOSED 2026-08-01. findFirstFile1 handed (uintptr)new @unsafe.Pointer(Ꮡdata) to the raw Syscall, and the kernel wrote a 592-byte WIN32_FIND_DATAW over a C# struct whose [MAX_PATH]uint16 field is an array<uint16> — an 8-byte managed reference, not inline storage. The write clobbered that reference, so the next read was an IndexOutOfRangeException in PinnedBuffer or a hard AccessViolation (0xC0000005) that killed the host. Fixed as the third member of the struct-passing class below: findFirstFile1/findNextFile1 are hand-owned against a blittable mirror in syscall/zsyscall_windows_impl.cs, guarded value-level by the FindFirstFileData behavioral output test. TestDriveLetterInEvalSymlinks — the crash site — and TestEvalSymlinksCanonicalNames, TestToNorm, TestGlob/TestWindowsGlob/TestGlobUNC, TestWalk/TestWalkDir all now match Go.
runtime.gogetenvfatal error: getenv before env init testenv.GOROOTruntime.GOROOT runtime.envs is never populated (Go fills it in goenvs during scheduler init); throw then re-faults on the unimplemented getcallerpc. Only TestBug3486 here, but it gates every testenv.GOROOT consumer.

Resolved with root 2, and worth remembering as a shape. While that AccessViolation stood, one full-suite run under-reported badly: the host died mid-TestDriveLetterInEvalSymlinks and every later verdict read C#="", which presents as a mass infrastructure wall rather than as one crash — so the package had to be bucketed per test. A single host-killing defect will do this to any package; the tell is a run whose empty verdicts all fall AFTER one particular test. filepath’s whole-suite run now has zero empty verdicts, so per-test bucketing is no longer needed here.

The remaining 15 split cleanly by root. Fourteen are the symlink-privilege family — Go’s testenv.MustHaveSymlink skips them for want of SeCreateSymbolicLinkPrivilege, while C# never reaches that decision: 3 die in os.MkdirTempruntime_rand first, 9 go on to attempt the symlink and fail on the privilege, and 2 infrastructure-error on the consequences of having attempted it (TestNTNamespaceSymlink’s mklink, TestWalkDirectoryJunction’s cleanup UnauthorizedAccessException over the junction it created): TestEvalSymlinks, TestEvalSymlinksAboveRoot, TestEvalSymlinksAboveRootChdir, TestEvalSymlinksIsNotExist, TestEvalSymlinksTooManyLinks, TestGlobSymlink, TestIssue13582, TestNTNamespaceSymlink, TestRelativeSymlinkToAbsolute, TestWalkDirectoryJunction, TestWalkDirectorySymlink, TestWalkSymlink, TestWalkSymlinkRoot, TestWindowsEvalSymlinks. The fifteenth is TestBug3486 (getcallerpc after the gogetenv throw). Clearing root 1 should convert most of the fourteen to matching skips, exactly as predicted — so filepath’s remaining distance is entirely os/runtime work, with nothing filepath-local left.

BANKED 2026-08-01 (r32 train): path/filepath validates — 61 matching, 20 of them privilege-gated skips agreeing with Go’s. The os-roots lane landed both remaining roots (os.runtime_rand → the fourteen become matching skips; runtime.envsgogetenv works), and TestBug3486 took one ruling on top: runtime.GOROOT() has no linker-baked defaultGOROOT in a converted assembly, so the pipeline now exports GOROOT to both children (go test and the C# host — user-ruled 2026-08-01, the run-time-export option over baking a machine path into committed host metadata; testConversion.go’s runCommandWithTimeout). One FOURTH root surfaced only on the merged tree — charter §9 layering: with the tempfile and mirror fixes in, TestNTNamespaceSymlink got far enough to create its junction-to-a-volume-root and then t.TempDir() cleanup died (UnauthorizedAccessException), because the host delegated to .NET’s Directory.Delete(recursive), which opens some junction targets during its walk. Go’s cleanup is os.RemoveAll, which removes a reparse point AS THE LINK. core/testing’s TempDir now walks with exactly those semantics (reparse points deleted as links, never traversed; read-only cleared and retried) — general for every future junction/symlink-creating suite, os’s own first among them.

net — syntax cascade closed; 94 semantic errors remain

CS1031 was one defect with a ~90-error blast radius, and it was not about net at all: the anonymous-struct lift probe descended exactly one level of the declared type, so []struct{…} lifted and []*struct{…} did not. ip_test.go’s var ipStringTests = []*struct{ in IP; str string; byt []byte; error }{…} therefore emitted raw Go type text into the C# declaration. (The shape had stayed hidden because a composed occurrence still resolves when some other declaration registered the identical signature first; the embedded error makes this signature unique.) The probe is now a recursive descent over the type-composing syntax — pointer, array/slice element, ...T, parens, map value then key, channel element — shared by the struct and interface extractors, and the separate one-off map-value probe it subsumes was deleted. Rule + the AnonStructComposedTypes guard: ConversionStrategies-Reference.md, An anonymous struct lifts from ANY depth of its declared type. Zero syntax errors remain in net — no CS1031/CS1003/CS1519/CS1002/CS1513.

net still does not compile. What the cascade was hiding, bucketed — charter §9’s layering lesson in its purest form, since Roslyn skips method-body binding while declaration errors stand:

Count Code Root
52 CS0426 The type name 'ConnᴠReader' does not exist in the type 'net_test_package'. The ᴠ value-adapter for a production↔production pair (net.Connio.Reader) is generated into the PRODUCTION class, but an external-test use site qualifies it with the TEST class. One root, in test-project-model record anchoring (splitExternalVariantRecords); 55% of all remaining errors.
14 CS1929 Two shapes: core/testing’s T declares no Deadline (so a same-named contextWithNonZeroDeadline extension is offered instead), and socktest.Switch methods want a ж<Switch> receiver where a value is supplied.
6 CS8130 deconstruction of a result whose type did not bind
4 each CS1061 / CS8183 / CS8917 member lookup, var-in-deconstruction inference, delegate-type inference
2 each CS1501 / CS1503 / CS0029 / CS8934 arity; ж<AddrError>error; a (ctx, cancel) tuple assigned to Context; lambda return type

Rooting those is the next net increment. Note net’s own init gap (the sync.OnceFunc nil panic at fd_windows.cs:27) sits behind all of it, and the Tier-0 channel/rendezvous frog behind that — so compiling is the realistic near-term goal, not validating.

Revised 2026-07-31 — six of those seven roots are fixed; ONE architectural blocker remains

Re-measured on a converter carrying the r27 adapter-resolver chip: 46 unique errors (the “94” above counts each twice — MSBuild reports every error once per pass). Six roots landed, each a general fix at its own layer; the count after each, in order:

# Root Layer Errors after
(start)   46
1 A white-box production type is FOREIGN to go2cs-gen, so the interface-sourced adapter name must carry the package prefix — the carve-out the value arm already had (whiteboxProductionTarget) converter 17
2 A pointer-receiver method value binds the address in assignment context too (poll.CloseFunc = sw.Closesocket) — the value-context arm already did converter 11
3 &x.(*T).field — a type-assertion base is a pointer rvalue, so it field-refs the box instead of copy-boxing converter 11
4 A literal whose every return arm is untyped nil states its return type (the single-result twin of the multi-result rule) converter 11
5 goǃ gains the Func<…, TResult> twins deferǃ always had — go f(…) discards results for any f, including a func literal with a named result golib 11
6 var a, b = f() gates on identHasHeapBox, not the blanket identEscapesHeap flag — every tuple with an interface or func result was falling back to the broken per-name path converter 3
7 The elided pointer element composite ([]*struct{…}{{…}}) routes its interface fields, like both sibling composite paths converter 2

Rows 3–5 cleared together on the same measurement (2–4 were independent roots whose sites overlapped in the same three files). Every one is documented in ConversionStrategies-Reference.md; behavioral CNR is byte-identical across all 517 projects for the whole set, which is the expected shape — five of the six converter roots are reachable only from Go that the behavioral corpus does not contain, and two only under -tests.

The original bucketing held up well with one correction worth recording: row 1’s mechanism was not splitExternalVariantRecords and not an anchor split. Both sides agreed on the anchor all along — the record lands in package_test_info.cs and the class is generated into the test metadata class — and only the simple name disagreed, because the converter asks “is the source type in another Go package?” where the generator asks “is it in another assembly?” Under the white-box model those differ for exactly one set of types. The board’s guess named the right file and the wrong seam; the diagnostic (does not exist in the type 'net_test_package') reads like an anchor problem and is not one.

The remaining blocker: testing.T.Deadline needs a type core/testing cannot name — CLOSED 2026-08-01, option (d)

CLOSED. The blocker was never about Deadline; it was about there being two go.time_package declarations on disk. On 2026-08-01 the stub baseline retired and the converted standard library moved into src/core (commit 2e8066da6), so core/testing simply references core\time like any other consumer — the answer none of (a), (b) or (c) below could be, because it removes the premise rather than working around it. Call it option (d): there is one time.

testing.T.Deadline() now returns a real (time.Time, bool), reporting the instant the package deadline (-timeout) expires — see src/core/testing/testing.cs and TestHost.PackageDeadlineUtc. DisableTransitiveProjectReferences is not a problem here after all: the host is a FIXED reference of every generated test project, so time arrives through it directly.

Everything below is the record of the blocker as it stood. The footprint table still says which packages the member unblocks.

Both remaining errors are t.Deadline() (net_test.go:78, dial_test.go:391). Go’s signature is func (t *T) Deadline() (deadline time.Time, ok bool), and net uses the result as a real time.Time (deadline.Add(-time.Until(deadline)/10), td.Add(-arbitraryCleanupMargin)) — so no primitive or golib stand-in can satisfy it.

core/testing is hand-owned and, per the F15b one-testing-package ruling, is bound by every test host by path ($(go2csPath)core\testing\testing.csproj). It references only golib and the analyzer today, and that is not an oversight — its whole public surface (TB, T, B, F) is expressible in primitives and golib types. Deadline is the first member that needs a converted stdlib type, and neither candidate works:

Note DisableTransitiveProjectReferences=true on the test projects makes this worse, not better: the reference would not flow, and a core/testing API mentioning time.Time would then be CS0012 at every consumer — the reference-closure family again.

Three ways out, none of them a converter fix, all of them a decision above a single package’s arc: (a) parameterize core/testing’s time reference per consumer (MSBuild AdditionalProperties on the ProjectReference — works, but makes the one testing package polymorphic in its dependency and touches every generated .tests.csproj); (b) promote time to a position both trees share, the way golib already is; (c) rule that testing’s time-typed surface is out of scope and accept that packages using it cannot compile their suites. Owed to a ruling, not to this arc.

The ruling came as (d): retire the second tree entirely (2026-08-01). (b) was the closest guess — it just turned out the position time needed to share was the one golib already had, and moving ONE package there would have left the same seam for the next member that needed a converted type.

Footprint, so the ruling is sized rather than guessed. Scanning GOROOT _test.go for a testing receiver (\b(t|b|tb)\.Deadline\(\), positive control net/net_test.go:78) and dropping what this platform and this campaign never build:

Package Note
net this row
net/http, net/http/httputil 4 sites
os/exec 1 site
runtime/pprof 1 site
context (x_test.go) 1 site
os/signal 7 sites, all in //go:build unix files — never built on Windows, which is how os/signal banks at 1 today while carrying the call
internal/poll splice_linux_test.go only
cmd/go, cmd/cgo/... not stdlib validation targets

So six packages, not the wider set a naive .Deadline() grep suggests (that one also catches context.Context.Deadline). The os/signal row is worth keeping visible: it is exactly the shape of counterexample that would look like it disproves this blocker, and does not.

net state: 2 errors, one root, no converter work left in it. Everything the r27 lane bucketed is closed. When the ruling lands, net should compile on the next run — and the init gap (sync.OnceFunc nil panic at fd_windows.cs:27) plus the Tier-0 channel frog are what stand between compiling and validating, exactly as this section said.

Updated 2026-08-01: the ruling landed (option (d) above) and net builds — see the Deadline banner. The init gap and the channel frog are what remain, exactly as predicted.

Ground-truthed 2026-08-02 (r37-poll scout) — the census, and the sync.OnceFunc row is STALE

Measured on the post-r37-poll tree, one pipeline invocation (-tests -test-action all -test-timeout 20m). The wall is exactly one root, and it is not the one recorded above.

   
production + test build 0 errors (warnings only)
Go side 138 top-level tests
C# side 0 reachedstatus: conversion-blocked, every row C#=""
excluded declarations 129 (unsupported capabilities)

The sync.OnceFunc nil panic at net/fd_windows.cs:27 does not reproduce. That line is poll.InitWSA(), and nothing gets far enough to execute it — InitWSA appears nowhere in the run. Whatever closed it closed it uncredited, exactly the staleness charter §9 warns about; probe, don’t inherit.

Today’s blocker is the OPEN pointer-PARAMETER nil-deref row, the one the os nil-receiver arc named as still outstanding (“the same defect is still open for pointer PARAMETERS … the complete fix is to give parameters the same unconditional DerefOrNull”). The chain is identical whether net is entered through a program or through its test host:

go.net_package..cctor()                          net/addrselect.cs
  → netip.AddrFrom16                             net/netip/netip.cs
    → go.net.netip_package..cctor()
      → unique.Make → go.unique_package..cctor() unique/handle.cs
        → concurrent.NewHashTrieMap
          → concurrent.newIndirectNode(nil)      internal/concurrent/hashtriemap.cs:372
            → PanicException: runtime error: invalid memory address or nil pointer dereference
func newIndirectNode[K, V comparable](parent *indirect[K, V]) *indirect[K, V] {
	return &indirect[K, V]{node: node[K, V]{isEntry: false}, parent: parent}   // parent is nil here
}
internal static ж<Δindirect<K, V>> newIndirectNode<K, V>(ж<Δindirect<K, V>> parent) {
    ref var parent = ref parent.Value;    // ← eager entry alias; the body never dereferences it
    return (new Δindirect<K, V>(node: new node<K, V>(isEntry: false), parent: parent));
}

The body only ever uses Ꮡparent; the alias exists and panics. Neither nilSafePtrParamNames heuristic fires (the parameter is not nil-compared in the body and no same-package call site passes a literal nilNewHashTrieMap’s does, but through a generic instantiation). So net is a one-root wall, and that root is already designed: the parameter arm of DerefOrNull. It is a much larger emission footprint than the receiver arm (3167 entry aliases) and wants its own measurement and ruling — but it now has a second package demanding it, and unique and internal/concurrent are blocked by the same line.

Nothing beyond it is measurable yet: with zero tests reached there is no second bucket to report. Re-run this census the moment the parameter arm lands.

time — builds and RUNS (2026-08-02, r35): 139 pass / 17 fail / 2 skip / 1 infra-error of 159

time was opened the day the channels frog was confirmed closed. It went from 260 build errors to 0, and the host now runs the whole suite in ~60 s with zero empty verdicts — the timer machinery in time_impl.cs (one global heap on a Windows high-resolution waitable timer) holds up: TestTicker, TestTickTimes, TestAfterTimes, TestAfterTick, TestTimerStopStress, TestTimerModifiedEarlier, TestAdjustTimers, TestLongAdjustTimers, TestAfterFuncStarvation and the sleep family all pass against real rendezvous. No channel-semantics defect was found; the one channel-shaped failure is a documented model divergence, not a wave3 regression (below).

Seven roots stood between the package and a build; all seven are fixed and none was time-specific. Six are in the converter or go2cs-gen, one is a hand-owned reach:

Errors Root Layer
1 (blocking all) A mixed-accessibility GoImplicitConv pair whose less-accessible side is in ANOTHER assembly has no legal operator — skip it (export_test.go’s type RuleKind int over production ruleKind) go2cs-gen
176 A DOT-imported collision-renamed CONST/VAR emitted its raw Go name (Second, UTC, Hour, …) converter
44 A collision-renamed member kept the RAW package qualifier where the file’s using is Δ-renamed (time.ΔNanosecond vs Δtime.ΔNanosecond) converter
33 A local/parameter that SHADOWS a package name was resolved as the packagegetAliasedTypeName applied to a rendered expression; time.Year()Δtime.Year(), time.Month()timeꓸMonth(), time.Hour()time.ΔHour() converter
3 A nested func literal’s captures hoisted to the ENCLOSING statement’s buffer, above the declaration they name converter
2 A folded constant of a NAMED type lost its type (8 * time.Hour → a bare long) — the loud half is CS1929, the silent half is d printing as digits converter
1 A concat of two SLICED string literals has no C# operator (span + span is literal-only) converter

Plus the runtime blocker behind the build: time/tzdata’s init() pulls time.registerLoadFromEmbeddedTZData by //go:linkname, which was a throwing stub — inside a MODULE INITIALIZER, so a blank import _ "time/tzdata" took the host down before main. Now a real forwarder (see A whitelisted target may be ORDINARY CONVERTED GO in the reference). That fix pays for itself twice: with tzdata registered, loadLocation falls back to the embedded database, which is how the suite’s initTestingZone reaches America/Los_Angeles at all — its hard-coded ../../lib/time/zoneinfo.zip cannot resolve from the C# host’s working directory.

Guard for the six general converter/generator roots: the PackageNameShadowing behavioral test (a describe(time time.Time) parameter, a time := local, Δ-qualified renamed members, a dot-importing sibling file, the named-type fold in both positions, and the sliced-literal concat — output-compared vs go run) plus FuncLitArgCapture case 15 for the hoist.

The 17 remaining failures, rooted, none of them time-local machinery:

Count Tests Root Owner
6 TestChan and its five subtests Documented model divergence, not a defect. Go 1.23 made a chan-based Timer/Ticker channel SYNCHRONOUS (#37196) by coupling the channel’s receive path to the timer inside the runtime; time_impl.cs reproduces Go’s own GODEBUG=asynctimerchan=1 mode instead, so tim.Stop() = false, want true and “extra tick” are exactly what that mode produces. ⚠ The asynctimerchan=1 SUBTEST also fails, which the divergence does NOT explain — either t.Setenv("GODEBUG", …) does not reach the converted godebug, or the async model has its own bug. That subtest is the honest next probe here. ⚠ HISTORICAL — superseded twice: the mode-1 failure was the one-firing-per-pass burst (r39-timer), and the mode-0 “divergence” is IMPLEMENTED (r39b); TestChan passes in all three modes. See RESOLVED — r39b lands the synchronous timer channel below. time / godebug
9 TestDefaultLoc, TestNanosecondsToUTC, TestSecondsToUTC, TestParse, TestTimeGob, TestTimeIsDST, TestTimeJSON, TestUnmarshalInvalidTimes, TestZoneBounds All die with the same nil pointer dereference inside GoFunc.HandleFinally. Every one of them formats a Time through fmt on its FAILURE path (%#v, %+v, %v of a struct with a *Location), so the NRE is plausibly SECONDARY to a comparison that already failed — the reflect/fmt bridge, not the clock. Not rooted; the next increment should print the pre-format comparison rather than reason about the stack. reflect/fmt bridge
1 TestParseErrors A REAL parse divergence: Go reports extra text: "07:00" where C# reports cannot parse "Z07:00" as "Z07:00" — the Z07:00 layout element consumes differently. format.go conversion defect, time-local. time
1 TestTruncateRound math/big.mulAddVWW is an unimplemented asm stub (NotImplementedException), reached through big.Int.Mul. CLOSED 2026-08-02 (r37-time-os-fin), and it was never a math/big ARC — it was a build-tag selection. math/big predates the purego convention and gates its portable fallbacks on its own math_big_pure_go, which the default tag set did not carry, so all EIGHT of arith_decl.go’s bodyless declarations became throwing stubs. The scope was not one test: the whole package compiled clean and could not do arithmetic — a direct probe dies inside big.Int.SetString, i.e. parsing a decimal string, because that is already a mulAddVWW. See purego is not the only spelling of this decision in ConversionStrategies-Reference.md. math/big arc done
1 TestUnmarshalTextAllocations got 3784 allocs, want 0 — the established alloc-count-semantics unit mismatch (AllocsPerRun counts mallocs in Go, BYTES on the CLR). A disclosure candidate by the class strings/io already established; not self-ruled here. ruling

So time’s distance is: one time-local parse bug, one probe (asynctimerchan=1), one shared reflect/fmt-bridge NRE family worth 9 verdicts, and two rows owned elsewhere. Nothing about timers, sleeps, tickers or channel rendezvous is in the way.

Re-measured 2026-08-02 (r37-time-os-fin): 146 pass / 11 fail / 2 skip / 0 infra-error of 159

Measured as a same-session A/B, both arms on this tree, only src/core/math/big differing:

Arm Split of 159 verdict rows (137 top-level + 22 subtests)
math/big asm stubs (the r36 state) 145 pass · 11 fail · 1 infrastructure-error · 2 skip — reproducing the r36 record exactly
math/big pure-Go arith 146 pass · 11 fail · 0 infrastructure-error · 2 skip

Exactly one row moved — TestTruncateRound, infrastructure-error → pass — which is what the math_big_pure_go build tag was expected to do and nothing else. The infrastructure-error column is now empty, so every remaining row is a real verdict disagreement rather than a host casualty.

The 11 failing rows, exhaustively, in three buckets:

Rows Tests Bucket
8 TestChan + asynctimerchan={0,1,2} + their Timer/Ticker children The timer-model item, recorded and deliberately not taken: time_impl.cs §”⚠ OPEN — a periodic timer can fire an UNBOUNDED BURST in one service pass”. The Timer half under asynctimerchan=0 is the accepted sync-mode divergence; the Ticker half fails in all three modes and is the burst. The faithful fix (“fire each timer at most once per pass”) changes the heart of the model and wants its own lane. The t.Setenv("GODEBUG", …) half of the old ⚠ is closed — r36 proved the converted godebug sees it. ⚠ HISTORICAL — both halves are now closed: the burst by r39-timer, the sync-mode divergence by r39b (see RESOLVED — r39b lands the synchronous timer channel below).
2 TestTimeJSON, TestUnmarshalInvalidTimes The reflect-bridge chip’s rows — the last two survivors of the old 9-verdict NRE family (r36’s honest traceback rooted the other seven at Location.lookup, and they pass). Untouched here by fence.
1 TestUnmarshalTextAllocations Alloc-count-semantics, awaiting the coordinator’s disclosure ruling — unchanged in status, but the number moved: got 3544got 2728, an exactly-predicted −816 B/run (6 × 136, the six parseUint range loops in parseRFC3339’s UTC path) from the allocation-free slice<T> enumerator. Also measured as an A/B on this tree; the board’s older 3784 predates other r36 fixes. Nonzero remains, so a ruling is still what settles this row — see docs/CleanupBacklog.md item 7 (IByteSeq<T> interface boxing) for the next lever.

TestParseErrors is gone from the failing set (r36’s fallthrough-placement fix), as are the seven Location.lookup rows. time’s distance to a bank is now: the timer-model item, the reflect-bridge chip, and one ruling — three owners, none of them the converter, and nothing time-local outside the timer model.

Re-measured 2026-08-03 (r39-timer): 152 pass / 5 fail / 2 skip of 159 — the timer model is CLOSED and every residual row is a RULING

The reflect-bridge chip’s two rows (TestTimeJSON, TestUnmarshalInvalidTimes) closed on their own between r37 and this lane — increment 5 landed, and the base commit 832f0960d already measured 148 pass / 9 fail / 2 skip: the eight TestChan rows plus the one alloc row and nothing else. This lane took the timer-model item and rooted the alloc row.

The timer-model item is fixed, and the faithful fix was one statement. The burst was never a “fire at most once per pass” heuristic waiting to be invented — it is what Go gets for free by sampling the clock ONCE per service pass. timers.check reads nanotime() once and threads that value through timers.run(now) into timer.unlockAndRun(now); the clock is never re-read inside a pass. serviceTimers was re-reading it on every drain iteration, so the theorem that bounds Go did not hold here. Moving int64 now = runtimeNano(); above the drain loop restores it, and the bound is then provable rather than enforced: for a periodic timer next = when + period*(1 + delay/period) with delay = now - when = q*period + r, 0 <= r < period, so next = now + (period - r) > now strictly — the re-peek always breaks. One-shots clear when. Hence every timer fires at most once per pass, for every period including the 1 ns testTimerChan resets to. It does not rate-limit: the pass then waits until the new head deadline, which for a fast ticker is already past, so the next pass begins at once — exactly Go’s scheduler calling check again. Recorded in ConversionStrategies-Reference.md, ONE firing per timer per pass.

Measured on this tree, same command both arms (go2cs -tests -test-action all -test-timeout 10m):

Row Base 832f0960d After the fix
TestChan/asynctimerchan=0/Timer fail — tim.Stop() = false, want true + extra tick fail — identical message
TestChan/asynctimerchan=0/Ticker fail — extra tick + early done fail — extra tick ×4, early done gone
TestChan/asynctimerchan=1/Ticker fail — extra tick ×2 + early done pass
TestChan/asynctimerchan=2/Ticker fail — extra tick pass
TestChan/asynctimerchan={1,2} parents fail pass
TestChan/asynctimerchan=0 parent, TestChan root fail fail (mode 0 only)
TestUnmarshalTextAllocations fail — got 216 allocs fail — got 216 allocs (untouched)

+4 rows, and early done — the burst’s signature — is gone from every mode.

What the mode-0 ruling now decides over: exactly 4 rows, and they are the documented divergence, row for row. With the burst gone, asynctimerchan=1 and =2 pass completely — Timer and Ticker. Those are the modes where testTimerChan sets synctimerchan=false and therefore drains stale values explicitly. Only mode 0 fails, and each of its failures sits either inside a block the test guards with if synctimerchan (the tim.Stop() = false, want true pair, which is #37196’s Stop-blocks-old-values semantics) or on a noTick() whose preceding drainAsync() is a deliberate no-op in sync mode (the four extra ticks). The same implementation passes the identical test body wherever the test expects asynchronous semantics and fails only where it switches to expecting synchronous ones. That is the accepted GODEBUG=asynctimerchan=1 divergence and nothing else — no residual burst, no channel-rendezvous defect. Closing it for real means implementing Go 1.23’s synchronous timer channel (the ignored syncTimer(c) argument), which lives inside golib’s channel implementation — a Tier-0 golib capability, not a time fix.

TestUnmarshalTextAllocations — rooted, and the board’s previous attribution was WRONG. The r38 train recorded “the FINAL 216 B live above parseRFC3339 in the Time.UnmarshalText wrapper chain”. Measured directly (a probe project borrowing InternalsVisibleTo("time.tests"), GC.GetAllocatedBytesForCurrentThread() over 2,000 runs), zero bytes are above parseRFC3339:

Frame B/run
Time.UnmarshalText(data) 88
parseStrictRFC3339(b) 88
parseRFC3339<slice<byte>>(b, Local) 88
Date(...), daysIn(...), isDigit(...) 0
the same parseRFC3339 body with the closure replaced by a static local function 0
a bare capturing lambda, isolated control 88
the converted TEST body: heap(new Time(), out var Ꮡt) alone 128
the converted TEST body: heap(...) + UnmarshalText 216

216 = 88 + 128, and both halves are converter emission, not time:

  1. 88 B — parseRFC3339’s parseUint func literal. It captures ok, so C# hoists ok into a display class and allocates that class plus a Func<> delegate on every call (24 + 64 = 88, matched exactly by the isolated control). Go stack-allocates both, because escape analysis proves the closure does not escape. The general converter fix is real and valuable — a func literal bound to a local that is only ever CALLED should be emitted as a C# local function, which captures without allocating — but it is a new emission mode in convFuncLit.go / captureModeOperations.go (847 + 1,136 lines) reaching every closure in the corpus.
  2. 128 B — the converter heaps the test’s own var t Time. The emission is ref var tΔ1 = ref heap(new Δtime.Time(), out var ᏑtΔ1); because t’s address is taken by the pointer-receiver call t.UnmarshalText(in). Go keeps it on the stack (that is why the assert says zero). Note ᏑtΔ1 is never referenced in the emitted body — the box is minted dead — so a narrow rule (“don’t heap when the emitted Ꮡx is unused, because a C# ref parameter provably cannot escape its callee”) looks sound and would be a headline win. It is still an escape-analysis change, which charter §7 puts behind an adversarially-reviewed design.

Consequence for the ruling: this row is NOT a clean disclosure candidate. The established alloc-profile class covers asserts the managed CLR provably cannot satisfy; both halves here are fixable converter gaps, and §5 says a real bug is never a disclosure candidate. Equally, neither half alone flips the row (216 → 128 still fails want 0), so it cannot be cleared incrementally either. The honest options are: (a) land both converter fixes as their own gated arcs and green the row outright, (b) hold the row open until they land, or (c) disclose it knowingly as a converter-gap rather than a CLR-semantics divergence — which would be a new disclosure class and should be decided as one. Not self-ruled here.

time’s distance to a bank is now two RULINGS and zero open engineering: the mode-0 sync-timer-channel divergence (4 rows, needs a golib channel capability to close for real) and TestUnmarshalTextAllocations (1 row, needs two converter arcs to close for real).

RESOLVED — r39b lands the synchronous timer channel; the 4 mode-0 rows close (2026-08-03)

Ruling #1 below commissioned the arc; it is implemented. The change is small because the guarantee is small, once stated as a guarantee rather than as plumbing:

A Stop or Reset prevents any tick generated before the call from being received after it.

Two mechanisms carry it, at Go’s own two layers. golib gains the hchan.timer hook the wave3 design deliberately left out — IChannelTimer installed by channel<T>.AttachTimer, Capacity/Length masked to 0 while the owner answers HidesBuffer (asked LIVE, because GODEBUG=asynctimerchan selects the model at every observation), and DrainBuffer() = runtime.timerchandrain, the only sanctioned way to un-send. time_impl.cs gains Go’s timer.sendLock + timer.seq: a service pass now only offers a tick — it captures seq with the firing decision and re-checks it under sendLock before sending, so an offer a Stop/Reset overtook is ABANDONED. seq is deliberately not gen (a firing bumps gen, so a delivery check must not key off it). Stop/Reset bump seq and drain inside ONE sendLock hold — stronger than Go’s ordering, and necessarily so: Go can drain outside the lock because a sync-mode chan timer is heaped only while a receiver blocks on it, and this model’s service thread is always eager.

A third mechanism has no Go counterpart and is the arc’s real lesson. The adversarial round measured that mechanisms 1 and 2 revoke correctly but cannot between them ANSWER correctly: in the window mechanism 1 exists to cover, a tick is in neither place a Stop looks — when cleared at commit, buffer not yet filled — so Stop revoked the tick and reported that there had been none. Hundreds of one-shots per run where Go answers true for every one. Go never reaches that state because a sync-mode chan timer nobody is receiving from is not heaped at all and therefore never fires; eager firing opens the window, so eager firing has to close itruntimeTimer.offered records the in-flight firing and Stop/Reset count it as pending. The general form of the lesson: a divergence in WHEN work happens is not free just because the observable end states match — check the states in between. Two more review findings landed with it: the mode selector no longer routes through the punned unsafe.Pointer cp (its non-nil-ness was an accident of two type layouts, and this very change added a field to ChanCore), and asyncTimerChan now reproduces runtime.atoi32’s parse, so asynctimerchan=00 is synchronous as in Go rather than asynchronous.

Measured on this tree, both arms with the same command, the fixed arm run twice with identical verdicts:

Row After r39-timer (df3da05d1) After r39b
TestChan/asynctimerchan=0/Timer fail — tim.Stop() = false, want true + extra tick pass
TestChan/asynctimerchan=0/Ticker fail — extra tick ×2 + early done pass
TestChan/asynctimerchan=0 parent, TestChan root fail pass
TestChan/asynctimerchan={1,2} × Timer/Ticker pass pass (async model untouched)
TestUnmarshalTextAllocations fail — got 216 allocs fail — unchanged (ruling #2’s arc)
package 152 pass / 5 fail / 2 skip 156 pass / 1 fail / 2 skip of 159

time is therefore down to ONE row, and it is the alloc row ruling #2 already commissioned an arc for. Nothing here is time-specific: the hook is on ChanCore, so any future owner-fed channel gets the same revocation primitive. ⚠ DrainBuffer revokes values the channel already accepted and is sound only for a channel whose producer owns it exclusively — it is not a general “clear the channel” utility. Guard: the SyncTimerChannel behavioral project (stdout byte-compared against go run), which asserts the pending answers, the absence of stale ticks, len/cap 0, that AfterFunc is untouched, 200 Reset-to-imminent timers that must still DELIVER (the counter-property that keeps the drain honest), 600 ticker Stop/Reset-vs-firing races that must revoke exactly nothing, and two 600-timer batches armed against ONE absolute deadline and stopped/reset at that instant. ⚠ That last shape is load-bearing and fragile in a way worth recording: it only samples the window because the batch and the caller’s sleep share an absolute deadline. The first draft gave each timer its own relative duration, so the caller woke milliseconds after the flush and the neutered control PASSED — a guard that proved nothing. Neutered controls now fire for all three mechanisms (drop offered: 315–483 of 600; drop the drain: 477 stale of 600; drop the seq check: stale ticks in all four race sections).

math/big224 of 226 (re-measured 2026-08-09, r58b); root 3 CLOSED, the two left are both the alloc model

r58b (2026-08-09): root 3 below is FIXED and both gob rows pass. The reflection bridge now packs the typed nil — reflect.Value.Interface() re-encodes a null read out of a POINTER-kinded slot as that slot’s canonical typed nil (ж<T>.NilBox, the same instance reflect.Zero and every emitted nil*T conversion already produce), so v.Interface().(GobEncoder) succeeds and big.Int.GobEncode’s if x == nil arm is reached. TestGobEncodingNilIntInSlice and TestGobEncodingNilRatInSlice both pass: 222 → 224 of 226, exactly those two rows. Guarded by the ReflectTypedNilInterface behavioral test.

math/big still does NOT bank, for the reason root 3’s own paragraph predicted: rows 1 and 2 are the alloc model and neither is disclosable. (Superseded 2026-08-13 — the ж-box A3 section at the end of this board carries the post-A2 pinned pipeline measure: 224/226, TestMulUnbalanced 20,499,128 B (+0.06 %, unmoved), TestNewIntAllocs exactly 1 obj/run.) Both re-measured on this tree — TestNewIntAllocs: “measured 81,600 allocated BYTES over 100 run(s) … got 816.000000”; TestMulUnbalanced: “multiplication uses too much memory (20487208 > 51 times the size of inputs)” (inputs = (50000+40)×8 = 400,320 B, so the converted nat.mul allocates ~51× where Go bounds at 10×). Row 1 waits on the AllocsPerRun ownership decision; row 2 is a truthful, comparable measurement of the box model, which a disclosure would launder rather than explain.

⚠ Attribution correction, measured as an A/B rather than reasoned. The paragraph below says this root “also blocks part of encoding/gob (99 of 106)”. The bridge half does not. r58b ran gob’s full pipeline with and without the fix on the same tree: 99 of 106 both ways, the same seven divergent rows. The typed-nil root has TWO halves that pay DIFFERENT packages — the reflection READ path (fixed here; it pays math/big’s gob rows, because gob reaches math/big’s types through reflect) and the EMISSION path (var ip *int boxed into an interface by ordinary converted code), which is what gob’s own TestNilPointerInsideInterface and the mustPanic family need. The emission half remains chip-class / design-with-user and is untouched.

Historical — the r57a state (superseded above)

math/big222 of 226 (measured 2026-08-09, r57a); three roots left, two of them the alloc model

Supersedes the 2026-08-02 state below and the board’s 9 of 226 census. Both were taken with r56f’s named-numeric shift-masking defect live — the defect whose corrupted Lehmer cosequences made GCD’s for len(B.abs) > 1 loop stop converging, i.e. an infinite loop inside this very package. With it fixed the suite runs to completion: 226 verdicts, 222 matching, 83 excluded (examples + benchmarks, Phase-4D). The “nil x/y GCD panic” recorded below did not reproduce; lehmerGCD’s converted entry guards its extended outputs correctly (if (Ꮡx != nil) { x = Ua.Value; … }, int.cs:970) and big.Rat’s SetFracnormGCD path runs, so that root is closed too.

The four remaining rows are three roots, and only ONE is a defect:

  1. TestNewIntAllocs — the AllocsPerRun-reports-BYTES shim, fifth member. Measured 81,600 B over 100 runs; the assert wants 0 allocations from x.Add(x, NewInt(0)) and is handed 816.000000 “allocations” that are really bytes per run. Report-never-disclose, per the standing rule.
  2. TestMulUnbalanced — the same alloc model, measured honestly in bytes on both sides. “multiplication uses too much memory (20487200 > 51 times the size of inputs)”. Go reads runtime.MemStats.TotalAlloc around the multiply and bounds it at 51× the input words, so unlike row 1 the units ARE comparable — the converted nat simply allocates far more per word, which is the ж<T>/slice box model r56d decomposed to the byte on nistec. Not a disclosure and not a correctness failure: it is the allocation-model overhead stated as a budget, and it will move when that model does, not before.
  3. TestGobEncodingNilIntInSlice / TestGobEncodingNilRatInSlice — a REAL defect, and a general one: Go’s TYPED-NIL interface does not survive the conversion. Both panic identically with “interface conversion: interface {} is nil, not gob.GobEncoder” inside gob.EncodeValue (encoder.cs:303). In Go, an element of make([]*Int, 1) is a nil *Int, so v.Interface() yields a non-nil interface carrying (type=*Int, value=nil); the assertion .(GobEncoder) therefore SUCCEEDS and GobEncode is dispatched on a nil receiver, which math/big handles explicitly — func (x *Int) GobEncode() { if x == nil { return nil, nil } } (intmarsh.go:18). The whole test exists to exercise that contract. In the conversion the nil ж<ΔInt> reaches the interface as a plain null, losing its type identity, so the assertion fails and gob’s catchError re-panics (correctly — Go re-panics on a non-gobError too). Scope is corpus-wide, not math/big’s: any x.(I) on a typed-nil pointer takes the wrong arm, and this is one of Go’s most load-bearing interface behaviors. Worth noting golib already has the vocabulary — ж<T> distinguishes IsNilStandardPointer from a null reference (DerefOrNull, ж.PointerExtensions.cs:359), so a typed nil is representable; what is missing is producing one where a nil pointer is boxed into an interface (the reflection bridge’s Value.Interface() knows the static type and is the narrow place to start). Chip-class / design-with-user, not a lane fix — it changes what == nil means for every converted interface. Also blocks part of encoding/gob (99 of 106).

Consequence for banking: math/big cannot bank on roots 1 and 2 regardless of root 3, so fixing the typed-nil defect pays encoding/gob and the corpus, not this row. The package is nonetheless now one of the most thoroughly exercised in the corpus — 222 verdicts across Int, Rat, Float, nat, decimal/float conversion, primality, GCD and the marshalling surfaces.

Historical — the 2026-08-02 state (superseded above)

Until r37-time-os-fin math/big was in the 302-package clean compile and could not perform a single operation: the math_big_pure_go build tag was missing from the default set, so all eight of arith_decl.go’s assembly-backed declarations converted to throwing partial stubs (detail in the TestTruncateRound row above and in ConversionStrategies-Reference.md). With the tag applied, a direct Go-vs-C# probe — SetString, Mul, Add, Sub, Lsh, Rsh, Quo, Rem, Exp, big.Float.Mul, and a 64-deep Mul chain — is byte-identical to go run. Before the fix the same probe died on its first line, inside big.Int.SetString.

One root remains before the package’s own suite is worth running: big.Int.GCD with nil x/y panics with a nil pointer dereference. Repro is three lines — new(big.Int).GCD(nil, nil, a, b) — and big.Rat reaches it on the ordinary path (SetFracnormGCD), so all of big.Rat is behind it. Go documents nil x/y as the normal non-extended call, so this is a real conversion defect, not an unsupported shape. Measured on BOTH the committed corpus and a fresh whole-stdlib reconvert, so it is not the pending deref-accessor rebank: lehmerGCD’s entry aliases already take the current DerefOrNull/DerefOrNil accessors in the reconverted emission and it panics identically. Not rooted further — it was found in passing while verifying the build-tag fix and is out of that lane’s scope.

Runtime failures

Package State
hash/maphash DONE 2026-07-29 — 22/22, banked. Computed float constants that directly use a named untyped integer wrapper now materialize once at the destination’s float width; TestSmhasherAvalanche’s mean is 50000 and the full SMHasher matrix matches Go.
compress/flate DONE 2026-07-31 — 64/64, banked. TestWriterReset was NOT a state difference: deepValueEqual’s Func arm returned false unconditionally, on the reasoning that two nil funcs would already have matched the invalid == invalid rule at the top. That holds only for a nil func boxed as any; a nil func reached as a struct FIELD is typed by its static func type and is a VALID nil Value, so the arm declared every pair of nil func fields unequal — and the test nils fill/step/bulkHasher/bestSpeed precisely so DeepEqual can compare the rest. Go’s rule is “equal iff both nil”; the arm now asks it. The tell was that every field compared equal individually while the enclosing struct did not.
image/gif DONE 2026-07-31 — 28/28, banked. TestWriter was the blank-import module-initializer gap and nothing else: with _ "image/png"’s init() forced, the PNG decoder registers and image.Decode reads ../testdata/video-001.png. No image/gif defect existed.
image/png DONE 2026-07-31 — 28/28, banked. The old “does not validate” probe was stale by weeks: a fresh run split 15 of 17 top-level tests passing, and the remainder was ONE defect with a second stacked on top of it. The real root is that Go’s slice-to-array-pointer conversion (*[N]T)(s) was emitted as a copy. png’s cbTCA8 row loop writes every un-premultiplied pixel through d := (*[4]byte)(dst), so a non-opaque RGBA source encoded as an all-zero image — and the two TestWriteRGBA subtests that did pass passed by luck (the opaque one takes cbTC8 entirely; the fully-transparent one wants all-zero output, which is also what a lost write produces). array<T> now carries a (low, length) window and the pointer form takes array<T>.Alias; the value form [N]T(s) still copies, because Go’s does. Above it sat a redundant value adapter — see the row below — which only ever surfaced on diff’s failure path, so fixing the aliasing greened the package on its own.
image/draw DONE 2026-07-31 — 9/9, banked. All four failures were two defects, both fixed at the root. TestDraw was the address-taken value parameter box-copy: DrawMask’s clip(dst, &r, src, &sp, mask, &mp) narrows all three in place, and Ꮡ(r) boxed a COPY, so the draw loop ran on the unclipped rectangle. (The empty-Pix panic above was that same unclipped geometry, not an assertion defect — the guess in this row was wrong.) The other three were value adapters carrying no Go dynamic type, so image.Image type switches took the wrong arm.

RETRACTED — the encoding/base32/base64 “mode-unstable production emission” was STALE BANKED OUTPUT

This section previously recorded the receiver-box drift on encoding/base32/base32.cs (3/3 lines) and encoding/base64/base64.cs (6/6) as a mode disagreement — the receiver-box analysis reaching a different answer under -tests than under -stdlib — and ruled the drift “expected sweep output, and must be restored, never banked”. Both halves of that are wrong. Re-measured 2026-07-31 on master:

Emission base32.cs / base64.cs
whole-stdlib -stdlib -comments reconvert, master converter boxed (encʗp + ref var enc = ref heap(…) + return Ꮡenc)
the -tests pipeline’s regenerated production .cs byte-identical to the above
the committed files unboxed — the pre-c23caf4f9 form

The two modes agree exactly. What actually drifted is the corpus: c23caf4f9 (an address-taken value RECEIVER heap-boxes) landed before this row was written and moved these two files, and they were never rebanked — so every sweep since compared a current emission against a stale bank and restored it again, three times over. The prior “three measurements” attribution is charter §9’s false-alarm trap (a) in its textbook form: a bin/go2cs.exe built before c23caf4f9 reproduces the reported result exactly, including the claim that -stdlib “equals the committed file”. Same origin as the internal/zstd / crypto/hmac retraction above — force go build -o bin/go2cs.exe before recording a coupling.

There is no mode-instability to close here, and there cannot be: a method’s receiver is function-scoped, so its address can only be taken inside its own method body. A production method’s body is production source; a _test.go file cannot add a statement to it. The receiver-box analysis therefore reads an input -tests mode cannot widen — structurally unlike the package-level-var case the sibling-scan fix above exists for, where a _test.go &g genuinely does address production storage. Recorded as a property of the rule in ConversionStrategies-Reference.md, An address-taken VALUE PARAMETER heap-boxes too.

Both files are banked (2026-07-31) at the boxed emission, and both packages re-validate at their exact counts (base32 26, base64 17). The standing sweep drift is closed.

Open CLOSED — the REDUNDANT adapter was a key mismatch (value 2026-07-31, pointer 2026-08-02)

DONE. Both halves landed at the converter: ONE key spelling shared by the record loader and the cast site (implementRecordKey / canonicalImplementRecordIfaceName, named valueImplementKey / canonicalValueRecordIfaceName until the pointer set joined them), and the func-type exclusion this row demanded (valueRecordRealizesAsPartialStruct, gating on the target’s Go underlying being a non-*types.Signature). Whole-stdlib A/B, both roots seeded, 302/302 converted per side: 13 files, 497 constructions removed, every changed line the same edit, plus the 16 records that existed only to generate those adapters; the rest of the corpus adapter census is identical count for count, HandlerFuncᴠΔHandler included.

Two corrections to the row as filed below, both measured rather than reasoned:

The deferred POINTER increment is now DONE too (2026-08-02). importedPointerImplements carried the same two divergences, and both sides now compose through the same shared implementRecordKey — no second naming path, and canonicalRecordIfaceName retired with its last caller. The trust rule really is different, and it turned out to be weaker, not stronger: (Pointer = true) is precisely the shape ImplementGenerator realizes as the adapter class <T>ж<Iface>, so the record’s existence IS the answer and no valueRecordRealizesAsPartialStruct analogue is needed (the delegate hazard cannot arise on a set whose every member already took the adapter route). Measured before deciding, per the row’s own discipline: an instrumented whole-stdlib run classified all 1,224 pointer lookups as 289 hits, 868 genuine no-records, and 67 near-misses — every one a true pair, no candidate-key regressions.

Whole-stdlib A/B, both roots seeded, 304/304 per side: 31 files, 66 constructions rewritten from the consumer’s local <pkg>_<T>ж<Iface> to the declaring package’s own <pkg>.<T>ж<Iface>, plus the 37 (Pointer = true) records that existed only to generate those local classes. Zero additions; the total adapter-construction census is unchanged at 4348, so this is a one-for-one redirection rather than a removal — the pointer form’s dead machinery is a duplicate class, not an extra allocation. By declaring package: text/template/parse 33, go/types 20, image 4, net/http 4, net/url 2, net/textproto 1, go/internal/srcimporter 1, go/build/constraint 1. go2cs-stdlib.slnx 0 errors on the overlaid tree; CNR byte-identical across all 544 behavioral packages.

Two findings worth carrying forward:

Rule, both compositions, and the trust gates: ConversionStrategies-Reference.md, A foreign implement record is keyed in ONE spelling, and a VALUE one is trusted only for a partial struct. Guarded by the ForeignValueImplementSuppression behavioral test (a multi-segment sibling that DOES convert its own values, a collision-renamed implementer, and a named FUNC type as the live negative — the pre-fix converter emits five adapters where the fixed one emits the func’s alone), with ValueAdapterDynamicType as its byte-identical complement, and by the pointer sibling ForeignPointerImplementSuppression (a self-converting sibling with a collision-renamed *Tone and an ordinary *Plain as the positives, against two live negatives: *Lone, a pair the sibling satisfies but never records, and shade.Level, an interface with the same SIMPLE name — pre-fix emits four local adapters, fixed emits the two negatives’ alone).

The row as originally filed follows.

Converting a foreign package’s value into an interface that package itself declares emits a local <pkg>_<T>ᴠ<Iface> adapter class even though the declaring assembly already implements the pair. The converter already knows not to (convCallExpr’s both-foreign value arm consults importedValueImplements, recorded from the dependency’s package_info.cs [assembly: GoImplement<T, Iface>] lines) — the lookup simply never matches for a multi-segment import path, because the two sides compose the interface key differently. Measured, not reasoned (canonicalRecordIfaceName called directly):

import path load side (from the package NAME) use side (the rendered C# name)  
bufio bufio_package.Reader bufio_package.Reader match
image/color color_package.Color image.color_package.Color miss
encoding/binary binary_package.ByteOrder encoding.binary_package.ByteOrder miss

Corpus footprint of the redundant constructions: 478 color_ΔRGBAᴠColor, 79 binary_{big,little}Endianᴠ ByteOrder, plus the rest of image/color’s models — every same-package value-form foreign record in the corpus is a nested path, and not one is single-segment.

It is not merely dead machinery. The adapter is a second identity for one Go value: reflect and fmt see the adapter object where the Value’s own type says the wrapped struct, which is how it surfaced — image/png’s diff printing %v of a color.Color died with System.ArgumentException: Field 'R' … is not a field on the target object which is of type 'go.image_package+color_NRGBAᴠColor'. (It masked the aliasing defect above: fixing the aliasing removed the failure that reached the print.) A direct-boxed NRGBA and an adapter-wrapped one also compare unequal in one direction.

Any fix must clear one hazard first. The record says nothing about how the DECLARING assembly realized the pair, and a named FUNC type cannot be realized as a partial struct — net/http’s [assembly: GoImplement<HandlerFunc, ΔHandler>] is realized as an adapter class there, so trusting the record for it would emit a bare delegate into an interface slot (CS0029) in expvar, net/http/cgi and three more. The usable gate is the target’s Go underlying: trust the record only when it is not a *types.Signature.

Two live consumers are named by the reflection arc (§6.1’s adapter-type Kind/Elem follow-up), and they do NOT overlap: this row removes adapters that were never needed, while the reflection chip must still unwrap the ones that genuinely are (color_PaletteᴠModel, syscall_ΔSignalᴠΔSignal, net_Connᴠ*). Both are real; neither subsumes the other.

Open — intermittent, on an already-banked package

Package State
hash/maphash INTERMITTENT (filed 2026-07-31, not rooted). Banked and validating at 22/22, but ONE validated sweep died mid-TestSmhasher* with a .NET FailFast on a worker thread, the fault attributed to go.UntypedInt.CastTo<ulong>(Int64) with RhThrowHwEx on the stack. Two sibling sweeps in the same wave ran maphash to its exact banked count, and so did the r26 integration train’s own 66-package sweep over the three lanes combined (66 pass / 0 fail, 2,454 s), which ran maphash to its exact 22. The attribution is almost certainly misleading: CastTo is a raw reinterpret and cannot raise a hardware exception, so the likely fault is an NRE/AV in an inlined caller credited to the frame it was inlined into — e.g. unboxing a null any into UntypedInt on the worker path. SMHasher seeds randomly, which is what makes it probabilistic and why it reproduces on no fixed input. Rooted enough to file, not enough to fix: the next sighting should capture the full FailFast stack and the seed.

The blank-import module-initializer gap — CLOSED (2026-07-31)

Go’s _ "image/png" imports a package purely for the side effect of its init(), and the language guarantees that initializer runs before main. The converter maps a Go init() onto [GoInit], which csproj-template.xml aliases to .NET’s [ModuleInitializer] — the right shape, and a weaker guarantee: a module initializer fires at first access to something in its module, so an assembly nothing in the program ever names is never loaded and its initializer never runs. A blank import is by definition the case that names nothing, and the observable form was a registry that stays empty: image/gif’s writer_test.go blank-imports _ "image/png" so png’s init() calls image.RegisterFormat (image/png/reader.cs), it never ran, and TestWriter failed with ../testdata/video-001.png image: unknown format at 27 of 28.

The converter now emits, at the top of the importing file’s class body, a hook that forces it:

// blank import: go.image.png_package (side effects only; no using emitted — a `using _` alias hijacks C# discards)
[GoInit] internal static void initᴛᴛblankImportimagepng() { builtin.initPackage(typeof(go.image.png_package)); }

builtin.initPackage is RuntimeHelpers.RunModuleConstructor, which the runtime guarantees runs a module constructor at most once (so several blank importers of one package are no-ops) and which is measured AOT-safe — under Native AOT the gap does not arise at all, since a single native image has no lazy assembly load. One hook per (assembly, imported package), named from the import path so two blank imports in one file cannot collide; Go’s pseudo-packages (unsafe, builtin, C) are skipped because the language gives them no initialization, which holds the corpus blast radius to three filescrypto/x509 (sha1/sha256/sha512), runtime/metrics (runtime), runtime/race (amd64v1) — rather than the seventy that carry import _ "unsafe" for //go:linkname. Full rule, the ordering reasoning, and the deliberately-deferred alternative (forcing every import eagerly in dependency order — the only way to reproduce Go’s init ordering in full, at the cost of loading the whole transitive assembly closure at startup): docs/ConversionStrategies-Reference.md, A blank import forces the imported package’s init to run. Guarded by the BlankImportSideEffects behavioral test (a registry two blank-imported siblings fill from their inits, read back by an importer that never names either) plus the TestBlankImportInitName / TestNoInitPseudoPackages converter unit tests.

The other consumers this unblocks are all registration-by-blank-import: database/sql drivers (_ "github.com/…/mysql"sql.Register), net/http/pprof (its init() installs the /debug/pprof handlers), image/png/image/jpeg as decoders for anything that calls image.Decode, and time/tzdata. A blank import was never invisible to the build — it is in go/packages’ import list, so the project reference already existed; only the load did not happen.

os — 681 of 683 rows agree + 1 disclosed; ONE residual, now ROOTED (r35-os → r39-osalloc, 2026-08-03)

CURRENT STATE, 2026-09-02 (lane claude/sub-os-row), measured on the i7 at Debug/tiered against a go1.23.12 oracle: os is 683 of 685 agreeing + 1 disclosed (TestUTF16Alloc), with exactly ONE real divergence left — TestWriteStringAlloc. Go: 665 pass / 20 skip. C#: 663 pass / 20 skip / 2 fail. 39 capability-excluded declarations, 4 gated capability entries. The NetShareAdd/SHARE_INFO_2 host-killer is CLOSED (see the ✅ block in os’s “REGRESSION” is a HOST CAPABILITY at the end of this file); the row moved 682 → 683 agreeing, two real divergences → one. os does not bank, and now for exactly one reason: ruling #1 holds a want-ZERO alloc assert to be satisfiable in principle, so TestWriteStringAlloc is a real divergence rather than a disclosure. It currently reads expected 0 allocs for File.WriteString, got 17 (1,700 golib allocations / 132,000 bytes over 100 runs). The sub-sections below are the arc that got it here.

Header as it stood before 2026-09-02: Current state is the r39-osalloc sub-section at the END of this block — 681 of 683 rows agreeing (173 of 175 top-level), 34 matching skips, 4 capability-excluded, and exactly one real divergence (TestWriteStringAlloc). r39 decomposed that divergence to the byte and closed 65.6 % of it in two golib fixes; the remainder is architectural and is recorded there as an arc, so os does NOT bank on this row. Everything between here and there is the arc that got it there, kept for its roots and its retractions. The header below is the r36 state.

Header as it stood before r38-os-fin: os — 164 of 178 match + 1 disclosed; the unreached block is gone (r35-os → r36-os-tail, 2026-08-02)

Measured with go2cs -tests -test-action all -test-timeout 35m "<GOROOT>/src/os" src/core/os. os builds with 0 errors and the host runs. Progression across the arc, all from one pipeline command: 48 agreeing → 141 → 158 → 164; the first jump from the build blockers, the second from the readReparseLink host-killer, the third from the element-alias arm and the run-directory shape below. ⚠ Give it 35 m, not 15 — at 15 m under sibling-worktree load the host self-terminated at 900 s and reported the tail as unreached.

  Go C# (r35) C# (r36)
top-level tests 178 (143 pass · 34 skip · 1 fail) 166 reached (123 pass · 34 skip · 8 fail · 1 infra-error) 177 reached (129 pass · 34 skip · 12 fail · 2 infra-error)
agreeing   158 164
disclosed   1 1 (TestUTF16Alloc, alloc-count-semantics)
real mismatches   7 13
unreached (host died)   12 1 (TestPipeEOF)

The mismatch count RISES while agreement rises because the r35 host died at test ~50: eleven of the thirteen rows below were never reached before, so they were counted as unreached rather than as failures. Six of them are load-sensitive (they pass standalone), and of the genuinely stable ones, every row is now rooted.

TestReadStdin’s 462 subtests still fill the errors list, and it is a NAME-ENCODING artifact, not a failure. Two of its inputs contain \x1a (SUB). go test -json renders that rune in the subtest name as the ESCAPED text \x1a; the C# host emits the raw rune, so the oracle pairs each subtest as Go="pass" C#="" plus Go="" C#="pass" — 924 lines that read like a mass failure and are not one. The top-level TestReadStdin AGREES. Fixing it means escaping non-printable runes in TestReporter’s reported names the way Go does; that changes every package’s reported subtest names, so it wants the full sweep as its gate and is recorded here rather than done in passing.

Closed in this arc

Closed in the r36-os-tail follow-up (2026-08-02)

A/B footprint of the element-alias arm — 13 files, all classified

Measured as a two-temp-root reconvert (base converter vs this one, same seed) rather than against the committed tree: src/core at af5df9e16 carries ~132 files of pre-existing drift from other lanes’ converter changes, which a diff-vs-HEAD would have mixed in. No file is in a validated package. The corpus builds with 0 errors on the overlaid reconvert (304 projects).

Sites Files Classification
(*[4]byte)(unsafe.Pointer(n.Data(off))) in abi.Name.pkgPath reflect/type.cs, internal/reflectlite/type.cs, runtime/type.cs Strict improvement. The name blob IS managed byte storage, so the window is real where the address route punned an array<byte> struct (a reference + bounds) out of four name bytes.
reflect.rtype.gcSlice over t.t.GCData reflect/type.cs Read-only GC-bitmap view. Was a ReadOnlySpan copy of raw memory, now a window (or the identical address fallback when GCData is not managed storage).
reparse PathBuffer decode internal/syscall/windows/reparse_windows.cs ×2, syscall/syscall_windows.cs ×2 Same raw-metal family as readReparseLink, unchanged in outcome. PathBuffer [1]uint16 is a variable-length tail standing in for kernel bytes, so a managed window over it is one element and the old span read GC heap past that one element. Neither can work; the new form fails LOUDLY (a Go-style slice-bounds panic) instead of returning garbage. os does not reach these — its own decode is hand-owned (os/file_windows_impl.cs).
Win32 DNS record strings net/lookup_windows.cs ×3 Native pointers, so golib takes the address fallback: byte-identical behavior.
runtime internals runtime/{select,heapdump,mbitmap,string}.cs Paths the managed runtime does not execute (selectgo is superseded by ChanCore).
AllowUnsafeBlocks truefalse internal.syscall.windows.csproj, net.csproj, reflect.csproj Consequence, and a welcome one: the span fusion was those packages’ ONLY unsafe usage.

The huge sentinel length is why the emission casts. runtime’s findnull/findnullw/ gostringw convert to *[1<<47-1]byte / *[1<<46-1]uint16; such a literal types as long in C# and has no implicit conversion to nint (CS1503 ×3, caught by the corpus build, fixed by csNintLiteral). It is also why AliasPointer CLAMPS: an unclamped (int) of that length would overflow.

The residual, every row rooted

Row Cost Root
host-killer: an ExecutionEngineException whose SITE MOVES between runs 12–29 Not a defect at the crash site. Three runs died in three different places (TestReadlink’s AV, then syscall.Environ, then syscall.encodeWTF16 under os.MkdirAll), and each site runs CLEAN standalone — syscall.Environ() was probed end-to-end in its own converted program and returns the real block. That is accumulated heap corruption, and the strongest candidate is os_windows_test.go’s own createMountPoint: it reinterprets a managed []byte as a windows.MountPointReparseBuffer and WRITES four uint16 fields through it. golib’s Reinterpret cannot alias a reference-bearing struct, so the fallback hands back (ж<TDst>)(uintptr)box — a transient pinned address of a managed slice, written through after its pin expired. Remedy candidates, both bigger than a package arc: make the non-representable fallback PIN the source for the derived box’s lifetime, or make it fail loudly instead of returning a stale address. A blanket “fail loudly” is NOT available — reflect’s prefix-downcast idiom ((*structType)(unsafe.Pointer(t))) deliberately depends on the address route.
TestDirectoryJunction 1 The same createMountPoint reinterpret, this time surfacing as a contained IndexOutOfRangeException at &buf.PathBuffer[0]. Raw metal on a non-native type, in TEST code that cannot be hand-owned — no converter or golib change can lay a managed array reference over inline OS bytes.
TestReadStdin (462 subtests) 1 CLOSED 2026-08-02 (r36-os-tail). The remedy this row named was the right one: (*[N]T)(unsafe.Pointer(p)) over a *T now emits array<T>.AliasPointer(p, N), a real window over the storage p is an element of, instead of the raw-address route whose [:n] fusion produced a slice<T> COPY. All 462 subtests pass. Guard ArrayPointerElementAlias; behavioral footprint one justified re-baseline (PointerCastSliceReinterpret’s same-element-type arm).
TestNilFileMethods 1 CLOSED 2026-08-02 (r36-nilrecv) — the alternative this row named is the one that works. See A nil RECEIVER is nil-deferring, not nil-safe below.
TestReadDir 1 CLOSED 2026-08-02 (r36-os-tail). The remedy this row named, implemented: the converter enumerates the package directory’s immediate subdirectory NAMES (testFixtureDirectories, part of the manifest and the input digest) and the host creates them EMPTY in its run root before staging fixtures (TestHost.CreateFixtureDirectories). ReadDir(".") now sees the same shape go test does. Blast radius is far smaller than feared: across the validated roster only os, io and math/rand have any subdirectory beyond the testdata already staged with contents.
TestCmdArgs 1 Newly REACHED 2026-08-02 (it was inside r35’s unreached block). Raw metal, pre-existing: syscall.CommandLineToArgv returns a NATIVE pointer, so (ж<array<ж<array<uint16>>>>)(uintptr)(r0) reads an array<T> STRUCT — a backing reference plus bounds — out of the pointer array’s own bytes, and (*argv)[:argc] then slices with fabricated bounds: ArgumentException: Indices low, high and max represent a range outside bounds of the array reference. Untouched by the element-alias arm, which requires a Go POINTER source; a uintptr source keeps the address route by design. Same family as TestDirectoryJunction and the createMountPoint reinterpret.
TestGetppid 1 Newly REACHED 2026-08-02. The child runs and answers, but syscall.Getppid() reports 0 where the parent’s pid is expected — getProcessEntry’s Process32First/Next walk finds no entry. A real, contained syscall gap (it does NOT fault, which is what the struct-passing census below already recorded for this wrapper).
TestReadlink 0–1 Newly REACHED 2026-08-02, and it is the symlink-privilege row (the os.runtime_randtestenv.MustHaveSymlink row above) surfacing at last: standalone, its six symlink_* subtests fail with “A required privilege is not held by the client” while the three junction_* subtests PASS. Go SKIPS the symlink arms for want of the privilege; C# runs and fails them. Confirms that row’s prediction — clearing MustHaveSymlink converts these to matching skips rather than passes. (It agreed in the final full run, so it is privilege/timing-sensitive as well.)
TestRootDirAsTemp 1 Newly REACHED 2026-08-02. The test re-execs the host with TMP/TEMP pointed at a drive ROOT to check TempDir(); the CHILD host then cannot create its own isolated run directory there — DirectoryNotFoundException: Could not find a part of the path 'Z:\go2cs-tests\os\…' out of TestHost.Run’s Directory.CreateDirectory(workingDirectory). The isolation model and the test’s premise collide: Go’s test binary needs no scratch directory of its own. Pre-existing (same line before and after this lane’s host change).
TestWriteStringAlloc 1 AllocsPerRun bounded at ZERO. Deliberately not disclosed: the byte-derived shim CAN report 0, so the io/strings unit-mismatch ruling does not cover it. Go’s WriteString avoids the copy with unsafe.Slice over the string’s own bytes; a go2cs @string is its own storage, so the write path allocates (measured 9088 bytes). A real divergence — an sstring-shaped optimization, not a disclosure.
TestRemoveAllWithExecutedProcess 1 ROOTED 2026-08-02 (r36-os-tail), and it is the .NET deployment model, not a conversion defect. The test copies os.Executable() — one file — into a fresh t.TempDir() 100 times and runs each copy, to make Windows hold an image handle. os.Executable() is CORRECT: it returns the test host’s apphost (os.tests.exe). But an apphost is a stub bound at build time to a managed assembly of the same base name that must sit BESIDE it, so a single-file copy can never run. Reproduced standalone by copying any converted project’s apphost alone into a temp directory: exit 0x8000809a = hostfxr LibHostAppRootFindFailure, message “The application to execute does not exist: ‘…<AssemblyName>.dll’” — byte-for-byte the code the test reports. Go’s test binary is statically linked, which is the only reason its premise holds there. The sole fix that would satisfy it is publishing every converted test host self-contained single-file (≈70 MB and a publish instead of a build, per package) — disproportionate to one test. Environment divergence; leave failing.
TestStartProcess/relative 1 RE-MEASURED 2026-08-02 (r36-os-tail): PASSES — 3/3 standalone and in the full run, with nothing in this lane touching joinExeDirAndFName/FullPath/StartProcess. It belongs to the load-sensitive child-output class below, not to a code defect.
load-sensitive child-process flakes ~6 New classification 2026-08-02 (r36-os-tail). A set of tests that pass standalone and fail only in the full parallel run, all with one signature: the child process produced NO output (system hostname of "", Child returned "[]", reports stdin is not pipe '') or a t.TempDir() that had vanished. The membership MOVES between runs, which is the tell: across the two full runs measured, TestFileReaddir/TempDir, TestStatLxSymLink and TestReadlink failed in one and passed in the other, while TestStartProcess and TestLongPath did the reverse; TestHostname, TestExecutable and TestStatStdin failed in both yet pass 3/3 standalone. Measured while three sibling worktrees ran their own pipelines. Treat any single-run failure in this set as unconfirmed until it is reproduced standalone — that is how TestStartProcess/relative came to be recorded as a rooted mismatch when it is not one.
TestStatLxSymLink intermittent t.TempDir() cleanup hit a file “used by another process” — a handle the host had not released yet. Same load-sensitive family as the row above.

A nil RECEIVER is nil-DEFERRING, not nil-safe — TestNilFileMethods closed (r36-nilrecv, 2026-08-02)

The row above asked for a ruling and named the alternative in its last clause. That alternative is the right one, and it is now built and gated: golib’s DerefOrNull binds Unsafe.NullRef<T>() for a nil box, and every pointer-receiver entry alias uses it unconditionally. A null ref is legal to HOLD and to pass on as ref T; it faults on USE. So the receiver panic is not raised at entry (today’s defect) and not discarded (the naive widening’s defect) — it lands where Go’s does, after any side effect the body performed first, as NullReferenceExceptionTryAsPanic → Go’s own runtime error: invalid memory address or nil pointer dereference, recoverable and printed verbatim.

Because it is faithful whether or not the body guards, there is no predicate: isComparedDirectBoxReceiverIdent is subsumed and deleted. The alias is emitted in TWO places and go2cs-gen’s ReceiverMethodTemplate — the bridge reaching a ref T receiver through a box — deref’d eagerly too, one call frame EARLIER than Go; both now take the accessor.

Measured. os pipeline: TestNilFileMethodspass (all fifteen methods return ErrInvalid). Footprint against a control reconvert with the base converter (so pre-existing corpus staleness is subtracted): 378 stdlib files in 132 packages, and every changed line is the alias — 1858 .Value

The same defect is still open for pointer PARAMETERS — 3167 entry aliases in the corpus keep the eager .Value, mitigated only by the two heuristics (nilSafePtrParamNames: nil-COMPARED in the body, or passed nil at a same-package call site), and those two route to the nil-SAFE DerefOrNil, which is the silent-default(T) accessor. Go’s rule is identical for a parameter and a receiver, so the complete fix is to give parameters the same unconditional DerefOrNull — mechanically trivial now, but a much larger emission footprint that wants its own measurement and its own ruling.

The os run that closed this row reached FARTHER than the banked one (177 top-level tests vs 166), because the moving-site ExecutionEngineException above did not fire. The newly-reached tests bring their own failures (TestCmdArgs slice-bounds in test code, plus TestExecutable/TestGetppid/TestStatStdin/ TestRootDirAsTemp/TestHostname/TestUserConfigDir/TestLongPathAbs), none of them receiver-shaped. Read the arc’s residual table as measured against 166 reached; a fresh baseline needs a quiet machine.

A NEW member of the -tests-closure production-file family, found by this arc’s canaries and owed to the next rebank. Since the validation-badge work (2026-08-02) every package’s .csproj carries an eight-line “Ship this package’s versioned validation proof sheet” block, emitted by the -stdlib driver, which has the roster. A single-package -tests run does not, so it regenerates the .csproj without those eight lines — 0 8 on git diff --numstat, in EVERY banked package a sweep touches. Confirmed on both canaries below (path/filepath, io) and on os itself; it predates this arc and is caused by no change in it. Classify it with the other -tests-closure files: restore, never bank, and let the whole-corpus regen level it.

Spot-canaries on the post-change tree, both at their banked counts: path/filepathstatus: validated, matched: true, 55 top-level (37 pass · 18 skip), 0 errors. iostatus: validated, matched: true, 54 top-level, 2 disclosed, 0 errors (its production package_info.cs shows the documented +2 satisfies-not-witnesses records — restore, don’t chase).

Re-run after the r36-os-tail changes, both still at their roster counts: path/filepath61 validated (20 skips agreeing), io59 validated, 2 disclosed. Their -tests-closure churn is the documented set and nothing else: the 0 8 validation-proof block on every .csproj, io’s +2 package_info.cs records, and — pre-existing, from converter changes landed since the last whole-corpus regen — io’s package_test_info.cs implicit-conv record set, io_test.cs and multi_test.cs. The committed go2cs_test_host.cs does NOT churn: the run-directory list is omitted entirely when a package has no subdirectories, which is 56 of the 71 banked packages, so only the 15 that genuinely have one differ (and only by the lines that describe it).

r36-pin, same day — the moving-crash row RETRACTED, and the real top row named. The r35 attribution of the moving-site ExecutionEngineException to createMountPoint’s transient-pin write was wrong: a pre-fix control run of the whole suite at base af5df9e16 produced ZERO ExecutionEngine/AccessViolation faults — whatever closed that crash closed it inside the r35 train itself, uncredited. (The transient-pin defect is nonetheless REAL and fixed — Reinterpret’s fallback pinned for one statement while the derived pointer lived on; deterministic guard ReinterpretPinLifetime, rule in ConversionStrategies-Reference — it just was not os’s crash.) os’s dominant remaining cost is the blocking-pipe family: internal/poll on Windows does not unblock an in-progress read on Close, hanging TestPipeEOF/TestPipeIOCloseRace + two siblings and starving six more tests of child stdout (TestHostname, TestExecutable, TestGetppid, TestStatStdin, both TestStartProcess arms, TestRootDirAsTemp) — its own future arc, and the reason pipeline invocations leak os.tests.exe. (Closed 2026-08-02 by r37-poll, below — with the diagnosis half right: the hang was real and is fixed, but it was not in internal/poll, and the six child-stdout rows did not follow it.) ⚠ Scheduling: never run two lanes against ONE package’s pipeline — the host is named per package, so the rename defence cannot apply; the tell for a sibling-killed run is go2cs_test_results.json carrying the PREVIOUS run’s mtime.

Attribution was measured, not asserted — FIVE runs, and only one test is converter-determined (r36-nilrecv). Three with the base converter, two with the fix. TestNilFileMethods: fail 3/3 on base, pass 2/2 with the fix. Every other test that moved, moved in BOTH arms — TestHostname (2 base, 2 fix), TestStatLxSymLink (2, 1), TestFileReaddir (1, 2), TestReaddirnamesOneAtATime (1, 1), TestProgWideChdir (1, 0), TestCopyFS (0, 1), TestLongPathAbs/TestUserConfigDir (0, 1 each). The same-converter run-to-run spread is 3–4 tests and the outcome distributions coincide (base run 2 landed on 125 pass · 14 fail · 3 infra — identical to the fix’s run 1). The lesson for the next arc: a single os run cannot attribute a one-test delta. Pair every claim with a control run of the unchanged converter.

The blocking-pipe family — CLOSED 2026-08-02 (r37-poll), and it was never internal/poll

Measured, -test-action all -test-timeout 35m, three runs on the fixed tree: 165 agreeing of 178 (twice, identically) against the banked 164, with 177 reached. The whole blocking-pipe family flips from HANG to PASS — TestPipeCloseRace, TestPipeIOCloseRace, TestFdRace, TestFdReadRace, TestCloseWithBlockingReadByFd, TestCloseWithBlockingReadByNewFile, TestClosedPipeRaceRead, TestClosedPipeRaceWrite — and no run leaks an os.tests.exe.

The conversion of internal/poll was faithful all along, and so was everything under it. Probed bottom-up rather than reasoned about: syscall.CancelIoEx really does abort a blocking ReadFile on a CreatePipe handle through the converted trampoline (a syscall-only program reproduces Go’s ERROR_OPERATION_ABORTED exactly), and FD.Read really does return Go’s read |0: file already closed. What never returned was FD.Close, parked forever in runtime_Semacquire(&fd.csema) after the reader had already finished — the stack says so directly.

The root is Go pointer identity, in two layers, both now fixed and both corpus-wide:

  1. A field promoted through an embedded POINTER was rooted at the OUTER allocation. os.File embeds *file, so &f.pfd reached through ж<File> and &file.pfd reached through ж<file> were different pointers where Go has one address. internal/poll’s semaphores are keyed by pointer identity, so os.read’s release and os.close’s acquire landed in different buckets. go2cs-gen now emits the pointer-crossing promoted accessor in a re-rooting shape (instance.@file.of(file.Ꮡpfd)), golib gains FieldPtrFunc<T,TElem> plus the matching of/at overloads, and no call site changes — the overload is chosen by the accessor’s return type. 340 accessors corpus-wide take the new form; a cross-package embed keeps the old ref form by design (its member list comes from metadata and can name fields the inner declaration never had — abi.Type.sysType, promoted into runtime.rtype, has no generated accessor to re-root through), and that fallback is fail-loud (CS0117 at the corpus build).
  2. A field reference’s SOURCE was compared by object reference, so a two-level of() chain broke. Ꮡo.of(Outer.Ꮡin).of(Inner.Ꮡv) mints a fresh intermediate box per access, so &o.in.v == &o.in.v was false at depth two (true at depth one) and a map[*T]V grew one entry per access. ж<T>.Equals/GetHashCode/PointerOrderToken now resolve the source through the chain, the way ReferentObject already did.

Guards: PipeCloseUnblocksRead (goroutine blocked on a pipe read, closer, output-compared) and EmbeddedPointerFieldIdentity (depth-2 equality, map[*T]V keying, both spellings of a pointer-embed-promoted field). Both are deterministic neutered-fix controls — on the base tree the first HANGS outright and the second prints depth2: false. Gates: full behavioral suite 535/535 + 505/505 output; CNR byte-identical across all 560 behavioral packages except the two new projects; go2cs-stdlib.slnx 304 projects, 0 errors; go test ./... in src/go2cs green.

The control run answered in twenty seconds, and it is worth knowing that it can. The five-run lesson above is about attributing a one-test delta; when the delta is a hang, the control does not need to finish — it needs a stack. With the change stashed and go2cs-gen rebuilt at base, os’s host was sampled 20 s in and had three threads already parked in internal.poll.Closeruntime_SemacquiretestClosedPipeRace twice and TestPipeIOCloseRace once, the very tests that pass on the fixed tree — plus testPipeEOF in the channel row below. Same call site, same run, before and after: that is the attribution, at a cost of one build and one sample rather than another 35-minute measurement.

What the fix did NOT do — two board predictions corrected

TestCmdArgs — the blittable-mirror remedy does NOT apply, and the reason is specific

syscall.CommandLineToArgv returns *[8192]*[8192]uint16 over a block the OS allocated, and the caller frees it: defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv)))). The converted wrapper makes a native-address box, so ~argv reads an array<ж<array<uint16>>> struct — a managed backing reference plus bounds — out of the pointer block’s own bytes, and (*argv)[:argc] then slices with fabricated bounds (ArgumentException). Hand-owning it to return a MANAGED materialization of the block fixes the walk and breaks the free: for a ж<T> whose pointee is a Go fixed array, uintptr(unsafe.Pointer(p)) takes ж.cs’s pinnedArrayData path and hands back the real GC-heap data address, so LocalFree would be asked to free GC memory — the exact STATUS_HEAP_CORRUPTION failure mode ж.cs’s own banner records for the GetEnvironmentStringsW/FreeEnvironmentStringsW pair. That trades a contained ArgumentException for a process kill, so it was not done.

What would close it is a pointer flavor golib does not have: a box that answers ADDRESS questions with the real native address while answering VALUE questions with a managed materialization — a snapshot pointer, sound precisely for read-only native output blocks. net/lookup_windows.cs’s DNS-record walks are the same shape, so it wants designing with them rather than minting for one test. Scope today is exactly one test: nothing in the converted stdlib calls syscall.CommandLineToArgv (the only other caller in GOROOT is the vendored x/sys/windows copy, which is not converted).

TestDirectoryJunction was characterized alongside it and is not the same family — no native block, no free. Its createMountPoint helper is TEST code that reinterprets a managed []byte as windows.MountPointReparseBuffer and writes four uint16 fields through it, then indexes &buf.PathBuffer[0] — a [1]uint16 inline tail standing in for kernel bytes. That is the raw-metal fork’s stub arm, in code that cannot be hand-owned, exactly as the residual table already recorded.

FOUND while attributing the above — t.TempDir() collides two tests that differ only by CASE

TestExecution.TempDir() (hand-owned src/core/testing/TestExecution.cs) builds <work>/.tmp/<SanitizeName(TestName)>/<seq>. On a case-insensitive filesystem — the Windows default — TestFileReaddir and TestFileReadDir resolve to one directory, and os’s suite runs both t.Parallel(). Whichever finishes first runs its Cleanup(() => RemoveAll(path)) and deletes the other’s temp dir mid-test; the loser fails open …\.tmp\TestFileReaddir\1: The system cannot find the file specified. Proven directly: creating TestFileReaddir\1 makes Test-Path TestFileReadDir\1 true and leaves ONE directory, and every leftover run root under %TEMP%\go2cs-tests\os\ contains exactly one of the two names, never both — while the passing test in each run is always the one that is present. Pre-existing, in the test HOST rather than in conversion, and independent of the nil-receiver change (it fired in a base-converter run too); the fix is to disambiguate the sanitized name (a case-marker suffix, or a per-execution sequence) rather than trust the test name to be a unique path component. TestFileReadDir vs TestFileReaddir is the only collision in os; the same generator will collide anywhere Go names two tests with case-only differences.

r38-os-fin (2026-08-03) — the premature-EOF root was the SYSCALL SEAM, and os lands on ONE residual

Measured twice, identically, -test-action all -test-timeout 35m: 681 of 683 rows agree (173 of 175 top-level), 1 disclosed, 34 matching skips, 4 capability-excluded, 1 residual. The run takes about five minutes now, where the base tree’s timed out at 35. Progression across the whole os arc: 48 agreeing → 141 → 158 → 164 → 681-of-683.

  base (85ce6744c) r38-os-fin
host run timed out at 35 m, wedged in TestPipeEOF completes in ~5 m
oracle error list 937 lines (462 name-encoding PAIRS + 13 real rows) 1
real top-level mismatches 13 1 (TestWriteStringAlloc)
rows agreeing (all levels) not measurable — the run never finished 681 of 683
top-level agreeing   173 of 175
disclosed 1 1 (TestUTF16Alloc)
capability-excluded 1 4

The root: every managed address handed to native code was a FORMER address

bufio.Reader.ReadBytes over a converted os.Pipe returning a premature io.EOF only under parallel load — the r37-chanrace handoff, with its probability gradient (-parallel 1: 0/4 · 2: 0/4 · 4: 1/3 · 8: 5/5 · 16: 2/2 · default: 100%) and its two surviving suspects — is neither a handle double-close nor a spurious zero-byte read. It is the ж<T>uintptr conversion, and the file that performs it had the defect written on its own front door: syscall/dll_windows.cs’s soundness note said the argument uintptrs the zsyscall wrappers capture are TRANSIENT addresses that “golib’s ж→uintptr conversion cannot pin across the call”, and judged the window “short and allocation-free”.

It is neither, for a BLOCKING syscall. Both operators end in a fixed block — a pin that lasts for one statement — and then RETURN the address as an integer, so the window is not capture→calli, it is capture→the kernel’s write. testPipeEOF parks in ReadFile on a pipe for 10 ms per read while the rest of a parallel suite allocates around it. Measured directly rather than argued: a heap(new uint32(), out var Ꮡdone) box and a Ꮡ(buf, 0) element pointer BOTH report a different address after one forced collection. The kernel then writes to neither — done stays 0, syscall.Read returns (0, nil), internal/poll’s FD.eofError turns that into io.EOF.

Every measured property of the row follows: monotone in parallelism (more threads ⇒ more allocation ⇒ more collections inside the same 10 ms window); indifferent to the finalizer bridge (a control had already ruled that out); and the buffer’s half of the same defect — 4 KB written into freed heap — is the moving-site ExecutionEngineException and the Fatal error. Internal CLR error. recorded beside it.

The fix is golib-only, and it makes the ADDRESS MODEL sound rather than patching a caller. ж<T>’s uintptr/void* operators now pin before they read (EnsureStableAddress), taking a lifetime GCHandle on the ROOT storage the pointer names — a heap box pins its own value slot, an element reference the canonical backing array, a field reference recurses to the containing allocation — on exactly the terms pinnedArrayData already used for the fixed-array case. The enabler is that a standard heap box’s value storage is now a one-element array for a T that contains no references (ж<T>.m_slot): a box is a class with reference fields and GCHandle refuses to pin anything containing pointers, so the value had nowhere pinnable to live. It is allocated EAGERLY and never migrated — heap<T>(out ж<T>) hands out a ref alias before any address is taken, so moving the storage on first address-take would strand that alias on the abandoned copy, which is this very bug one level down. A reference-bearing T gets no slot and keeps the old transient address; its C# layout is not a native layout either, so nothing can meaningfully be handed its address. RuntimeHelpers.IsReferenceOrContainsReferences<T>() is a JIT constant, so a managed-T box pays neither the branch nor the allocation. This also makes Go’s unsafe.Pointer rule 3 (pointer arithmetic through uintptr) sound, which it silently was not.

Guard: src/tests/GolibTests/NativeAddressStabilityTests.cs — a neutered-fix control across all four box kinds plus the reference-bearing negative case; with EnsureStableAddress removed every address assertion fails on the first forced collection. Rule in ConversionStrategies-Reference.md.

The gradient closes at every point it was measured at. Same matrix, three host runs per point, TestPipeEOF counted as a pass verdict rather than as the absence of an abort:

-parallel before (aborts) after (passes)
4 1 of 3 3 of 3
8 5 of 5 3 of 3
16 2 of 2 3 of 3
default (24) 6 of 6, plus 2 of 2 through the pipeline 3 of 3, plus 2 of 2 through the pipeline

What it closed, in one change: 13 residual rows → 3. TestPipeEOF and the whole child-stdout family — TestExecutable, TestStatStdin, TestHostname, both TestStartProcess arms, TestRootDirAsTemp’s spawn — because an empty child result WAS the same premature EOF, read through os/exec’s pipe. The r37-poll prediction that those six “do not follow” was right about the pipe-close fix and wrong about the family: they had one root after all, one layer down.

The other four rows, each rooted and closed

Capability-exclusions — the three sanctioned by the 2026-08-02 ruling, implemented

unsupportedRuntimeCapabilities now maps a SYMBOL to the NAME of the capability it requires, so the manifest, the comparison and the proof page show “relocatable single-file test executable” rather than a bare symbol. A key may name the test DECLARATION itself, which requiredFor honors by gating a listed function on its own account — the shape a HOST capability takes, since nothing NAMES a test and the caller-side arm can therefore never record it.

Test Capability Key
TestCmdArgs native output block with caller-side LocalFree syscall.CommandLineToArgv
TestDirectoryJunction raw-metal struct overlay on managed bytes os_test.createMountPoint
TestRemoveAllWithExecutedProcess relocatable single-file test executable os_test.TestRemoveAllWithExecutedProcess

§9 roster scan, with positive control. All 72 validated packages’ _test.go files scanned for the three keys: zero hits. Controls fired: AllocsPerRun finds 18 of the same 72 (so the loop and the paths resolve), and os itself — deliberately off the roster — hits all three. Guarded by TestUnsupportedRuntimeCapabilityGate (the lookup answers with the capability, stays package-scope, and every entry must name one) and TestUnsupportedRuntimeCapabilityGatesTheDeclarationItself (the self-gating arm, with an unlisted sibling as the negative control).

The ONE residual — TestWriteStringAlloc, and it is honestly a residual

AllocsPerRun bounded at ZERO, measured 9184 bytes per f.WriteString(…). Not a disclosure candidate — ruling #1 of 2026-08-02 stands, a want-zero assert is satisfiable and disclosing it would soften the doctrine the badges depend on — and not a capability exclusion either, since nothing here is unownable. It is a real divergence with a known shape and no cheap fix: Go’s WriteString avoids the copy with unsafe.Slice(unsafe.StringData(s), len(s)), while the converted path allocates a PinnedBuffer + box for StringData, then pays the func<T>((defer, recover) => …) closure and defer context of os.File.Write and internal/poll.FD.Write, then the syscall’s own boxes. The defer machinery dominates, so this is the sstring/GoFunc performance arc, not an os row. It moved from 9088 to 8856-9184 bytes across the arc — noise, not regression.

RETRACTED by r39-osalloc (2026-08-03): the defer machinery does NOT dominate — it is 440 of 9,208 bytes, under 5 %. The sentence above was an attribution, never a decomposition, and it named a component costing a twentieth of the bill. 62 % was two silent allocations inside ж<T>: IsNull boxing the whole pointee on every dereference (4,760 B) and of(…) minting its untyped accessor wrapper per call (968 B). Both are fixed; see the r39-osalloc sub-section below for the byte-exact decomposition and the arc that owns the rest.

os is therefore an honest NEAR-BANK: every row accounted — 681 agreeing, 1 disclosed, 34 matching skips, 4 capability-excluded — with exactly one real divergence, stable across two identical pipeline runs and five direct host runs.

Owed to the rebank: every proof page’s Excluded declarations preamble is one sentence out of date. The generator now says a declaration may need “a capability the managed runtime does not provide — a testing member the host has not implemented, or a platform behavior it provably cannot reproduce”, because a runtime capability is no longer hypothetical. The 72 committed pages still carry the old “a testing capability the host does not yet provide”. Regenerating them here would mean banking 72 pages that also carry a fresh date/converter stamp — a partial rebank by another name — so they are RESTORED with the rest of the sweep’s drift and will level at the scheduled whole-corpus regen (ruling #6). The per-entry text, which is the substance, is already correct in the pages that have such an entry.

r39-osalloc (2026-08-03) — the 9,184 decomposes, and it was NOT the defer machinery

r38 attributed the residual to the func((defer, recover) => …) closure and defer context of os.File.Write / internal/poll.FD.Write, and filed it against “the sstring/GoFunc performance arc”. That attribution was plausible and wrong. Decomposed to the byte, the defer machinery is 440 of 9,208 bytes — under 5 %; 62 % was two silent allocations inside ж<T> itself, both of which are now gone. The lesson generalizes: an attribution that was never decomposed is a hypothesis, and this one sent the fix at a component costing a twentieth of the bill.

Method (reproducible). A console probe references core/os, core/syscall, core/internal/poll and golib and measures GC.GetAllocatedBytesForCurrentThread deltas across N calls — the same instrument the AllocsPerRun shim uses, so the numbers ARE the ones the test sees. A temporary AllocMark slot table (begin/end pairs with depth suppression, so nesting is charged once) was threaded through every frame of WriteString → File.Write → file.write → FD.Write → syscall.Write → WriteFile, plus per-TElem buckets inside ж<T>.of and ж<T>.Value. The instrumentation is temporary by construction and was reverted; what survives is the arithmetic, which closes exactly at every level — the ibyteseq standard. (The probe reads 9,208 where the pipeline read 9,184: the probe writes to its own file rather than the host’s t.TempDir() one, a 24-byte difference in the path taken above File.Write. The two agree to the byte AFTER the fix, both at 3,168 — the figure the pipeline now prints in expected 0 allocs for File.WriteString, got 3168.)

Cost B/op Share Root
ж<T>.IsNull boxing the pointee on every standard-box deref 4,760 51.7 % m_val is null on an unconstrained T compiles to box !T — 8 × a 592-byte os.file copy, + 24 for one os.File
of(…) minting the untyped accessor wrapper per call 968 10.5 % display class + delegate, 88 B × 11 field pointers
the ж<T> boxes themselves 1,488 16.2 % 11 boxes; ж<FD> alone is 608 B because a field-ref box still carries an inline m_val of the pointee type
syscall seam 1,048 11.4 % heap(new uint32()) 136 · Ꮡ(buf,0) 152 · 3 × new unsafe.Pointer 664 · procWriteFile.Addr() 96
GoFunc + defer machinery 440 4.8 % func<> object + closure + delegates 224 · defer delegates 128 · the Stack<Action> 88
unsafe.Slice(unsafe.StringData(s), len(s)) 136 1.5 % PinnedBuffer + ж<byte>; free in Go
loop/slice residues 368 4.0 %  

The two fixes, both golib-only, both pure defect removal (detail and the emitted-form rule: ConversionStrategies-Reference.md Reading a pointer and taking a field pointer allocate NOTHING). IsNull’s value-peeking term is now guarded by a per-T s_valueCanBeNull — the question is only answerable for a reference type or a Nullable<>, and for everything else evaluating it boxed the whole pointee for a constant-false answer; the guard also made the peek read the right slot, correcting ж<Nullable<T>> (latent — Go has no Nullable). of(…)’s untyped wrapper is a pure function of the accessor, and the accessor is a compiler-cached static method group, so the wrapper is now memoized per accessor in a weak-keyed table.

probe measurement before after
os.File.WriteString(s) 9,208 B/op 3,168 B/op (−65.6 %)
os.File.Write(b) 9,072 3,032
syscall.Write(h, b) 1,072 784
ж<Mutex>.Value (a field-pointer deref) 592 0

Guard: GolibTests.PointerDereferenceAllocationTests, a neutered-fix control — with the fixes removed it reports 528 B/deref for a 512-byte pointee, 288 for a reference-bearing one, 32 through a field-pointer chain, and 200-vs-112 B/call for of(…) against a bare box of the same type.

TestWriteStringAlloc still does not reach zero — and the reason is architectural, not a missing fix

3,168 bytes remain and none of them is waste; each is the current model charging for something Go gets from its compiler. os therefore does not bank on this row, ruling #1 still stands (a want-zero assert is satisfiable in principle, so it is not a disclosure), and the honest statement is that the row is an ARC, not a defect. The arc, in descending value, with what each item would cost:

  1. ж<T> serves four box kinds from one class (1,488 B, 47 % of the remainder). A field-reference box, an element box and a native-address box all carry an inline m_val slot of the pointee type that they never read — ж<FD> is 608 bytes for a pointer. They also each carry BOTH m_structFieldRef (a Nullable<(object, Delegate, Delegate)>, 32 B) and m_arrayIndexRef (Nullable<(IArray,int)>, 24 B) although the kinds are mutually exclusive. Two independent moves: flattening the two nullable tuples into four plain fields is contained and worth ~28 B per box (~308 B here); removing the inline m_val from the three non-standard kinds needs the class split into per-kind subclasses, or m_val moved into m_slot unconditionally — which would ADD an allocation to every standard box, so it is a real trade and wants the whole-corpus measurement before it is taken. Blast radius: every converted package. Chip-class, design-WITH-user.
  2. uintptr(unsafe.Pointer(x)) materializes a dead Pointer object (496 B here, 15.7 %). The converter emits (uintptr)new @unsafe.Pointer(x) for Go’s most common syscall idiom; the object is provably dead — the ctor takes (uintptr)x and the cast reads it straight back. A converter peephole would remove three allocations from EVERY zsyscall wrapper in the corpus. This is the cheapest remaining increment and the one with the widest reach outside os; it was deliberately NOT taken in this lane because it is a different change class (converter → CNR + corpus build + goldens) and would have made the A/B footprint non-minimal for a row that cannot bank either way.
  3. GoFunc is a heap frame (440 B, 13.9 %). The func<T>((defer, recover) => …) shape costs a GoFunc<T>, a display class, the body delegate, one delegate per defer, and a Stack<Action> on the first registration. Go’s defer record is stack-allocated and, since Go 1.14, usually open-coded into the frame. The managed analogue is a ref struct frame with the defers in inline fields — which cannot hold the body as a lambda, so it is an EMISSION change (the converter would have to emit the body as a local function taking ref to the frame). Chip-class; do not attempt it as a golib-local edit.
  4. The syscall seam boxes the arguments (288 B beyond item 2). heap(new uint32(), out Ꮡdone) is Go’s var done uint32; &done — a stack variable in Go, a heap box here — and Ꮡ(buf, 0) is &buf[0]. Both fall out of item 1 if a pointer stops being a class.
  5. unsafe.StringData pins eagerly (136 B). It builds a PinnedBuffer view over the string’s bytes so the pointer has a stable address. Since r38, ж<T>’s address operators pin on demand (EnsureStableAddress), so the eager pin is no longer load-bearing: returning an element reference into the string’s own backing array would drop the PinnedBuffer, make unsafe.Slice(StringData(s), len(s)) a true aliasing window (which is what Go’s does), and give StringData(s) == StringData(s) for free. Small, principled, and touching a hand-owned file with subtle empty-string history — worth doing WITH the item-1 work rather than alone.

    AMENDED 2026-09-01 (the +136 attribution run): this item is DONE and the entry above is history — e1ef6ca85 deleted the eager pin on 2026-08-30 (it was a leak class: the pinned handle strong-rooted every outliving string’s backing, and a non-zero-offset window materialized a COPY, breaking Go’s sub-string aliasing; StringDataIdentity guards the repair). Its replacement — a boxed slice<byte> window handed to ElemRefBox — costs +24 B/op, and the “136 B” this item recorded turned out to be a MAGNITUDE COINCIDENCE with the later os TestWriteStringAlloc +136.00 regression, whose dominant +112.00 is the element-aliasing merge’s 8-byte m_publishedArrayBacking field on the abstract base ж<T> (× 14 boxes on that path; correctness-load-bearing, the per-box publish gate — not revertible). Full A/B decomposition, reconciled to the byte with two cross-controls (TestUTF16Alloc +8.00 = one box; utf16 TestAllocationsDecode +0.00 = zero boxes): the census record’s 2026-09-01 amendment. The surviving surgical item: an ElemRefBox<T> internal (T[] backing, nint absoluteIndex) ctor removes one object + ~56 B per StringData call (row lands BELOW pre-regression, 17 → 16 allocs) — queued as the WriteStringAlloc arc’s first increment.

What this lane changes about os’s accounting: nothing. The row still diverges, so os stays at 681 of 683 agreeing + 1 disclosed + 34 matching skips + 4 capability-excluded, with one real divergence — now measured at 3,168 bytes instead of 9,184, and rooted rather than attributed.

encoding/gob — build blocker CLOSED; first real census: 86 of 106 match (2026-08-02, r37-gob)

gob had never been measured. package_info_internal_test.cs emitted [assembly: GoImplement<gob_internal_test_package.Point, Pythagoras>] — the EXTERNAL suite’s pair anchored at the BRIDGE, where Pythagoras (declared only in example_interface_test.go) is not in scope. One CS0246, therefore no test host, therefore all 106 verdicts read empty: a missing host masquerading as mass runtime failure, and the reason DESIGN-reflection-bridge.md’s “gob 79/98” residue list could not be re-measured.

Root — test-project-model record anchoring (the splitWhiteboxVariantRecords family), not reflection. The bridge’s declared-name set is a set of SIMPLE names, and the two -tests variants are separate Go packages free to declare the same one: gob declares Point in codec_test.go (package gob, implementing the internal Squarer) and again in example_interface_test.go (package gob_test, implementing Pythagoras). Each variant’s records are split as that variant converts, and every cross-variant reference is routed by go/types.Object identity to a CLASS-QUALIFIED spelling — so a BARE name recorded by the external suite is external-declared by construction. The set is now consulted only while splitting the BRIDGE variant’s own records, and the emission mirror that names an adapter through its record’s anchor carries the identical gate, so the two cannot disagree. Write-time qualification could not have repaired it: it roots an ambiguous bare name at the file it is ALREADY being written into, so a mis-anchored record merely comes out qualified to the wrong variant. Rule: ConversionStrategies-Reference.md, A BARE record name resolves in the variant that RECORDED it; guard TestSplitWhiteboxVariantRecordsResolvesBareNamesInTheRecordingVariant (a fixture module declaring Point in both variants, asserting the collision through the real go/types scan before exercising either split). The fix is test-model-only — verified, not asserted: CNR is byte-identical across all 558 behavioral packages, and the whole-stdlib A/B reconvert shows it changing no production file.

First measurement (go2cs -tests -test-action all -test-timeout 20m, one run, zero empty verdicts): 86 of 106 match — C# 81 pass + 5 skip against Go’s 101 pass + 5 skip; 19 declarations capability-excluded, 0 disclosed. The 20 mismatches reach seven roots, none of them new-and-unrooted:

Root Tests Note
A pointer REINTERPRET used as a VALUE boxes a copy TestGobEncoderField, TestGobEncoderNonStructSingleton, TestGobEncoderPointerThenValue, TestGobEncoderValueThenPointer, TestGobEncoderValueEncoder (5) The largest single root, and precisely located. Gobber.GobDecode writes back through a reinterpreted named-type pointer — fmt.Sscanf(string(data), "VALUE=%d", (*int)(g)) — which emits fmt.Sscanf(…, Ꮡ((nint)(g))): the POINTEE is converted to a value and that temporary is boxed, so Sscanf’s write lands in a throwaway box and g never changes (“expected ‘23 got 0”; TestGobEncoderValueEncoder NREs on the unwritten value instead of mismatching). The managed-reinterpret route (Reinterpret<U>(), which aliases the source box) exists and is correct — but reinterpretManagedEmission is reached only when context.isPointerCast (the conversion is the operand of a deref) or the source is a RAW address. A (*U)(p) whose result is used as a VALUE — passed as an argument — satisfies neither and falls through to the ordinary value-conversion path. Reinterpret area ⇒ chip-owned; recorded, not fixed here. Fifth sighting of the address-of-copy-boxing shape, one base shape per fix.
GobDecode write-back for a named-ARRAY pointer receiver TestGobEncodeIsZero (1) isZeroBugArray [2]uint8’s GobDecode writes a[0]/a[1] through the pointer receiver, and the embedded time.Time decodes the same way; the round-trip returns [0 0] and a zero Time where Go returns [1 2] and time.Unix(1e9,0). The direct-field-write case (ByteStruct) passes, so Value.Addr’s write-back path is sound — this is the element/receiver storage shape, adjacent to the root above.
Reflection bridge TestSingletons, TestIndirectSliceMapArray, TestIgnoreDepthLimit (3) Already recorded in DESIGN-reflection-bridge.md and now confirmed by measurement rather than inference. array<T> does not carry its LENGTH, so a type-only walk sees a slice where the wire says [7]int (gob: decoding into local type *[]int, received remote type [7]int) and a [3]int mismatch for a field declared [3]int; TestIgnoreDepthLimit is reflect.ArrayOf → the typelinks stub (a NotImplementedException, so it reports infrastructure-error, not fail). Chip-owned.
Typed-nil pointer identity through any TestTopLevelNilPointer, TestNilPointerPanics, TestNilPointerInsideInterface (3) var ip *int emits ж<nint> ip = default!, so encodeAndRecover(ip) hands gob a plain null and gob answers gob: cannot encode nil value where Go sees a typed *int nil and panics “nil pointer”. Same shape for the four mustPanic cases and for a nil pointer inside an interface (“expected error, got none”). The canonical typed-nil boxing (ж<T>.NilBox) exists; a nil pointer VARIABLE’s zero value does not reach it. One root, three tests.
A nil deref inside the engine, re-panicked through catchError TestEndToEnd, TestLargeSlice + /byte + /struct (4) The stack ends at error.cs:45catchError’s throw panic(e) re-raising a value that is NOT a gobError, i.e. a genuine NullReferenceException from inside Encode/DecodeValue, with the original site consumed by recover(). Differential worth keeping: TestLargeSlice’s int8 and string subtests PASS while byte and struct fault, so it is shape-dependent, not size-dependent. Unrooted below the recover boundary; the next visit should print before recovering rather than reason about the stack.
Wire-level error-path divergences TestBadData, TestIgnoreRecursiveType, TestOverflow (3) TestBadData case #8 gets gob: bad data: field numbers out of bounds where Go reports exceeds input size; TestIgnoreRecursiveType gets that same message on a stream Go accepts; TestOverflow produces no range error for complex64 only (every int/uint/float width matches). Small, separable, and each names its own expected string.
unique’s package initializer TestNetIP (1) Two roots stacked in internal/concurrent.NewHashTrieMap. The FIRST — a dead deref alias, described below — is fixed this arc, and it was neither net nor reflection (the r18-era claim that this is net’s sync.OnceFunc in fd_windows is retracted; that is not on the stack). Fixing it MOVED the error site rather than greening the test: NewHashTrieMap now fails one line later with ArgumentException: Delegate to an instance method cannot have null 'this' at keyHash: new Func<…>((~mapType).Hasher), i.e. abi.TypeOf(m).MapType() over a zero map yields a descriptor with no hasher. That second root is the descriptor surface — chip-owned.

The first TestNetIP root, fixed: a dead deref alias kept alive by a NAMED-ARGUMENT LABEL. TestNetIP reported TypeInitializationException for go.net.netip_packagego.unique_package → a nil deref in internal/concurrent.newIndirectNode. Go’s newIndirectNode(parent *indirect) { return &indirect{node: …, parent: parent} } never dereferences parent, but the converter’s alias-liveness scan is a whole-word TEXT match over the converted body and the composite literal’s field key emits as the C# named argument parent: Ꮡparent — so the LABEL matched, the alias survived as a dead local, and ref var parent = ref Ꮡparent.Value dereferenced the box at entry. NewHashTrieMap builds its ROOT node with newIndirectNode(nil), so unique’s package initializer threw and took net/netip and every dependent with it. The scan now excludes a named-argument label (isNamedArgumentLabel); rule and A/B in ConversionStrategies-Reference.md, A pointer parameter used only through its box gets no deref VALUE alias, guarded by the extended NilPointerParamUnsafePointer behavioral test (the composite-literal shape plus a dereferencing positive control). Whole-stdlib A/B: 39 files, every hunk one removed dead ref var line and nothing else; the reconverted corpus builds 304/304, 0 errors.

It moved the site, it did not green the test — the charter’s root-cause-layering warning, in the wild again. Proving even that much needed the dependency regenerated: a -tests run regenerates only the package under test, so gob’s first re-measurement still linked the COMMITTED internal/concurrent/hashtriemap.cs and reproduced the original stack verbatim. Overlaying that one file from the reconvert is what showed the newIndirectNode frame gone and the next root exposed. gob’s verdict split is identical before and after (86/106) for exactly that reason; the value banked here is the general converter defect and its 39-file corpus footprint, not a verdict.

gob does not bank (86 of 106), so the roster is unchanged and no gob artifact is committed.

encoding/gob re-measured: 88 of 106, and four of the seven roots above were mis-attributed (2026-08-03, r38-gob-fin)

Re-run on the same command (-tests -test-action all -test-timeout 20m, zero empty verdicts): 88 of 106 — C# 83 pass + 5 skip against Go’s 101 pass + 5 skip; 19 capability-excluded, 0 disclosed. The +2 is TestGobEncoderField and TestGobEncoderNonStructSingleton, greened by the aliasing-reinterpret converter fix below. The other 18 rows re-bucket to seven roots, and the re-bucketing matters more than the +2: three separate rows above were one root, and it is not the row any of them named.

Root Tests Owner
reflect.Value.IsZero is wrong for a named STRING and for an ARRAYCLOSED (increment 8); it was wrong for EVERY array and EVERY struct TestGobEncoderPointerThenValue, TestGobEncoderValueThenPointer, TestGobEncoderValueEncoder, TestGobEncodeIsZero (4) reflect bridge — chip, LANDED
reflect.Value.Grow nil-derefsCLOSED (increment 8) TestLargeSlice + /byte + /struct (3 rows) reflect bridge — chip, LANDED
Typed-nil identity through any TestTopLevelNilPointer, TestNilPointerPanics, TestNilPointerInsideInterface (3) converter — LANDED r39 (r39-nilcomplex); 2 of 3 closed, the third re-rooted to the bridge
array<T> carries no LENGTH TestSingletons, TestIndirectSliceMapArray, TestEndToEnd (3) reflect bridge — chip
reflect.ArrayOf → the typelinks stub TestIgnoreDepthLimit (1) reflect bridge — chip (reports infrastructure-error)
The decoder’s IGNORE path rejects a valid field number TestBadData #8, TestIgnoreRecursiveType (2) gob decode path — unrooted
MapType().Hasher over a zero map TestNetIP (1) reflect bridge — CLOSED 2026-08-03 by the ruled internal/concurrent hand-own (see the r39d section at the end of this file); TestNetIP still fails, on the linkname-PUSH root now behind it
An untyped complex constant narrows to complex64CLOSED r39 (r39-nilcomplex) TestOverflow (1) converter — LANDED

Root 1 — four tests, one root, and it is an ENCODE-side skip, not a decode write-back. r37 read the three TestGobEncoder*Value* failures as residue of the reinterpret row and TestGobEncodeIsZero as a separate “GobDecode write-back for a named-ARRAY pointer receiver”, reasoning from ByteStruct passing that “Value.Addr’s write-back path is sound”. ByteStruct is reached through a POINTER field (GobTest0{17, &ByteStruct{'A'}}), so it never exercised Value.Addr at all — and a direct probe shows reflect.Value.Field(i).Addr(), including a reinterpret through it, writes back correctly in C#. The actual root is one line up, on the ENCODE side: gobEncodeOpFor’s if !state.sendZero && v.IsZero() { return }. Probed directly against go run:

value Go C#
NS("val") (type NS string) IsZero=false Len=3 IsZero=true Len=0
"val" (plain string) IsZero=false Len=3 IsZero=false Len=3
[2]uint8{1,2} IsZero=false IsZero=true
NA{1,2} (type NA [2]uint8) IsZero=false IsZero=true
NI(3), NB("ab") (named int / named slice) correct correct

So gob omits the field from the wire entirely and the decoder leaves the zero value — visible as v = "", want "forty-two" for the VALUE fields while the POINTER fields of the same type pass, and as TestGobEncodeIsZero’s [0 0] where Go has [1 2]. A minimal gob.Encode probe confirms it at the byte level: Go’s wire carries \x01\tVALUE=val\x01\tVALUE=ptr, C#’s only \x02\tVALUE=ptr. In the converted reflect/value.cs the String arm delegates to v.Len() (broken for the [GoType("str")] wrapper — it sees the wrapper struct, not the underlying @string) and the Array/Struct arms take raw-memory shortcuts (typ.Equal(…) against zeroVal, isZero(unsafe.Slice(v.ptr, size))) that cannot mean anything in the managed model. Chip-owned; recorded, not touched.

Root 2 — reflect.Value.Grow, and it is SIZE-dependent, not shape-dependent. r37 kept the int8-passes/byte-faults differential as evidence of shape-dependence. It is a threshold: []byte round-trips fine at 1 MiB and faults at ≥ 10 MiB, which is internal/saferio’s chunk = 10 << 20. Above it gob only partially allocates and grows incrementally — decUint8Slice (decode.go:387) and decodeArrayHelper (decode.go:553) both call value.Grow(1) — and reflect.Value.Grow nil-derefs. The four-line probe is decisive on its own: reflect.ValueOf(&s).Elem().Grow(1) on a []byte prints len/cap 4 8 in Go and panics in C#. int8 and string pass only because their decHelper fast paths (decInt8Slice, decStringSlice) return before the Grow loop. The stack that “ends at catchError’s throw panic(e)” is genuine but says nothing; the probe is what roots it. Chip-owned.

Root 3 — typed nil, rooted precisely, and deliberately NOT landed here. var ip *int emits ж<nint> ip = default! — a plain C# null — so boxing it into any yields interface-nil, and encodeAndRecover(ip) gets gob: cannot encode nil value where Go sees a typed *int nil. The control that names the root exactly: ip2 := (*int)(nil) emits ((ж<nint>)nil), goes through golib’s canonical ж<T>.NilBox, and probes IDENTICAL to Go (kind=ptr isnil=true type=*int). A nil pointer FIELD has the same defect (st.P → interface-nil); a nil MAP is already correct. So the canonical typed-nil representation exists and works, and the gap is only that a pointer VARIABLE’s (and field’s) zero value never reaches it. Two candidate remedies — emit ж<T>.NilBox for a pointer variable’s zero value, or box at the interface-conversion boundary (box ?? ж<T>.NilBox) — and both change emission at every pointer declaration or every pointer→interface conversion in the corpus, i.e. a change whose gate is the full 71-package validated sweep plus a corpus rebuild, not something to land at the tail of an arc for three tests. Handed on rooted rather than half-gated (charter §2/§5).

Root 6 — the two IGNORE-path rows share a symptom and a reproducer. TestBadData #8 (expected exceeds input size) and TestIgnoreRecursiveType (a stream Go accepts) both die with gob: bad data: field numbers out of bounds, and both decode into nil — the ignore path. The converted ignoreStruct is faithful line-for-line, so the divergence is upstream, in how the ignore ENGINE is compiled for a self-referential type: fieldnum >= len(engine.instr) rejects a field number Go accepts. TestIgnoreRecursiveType’s 36-byte data literal is a complete standalone reproducer. Not reflect; unrooted below the engine compile.

TestEndToEnd moved rather than greened — the charter’s root-cause-layering warning again. It was an NRE below catchError; it now reports gob: length mismatch in decodeArray, i.e. the array-length row, which the crash had been masking. Counted under root 4, not as a fix.

Root 7 — TestOverflow’s complex64, rooted precisely, and an attempted fix REJECTED by the gate. Not a decode-path divergence at all: complex(math.MaxFloat32*2, math.MaxFloat32*2) produces a complex64 of +Inf in C# and 6.8e38 in Go. UntypedFloat converts implicitly to BOTH float32 and float64, so both golib complex overloads are applicable and C# prefers the better conversion target — the NARROWER one. gob’s float32FromBits treats +Inf as legal in both widths, so the decode produced no range error at all while every int/uint/float width matched. The general class is worth more than the row: any golib builtin overloaded on float width silently narrows an UntypedFloat operand.

The obvious remedy — name the untyped pair explicitly (complex(UntypedFloat, UntypedFloat) => complex128, Go’s default type) — does not work, and the full behavioral suite is what proved it. It made every MIXED call ambiguous: complex(0D, gHalfPi) has the float64 overload better on the first operand and the untyped one better on the second, so neither wins (CS0121 in the ComplexConstContext guard). Completing the set with all four width pairings does not rescue it either: UntypedFloat converts implicitly in both directions with float32 and float64, so for an operand that is neither — complex(7/2, 0D), an int — no candidate is strictly better and the ambiguity simply moves. Overload resolution cannot express this rule; the change was reverted rather than banked.

The remedy that can work is CONVERTER-side and deterministic: emit each complex() argument at the element width Go’s typing gives the call — complex((float64)(x), (float64)(y)) for a complex128 result, float32 for complex64 — which is the rule assignUntypedConstContext already computes for literal rendering but cannot apply to a named untyped const (Δmath.MaxFloat32) or a constant expression over one. Its footprint is every complex() site in the corpus (math/cmplx above all), so it wants its own A/B, corpus build and re-validation of the math packages — deliberately not squeezed in at the tail of this arc.

encoding/gob fixes landed this arc

  1. A pointer REINTERPRET used as a VALUE boxed a copy — CONVERTER, fixed. r37 located this precisely and routed it to the chip as “Reinterpret area”. It is not: the shape never reaches reinterpretManagedEmission’s gate at all, because the namedToNamed || namedToBasic || basicToNamed re-box arm returns first — which is also why context.isPointerCast was a red herring (a deref of the same conversion took the copy route too). The arm now tries the aliasing emission first. Rule and the 14-file / 41-hunk A/B in ConversionStrategies-Reference.md, These three arms now ALIAS instead of boxing a copy; guard = the extended NamedNumericPointerReinterpret behavioral output test (neuter-proven). The blast radius is far larger than gob: the copy silently broke write-through in flag (a parsed flag never reached the caller’s variable), crypto/tls key-share/signature-scheme parsing, crypto/cipher’s CBC IV, image/png’s pooled encoder buffer and go/types. Reconverted corpus builds 304/304, 0 errors.
  2. The reference closure’s MEMBER-ACCESS edge — CONVERTER (test model), fixed. Landed for unique (below); it changes nothing for gob, whose host already linked.

gob still does not bank (88 of 106) and no gob artifact is committed.

encoding/gob re-measured: 91 of 106 — both deferred converter items land (2026-08-03, r39-nilcomplex)

Same command, zero empty verdicts: 91 of 106, 15 mismatches. The +3 is TestTopLevelNilPointer and TestNilPointerPanics (the typed-nil boundary) and TestOverflow (the complex width pin). The remaining 15 re-bucket to seven roots, and not one of them is the converter’s any more — six are the reflection bridge (the chip) and the seventh is gob’s own decode path:

Root Tests Owner
reflect.Value.IsZero is wrong for a named STRING and for an ARRAY TestGobEncoderPointerThenValue, TestGobEncoderValueThenPointer, TestGobEncoderValueEncoder, TestGobEncodeIsZero (4) reflect bridge — chip
reflect.Value.Grow nil-derefs TestLargeSlice + /byte + /struct (3) reflect bridge — chip
array<T> carries no LENGTH TestSingletons, TestIndirectSliceMapArray, TestEndToEnd (3) reflect bridge — chip
reflect.Value.IsNil on an INTERFACE asks the POINTEE TestNilPointerInsideInterface (1) reflect bridge — chip, NEW, rooted below
reflect.ArrayOf → the typelinks stub TestIgnoreDepthLimit (1) reflect bridge — chip (infrastructure-error)
MapType().Hasher over a zero map TestNetIP (1) reflect bridge — chip (infrastructure-error)
The decoder’s IGNORE path rejects a valid field number TestBadData #8, TestIgnoreRecursiveType (2) gob decode path — unrooted

The two that closed. TestTopLevelNilPointer needed only the boundary: encodeAndRecover(ip) now hands gob a typed nil, reflect.ValueOf sees kind ptr with IsNil true, and gob panics “nil pointer” exactly as Go does. TestNilPointerPanics needed one slot more — its table is []struct{ value any; mustPanic bool }{{nilStringPtr, true}, …}, a POSITIONAL element of a struct literal whose field is any, which the first cut of the boundary did not cover; the rule and its (zero-site) corpus footprint are in ConversionStrategies-Reference.md, A pointer crossing into an interface carries its static type. TestOverflow closed on the complex() element-width pin (same reference, complex() over a NAMED untyped constant pins the element width).

The one that did NOT, and why — a NEW chip root, rooted with a five-line probe. TestNilPointerInsideInterface builds struct{ I any }{I: ip} and expects Encode to fail with “nil pointer … interface”. The converter’s half is done and visible in the emission (I: ip.OrTypedNil()), but the C# still reports expected error, got none. The reason is one layer down: reflect.Value.IsNil on an INTERFACE-kind value answers about the POINTEE, not about the interface. Probed directly against go run:

  Go C#
reflect.ValueOf(si).Field(0).Kind() interface interface
…Field(0).IsNil() false true
…Field(0).IsZero() false true
…Field(0).Elem().Kind() ptr ptr
…Field(0).Elem().IsNil() true true

IsZero for an interface IS IsNil (reflect/value.cs’s Chan/Func/Interface/Map/Pointer/Slice/ UnsafePointer arm), so the wrong answer makes gob’s if !state.sendZero && v.IsZero() { return } skip the field outright — encodeInterface, which is where the expected error lives, is never reached. It is the same encode-side skip as root 1, from a different wrong predicate, and it is the bridge’s to fix: an interface value’s nilness is a property of the interface, not of whatever pointer it happens to carry. Chip-owned; recorded, not touched (the boundary fence).

gob still does not bank (91 of 106) and no gob artifact is committed.

unique builds and RUNS for the first time — 0 of 19, one chip-owned wall (2026-08-03, r38-gob-fin)

unique had never linked a test host. handle_test.cs calls cleanupMu.Lock() on the production package’s var cleanupMu sync.Mutex, and the -tests csproj emitter did not reference syncCS0012 … 'sync_package.Mutex' ×2. Root: the reference closure was missing its MEMBER-ACCESS edge (declarationClosureImports covered a named type’s interface bases and struct fields, but not the type of a RECEIVER — resolving x.M requires binding x’s type, and when x is declared elsewhere that type is spelled nowhere in the compilation); rule, minimality probe and the recompile-model no-op argument in ConversionStrategies-Reference.md, The third closure edge — a MEMBER ACCESS. Test-model only, and zero-drift: regenerating all 73 banked .tests.csproj changes exactly one line, unique’s own sync reference.

⚠ The minimality probe is not ceremony — it rejected two successive forms of this rule that a reading of C#’s binding rules would have justified. “The type of every var/const/func the compilation NAMES” drifts 23 of 73; narrowing to receivers but still seeding from the production sources drifts 13. Both were caught only by running it. (And the per-file scoping is load-bearing: go/packages loads the INTERNAL test variant with the production files alongside its own, so a per-package gate lets every production receiver straight back in — the 13-drift form, wearing the fix’s clothes.)

First census, with internal/concurrent/hashtriemap.cs overlaid from a fresh reconvert (the committed corpus predates r37’s dead-alias fix, and a -tests run regenerates only the package under test — without the overlay all 15 rows report r37’s already-fixed newIndirectNode stack, which reads exactly like a live defect): 0 of 19 — Go 19 pass; C# 4 fail + 15 infrastructure-error. Every one of the 15 is the same TypeInitializationException, and it is the second root r37 already named as chip-owned: NewHashTrieMapkeyHash: new Func<…>((~mapType).Hasher)ArgumentException: Delegate to an instance method cannot have null 'this', because abi.TypeOf(m).MapType() over a zero map yields a descriptor with no hasher. unique is therefore a one-root wall, and that root is the chip’s; nothing else about the package is measurable until it clears. ⚠ Increment 8 rooted that wall and reported it NOT landable in the bridge — the hasher’s contract is “hash the value at this address” and a managed address names no value (two boxes holding equal strings have different addresses; a reference-containing pointee’s address moves across a GC). The recommended remedy is a hand-owned internal/concurrent/hashtriemap.cs, which needs an ownership ruling; see Increment 8 below. (The 4 fail rows are TestMakeCloneSeq subtests whose names Go takes from reflect.TypeFor[T]().String(); C# reports that as "", so Go’s testString becomes C#’s #00 — the rtype.String/TypeFor surface, also chip.) unique does not bank; the overlaid dependency was restored, not banked.

SUPERSEDED 2026-08-03 — the hand-own landed and this census is re-measured at 1 of 19 with the single wall replaced by five distinct downstream roots; see internal/concurrent.HashTrieMap HAND-OWNED at the end of this file. (The rtype.String/TypeFor chip row above is now known to be the SAME defect as the third root there: abi.TypeFor<T>() returns the descriptor’s Equal delegate for an interface T.)

Overlaying a dependency .cs is not enough to re-measure itCopy-Item preserves the source’s LastWriteTime, so the older-than-the-.dll copy was skipped by MSBuild and the run reproduced the ORIGINAL stack verbatim, which reads as “the fix did not work”. Touch the file after overlaying.

RETRACTED — TestPipeEOF is NOT a channel row: the channel was never CLOSED (r37-chanrace, 2026-08-02)

The r37-poll handoff recorded TestPipeEOF’s post-pipe-fix hang as “a for range over an already CLOSED, drained channel that never wakes — a lost wakeup, in golib/channel.cs, and routed it to the channels lane as the first real channel-semantics defect since wave3. It is not one. The “already closed” half was inferred from reading testPipeEOF’s source flow — close(write) sits above the deferred <-writerDone, so a main goroutine parked in that defer looks like it must have closed. Measured instead of inferred, it had not: this is §9’s don’t-trust-a-plausible-reading trap, one hop further in.

The instrument. ChanCore<T>.Recv/Send’s park was env-gated onto a timed wait that reports the core’s state and the parked thread’s stack once a threshold elapses, plus a line per closechan. That is the cheap general answer to any future “a channel never woke” sighting: it distinguishes a lost wakeup from a close that never ran, in one run, without a debugger.

What it captured — identically in both instrumented pipeline runs, and a third time driving the host directly:

STUCK recv core#281  closed=False qcount=0 cap=1  recvqEmpty=False  elem=IntPtr
     channel<T>.GetEnumerator+MoveNext  ←  testPipeEOF's `for i := range write`   (the writer goroutine)
STUCK recv core#280  closed=False qcount=0 cap=0  recvqEmpty=False  elem=EmptyStruct
     GoFunc.HandleFinally → builtin.ᐸꟷ  ←  the deferred `<-writerDone`            (the test goroutine)

Both channels open. And the cross-check is absolute: across the whole suite run the close log contains 125 closes, not one of them a chan intclose(write) never executed on any core.

The real control flow. rbuf.ReadBytes('\n') returned io.EOF, so t.Fatal(err) fired at pipe_test.go:395. FatalFailNowTestAbortException unwinds → GoFunc.HandleFinally runs the deferred func → <-writerDone. close(write) on the line below never runs, so the writer goroutine ranges over a channel that will never close, and writerDone therefore never closes either. Real Go deadlocks identically here — Go’s own test code is not hang-safe on that branch; Go simply never takes it, and its binary-level timeout panic would dump it if it did. The channel runtime did exactly what Go specifies at every step.

So the actual os row is: bufio.Reader.ReadBytes over a converted os.Pipe returns a premature io.EOF, and only under parallel load. Characterized on the r37-poll tree, driving os.tests.exe directly:

configuration runs where TestPipeEOF aborts
-run TestPipeEOF alone 0 of 5
-run the whole pipe/fd family 0 of 3
full suite, -parallel 1 0 of 4
full suite, -parallel 2 0 of 4
full suite, -parallel 4 1 of 5
full suite, -parallel 8 5 of 5
full suite, -parallel 16 2 of 2
full suite, default (TestOptions.Parallel = Environment.ProcessorCount, 24 here) 6 of 6, plus 2 of 2 through the pipeline

Monotone in the concurrency level and not attributable to one interfering test — every test still runs at -parallel 1, and the abort signature is unmistakable in the host’s own output (the whole suite reports ~650 results and TestPipeEOF contributes no line at all, because its goroutine never returns).

The knee is a gradient, and an earlier revision of this row got that wrong. It claimed a clean threshold at 8 — 100% either side — on the strength of only two samples at -parallel 4. A host reboot forced the whole measurement to be re-established from scratch, and on the quiet machine -parallel 4 aborted 1 of 3. So 4 is not a safe configuration, it is a low-probability one, and any future bisection of this row must budget more than two runs per point near the knee. What the reboot did not move is the headline: default parallelism aborts 100% both before and after (3/3 loaded, 3/3 cold), which is what makes the zero rows at -parallel 1/2 worth trusting rather than dismissing as luck. One default-parallelism run also died with Fatal error. Internal CLR error. (0x80131506), the same crash r37-poll saw once; whether that shares the root is open.

Premature finalization is RULED OUT, by control rather than by argument. os.newFile registers runtime.SetFinalizer((~f).file, close) and runtime/mfinal.cs’s native bridge honors it for real — instrumented, it runs 21 finalizer-driven close calls per three suite runs, which is exactly the mechanism Go’s own KeepAlive doc warns about and made a compelling root. It is not this one: with the bridge disabled outright (SetFinalizer registering nothing), TestPipeEOF still EOFs 3/3. Handle double-close / handle-value reuse across parallel tests, and a spurious zero-byte read reaching FD.eofError, are the candidates left standing.

The negative control for the channel verdict. 93,000 racing instances across five shapes — ranging receiver woken by close, direct hand-off racing close, the testPipeEOF choreography itself, a blocked select woken by close, and select single-fire under contention — under ThreadPool and GC pressure, zero hangs and zero invariant violations. Separately, testPipeEOF’s exact choreography over the REAL pipe/bufio/fmt/time stack (transpiled, not synthetic) completed 200/200 rounds in C# and under go run. The select park path was checked as the twin of the suspected window and is clean on the same evidence. Three of those shapes are now standing guards in src/tests/GolibTests/ChannelWakeupStrainTests.cs; they are neutered-fix controls (with closechan not draining Recvq, all three fail as a parked channel operation was never woken).

Owed. The premature-EOF root goes back to the os/internal/poll arc with the table above. And the wedged host is not reaped: it outlived -test-timeout 6m by minutes and had to be killed by PID — the leaked-os.tests.exe symptom already on this board is this, and Go’s binary-level timeout panic is the behavior the host still lacks.

CLOSED 2026-08-03 (r38-os-fin) — and it was neither surviving suspect. Not a handle double-close and not a spurious zero-byte read: the ж<T>uintptr conversion returned an address whose fixed pin had already expired, so a gen0 collection during the 10 ms blocking ReadFile moved the *uint32 byte-count box out from under the kernel and done stayed 0. The gradient this table measured is exactly the probability of a collection landing in that window. Full account in the os block’s r38-os-fin sub-section. The wedged-host / no-timeout-panic half of this Owed is untouched and still open — it simply stopped firing once nothing hangs.

Open — the syscall STRUCT-PASSING seam: 6 wrappers still hand a non-blittable struct to the kernel

Down from 8 on 2026-08-03 (r38-os-fin): Process32First / Process32Next joined the fixed set, and correct a claim this section made — the row below reads “reached-and-working”, which it was not. It failed SILENTLY: syscall.Getppid answered 0, because the kernel wrote a 568-byte PROCESSENTRY32W over a ~56-byte managed record and the caller read whatever landed. A quiet wrong ANSWER is the worst shape this class takes — a fault at least announces itself, and “it did not crash” is not evidence a wrapper works.

Named as a class 2026-08-01, after syscall.GetTimeZoneInformation became the second member of it to be hand-owned (the first was StartProcess/_STARTUPINFOEXW, 2026-07-19). findFirstFile1 / findNextFile1 followed the same day — the first members a real Go test suite reached rather than a census predicted, and the reason path/filepath’s EvalSymlinks family took the C# test host down mid-run.

Amendment 2026-09-02 (C2, from the Sendto cut) — the class has a THIRD outcome, and it is the quietest: not a fault and not a wrong answer, but a wrong DESTINATION that the network forgives. Linux syscall.Sendto handed the kernel the address of a managed RawSockaddrInet4; the sixteen bytes it actually received were 02 00 | AE 54 | 00 00 00 00 | <a managed reference> — family and port CORRECT, because they are inline scalars, and the destination address 0.0.0.0, because the four bytes at that offset are the managed struct’s padding while the reference that should hold the octets sits eight bytes further on. Linux treats 0.0.0.0 as a destination meaning “this host”, so every loopback send arrived at the right socket and a loopback guard read GREEN over a send to entirely the wrong address. A guard for an address-encoding member of this class needs a destination the kernel must actually honourSendtoSeam binds 127.0.0.2 for exactly this reason, and its first draft on 127.0.0.1 passed against the defective body.

And net.Interfaces() was safe for a reason that does not generalize. NetlinkRIB calls Sendto with a SockaddrNetlink, whose raw struct is Family/Pad/Pid/Groups — all scalars, so it is accidentally blittable and has no reference to misplace. Membership in this class is decided per ADDRESS FAMILY, not per wrapper: the same wrapper is correct for netlink and wrong for Inet4/Inet6/Unix/Linklayer. That is why nothing had noticed.

The class. A generated wrapper passes uintptr(unsafe.Pointer(&s)) for a converted struct whose C# layout is not the native one — any struct holding a golib array<T> (Go’s inline [N]T) or a ж<T> (Go’s pointer field) where Windows expects inline bytes or a raw address. The kernel then writes the NATIVE-sized record over a smaller managed object: heap corruption past its end, and fabricated object references in the reference-typed fields. It does not fail at the call; it fails at the next read of one of those fields, usually as an ACCESS_VIOLATION deep inside golib. That is why time.Now().Weekday() died in slice<ushort>..ctor and not in GetTimeZoneInformation.

Census (src/core/syscall, positive control = Timezoneinformation): 32 non-blittable structs, 11 wrappers passing one by address (the earlier count of ten collapsed the findFirstFile1/findNextFile1 pair into a single row). Three are fixed; the other eight are latent — nothing in the behavioral suite or the 69-package sweep exercises them today:

Wrapper Struct Reached by
findFirstFile1 / findNextFile1 win32finddata1 (FileName, AlternateFileName) FIXED 2026-08-01path/filepath.EvalSymlinkstoNormnormBase; guarded by the FindFirstFileData behavioral output test
Process32First / Process32Next ProcessEntry32 (ExeFile) FIXED 2026-08-03os’s TestGetppidsyscall.GetppidgetProcessEntry; the mirror owns dwSize too, since Go computes it from unsafe.Sizeof
Bind / Connect / ConnectEx / Getsockname / Getpeername (+ the two sockaddr() encoders) RawSockaddrInet4 / RawSockaddrInet6 (Addr, Zero) FIXED 2026-08-11 (lane L10)net.ListenlistenStreamsyscall.Bind; guarded by the SockaddrRoundTrip behavioral output test. The first member with TWO defects: a (*[2]byte) port alias panicked (index out of range [0] with length 0) before the struct-passing seam was even reached. Mirrors are stack LOCALS, and no new [DllImport] was needed — golib models unsafe.Pointer as an address box, so the package’s generated bind/connect/connectEx already take any address. ⚠ Hand-owning RawSockaddrAny.Sockaddr (the decode) was REJECTED on measurement at L10: its body held the only ΔSockaddr casts in the package, so skipping its emission dropped the GoImplement records and made net mint duplicate adapters. TAKEN 2026-08-14 (netpoll S2b), once the pointer method-set recorder made those records independent of that body — re-measured on the taking lane’s own build (all three records present with the body displaced, net referencing syscall’s adapters at all seven sites and minting none, A/B footprint exactly one file) and guarded at value level by four new SockaddrRoundTrip lines, including an IPv6 address that deliberately crosses the Addr.Data/Pad boundary. It decodes by FLATTENING the managed struct back to its 116-byte native image and reusing readNativeSockaddr, the encoders’ own decode — see the ConversionStrategies-Reference.md entry
WSARecv / WSASend / AcceptEx / GetAcceptExSockaddrs / CancelIoEx / WSAGetOverlappedResult (+ LoadConnectEx, and ConnectEx extended) WSABuf (Buf), Overlapped (as the operation’s kernel-side IDENTITY), the AcceptEx sockaddr block, GUID (Data4) FIXED 2026-08-15 (netpoll S2b) — every TCP read/write/accept/dial; guarded at VALUE level by the TcpLoopbackRoundTrip and NetDeadlineMatrix behavioral output tests. The first ASYNC members of this class, and async breaks the mirror-is-a-local rule twice: the kernel keeps the OVERLAPPED and the buffers until COMPLETION (unbounded), and CancelIoEx matches BY ADDRESS, so a fresh copy per call would break cancellation. Remedy is a per-operation RECORD keyed by the ж<Overlapped> (whose field-reference equality resolves an of() chain recursively, so all of execIO’s call sites AND separate FD.Read calls hit one record), holding the PreAllocatedOverlapped, the native staging and the ж<byte> boxes whose pins hold the caller’s buffers. The completion→waiter signal is pushed through a new platform-neutral golib rendezvous (GoAsyncIO), because internal/poll references syscall and Go’s own trick — reading the enclosing operation back out of the OVERLAPPED — has no go2cs equivalent. ⚠ LoadConnectEx was NOT on this census and the netpoll design recorded it as “already working”: it is not. syscall.GUID’s Data4 [8]byte makes the struct reference-bearing, so WSAIoctl compared a CLR auto-layout image and answered WSAEINVAL on every host — failed to find ConnectEx: An invalid argument was supplied, the shape crypto/tls banked nine times. “Synchronous, therefore fine” is not a safe inference; only a census is
WSAStartup / WSAEnumProtocols WSAData (Description, SystemStatus, VendorInfo), WSAProtocolInfo (ProtocolName, and the nested GUID.Data4 / WSAProtocolChain.ChainEntries) FIXED 2026-08-16 (lane claude/wsaenum-mirror)internal/poll’s InitWSA, once per process that imports net; guarded at VALUE level by the WsaProtocolInfo behavioral output test. NEITHER was on this census (see the ⚠ below), and the pair is the class’s largest overwrite by an order of magnitude: WSAPROTOCOL_INFOW is 628 bytes native with three inline arrays the conversion collapses to references, so the managed record is ~120 bytes — and checkSetFileCompletionNotificationModes asks for 32 of them, telling the kernel unsafe.Sizeof(buf) = 20,096 bytes while handing over a ~3.8 KB managed array. WSAStartup is the same shape over WSADATA (408 native vs ~40 managed) and is UPSTREAM of it: reading data.Description after it dies with ACCESS_VIOLATION in slice<byte>..ctor before the enumeration is reached, which is why the two arrived together. ⚠ The WSAStartup overwrite has been live since the corpus first dialled a socket and was SILENT only because net never reads the WSAData it passes — the strongest instance yet of “it did not crash” proving nothing. The enumeration’s answer is load-bearing too: it sets useSetFileCompletionNotificationModesFD.skipSyncNotif, the IO path the netpoll design’s OQ5 ratified keeping. Both mirrors live in syscall/windows/zsyscall_windows_wsa_impl.cs (the ws2_32 family, beside LoadConnectEx), and WSAEnumProtocols adds a third SIZE-IS-AN-INPUT edge after Process32First’s dwSize: the byte count is also an OUTPUT, rewritten on WSAENOBUFS with a required size expressed in NATIVE strides
GetIfEntry MibIfRow (Name, PhysAddr, Descr) net.Interfaces
getStartupInfo StartupInfo (Desktop, Title) ⚠ NOT os startup — corrected 2026-08-02 by the r35-os arc, which ran the whole suite without reaching it. Nothing in os calls it; in Go 1.23 the only caller is the public syscall.GetStartupInfo, exercised by syscall’s own test. Process32First/Next above ARE reached from os (TestGetppidsyscall.GetppidgetProcessEntry) and did not fault, so that row is reached-and-working rather than latent.
GetAddrInfoW / FreeAddrInfoW AddrinfoW (Canonname, Addr, Next) FIXED 2026-08-16 (lane claude/tls-endgame)net.DialresolveAddrListLookupPort, i.e. every converted program that resolves a name or a service; measured first as a process AV from crypto/tls’s TestVerifyHostname. The class’s first LINKED output, and the first where copying the top-level record is NOT enough: net reads the sockaddr THROUGH the result, so the whole chain and its sockaddrs are transcribed into managed boxes and the managed pointer is carried across the unsafe.Pointer field by ManagedPointerTokens (its second minter ever; the reflection bridge was the first). FreeAddrInfoW becomes a hand-owned NO-OP, because the native chain is freed eagerly at the copy and nothing native escapes the call. Guarded at VALUE level by the LookupServicePort behavioral output test, proven failing-first (exit code mismatch: C# -1073741819 vs Go 0). ⚠ Fixing it does NOT make TestVerifyHostname pass — net.adapterAddresses is the wall behind it; see the entry at the end of this file
CertEnumCertificatesInStore, CertFreeCertificateChain, CertFreeCertificateContext CertContext, CertChainContext crypto/x509 on Windows
A SECOND class, censused 2026-08-16 and distinct from this one: 13 wrappers take a **T OUT-parameter and receive NULL, silently — ж<T> → uintptr answers 0 for a ж<ж<T>> whose held pointer is null, which is every out-parameter before the call. Measured through CertAddCertificateContextToStore (err == nil, storeCtx == nil, and crypto/x509’s systemVerify then nil-derefs). CLOSED AT THE BOUNDARY 2026-08-17 (lane claude/x509-cryptoapi): the operator has TWO wrong answers here, not one — a live MANAGED address once the held pointer is non-null, which is worse — and neither is fixable in ж.cs, so the remedy is a native out-cell plus a ValueSlot publish in the wrapper. 5 of the 13 taken (the SID pair, NetGetJoinInformation, and the two crypt32 members), guarded by the PointerOutParameter behavioral test; 8 left for stated reasons. ⚠ Attribution correction: NetGetJoinInformation and NetUserGetInfo are in syscall, not internal/syscall/windows — the split is 11 + 2. Full disposition in the entry at the end of this file

Remedy, per member: the established one — a blittable [StructLayout(LayoutKind.Sequential)] mirror with fixed buffers for the inline arrays, a direct [DllImport], and an explicit field-for-field copy at the boundary, declared in manualConversionFuncs so the generated wrapper becomes a placeholder. Worked example: src/core/syscall/zsyscall_windows_impl.cs.

The census UNDER-COUNTS, twice measured — treat 11 as a floor, not a total. LoadConnectEx (2026-08-15) and now WSAStartup / WSAEnumProtocols (2026-08-16) were all three defective members that this wrapper list never held, even though each passes a struct of exactly the censused shape by address. Two of them are on the net init path, and one of them — WSAStartup — has been overwriting the heap in every converted program that imports net since the corpus first dialled a socket. So a wrapper’s ABSENCE from the table below is not evidence it is sound; it is evidence nobody looked. When a lane reaches a new Winsock/kernel32 wrapper, re-derive the shape from the struct it passes rather than consulting this list, and add the row.

This census is scoped to src/core/syscall, and that scope is not the class’s boundary. internal/syscall/windows holds six more wrappers of the same shape — one of them, NetShareAdd/SHARE_INFO_2, is FATAL today on any host where the Server service is reachable, and it is what a real suite reached first. Its table is in os’s “REGRESSION” is a HOST CAPABILITY at the end of this file, together with the second failure shape the class takes: the kernel READING a managed record as a native one, dereferencing a value field as a pointer, and faulting AT the call rather than arbitrarily far away.

Do them when a suite reaches them, not speculatively — each needs its own value-level verification (a mirror with wrong offsets returns garbage without faulting, so “it no longer crashes” proves nothing; LocalTimeZone compares real zone abbreviations and offsets against Go, and FindFirstFileData compares real directory entries — long names ASCII and non-ASCII, 8.3 alternate names, the directory bit, byte sizes, and a distinct per-entry LastWriteTime). net and crypto/x509 are the two packages that will surface most of the rest.

Two details of the findFirstFile1 implementation generalize and are worth cribbing for the next member: the caller’s UTF-16 name buffer is pinned with a fixed block wrapped around the call rather than handed golib’s TRANSIENT жuintptr address, and an inline WCHAR[N] buffer is copied back whole, NULs included — Go reads it as UTF16ToString(buf[:]), which stops at the first NUL, and the struct is reused across an enumeration, so a copy that stopped at the terminator would leave the previous entry’s runes behind it. Full write-up: ConversionStrategies-Reference.md, A STRUCT handed to the kernel by address must be blittable.

Recurring classes worth a general fix rather than another point repair

RETRACTED — the internal/zstd / testing.B “trap” was a false alarm

internal/zstd is worth 534 verdicts, and the fix is what it looked like: Go’s B and T both embed common, so a benchmark body may call Cleanup, Error, Log, Name, TempDir and the rest, while core/testing’s compile-only B surface declared almost none of them. Adding the missing common members makes internal/zstd validate at 534/534 — banked 2026-07-27.

Two claims previously recorded here are wrong, and both were re-measured on master before the retraction:

  1. “Completing B’s surface breaks crypto/hmac.” It does not. With all 14 members added, crypto/hmac regenerates with its <ProjectReference … io.csproj /> intact and validates at 172/172. The stated mechanism cannot hold: core/testing is hand-owned C#, the closure is computed in Go from go/types, and the converter never reads the shim — no edit to testing.cs can change a byte of converter output. (Adding extension methods would not make B implement TB in C# either.)
  2. crypto/hmac’s closure is not reproducible from a standalone regeneration.” It is. Deleting crypto.hmac.tests.csproj outright and re-running the pipeline on the committed tree regenerates it byte-identically, io.csproj included, with and without the B members.

The likely origin of both is charter §9’s false-alarm trap (a): a bin/go2cs.exe built before 60f99c505 — the commit that added the interface-base closure, and the one immediately before hmac’s banking commit — regenerates hmac without the io reference and fails exactly as described. Lesson to carry forward: when a change in one language appears to alter output produced by another, force go build -o bin/go2cs.exe and re-measure before recording a coupling.

  1. time/TestUnmarshalTextAllocations: NO disclosure. A want-zero alloc assert is satisfiable, so disclosing it would soften the doctrine the badges depend on. The IByteSeq<T> boxing redesign (CleanupBacklog #7) is PROMOTED onto time’s critical path. The doctrine paid off twice on os’s instance (r39-osalloc, 2026-08-03). Refusing the disclosure forced TestWriteStringAlloc’s 9,208 bytes to be DECOMPOSED rather than argued about, and the decomposition found two silent allocations in ж<T>IsNull boxing the whole pointee on every dereference, and of(…) minting its untyped accessor wrapper per call — worth 62 % of the bill and paid by every pointer read and field address in the corpus, not just by os. A disclosure would have banked the package and left both in place. The row still does not reach zero and os still does not bank; the remainder is an architectural arc, recorded in the os block’s r39-osalloc sub-section.
  2. Capability-exclusion SANCTIONED for the provably-unownable os class — the hostfxr apphost-relocation limitation (TestRemoveAllWithExecutedProcess), TestCmdArgs (a managed materialization would let Go LocalFree GC memory), and TestDirectoryJunction (raw-metal on non-native types in test code). Implement via the established unsupportedRuntimeCapabilities mechanism, WITH the mandatory §9 roster scan (positive control) before widening. This plus the fixable rows is os’s path to a bank. IMPLEMENTED 2026-08-03 (r38-os-fin) — all three, with the roster scan clean (zero hits across 72 packages) and both controls firing. The mechanism gained one generalization it needed: an entry now maps a SYMBOL to the NAME of the capability, so the proof page reads “relocatable single-file test executable” instead of a bare symbol, and a key may name the test DECLARATION itself for a capability that belongs to the host rather than to anything the test calls. Detail in the os block’s r38-os-fin sub-section. The fixable rows all closed too; the path led to one residual, not to a bank — see ruling #1, which TestWriteStringAlloc is now the second instance of.
  3. Timer mode-0 divergence ruling DEFERRED until the recorded one-fire-per-pass timer-model fix lands and reshapes the residual — no ruling on a measurement about to change.
  4. GoUntypedGoBigConst (see the charter §6.1 math/big row); rides the rebank. LANDED 2026-08-04 (r40-rebank, commit A) — a pure rename of the System.Numerics.BigInteger csproj <Using Alias>: converter emission + templates, golib.csproj, the behavioral goldens that carry it, and the strategy docs. The corpus said GoUntyped until the rebank’s own regen levelled it in commit B. The behavioral project GoUntypedConstArg keeps its name — it is named for the Go-language untyped const concept, not for the C# alias.
  5. The native-address+managed-snapshot pointer flavor is DEFERRED until net’s DNS work demands it; then a design-with-user session — not designed against one test.
  6. Whole-corpus rebank: scheduled immediately after the r37 train lands (carries the accumulated intended drift + the param-unification footprint + the GoBigConst rename).
  7. NuGet release: after the rebank, so the first badged release ships a corpus byte-current with the converter.

The r37 train’s sweep catch — reflection increment 6 REVERTED pending its atomic twin (2026-08-03)

The all-ships sweep failed math/rand AND math/rand/v2 on the assembled train: panic: reflect: Method index out of range in TestRegress — the EXACT successor gap increment 6’s own report recorded (“a NumMethod() > 0 gate lets method-enumeration loops get further; the first consumer that walks one demonstrates it”). The demonstration arrived one session later, in two BANKED packages no lane had canaried — which is precisely the coverage the sweep exists to provide. Reverted from the train (39de5dd77 reverts d75e0afcd); both packages re-validate at their exact banked counts (43, 36); time returns to 145 (its two JSON rows re-land with the pair). The durable scoping lesson: NumMethod and Method(i)/Value.Method/Call are one ATOMIC increment — a count without an enumerator converts silent vacuous passes into hard panics. Increment 6’s work survives on claude/elated-hodgkin-12581e (d75e0afcd); the chip’s increment-7 chit carries the pair, with TestRegress’s loop as the primary gate and math/rand ×2 as mandatory canaries.

RESOLVED — increment 7 lands the pair (2026-08-03)

The count and the walk shipped together: rtype.{NumMethod, Method, MethodByName} + Value.Method over ONE ordered table whose .Count IS NumMethod, with a method value represented as an ordinary receiver-bound delegate so Type()/NumIn/In/Out/Call are existing surface unchanged. Measured on this tree:

Package Before (master) After Note
math/rand 43 (TestRegress passing vacuouslyNumMethod 0 ⟹ zero loop iterations) 43 TestRegress now genuinely runs its 320 golden comparisons; the bridge reports *rand.Rand NumMethod: 16 in Go’s order
math/rand/v2 36 (same vacuous pass) 36 18-method table, same shape
time 146 pass / 11 fail / 2 skip of 159 (the r37 re-measure above) 148 pass / 9 fail / 2 skip TestTimeJSON + TestUnmarshalInvalidTimes re-land. Remaining 9 = TestChan ×8 (timer-model item) + TestUnmarshalTextAllocations (disclosure ruling) — neither this arc’s

Note the board’s “time returns to 145” above was written against the older 145 figure; the correct successor of the r37 re-measure (146) is 148. The vacuous-pass detail is the part worth carrying forward: the banked 43/36 were never evidence that TestRegress worked, because with NumMethod at 0 its loop body never executed — a count of zero is indistinguishable from a type with no methods, which is the same silent-degradation class as the "" type name (increment 5).

Also fixed here, and it retroactively invalidates increment 6’s numbers: a this object extension method (golib’s TryCastAsInteger) was entering every type’s method table through the candidate source’s assignability safety net, and doing so nondeterministically — the same binary reported NumMethod 4 or 6 for the same type depending on which assemblies had loaded when the cache was first filled.

Increment 8 — the ZERO test, and the one row that must NOT be landed (2026-08-03)

Two of gob’s chip-owned roots close; the third is rooted and handed back with a recommendation rather than a fix.

Measured on the post-fix tree: encoding/gob 88 → 95 of 106. One -tests -test-action all -test-timeout 20m run, zero empty verdicts: the mismatch list goes from 18 rows to 11, and the seven that vanished are exactly the two roots below — TestGobEncoderPointerThenValue, TestGobEncoderValueThenPointer, TestGobEncoderValueEncoder, TestGobEncodeIsZero, TestLargeSlice + /byte + /struct. No new mismatch appeared, so this is not the root-cause-layering case where one row’s fix merely unmasks another. gob still does not bank and no gob artifact was committed (the measurement tree was restored). The remaining 11 keep their existing owners: array-length model (3), ArrayOf/typelinks (1), MapType().Hasher (1, below), typed-nil converter (3), the gob ignore path (2), untyped complex narrowing (1).

Closed — root 1 (Value.IsZero, 4 rows) and root 2 (Value.Grow, 3 rows). The census understated root 1 considerably. It is not “wrong for a named STRING and for an ARRAY”: both the Array and the Struct arm fall to v.ptr == nil, which the bridge never populates, so IsZero answered true for every array and every struct in the corpus whatever it held. Measured against go run on a purpose-built probe before the fix — [2]uint8{1,2}, NA{1,2}, inner{N:1}, outer{P:&n}, outer{I.S:x} — every one true in C#, false in Go. A fourth read had to land with it: IsZero’s String arm is Len() == 0, and Len was blind to a [GoType("str")] wrapper (every other named container answers through its golib interface; a named string implements none), so the arm could not be right until Len was. Both now hand-owned, plus Grow, which read a *unsafeheader.Slice off the same absent v.ptr and nil-deref’d for every caller. Guard: tests/Behavioral/ReflectZeroAndGrow, byte-identical to go run across 33 rows. Design: ConversionStrategies-Reference A ZERO test is a descriptor read too.

NOT landed, deliberately — MapType().Hasher / Key.Equal (unique’s 15 of 19, net’s last cctor root, gob’s TestNetIP). This row is not the same shape as the others and populating it would be a regression, not a partial fix. Hasher(unsafe.Pointer, uintptr) uintptr must hash the value at an address; the address that call site produces cannot name a managed value. Three measurements settle it: two boxes holding equal @string values necessarily have different addresses (so no address-derived hash can make unique.Make("hello") agree with itself — the package’s whole point); a box whose pointee contains a reference has no pinnable slot and its address moved across a forced GC; and the unsafe.Pointer the call site builds retains no link to its source box, its constructor taking a uintptr. Key/elem types are recoverable from the carried System.Type, but landing only those is strictly worse than today: Key.Equal is the comparability SIGNAL — a pointer-identity compare — so a half-populated descriptor turns a loud NewHashTrieMap construction failure into a map that silently mislays every key. The increment-6 lesson inverted: a descriptor field whose read cannot be honored must not be populated to look truthful.

Recommendation (needs a coordinator ownership ruling). The remedy is one layer down and outside this arc’s declared files: hand-own internal/concurrent/hashtriemap.cs on the sync.Mutex precedent. Its CONTRACT — a concurrent map from comparable K to V — is answered natively and correctly by the CLR; only its MECHANISM (hash the bytes at an address) is raw-metal that the managed model cannot express. That is exactly the documented S1 fork. It would clear unique’s single wall (making 19 rows measurable for the first time), net’s last initializer root, and gob’s TestNetIP. The chip did not take it unilaterally because internal/concurrent belongs to no lane’s declared ownership and the file is a whole-package hand-own, not a bridge _impl.cs.

Rulings — 2026-08-03 (user; both recommendations adopted)

  1. The mode-0 timer residual (time’s 4 rows): COMMISSION THE SYNCHRONOUS-TIMER-CHANNEL ARC rather than ruling a divergence — Go 1.23’s sync timer channel (#37196: Stop/Reset that blocks stale values; no drain needed) implemented in golib’s channel layer. Wave3’s successor arc, §7 adversarial discipline, r39-timer’s zero-margin drain constraint (at Stop/Reset at most 2 ticks exist: 1 buffered + 1 committed-unsent) is required reading. time banks when it lands (152 + 4 mode-0 rows + the alloc row below). IMPLEMENTED 2026-08-03 (r39b-synctimer) — all 4 rows closed, time at 156/1/2 of 159; detail in the time block’s RESOLVED — r39b lands the synchronous timer channel sub-section. The bank is now gated solely on ruling #2’s arc.
  2. time’s alloc row (216 B, both halves fixable): NO disclosure — commission the CLOSURE-EMISSION arc. The 88 B half = the local-function emission mode (a func literal bound to a local that is only ever CALLED emits a C# local function — captures without allocating; corpus-wide fidelity + perf win). The 128 B half = escape-analysis refinement (an address-taken local Go stack-allocates need not heap-box). Sequenced AFTER r39-osalloc’s dock so its defer-closure findings unify with the local-function mode into ONE reviewed closure-emission design. IMPLEMENTED 2026-08-03 (r39e-closure) — both halves landed, time at 157 pass / 0 fail / 2 skip of 159 and banked as package #73. The unified design the ruling asked for is DESIGN-closure-emission.md: §3 records what landed, §4 is the ref-struct frame (r39-osalloc arc item 3) written up as a proposal for user review, NOT implemented. Detail in the section below.

r39e-closure (2026-08-03) — 216 = 128 + 88, both halves are converter emission, and time banks

Ruling #2 commissioned this arc on r39-timer’s decomposition. That decomposition was exact: each half was re-measured here in isolation, by reverting one emitted form at a time in the built test host and re-running the single row.

time TestUnmarshalTextAllocations allocs
branch base 18423efaf 216
local-function fix only (test-body box restored by hand) 128
escape-narrowing fix only (parseUint lambda restored by hand) 88
both 0 — passes

Fix 1 — a func literal that is only ever CALLED emits as a C# local function (88 B). A capturing lambda allocates a display class AND a delegate on every evaluation of the lambda expression — per call of the enclosing function, whether the closure runs or not. A local function that is never converted to a delegate captures through a by-ref STRUCT closure: same single storage location per captured variable, no heap object. The gate is the proof that keeps that compilation available — every reference other than the declaration must be a call callee, which also subsumes reassignment and address-taking. Emission is a new LambdaContext.localFuncName mode in convFuncLit, so the whole body pipeline (capture hoisting, boxed value params, variadic prologue, array clones, named results, the single-return collapse) is shared verbatim with the lambda path. A literal that defers or recovers is deliberately excluded: its 440 B execution context dominates the 88 this removes, and lifting the exclusion is §4 of the design, not a workaround here.

Fix 2 — a variable DECLARED INSIDE a closure is not captured BY it (128 B). The escape analysis’s function-literal arm matched any mention of an object lexically inside a literal’s body, and for a variable declared there that mention is its own declaration. var t Time; t.UnmarshalText(in) inside a closure heap-boxed t — and the box Ꮡt was never referenced in the emitted body — while the identical statements outside a closure emitted a plain local. One containment test fixes it, and the skip keeps descending so a literal NESTED inside still marks the escape it genuinely causes. The narrowing direction is the dangerous one, so the proof is explicit: Go scoping puts a literal’s own local out of reach of every other frame, and every route by which such a local can still escape (&x, &x.f, &x[i], a pointer argument, a capture-mode method, a pointer-receiver method value, a go/defer use) is decided by an arm that walks the whole enclosing body, literal bodies included.

Whole-corpus footprint — two-temp-root A/B (both roots seeded per CLAUDE.md §1/§1a; base exe built from HEAD versions of the four changed converter files):

  files sites
local-function emission 91 152 (133 block-bodied, 19 expression-bodied)
heap box removed 22 32
both families in one file 8  
total changed 105  

Every changed line in the 105 files falls in one of the two families — verified by attribution, not by sampling: each removed line is a lambda declaration, a };} close, a = ref heap box, or a statement in a file that has a box removal (the collapse’s second line, and in reflect/iter.cs the valueᴛ1 for-loop temp that only existed because the variable was a ref local). Marker gate: 39 [module: GoManualConversion] files, line-anchored, 0 clobbered, 16 carrying a .cs.auto.

Behavioral CNR: 41 files, 96 local-function sites + 1 box removal, 168 added / 169 deleted — arithmetic closes exactly (96 + 71 }; + 2 box lines removed = 169; 96 + 71 + 1 = 168). Guards: LocalFunctionEmission (10 probes, 5 negative controls — one per disqualifying reason) and ClosureLocalNoHeapBox (8 probes, 5 of them boxes that must survive, each writing through the escaping alias and reading it back). Both neuter-proven: with the fix removed each golden mismatches (24 and 11 changed lines respectively) and restoring it returns them to green.

One incidental finding worth recording. The committed src/core is stale by 685 files against a seeded reconvert with the BASE converter — the r36 four-deref-accessor change (Ꮡp.ValueᏑp.DerefOrNull() on pointer receivers and parameters) landed as a converter fix without a corpus regen, which is correct policy but means a plain overlay-then-git diff is NOT a usable A/B instrument on this branch. The two-temp-root form is, and it is what the numbers above come from. The same staleness is what makes a time -tests run show DerefOrNull and fallthrough-placement diffs in its production .cs; those are pre-existing, not -tests-closure drift, and they are restored rather than banked.

The sweep found a 74th thing: a disclosure that was never a CLR limit. The full 73-package validated sweep (2,783 s) reported 72 pass / 1 “fail”, and the one flagged row was bytes at count 82, banked 81 — MORE matching verdicts than the roster claimed. TestEqual had an alloc-profile disclosure since 2026-07-18 reading “the managed runtime allocates during the converted Equal comparison loop where Go’s compiler-optimized code does not”. It does not. The converted test body was

foreach (var (_, v1) in compareTests) {
    ref var tt = ref heap(new compareTests1(), out var tt);   // ← per ITERATION
    tt = v1;
    
}

— the range variable of a loop inside the AllocsPerRun closure, heap-boxed by exactly the arm this train narrowed, once per iteration of an assert that wants zero. It now emits foreach (var (_, tt) in compareTests) and the test passes on its own merits. The disclosure is retired, not re-signed: §5 of the disclosure policy says a real bug is never a disclosure candidate, and this one had been standing in for a converter defect for two weeks. bytes moves to 82 matched · 6 disclosed (re-run twice, identical), the roster to 2,713 matching · 50 disclosed, and its TestEqual verdict is now earned rather than excused.

That is also the general lesson worth keeping: a want-zero alloc assert is a converter test, and a disclosure filed against one should be re-examined every time the emission changes. Five alloc-profile disclosures remain in bytes and one in bufio; nothing here says they are wrong, but nothing has re-derived them either. The cheap instrument is the one this lane used by accident — run the sweep and read a count that is HIGHER than banked as a finding, not as noise.

Sweep aftermath, classified: 60 proof pages regenerated (a renderer wording change from an earlier lane plus provenance — restored, they belong to a rebank), the documented 7-file -tests-closure emission class, and corpus-wide production .cs churn that is the same 685-file staleness recorded above. Only bytes’s test sources, its disclosure manifest and its proof page were banked, because only they are the evidence for a row that changed.

internal/concurrent.HashTrieMap HAND-OWNED — the wall falls, and three walls stand behind it (2026-08-03, r39d-hashtriemap)

The user-ruled hand-own landed: src/core/internal/concurrent/hashtriemap.cs is now a whole-file managed reimplementation carrying [module: go.GoManualConversion] (corpus marker census 39 → 40). Rationale, the API map, and the equality-bridge verification live in ConversionStrategies-Reference.md, internal/concurrent.HashTrieMap; the hand-own mechanics in Baseline-vs-FullConversion.md. Summary of what was measured, because the shape of the result matters more than the row count:

Gates. internal/concurrent and unique build clean; go2cs-stdlib.slnx builds 304/304, 0 errors. A seeded full -stdlib -comments reconvert leaves hashtriemap.cs and package_info.cs MD5-identical; strip the marker and the same run overwrites hashtriemap.cs with its own 21 KB emission and rewrites package_info.cs — the protection proven in both directions. The behavioral suite and the 72-package validated sweep are green/unchanged (internal/concurrent is in no banked package’s closure — the gates were insurance, not measurement).

The equality bridge is NOT a problem — verified by probe, not by reading. EqualityComparer<K>.Default is Go’s == for every key shape the corpus interns: ж<T> (pointer identity + matching identity hash, and abi.TypeFor<T>() interns one descriptor box per System.Type, so a second call finds the first call’s entry), a [GoType] struct of {bool; @string} — netip’s addrDetail shape — (generated field-wise Equals + HashCode.Combine, matching for two keys built from distinct string storage), and @string (content). LoadOrStore was contention-probed: exactly 1 winner in 64 racing callers.

encoding/gob: 95 of 106, unchanged — TestNetIP does NOT flip. Its root MOVED one frame, from NewHashTrieMapArgumentException: Delegate to an instance method cannot have null 'this' to NotImplementedException: runtime_registerUniqueMapCleanup. No row regressed (TestNetIP is the only gob row whose closure reaches unique; the other ten failures are gob-internal and untouched).

unique: 0 → 1 of 19 — and it is no longer a ONE-root wall. That is the real deliverable. Its 15 identical TypeInitializationException rows resolve into five distinct downstream roots, each now separately actionable:

Root unique rows Shape
//go:linkname PUSH never links: unique.runtime_registerUniqueMapCleanup 1 (+ gob’s TestNetIP, + net’s cctor) CLOSED 2026-08-07 (r43b-linkname). runtime/mgc.go PUSHES its body into unique’s bodyless declaration and the converter’s forwarder handled the PULL direction only, so the consuming side was a throwing PartialStubGenerator stub. It now FORWARDS to runtime’s converted body — see the linkname PUSH direction below
Same class: internal/weak.runtime_registerWeakPointer / runtime_makeStrongFromWeak 4 → 7 → 0 CLOSED 2026-08-07 (r43e-weak). runtime/mheap.go pushes both; hit inside weak.Make, i.e. unique.Make’s newValue(). r43b took the linkname half only (registered UNHONORABLE, announcing itself by name), because linking was never the remedy — runtime’s converted bodies walk mheap_ span metadata the managed model does not populate. The remedy was the hand-own it announced, and it has landed: internal/weak/pointer.cs on System.WeakReference + a ConditionalWeakTable canonical index. The seven rows it had absorbed now advance INTO the test body — see internal/weak HAND-OWNED at the end of this file for where each one stops instead
abi.TypeFor<T>() is silently WRONG for an INTERFACE T 1 TypeFor’s interface branch is TypeOf((*T)(nil)).Elem(), and Type.Elem() for Kind == Pointer reinterprets the descriptor as a PtrType and reads .Elem — which under the managed layout lands on the descriptor’s Equal field. TypeFor<any>() and TypeFor<error>() return a System.Func<unsafe.Pointer, unsafe.Pointer, bool>, not a ж<abi.Type>. Shared generics store it into ConcurrentDictionary<ж<abi.Type>, any> uncast-checked, and the first key comparison dispatches IEquatable<ж<abi.Type>>.Equals on a delegate → EntryPointNotFoundException. Corpus-wide, and it was invisible until now: the old trie compared raw addresses through keyEqual and never dispatched on a key’s runtime type. Reflection-bridge row
GCHandle: Object contains references 1 abi.Escape pinning a managed pointee on the weak.Make path
IndexOutOfRangeException in go.slice<T>.Enumerator.get_Current 6 CLOSED 2026-08-07 (r41c-cloneseq). Not the enumerator, and not “neither linkname nor reflection” — see the makeCloneSeq root, closed immediately below

Plus the 3 fail rows the r38 census already recorded (TestMakeCloneSeq/#00, #01, interface_{} — Go names those subtests from reflect.TypeFor[T]().String(), which C# renders ""; note this is the same TypeFor surface as the third root above). unique does not bank; its test artifacts were restored, not committed.

Two traps this arc paid for. (1) In the PowerShell tool, [System.IO.File] resolves a RELATIVE path against the process working directory, which is the MAIN checkout — not Set-Location’s. A read-modify-write with a relative path silently read H:\Projects\go2cs’s copy of the file and wrote it over the worktree’s, reverting the hand-own. Always use absolute paths with the [System.IO.File] APIs. (2) emitAutoConversionSiblings — the fully-hand-owned-package branch — runs only six of the whole-package pre-passes, and panics on a generic file (WARNING: visit file error: … nil pointer dereference in "hashtriemap.go" (auto-conversion sibling skipped)), so no .cs.auto review sibling is produced for internal/concurrent. Pre-existing converter defect, harmless to the marker’s protection, not chased.

The makeCloneSeq root, CLOSED — unique 1 → 4 of 19 (2026-08-07, r41c-cloneseq)

The board’s guess about this root was wrong in both halves, and the way it was wrong is the finding. It is not a slice<T>.Enumerator edge — the enumerator behaves correctly given the header it is handed — and it is squarely reflection-bridge territory rather than “the only root that is neither linkname nor reflection”. What made it look otherwise is that the diagnostic names golib and the cause is two frames up, which is charter §9’s layering lesson in a new dress: a first diagnostic is a starting point, not a diagnosis.

The root. makeCloneSeqbuildStructCloneSeq opens with styp := typ.StructType(), and Go’s (*structType)(unsafe.Pointer(t)) is the PREFIX-DOWNCAST idiom — the linker really allocated a structType behind the Type header. Nothing sits behind a ж<abi.Type>, and golib’s Reinterpret rightly REFUSES to alias managed storage for a reference-bearing pair (aliasing would fabricate object references), so it fell through to the raw-address route and read ΔStructType’s fields out of the memory following the descriptor’s value slot. Probed on abi.TypeFor[testStringStruct]():

Fields.Length   8830452760576   <- an address fragment read as a slice length
Fields.Capacity 16              <- the descriptor's OWN Size_, bleeding through the shifted view

m_array landed on a real heap object, so the first Current threw IndexOutOfRangeException instead of access-violating: a CLR type-safety break that happened to be caught. internal/reflectlite’s NumField/Len read the same garbage.

The fix, at the root’s own layer. Type.StructType and Type.ArrayType join TypeOf in manualConversionFuncs["internal/abi"] and are SYNTHESIZED in type_impl.cs from the descriptor’s carried System.Type — field types via synthType, Go (amd64) field offsets and array Len/Elem/ Slice via golib. Offsets come from the same walk that stamps a descriptor’s Size_ (GoReflect.GoFieldOffsets, factored out of GoSizeOf’s struct arm), so the two cannot disagree. Nothing unknowable is invented: no System.Type, or a field whose Go size is unknowable, answers Go’s nil; StructField.Name/PkgPath stay the zero ΔName (its readers walk addChecked raw addresses — the same route that produced the garbage — and Go’s own Name() answers "" for a nil Bytes, so the zero value is a state the format defines). Full rationale: ConversionStrategies-Reference.md, abi.Type’s SPECIALIZATIONS are synthesized, not downcast.

A second, independent defect in the same file — a converter one, and silent. buildArrayCloneSeq’s whole body was emitted as a /* … */ COMMENT. visitRangeStmt recognized range-over-integer only for types.Int/untyped-int, so for range atyp.Len (a uintptr) fell through to the “unexpected expression” arm and the loop VANISHED — unique’s cloneSeq for any array-of-string type came back empty. It was the only such comment in the entire converted stdlib. Fixed generally (any integer kind, golib range<T> with the operand’s own Go width, explicit type argument at each non-int site) and guarded by the RangeOverIntegerTypes behavioral test; details in the same reference doc, Range-over-integer covers EVERY integer type. ⚠ Worth remembering: the first attempt at the golib overload REGRESSED range(3) to System.Int32, because the generic is an identity match where range(nint) needs a conversion and C#’s prefer-non-generic tie-break never fires — caught by probe, not by reading, and closed with a third range(int) overload.

Census, unique, matched rows: 1 → 4 of 19. All six IndexOutOfRangeException rows are gone. The three TestMakeCloneSeq ones (testStringStruct, testStruct, testStringStructArrayStruct) now PASS; the three TestHandle ones MOVED to the internal/weak.runtime_registerWeakPointer root that was always behind them. A fifth subtest, TestMakeCloneSeq/testStringArray, now computes the correct {[0 16 32]} but still cannot MATCH, because C# names it #01 — that is the TypeFor/Name root, row three of the table above, untouched. TestHandle/interface_{}/<nil> also moved (from EntryPointNotFoundException to a null HashTrieMap key); that root’s Type.Elem() reinterpret is the SAME defect class as this one and simply read different garbage this run — it was not chased, and neither were MapType()/FuncType()/InterfaceType()/Key()/Len(), which all still reinterpret. unique still does not bank; its test artifacts were restored, not committed.

Blast radius. StructType()/ArrayType() have exactly two corpus callers (unique, internal/reflectlite), and the range widening has exactly one corpus site. Gates: behavioral CNR byte-identical across all 570 packages apart from the new test project; run-behavioral.ps1 full 545/545 transpile+compile+golden and 515/515 stdout (30 skipped, no package main), 1,081 s; go2cs-stdlib.slnx 304/304, 0 errors; go test ./... in the converter ok; GolibTests 69/69 (60 + 9 new), ChannelTests 24/24.

The converter’s forwarder handled only the PULL direction (a bodyless declaration naming another package’s symbol). Go’s other direction — the DEFINING package carries the body and names another package’s declaration, the consumer being a bodyless func under a one-arg //go:linkname handle — linked nothing, so every consumer fell to the PartialStubGenerator. Mechanism and rationale: ConversionStrategies-Reference.md, A cross-package //go:linkname PUSH resolves per recorded disposition.

Pair Disposition Why
runtime.unique_runtime_registerUniqueMapCleanupunique.runtime_registerUniqueMapCleanup FORWARDED The pushed body is ordinary converted Go — a chan struct{} plus a goroutine that drains it and calls the callback. The managed model runs the real thing; nothing signals the channel because clearpools() is driven by Go’s GC, which does not run. That is Go’s own behavior for a program whose GC never fires (the intern map keeps its entries), not a fabricated answer
runtime.internal_weak_runtime_registerWeakPointerinternal/weak.runtime_registerWeakPointer LOUD STUBHAND-OWNED getOrAddWeakHandlespanOfHeapthrow("getWeakHandle on invalid pointer"): the body walks mheap_ span metadata the managed model does not populate. Answered 2026-08-07 (r43e-weak) by the internal/weak hand-own; the registry row STAYS, because it is what a conversion into a root without the hand-own must still emit
runtime.internal_weak_runtime_makeStrongFromWeakinternal/weak.runtime_makeStrongFromWeak LOUD STUBHAND-OWNED Re-derives an object pointer from a heap address. A forwarder would fault or — worse — return a plausible pointer derived from garbage, the inverse-atomic rule’s exact prohibition. Same disposition, same answer, same reason for keeping the row

The registry is curated, and the reason is structural, not caution. The converter never sees the pushing package’s directives while converting the consumer — a package is converted from its own syntax, dependencies contribute types rather than comments, and the pusher need not even be a dependency. Go 1.23 carries ~200 pushes outside cmd/; the corpus exposes eleven as bodyless one-arg-handle declarations, and linking those wholesale would REGRESS working packages: time’s timer trio is already answered by time_impl.cs and a converter-emitted body would collide with it, while internal/syscall/windows’s stdcall wrappers and internal/coverage/cfile’s linker-section walk push bodies the managed model cannot run at all.

Measurement — the honest read is “the root moved”, not “rows flipped”.

What internal/weak is now waiting on — and it is the ONLY thing. A hand-owned managed weak reference (System.WeakReference over the ж<T> box) under [module: go.GoManualConversion], the same shape sync’s Mutex family and internal/concurrent.HashTrieMap took: honor the observable contract, never emulate the mechanism. Deliberately NOT attempted in this lane — the linkname mechanism and a semantic hand-own are separate units of work, and the loud stub is what makes the second one findable. Its single file (internal/weak/pointer.go) makes a whole-file replacement the natural form. ✅ Landed the same day (r43e-weak), in exactly that shape — see internal/weak HAND-OWNED at the end of this file.

Gates. go test ./... in the converter ok (new TestRecurseLinknamePush, both arms neuter-proven); CNR byte-identical across all 571 behavioral packages; a seeded full -stdlib -comments reconvert is byte-identical to the committed tree across every .cs/.csproj/ README.md (zero unclassified; hand-own clobber gate 0 violations; no DYNTYPE markers); go2cs-stdlib.slnx 304/304, 0 errors. A/B footprint: 3 corpus files.

A NEW environmental failure shape worth recognizing: the host DISK FILLED mid-suite. The full run-behavioral.ps1 reported FAIL (546 projects, 1,413.5s) with 115 Go build failures plus one Output mismatch — and every one of the 115 reads verbatim compile: writing output: write $WORK\b001\_pkg_.a: There is not enough space on the disk (C: was at 2.8 GB free of 1.86 TB, three lanes deep). The C#-side phases, the ones a converter change can actually move, all passed the WHOLE corpus: Transpile 546/546, Compile 546/546, Target 546/546. Output read 404 pass / 1 fail / 141 skip, where the skips are the 115 disk-killed Go builds on top of the usual no-package main set. The single Output failure, FindFirstFileData, re-runs PASS 1/1 across all four phases in isolation once space is freed — the standing rule for a Go-toolchain-side failure under load (re-run that one project filtered before believing it) applied to a new cause. Read a wall of identical not enough space on the disk lines as the machine: check Get-PSDrive C FIRST, and do not go hunting for a converter regression — Target passing 546/546 already proves no golden moved.

internal/weak HAND-OWNED — the announced pair gets its answer (2026-08-07, r43e-weak)

The third instance of the ruled precedent, after sync’s Mutex family and internal/concurrent.HashTrieMap, and the easiest fit of the three: src/core/internal/weak/pointer.cs is now a whole-file hand-own under [module: go.GoManualConversion] built on System.WeakReference over the ж<T> box, with a ConditionalWeakTable keyed on ж<T>.ReferentObject standing in for the runtime’s canonical per-address specialWeakHandle. Design, the clause-by-clause contract table, the ephemeron argument for why the canonical index does not pin what it indexes, and the guarding measurements: ConversionStrategies-Reference.md, internal/weak.Pointer. Marker census 39 → 40 (line-anchored; note 39, not the 40 CLAUDE.md records from r40 — math/unsafe.cs shed its marker in the interim). internal/weak joins internal/godebug and internal/concurrent as fully hand-owned: internal.weak.csproj, package_info.cs and README.md stop re-emitting and no .cs.auto sibling is produced — all three confirmed in place, since a -tests run over the package left every one of them untouched.

A SECOND defect was standing behind the first, and it is not weak’s. The [GoType] generator gates struct equality on every type parameter carrying an IEqualityOperators constraint, so a Go type declared [T any] (or [T comparable], which the converter renders new()) emits Equals(other) => false /* missing equality constraints */even when no field’s type mentions the parameter at all. Both weak.Pointer[T] (field unsafe.Pointer) and unique.Handle[T] (field ж<T>, which defines == for every T) were victims. Pointer[T]’s copy is fixed here by hand-writing the struct; unique.Handle’s is a GENERATOR fix, chipped, and it is what six TestHandle rows now report.

Measurements — every root moved, no row count did

Package Before After What actually changed
internal/weak (own suite, first ever run) 1 of 3 TestPointerEquality PASSES vs go test — the canonicalization clause, the hardest one, validated end to end. TestPointer/TestPointerFinalizer fail on the roster’s codegen-liveness class (below). Does NOT bank, and not because the count is short of the bar: TestPointerFinalizer does not fail an assertion that could be disclosed, it BLOCKS forever on <-done awaiting a finalizer a still-rooted object can never queue
unique 4 of 19 4 of 19 the announced weak panic is gone from every row; the host stops DEADLOCKING; the seven TestHandle rows resolve into four distinct new roots
encoding/gob 98 of 106 98 of 106 TestNetIP no longer throws — net/netip’s package initializer completes for the first time and the test produces a value: decoded to ::ffff:1.2.3.4%, want 1.2.3.4. A netip 4-in-6/zone rendering difference, in nothing this arc touches. The other seven failures are the same gob-internal set

The unique host used to hang, and closing weak is what exposed it. handle_test.go’s drainMaps arms a one-shot notification, calls runtime.GC(), then BLOCKS on <-wait until the intern-map cleanup runs. runtime.GC()’s hand-owned managed body (runtime/managed_impl.cs) wired only the sync.Pool arm of clearpools(), so the cleanup could never run and every TestHandle subtest deadlocked — taking the whole test host to its package timeout and erasing the verdicts of the 12 rows that had nothing to do with it. That deadlock only became reachable once internal/weak stopped panicking one frame earlier. GC() now also does clearpools’ unique arm — the same non-blocking send on uniqueMapCleanup, inert until unique.Make has run. Result: a 10-minute timeout with 3 usable verdicts becomes a 2-minute run with 19.

unique’s four surviving roots, all measured this arc:

Root Rows Shape
[GoType] equality gate — unique.Handle<T>.Equals is false 6 every reachable subtest reports v0 != v1 and never v0.Value() != v1.Value(), i.e. both Make calls interned the SAME ж<T>. Generator fix (chipped), not a hand-own
codegen-liveness — a live C# local roots what Go proves dead 6 (same rows) checkMapsFor reports value X still referenced a handle. The cleanup now RUNS and CompareAndDelete is reachable for the first time; v0/v1 are simply still rooted where Go’s per-safepoint liveness maps have already dropped them. The roster’s existing disclosure class (sync carries several)
abi.TypeFor<T>() for an interface T 2 EntryPointNotFoundException at IEquatable<ж<abi.Type>>.Equals — unchanged, reflection-bridge row
GCHandle: Object contains references 1 now reached in cloneж<T>uintptrpinnedArrayData, not on the abi.Escape path the old row named
array<T>.Equals structural comparer 1 ArgumentException: Type of argument is not compatible with the generic comparer for an array OF [GoType] structs. New row, previously masked

Plus the standing TypeFor().Name() subtest-naming rows, which pair up by content but cannot match by name.

TestPointer’s failure is GC precision, and that is proven rather than argued. Go’s own pointer_test.go does st := wt.Strong(), then runtime.GC(), then asserts nil — Go’s compiler proves st and bt dead; a C# frame reports them live. A dedicated probe separates the two by creating and dropping the referent inside a [MethodImpl(NoInlining)] helper:

PASS  CONTROL plain object collects
PASS  CONTROL ж<int> in a self-keyed ConditionalWeakTable collects
PASS  CONTROL two-level CWT->ConcurrentDictionary keyed on ж<int> collects
PASS  Strong() is nil once the referent is unreachable (never probed)
FAIL  Strong() is nil once the referent is unreachable (probed first)

16 of 17 assertions pass, and the single failure is the probe’s own frame holding the ж<int> that an earlier Strong() returned. The controls make the ephemeron reasoning measured rather than assumed.

Linkname PUSH registry — disposition unchanged, prose updated. The two internal/weak rows STAY in linknamePushTargets as loud stubs, because they describe what a conversion into a root that does not already carry the hand-own must emit; the deployed corpus never regenerates the marked file. Their reason strings now name internal/weak/pointer.cs instead of asking for it.

Gates. internal/weak + unique + runtime build clean; go2cs-stdlib.slnx 304/304, 0 errors; seeded full -stdlib -comments reconvert with the hand-own clobber gate; go test ./... in the converter ok; GolibTests / ChannelTests at baseline; full behavioral suite. A/B footprint: 2 corpus files (internal/weak/pointer.cs, runtime/managed_impl.cs) + the converter’s registry comments.

The WHOLE-CORPUS REBANK — 1,316 files, sixteen families, zero unclassified (2026-08-04, r40-rebank)

User ruling #6’s one deliberate regeneration. The campaign’s standing discipline is that the unit of work is the CONVERTER FIX and that a corpus regen must never bury it, so arc after arc landed a gated converter change and left src/core behind. This paid that debt in one session, in commits whose only job is to BE that diff.

It is a bank, not a repair — every file is the already-gated output of a change that shipped with its own behavioral guard, and the reconverted corpus builds 304/304 with zero errors.

Family census — 1,299 files from the overlay (703 .cs, 298 .csproj, 298 README.md)

A file may carry several families; the count is files touched by that signature.

Family Files What moved
deref-accessor 592 Ꮡx.Value / .ValueSlot.DerefOrNull() at pointer ENTRY aliases (r36 four-accessor, r37b param unification)
dead-param-alias 541 the entry alias is dropped outright where nothing reads it
GoBigConst 304 the rename reaching every emitted .csproj + 6 const sites
README-badge 298 Go_testsTests label (r39) + matched/total refresh; 298 removed / 298 added, so no README lost its badge
typed-nil 145 ᏑfdᏑfd.OrTypedNil() (r39-nilcomplex)
local-func 90 an only-called closure literal becomes a local function (r39e)
GoImplement 44 satisfies-not-witnesses: encoding/binary now records bigEndian → ByteOrder, which nothing ever cast to witness
value-adapter 40 …and therefore consumers stop minting binary_bigEndianᴠByteOrder
implicit-conv 23 importedPointerImplements retirement: text/template stops recording the foreign parse package’s pairs
closure-box 22 a closure’s own local needs no ref heap<T> box (r39e)
import-alias 20 the using x = go.y_package those records required, now unused
wrapper-qualification 17 srcimporter_ImporterжImportersrcimporter.ImporterжImporter
pointer-reinterpret 15 Ꮡ((T)(~p))p.Reinterpret<F,T>() (ruled, 70cbcad69)
named-const-cast 12 an untyped const argument takes its named parameter type (time.Sleep((time.Duration)(…)))
fallthrough 12 the flag moves INSIDE the do{}while(false) so an early break no longer sets it — a real semantic fix
alias-pointer 10 (ж<array<T>>)(uintptr)(new @unsafe.Pointer(x))array<T>.AliasPointer(x, n), which stops copying the run

Reconciliation with the forecast. 699 .cs carry a genuine drift family against the 695 r39-nilcomplex measured on 2026-08-03 — agreement to within the arcs that landed between. The other 600 files are the rebank’s own two corpus-wide relabels (302 GoBigConst, 298 badges), neither of which existed when the forecast was taken. The r39c pointer peephole showed no new drift, as predicted: every @unsafe.Pointer line in the diff belongs to alias-pointer or local-func, none to the peephole.

Plus 17 files the regen structurally cannot reach: the three hand-owned packages whose .csproj is never re-emitted (unsafe, internal/concurrent, internal/godebug), the 13 Perf*.csproj (regenerated by transpiling each benchmark — the Perf .cs proved to carry no drift at all), and the Go comments in BigUntypedConstComparison that name the emitted type.

Restored, not banked — and the third phantom shape

  1. 28 auto-normalized CRLF phantoms — dirty in git status with no diff hunks at all (they do not even appear in --numstat), each proven content-identical modulo CR, positive control fired.
  2. -text testdata copies — ⚠ the trap: src/core/compress/testdata/* is marked -text, so git does NOT normalize it and a pure CRLF flip shows as a real non-empty numstat (gettysburg.txt 29/29). The standing rule “a phantom has an empty numstat” is therefore false for -text paths — test CR-equality directly instead of trusting the numstat.
  3. The -tests-closure production re-flip — see the correction in DESIGN-named-interface-wrappers §7: the corpus now RESTS on the -stdlib side, but the asymmetry is intact and every sweep re-flips using io = io_package; to using Δio = io_package;. Restore, never bank.

Confirmations this rebank was the right place to make

The sweep — the policy inversion, and what it proved

A validated sweep is normally a GATE whose dirt is restored. Here the corpus itself had moved, so the sweep’s OUTPUT was the deliverable: 73 packages, 2,713 expected verdicts, 73 pass / 0 fail in 2,736 s (45.6 min), every package at its exact banked count. 299 files banked — 137 *_test.cs, 73 *.tests.csproj, 59 proof pages, 15 go2cs_test_host.cs, 12 package_test_info.cs, 3 package_init.cs — and 36 restored across four shapes (13 closure re-flips, 10 -text testdata, 8 .cs.auto, 5 CRLF phantoms).

That a 1,316-file corpus bank moved no verdict anywhere the roster reaches is the strongest single statement available that this was a bank and not a repair.

It also closed backlog residual #15 exactly as written — the seven banked DerefOrNil() sites in container/ring, go/token, index/suffixarray and testing/quick re-emitted as DerefOrNull() the moment each package’s -tests pipeline ran, with no separate work. Zero remain.

Two traps this half paid for, both worth carrying.

  1. -text paths break the phantom rule. src/core/compress/testdata/* is marked -text, so git does not normalize it and a pure CRLF flip shows a REAL non-empty numstat (gettysburg.txt 29/29). The standing “a phantom has an empty numstat” test is false there — compare CR-stripped content directly instead of trusting --numstat.
  2. Never amend a commit while a run that stamps its SHA is in flight. The proof pages record the tree they validated against, so amending the corpus bank’s message mid-sweep left 17 of 59 pages naming a commit that no longer existed. Recoverable only because the amend preserved the tree exactly (both SHAs point at tree 15e4eca18), which made the stamp correctable textually rather than by re-running 17 packages.

The r43c breadth pass — 58 candidates measured, 23 bank, every non-bank rooted (2026-08-07)

The charter says the cheapest scout is the pipeline itself, and until now it had only ever been pointed at packages some other arc had named. This pass pointed it at the long tail as a batch: 58 never-measured candidates run back to back through go2cs -tests -test-action all -test-timeout 10m, selected only by “its dependency closure is already validated and it is not behind a known deep wall.”

Twenty-three validated on the first run, with no converter, golib or host change of any kind — roster 73 → 96 (44.7%), 13,070 matching verdicts. One candidate (image/color/palette) has no eligible Test declarations at all. The remaining 34 are rooted below.

The finding worth carrying forward is the negative one. None of the twenty-three needed anything; the corpus had already grown past them, package by package, as forty banked packages’ worth of shared machinery landed, and nobody had looked. The roster’s denominator was limited by attention, not by defects. So: re-scout the tail after any capability lands, not just the packages that capability was aimed at — a Tier-0 frog closes silently for packages nobody associated with it.

Two mechanical notes for the next person running a batch like this:

The twenty-three

crypto · crypto/aes · crypto/des · crypto/rc4 · crypto/internal/alias · crypto/internal/bigmod · go/constant · go/doc/comment · go/format · go/printer · hash · image · image/color · internal/buildcfg · internal/coverage/cformat · internal/coverage/cmerge · internal/coverage/pods · internal/dag · internal/diff · mime/quotedprintable · net/url · testing/iotest · text/template/parse

go/doc/comment alone contributes 10,059 verdicts — its TestTestdata walks every doc comment in the standard library’s own Go sources — and is now the largest single suite banked. hash, crypto and image are worth noting for a different reason: each is a tiny contract package whose suite exercises the whole family beneath it (hash’s marshal round-trip runs across all eighteen stdlib digests; crypto’s out-of-bounds guards run every stream mode).

Four of the twenty-three flip a production .cs on every sweep, per the standing -tests-closure family (crypto/crypto.cs, hash/hash.cs, image/format.cs — the Δio alias; and internal/buildcfg/package_init.cs — the init-tests hook, which its test half implements nothing of). All four are added to run-validated-sweep.ps1’s documented $closureFiles set so the sweep keeps classifying them rather than reporting them as content drift.

Build-blocked — eight roots

Package First diagnostic Root, as far as it was taken
log, go/scanner CS0012 CLOSED 2026-08-07 (r43f-closure-edge): both edges landed, go/scanner BANKED 11/11, log does NOT bank — two roots stand behind the closure one. Full account in the last section of this file. The rooting below called both mechanisms correctly and was wrong about two details worth carrying: log’s literal is not log.Logger{} but var l Logger (a zero-value DECLARATION, in the INTERNAL white-box half — no composite literal exists, which is exactly why no literal walk could see it), and the implemented-interface gate is not types.Implements but the package’s own emitted VALUE-form GoImplement RECORDS: satisfaction alone drifts 16 of the 96 banked projects. Original rooting: A fourth declaration-closure edge, the same family the 2026-07-27 arc closed for interface bases, struct fields and member-access receivers. log’s external test half writes log.Logger{}; under the white-box InternalsVisibleTo grant the package-under-test’s internal fieldwise constructor IS a resolution candidate, so binding it needs atomic.Bool’s assembly. go/scanner’s generated ErrorListerror witness calls m_value.Equals(…), and binding a member on ErrorList needs the assemblies of the interfaces its own declaration implements (sort.Interface, ×13). The existing rule’s minimality gate fires the struct edge on an EMPTY literal only for a ROOT package — log’s case says the white-box grant is the same situation by a different route. Both are one edge each on declarationClosureImports, and both must be measured with that rule’s own instrument: regenerate every banked .tests.csproj and require zero drift. The cheapest two banks left on this list.
slices CS0305 / CS0411 Go infers S ~[]E and E from a single argument; C# cannot infer E from S. Equal/EqualFunc/CompareFunc/Reverse/Insert/CompactFunc emit as two-parameter generics and essentially every call site fails. Needs element-type deduction (or witness parameters) for constrained slice generics — the widest root in the batch, and it blocks the largest unbanked leaf (63 Test funcs).
archive/tar CS1537 ×3 writer_test.cs emits the same using alias twice in one file (testFnc, fileMaker), plus one CS0111. A test-half alias emission that does not dedupe within a file. Shallow.BANKED 97/97 2026-08-15 (claude/dup-append-emission). The rooting was right about the symptom and wrong about the scope in two ways: the collision is not per-file but per-COMPILATION (global using), and dedupe is not the fix — two functions declaring type testFnc any declare two unrelated Go types, so the alias takes the same enclosing-function lift every other local type-declaration kind already took. CS0121 on a one-field nil constructor stood behind it. See CLOSED for archive/tar at the end of this file.
archive/zip CS1929 The generated ReadCloserfs.FS witness binds Open against a ж<Reader> receiver while holding a value ReadCloserBUILD ROOT CLEARED 2026-08-09 (r56g). The receiver split was a symptom: Open is a pointer-receiver method promoted from ReadCloser’s exported Reader value embed, and that promotion was not emitted at all (root 1), then emitted internal because the scope heuristic reads a tuple return’s trailing error) as unexported (root 3). Package now BUILDS and RUNS at 95 of 98; the residual is TestZip64LargeDirectory + 2 subtests as a performance row (Go 13.2 s, C# > 45 m), not a defect. See r56g below. — BANKED 98/98 2026-08-09 (r57c): the performance row was @string slicing in O(n); see r57c at the end of this file.
testing/fstest CS0030 Converting the test-local named type shuffledFS to its underlying map[string]*MapFile.
internal/types/errors CS0246 Error / Info — names the emitted code does not declare for a test-local enumeration.
crypto/ecdh (CS1001), crypto/ed25519 (CS0030), crypto/internal/mlkem768 (CS0315), runtime/debug (CS0264) not taken past the first diagnostic.

Runtime — rooted, not fixed

Package Root
html map[K][N]T’s missing-key read yields a zero-LENGTH array, not Go’s zero-VALUE array. unescapeEntity does x := entity2[name] over a map[string][2]rune and then tests x[0]; C#’s default(array<rune>) carries m_length == 0, so the read throws IndexOutOfRange where Go sees 0. The class is wider than maps: anywhere the Go zero value of [N]T (or of a struct containing one) is produced by C#’s default(T), it is wrong the same way. The converter already knows how to render the right thing (arrayZeroValueArgs, visitArrayType.go) — it simply is not consulted at a map read.
internal/platform, crypto/internal/hpke Same shape, two packages: json: cannot unmarshal array into Go value of type []T, where T is a converter-lifted anonymous struct ([]platform_test.listEntry, []hpke.TestRFC9180Vectors_vectors). A JSON array of arrays/objects decoded into a slice of a lifted type — worth one look, since two independent packages reach it.
net/http/internal TestChunkReaderAllocs — an exact allocation-count assert, the established alloc-count-semantics class. Would be a disclosure candidate only after re-deriving the measurement; the rest of the package matched.
go/ast ast.Fprintreflect.MapKeysmapType.get_MapType() fails an interface conversion inside go2cs-gen’s promoted-field accessor. Reflection-bridge territory — that chip’s, not a breadth lane’s.
go/parser performance_test.cs’s package initializer reads a testdata file at cctor time and panics, taking every test in the internal variant with it — the -tests init-relocation shape internal/fmtsort already needed a rule for.
expvar Type-initializer failure inside a generated ᴛRegisterAdapter for ΔStringжVar; first divergent verdict TestAppendJSONQuote.
internal/cpu getGOAMD64level is an unimplemented PartialStubGenerator stub; every GODEBUG-driven feature-mask row reaches it.
testing/slogtest BANKED 2026-08-07 (r44b-slog) — 17/17, no disclosures. Both log/slog roots below are closed; see testing/slogtest banks at the end of this document. runtime.Caller → the getcallersp stub, reached from a package initializer, so the whole package infrastructure-errors. Same getcallersp row the reflection arc carries. Caller root CLOSED 2026-08-07 (r43g-caller) — the package now initializes and RUNS its whole matrix for the first time: TestRun 7 of 18 subtests pass. Two log/slog roots stand behind it, neither a slogtest defect: (1) unsafe.SliceData over a reference-bearing element typeslog.GroupValue’s groupptr(unsafe.SliceData(as)) on []Attr reaches slice<T>.bufferPinnedBufferGCHandle.Alloc(…, Pinned), which throws ArgumentException: Object contains references (5 infrastructure-errors: groups, empty-group, inline-group, resolve-groups, resolve-WithAttrs-groups); (2) a WithAttrs attribute-loss (4 fails: WithAttrs, multi-With, empty-group-record, resolve-WithAttrs — all “missing key”), whose likely shape is Value.Kind()/isEmptyGroup misclassifying a non-group value so commonHandler.withAttrs’s countEmptyGroups(as) == len(as) early-return drops the attrs. Both belong to a log/slog operational arc, which is unmeasured (log/slog is on neither the roster nor this board).
internal/unsafeheader TestTypeMatchesReflectType / TestWriteThroughHeader: the converted unsafeheader.Slice/String do not alias the same storage a slice<T> does, so a write through the header is invisible. Structural — a managed slice is not a {Data,Len,Cap} triple.
io/ioutil TestReadDir reads .. and expects the sibling package’s io_test.go. The pipeline stages Go sources only for the package under test, so the parent directory holds none. Environment, not conversion.
internal/singleflight BANKED 2026-08-14 (SCHED-S2) — 5/5, no disclosures. The only hang in the batch: TestDoAndForgetUnsharedRace never returns and the package hits the deadline. The hang was never singleflight: it was the ThreadPool executor charging a parked goroutine against the capacity of ones that had not started. A dedicated thread per goroutine converges the test in 1.2 s (was 28.7 min); see Convergence measured 2026-08-12RESOLVED 2026-08-14 above.
crypto/cipher (TestGCMAsm), internal/godebugs (TestAll) one row each, both Go="pass" C#="skip" — a build-tag/capability gate the C# side answers differently.
crypto/elliptic (TestInfinity/P224/Params), crypto/internal/edwards25519/field (TestBytesBigEquivalence), crypto/internal/boring/bcache (init in cache_test.cs), internal/chacha8rand (TestBlockGeneric), internal/profile (TestPackedEncoding encodes empty), encoding/asn1 (TestCertificate), go/doc (Test/default/a), net/mail (TestAddressParser), net/http/httptrace (TestCompose), mime/multipart (TestLineContinuation) first divergent verdict recorded; not root-attributed.

One trap this pass hit, worth writing down: a corrupted GO BUILD CACHE reads exactly like a package failure. A host reboot mid-run left twelve zero-filled entries in %LOCALAPPDATA%\go-build, and crypto/internal/alias then failed with could not import crypto/internal/alias (EOF) — reported by the pipeline as FAIL … [build failed] on the Go side, i.e. the oracle itself. The tell is that the same go test passes from a different working directory. go clean -cache is the blunt fix and is machine-global (bad while siblings are running); the surgical one is to delete only cache files whose first bytes are zero, which is a cache MISS rather than a corruption and is safe concurrently. The same reboot zero-filled 566 files under src/core/**/{bin,obj} — those read as build failures too.

The gate — 96 of 96, and what the aftermath said

The bank’s gate is the full validated sweep at the NEW roster, and it ran clean: 96 packages, 96 matching at their exact banked counts, zero COUNT mismatches and zero failures. (81 through run-validated-sweep.ps1, which was killed externally at path/filepath — the machine-global kill signature §9 warns about, not a verdict — and the remaining 15 driven straight through the pipeline and cross-checked against the table’s counts by hand.)

Two things in the aftermath are worth recording because neither is drift and both will recur.

src/core/time/package_init.cs was a standing restore that no list named. The time bank recorded it in prose (“no committed package_init.cs in the corpus carries the hook, and time’s implements nothing”) but never added it to run-validated-sweep.ps1’s $closureFiles, so every sweep since has reported it under CONTENT drift — inspect before banking or restoring. It is now listed, alongside the four this arc’s own banks contribute.

Twelve banked TEST sources are stale against the current converter, and it is pre-existing. bytes/reader_test.cs, compress/flate/deflate_test.cs, context/benchmark_test.cs, strings/reader_test.cs, sync/{cond,map,mutex,rwmutex,waitgroup,example}_test.cs, time/{sleep,time}_test.cs all re-emit differently — almost entirely the capture suffix renumbering (ʗ2ʗ1) that a later converter arc introduced, plus one comment-emission difference in sync/example_test.cs. This lane changed no converter, golib or generator source (git diff master..HEAD -- src/go2cs src/core/golib src/gen is empty), so the staleness is master’s: those packages were banked before the change and their test sources were never refreshed. Restored here rather than banked — refreshing another package’s test sources is a rebank’s job, not a breadth lane’s — and owed to the next one, alongside the .cs.auto review siblings (CleanupBacklog item 18), eight of which drift the same way.

One more environmental trap, alongside the build-cache one above: a full sweep at 96 packages can FILL THE DISK. Each package’s test bin holds a copy of its whole closure, so a cold sweep writes tens of gigabytes; this one exhausted C: mid-run with sibling lanes also building. The failure is loud but misleading — the converter reports failed to write to output source file … There is not enough space on the disk for crypto/sha1/sha1.cs and friends, i.e. it truncates TRACKED corpus files, which then read as corpus corruption. git checkout -- src/core restores all of it, but the lesson is to check free space before a full sweep and to prune bin/obj between chunks on a contended box.

The fourth and fifth closure edges — CLOSED; go/scanner banks 11/11, log has two roots behind it (2026-08-07, r43f-closure-edge)

The r43c rooting above named log and go/scanner “the cheapest two banks left on this list.” One of them was. Both build blockers are fixed by two new edges on declarationClosureImports — the same family the 2026-07-27 arc closed for interface bases and struct fields and r38 extended to member-access receivers — but only go/scanner banks. Full technical account, with both edges’ gates and their guards, in ConversionStrategies-Reference.md, The fourth and fifth closure edges.

Edge 4 — a ZERO-VALUE DECLARATION is a constructor call. r43c read log’s blocker as log.Logger{} in the external half. It is var l Logger in the INTERNAL (white-box) half, and the difference is the whole point: there is no composite literal in the package’s test sources at all, so no *ast.CompositeLit walk could ever have found it. The converter renders Go’s zero value of a struct as a constructor call (ref var l = ref heap(new Logger(), out var Ꮡl) when the address is taken, new Logger() otherwise), C# overload resolution materializes every accessible constructor’s signature before choosing one, and the white-box InternalsVisibleTo grant makes the internal fieldwise overload accessible — CS0012 … 'atomic_package.Pointer<>' … 'sync.atomic'. It is the existing root-scoped empty-literal edge’s exact demand by another route, so it feeds the same seed under the same gate.

Edge 5 — a concrete type’s bases live in its package’s RECORDS, not in its declaration. [GoType("[]ж<ΔError>")] partial struct ErrorList; names no interface. sort.Interface reaches it as a VALUE-form [assembly: GoImplement<ErrorList, sort_package.Interface>] record that go2cs-gen realizes as partial struct ErrorList : global::go.sort_package.Interface inside go.scanner.dll — so the metadata type declares the base and binding any member on it must resolve it. Thirteen sites failed: list.Sort(), len(list), Ꮡlist.RemoveMultiples(), and the generated ErrorListerror value adapter’s own m_value.Equals(…).

The correction worth carrying: the gate is the RECORDS, not types.Implements. The natural go/types statement of edge 5 — “the interfaces the receiver’s type implements, from the declaring package’s imports”, mirroring interfaceBaseCandidates one type-kind over — passes every unit test, fixes go/scanner, and drifts 16 of the 96 banked projects. A record exists only where the converter converted a CAST, so Go satisfaction wildly over-approximates the emitted base list: os.File satisfies syscall.Conn and hands syscall to thirteen projects, though os records File only against io/fs.File and io.Writer and both in POINTER form, which generates an adapter CLASS rather than a base and demands nothing of a member binding; bytes.Buffer satisfies most of io and hands io to sort and unicode/utf8 though bytes emits no records at all; internal/buildcfg’s Stringer hands it fmt from an equally empty set. All sixteen compile clean today with none of it. Gating on the package’s own value-form records — keyed per type, because os’s one genuine syscall record is for rawConn and not File — is zero-drift across all 96. Two lessons generalize: (1) satisfying an interface in Go is not carrying it as a base in C#, and the emitted package_info.cs is the authority on which is which; (2) this family’s instrument keeps earning its keep — it has now rejected six rules that a reading of C#’s binding rules justifies, and this one was the most convincing of them.

go/scanner — BANKED, 11 of 11, roster 96 → 97 (45.1%), 13,081 matching verdicts. Whole token and literal matrix, semicolon insertion, //line directives, ErrorList sort + one-per-line dedup, CR stripping. No production .cs drift and no closure-family restore — the package contributes nothing to $closureFiles.

log — builds and RUNS for the first time, and does NOT bank. Seven of its nine test functions agree with go test; two roots stand behind the closure one, neither of them this family’s:

Test Verdict Root
TestAll infrastructure-error runtime.Callerruntime.callersgetcallersp, an unimplemented PartialStubGenerator stub. log.output calls runtime.Caller(calldepth) whenever the logger carries Lshortfile/Llongfile, and TestAll sweeps every flag combination. This is the SAME getcallersp row testing/slogtest carries and the reflection arc tracks — not a log defect, and the one root standing between log and a bank. A real runtime.Caller (managed StackTrace, or a hand-owned extern.cs) would likely bank log and slogtest together, and is worth its own arc.
TestDiscard fail got 424 allocs, want at most 1 — an exact allocation-count assert, the established alloc-profile class. A legitimate disclosure candidate once TestAll clears; disclosing it alone banks nothing, so nothing was disclosed and no log artifact is committed.

Both were reachable only after the closure fix, so the edge paid for itself twice over even where it did not bank: log’s suite had never linked a host and had never been measured.

runtime.Caller lands — and log still does not bank, for a reason worth naming (2026-08-07, r43g-caller)

The row above predicted “a real runtime.Caller … would likely bank log and slogtest together”. The Caller half was right and cheap; the prediction was wrong, in both packages, and the reasons are different and both worth carrying.

The fix is one entry, and it is on the FUNNEL. runtime.Caller’s auto body calls the lower-case callers, not the exported Callers the 2026-07-31 reflection chip hand-owned — and callers is the declaration that opens with getcallersp(). Four call sites funnel through it (Caller, mprof, proc.createstack, tracestack), so "callers": true on manualConversionFuncs["runtime"] fixes all four and leaves Caller itself auto-converted and Go-shaped. Corpus A/B footprint: one file, src/core/runtime/traceback.cs (the body becomes the standard placeholder comment). Mechanism, the skip + 1 / skip + 2 frame budgets, the NoInlining requirement, and the honesty boundary are in ConversionStrategies-Reference.md, runtime.Callers / Frames.Next walk the managed stack. Guarded by the RuntimeCallerFrames behavioral test.

log — 7/9 still, and TestAll is now an honest, measured divergence instead of a crash. With Caller alive, TestAll runs its whole flag matrix and produces real output. It fails on Go source geometry, which the fix was never going to supply:

Go asserts The converted run reports
^[A-Za-z0-9_\-]+\.go:(63\|65): hello 23 world$ C:\…\src\core\log\log_test.cs:69: hello 23 world
^.*/[A-Za-z0-9_\-]+\.go:(63\|65): … (Llongfile) same, with \ separators

Three separate mismatches in one assert: the .go extension, the / path separator, and the exact line numbers of the Printf/Println calls inside log_test.go (the test’s own comment says “must update if the calls to l.Printf / l.Print below move”). Caller reports the converted .cs position because that is the source the running program has.

This is deliberately NOT disclosed. The bar for the disclosed-divergence manifest is an assertion unsatisfiable at any layer go2cs owns (alloc-profile, codegen-liveness). This one is satisfiable at a layer go2cs owns — a Go-source position map: either #line directives in the emitted C# (the CLR’s own transpiler mechanism; the PDB would then carry .go files and lines, and StackFrame.GetFileName/GetFileLineNumber would answer in Go’s terms for free), or a side-car map per package consulted by internCallerFrame. Both are whole-corpus emission changes with real trade-offs — #line noise cuts against the readability goal, a side-car adds a file and a csproj item to every package — so this is an architectural arc to design with the user, not something to slip in behind a bank. Until it lands, log stays off the roster; disclosing around it would launder a missing capability as an unsatisfiable assert.

TestDiscard re-derived under the fix: still got 424 allocs, want at most 1, unchanged by this arc (flag is 0, so Output never reaches Caller). The figure is go2cs’s AllocsPerRun shim reporting bytes per run, not mallocs; Go allocates once (the []any{s} variadic pack) for l.Printf("%s", s) over a 102 400-byte string. It remains a legitimate alloc-profile candidate and remains undisclosed, because disclosing it alone banks nothing — the same call r43f made.

testing/slogtest — initializes and runs for the first time, 7 of 18 subtests pass, two log/slog roots behind it. Detail in the runtime-roots table above. The lesson is that slogtest is a thin wrapper over log/slog: banking it is a log/slog operational arc, and log/slog has never been measured at all. That is the recorded next candidate out of this lane.

testing/slogtest banks, and log/slog gets its first census (2026-08-07, r44b-slog)

Both roots r43g left behind were real, both were converter/runtime defects with corpus-wide reach beyond slog, and both closed. testing/slogtest went 7 of 18 -> 17/17 matched, no disclosures in one pass. log/slog was measured for the first time and does not bank, for reasons that are now named rather than guessed at.

Root 1 - unsafe.SliceData was a PIN where Go means an INTERIOR POINTER

slog.GroupValue stores a group as groupptr(unsafe.SliceData(as)) plus len(as) and rebuilds it with unsafe.Slice in Value.group(). That is identity and aliasing, never an address - but golib answered SliceData with a pinned-buffer box over slice.buffer, and GCHandle.Alloc(..., Pinned) refuses any storage whose element type carries a managed reference. Every grouping path in the package infrastructure-errored with ArgumentException: Object contains references.

Go DEFINES unsafe.SliceData(s) as &s[:1][0], so the faithful model is the array-element reference the converter already emits for &s[0]. Pinning was never SliceData’s job: an address is needed only at a uintptr/void* conversion, and the pointer box pins there on demand (EnsureStableAddress), declining gracefully for storage that cannot be held still. Two further latent defects fell out with it: the pin covered the whole backing array from index 0, so SliceData(s[2:]) addressed the wrong element and failed Go’s == &s[2] identity; and PinnedBuffer implements IArray<byte> alone, so the derived pointer was undereferenceable for every element type but byte. A/B footprint: one hand-owned file, src/core/unsafe/unsafe.cs. Guarded by the new UnsafeSliceDataAliasing behavioral test. Full mechanism in ConversionStrategies-Reference.md, unsafe.SliceData is an INTERIOR POINTER, not a pin.

Root 2 - the named-slice pointer reinterpret boxed a COPY, so out-parameters wrote nowhere

r43g’s guess (a Value.Kind()/isEmptyGroup misclassification driving countEmptyGroups) was wrong, and the real root is upstream of slog entirely. commonHandler.withAttrs writes its pre-formatted attributes through (*buffer.Buffer)(&h2.preformattedAttrs) - a pointer conversion from *[]byte to a named-slice pointer, whose whole purpose is that the bytes land in h2’s own field. The converter emitted a wrapper box over a copy. Its own comment recorded the assumption

The fix routes the shape through golib’s existing storage reinterpret - Reinterpret<slice<byte>, buffer.Buffer>() over the field’s own pointer - which re-views the same slot as the wrapper. A generated named-slice wrapper is a single-field struct over the slice header, precisely the correspondence ReinterpretAliasesStorage recognizes, so the managed alias arm engages and writes reach the addressed storage. The reach is wider than slog: crypto/tls’s readUint{8,16,24}LengthPrefixed and parseECHConfigList, and cryptobyte’s ReadASN1Bytes, all take (*cryptobyte.String)(out) on an out-PARAMETER or a struct FIELD - every one of them was silently discarding what it parsed. Corpus A/B footprint: 5 files, 8 sites. Guarded by the extended NamedSlicePointerReinterpret behavioral test, whose previous version had written the defect into its own comments as expected behavior and deliberately never read the source back; it now does, on all four source shapes.

log/slog - first census: 185 pass, 28 fail, 1 crash. It does not bank.

Class Rows Disposition
Go-source geometry - TestCallDepth, TestConnections (+1 sub), TestJSONAndTextHandlers (+3), TestPanics, TestRecordSource 9 The SAME class r43g named on log’s TestAll: runtime.Caller honestly reports logger_test.cs:905 where the assert wants ^logger_test\.go:\d+$. Not disclosable - satisfiable at a layer go2cs owns (a Go-source position map: #line directives, or a per-package side-car). It is the architectural arc to design with the user, and it is what actually gates log/slog and log.
alloc-profile - TestAlloc (+13 subs), TestAnyLevelAlloc, TestAttrNoAlloc, TestTextHandlerAlloc, TestValueNoAlloc 18 The established disclosure class. Nothing disclosed - disclosing them alone banks nothing while the geometry class stands, which is the same call r43f and r43g made on log.
Package initialization ORDER - TestLogLoggerLevelForDefaultHandler (fail), TestSetDefault (the crash) 1 + crash A new, general root; see below.

Root 3 (found, NOT fixed) - Go initializes an imported package before its importer; C# does not

slog’s init captures log/internal.DefaultOutput, which log’s own init installs. Go’s spec orders that by the import graph, so the capture is always non-nil. A .NET module initializer fires at first access to its own module, so whichever of log/log/slog is touched first wins: touch slog first and defaultHandler.output is captured nil, and the next slog.Info is an unrecovered nil-pointer panic that kills the process. In the census it aborted the run at TestSetDefault and hid 34 further rows (re-measured separately: 32 of them pass).

Reduced to a 12-line standalone program that go run handles and the transpiled build crashes on:

func main() {
	slog.Info("hello from slog")   // touch slog BEFORE anything in log
	var buf bytes.Buffer
	log.SetOutput(&buf)
	slog.Info("second")
	fmt.Printf("log buffer: %q\n", buf.String())
}

The mechanism to fix it already exists and is already documented - golib’s builtin.initPackage(Type) (RuntimeHelpers.RunModuleConstructor), which the converter emits today for blank imports. Making every package force its DIRECT imports at module-init time reproduces Go’s ordering exactly and transitively (the import graph is a DAG, so direct-imports-only composes to the full closure in post-order). That is precisely the extension ConversionStrategies-Reference.md records as “deliberately deferred, not overlooked”: it trades eager loading of the whole transitive assembly closure at module init for fidelity. log/slog is the first case that NEEDS it, and it is a whole-corpus emission change with a real startup trade-off - so it is left as an architectural arc to design with the user, alongside the position map, rather than slipped in behind a bank. It buys 2 log/slog rows on its own and would not bank the package.

Aftermath noticed in passing: 24 README validation badges are stale

A full seeded -stdlib reconvert on this lane’s converter differed from the committed tree in 28 files: 3 are this lane’s fix (above), 1 is testing/slogtest’s own new badge, and the other 24 are src/core/<pkg>/README.md badges still reading not_yet_validated for packages that validated in 47ec27319 (“bank 23 packages from a measure-first breadth pass”). That bank wrote the proof pages under docs/validation/current/ but never overlaid the READMEs the converter composes from them, so those 24 packages currently under-report themselves on nuget.org. Deterministic converter output, zero risk to refresh - left for an idle-point overlay rather than folded into this lane’s commits. Affected: crypto, crypto/aes, crypto/des, crypto/rc4, crypto/internal/alias, crypto/internal/bigmod, go/constant, go/doc/comment, go/format, go/printer, go/scanner, hash, image, image/color, internal/buildcfg, internal/coverage/cformat, internal/coverage/cmerge, internal/coverage/pods, internal/dag, internal/diff, mime/quotedprintable, net/url, testing/iotest, text/template/parse.

Resolved, and the CAUSE is standing (2026-08-08, r45b). Those 24 were leveled by a later regen, and r45b’s Docs-badge overlay leveled the next batch — 12 of r44a/r44b’s banks, plus internal/concurrent’s label, which had frozen on the pre-2026-08-03 Go_tests spelling because the package is hand-owned by consequence. The mechanism that creates them has not changed: a bank writes docs/validation/current/<dot-id>.md and never re-emits the README the converter composes from it, so every bank leaves its own badge stale until the next corpus README overlay. Treat a handful of stale Tests badges as the EXPECTED state between overlays, not as a finding — and level them whenever a lane is regenerating the corpus anyway.

Recorded next candidates out of this lane. Two architectural arcs, both now with named beneficiaries: the Go-source position map (unblocks log and log/slog, ~9 rows across the two) and import-ordered package initialization (correctness, not just verdicts - any converted program that touches log/slog before log crashes today).

The r44a re-scout — r43c’s own lesson executed; 12 bank, and the tail’s roots are named (2026-08-07)

r43c ended with an instruction rather than a finding: re-scout the tail after ANY capability lands, not just the packages that capability was aimed at. Five capabilities landed between it and this pass — managed weak references (internal/weak), per-field [GoType] struct equality, runtime.Caller over a managed frame walk, range-over-every-integer-kind, and the abi.Type StructType/ArrayType specializations — plus the linkname PUSH direction. This pass ran the pipeline over BOTH halves of the tail that instruction names:

108 pipeline runs, -test-action all -test-timeout 4m, serial, on a corpus prewarmed by one go2cs-stdlib.slnx build (304/304, 0 errors, 113 s), plus a 12-package re-run (below). Roster 97 → 109 (45.1% → 50.7%), 13,081 → 13,611 matching verdicts, 50 disclosed (unchanged).

The re-scout of r43c’s own roots yielded exactly one package

expvar — r43c’s “type-initializer failure inside a generated ᴛRegisterAdapter for ΔStringжVar” — now validates 11 of 11, with no change of any kind in this lane. Every other package on r43c’s list re-measured verbatim, down to the error code: the eight build blockers are unmoved and the runtime roots reproduce their recorded shape.

That is a result, not a null: the re-scout instruction is right, and its yield on an already-rooted list is small. A rooted non-validator has been looked at. The yield is in the packages nobody has run — eleven of the twelve banks came from there.

The twelve

crypto/internal/boring · crypto/rand (298) · database/sql/driver · debug/buildinfo (197) · debug/plan9obj · expvar · go/importer · internal/cpu · internal/sysinfo · os/exec/internal/fdtest · plugin · runtime/internal/sys

Eleven needed nothing at all. internal/cpu is the lane’s ONE fix, below. crypto/rand (298) and debug/buildinfo (197) carry the volume; os/exec/internal/fdtest’s single verdict is a platform-gated skip on both sides — the converted run reaches Go’s own runtime.GOOS guard and declines exactly where Go does, which the proof page states plainly rather than dressing up.

The one fix — internal/cpu.getGOAMD64level, and why 1 is a measurement

TestDisableSSE3 opens if GetGOAMD64level() > 1 { t.Skip(…) }. Go reads 1 and walks on to a skip inside runDebugOptionsTest; the converted run hit an unimplemented PartialStubGenerator stub and infrastructure-errored, and that one row was the whole gap (7 of 8).

getGOAMD64level is declared in cpu_x86.s and its body is a compile-time constant — the GOAMD64_vN define the toolchain sets from go env GOAMD64, with #else MOVL $1 as the fall-through. It answers which microarchitecture level was this BINARY built for, never which does this CPU support; a v3 machine running a v1 build still reports 1, which is exactly why doinit keeps the sse3/avx/avx512 GODEBUG knobs switchable at level 1. go2cs emits portable C# with no GOAMD64 define and no microarchitecture-gated emission, so 1 is the same constant Go’s own assembly produces for go2cs’s build configuration — a measured property of the emission, not a placeholder, and probing the CPU here would answer a different question. Registered in manualConversionFuncs["internal/cpu"] with the body in cpu_x86_impl.cs. A/B footprint: one corpus file (cpu_x86.cs’s declaration becomes the standard placeholder comment) plus the hand-own. Marker census +1.

ONE ROW AWAY — the list this pass most wants read

Eighteen packages match every verdict but one or two. Each cell is the whole gap.

Package Census The row, and its root
runtime/internal/math 0 of 1 BANKED (roster line 143) — re-measured 1/1 by r57b
internal/platform 0 of 1 json: cannot unmarshal array into a slice of a converter-LIFTED anonymous struct (crypto/internal/hpke is the same shape). ⚠ r57b’s naming arm changed the TEXT to Go’s structural []struct { GOOS string; … }; the row is a Kind question about the lift, not a naming one
internal/profile 0 of 1 BANKED (roster line 117) — re-measured 1/1 by r57b
internal/godebugs 0 of 1 TestAll reads GOROOT-relative ../../../doc/godebug.md; the pipeline’s working dir has none
html 2 of 3 the array<T> unshaped-instance class, producer (1)
internal/chacha8rand 3 of 4 the same class, producer (2)
internal/singleflight 4 of 5 BANKED 5/5 by the scheduler arc — the hang was the ThreadPool executor, not the package
internal/cpu 7 of 8 BANKED this arc
go/ast 8 of 9 BANKED by r57b at 9/9 — two roots: the unbridged map read pair, then the lift’s leaked C# name
debug/gosym 8 of 9 TestPCLine’s child process exits 1
debug/pe 9 of 10 the array<T> unshaped class — _ [3]uint8 prints [0 0 0 0 0 0 0 0] vs Go’s [0 0 0] (r57b)
net/http/internal 9 of 10 TestChunkReaderAllocs — re-measured r58a as 2 objects/run against Go’s budget of 1, a lower bound; ruling pending, below
net/http/fcgi 11 of 12 BANKED (roster line 133) — re-measured 12/12 by r57b; the TestGetValues mismatch is gone
crypto/cipher 13 of 14 the oracle’s build tags, below
crypto/internal/edwards25519/field 13 of 16 the array<T> class, producer (3)
internal/poll 18 of 19 runtime_pollServerInit — the netpoller has no managed body
net/textproto 25 of 26 BANKED at 26/26 by L11 (2026-08-12) — the want-ZERO row closed honestly: the 3 objects/816 B per canonicalMIMEHeaderKey call decomposed into Once.Do’s two ж field-box mints (fixed: atomic scalar ops → [GoRecv] ref form, once.cs hand-owned), the m[string(b)] probe-key copy (fixed: the converter now emits golib’s zero-alloc tmpstring transient for map-READ keys, Go’s own compiler special case), and 14 dead per-call BigInteger.Parses from the emitted-but-folded 128-bit mask local (fixed: function-local int-kind GoBigConsts hoist their parse to a static readonly field). No disclosure, no test-shaping
io/ioutil 27 of 28 TestReadDir looks in .. for the SIBLING package’s io_test.go; also ORDER-DEPENDENT, since a sweep that ran io first leaves that file staged — a reason not to bank it even when it passes
net/http/cgi 36 of 39 three rows
syscall 61 of 62 the pipeline’s own path depth — below

syscall — 61 of 62, and the one row is a bank the PIPELINE is costing itself

TestGetwd_DoesNotPanicWhenPathIsLong (Go issue 60051) calls t.TempDir(), then os.Chdir, and skips itself if the Chdir fails. Go’s run succeeds; the converted run’s Chdir fails and the test skips, because the C# host’s temp root is %TEMP%\go2cs-tests\syscall\<32-hex-digest>\syscall\.tmp\<TestName> — already deep before a test whose entire purpose is to build a path past MAX_PATH adds its own. Shorten the staging root (short prefix, truncated digest) and 62 verdicts should land. Rooted, not fixed: the staging path feeds the input-digest manifest, so it is its own change with its own gate.

The array<T> UNSHAPED-INSTANCE class — three producers, three packages, five rows

array<T> carries its Go length N in the INSTANCE — golib’s own IGoZeroShaped says so, because [4]int32 and [8]int32 are the same C# type. So every path that materializes one from TYPE information alone must supply N, and three such paths do not:

  1. a map misshtml.unescapeEntity reads entity2[name] over map[string][2]rune; the miss yields default(array<rune>), length 0, and x[0] throws (html 2 of 3). r43c named this one.
  2. an unsafe reinterpret of an array pointerinternal/chacha8rand.setup reaches (*[16][4]uint32)(unsafe.Pointer(buf)) over a *[32]uint64, and the reinterpreted ж<array<array<uint32>>> has length 0 (internal/chacha8rand 3 of 4).
  3. the reflection bridge generating a valuetesting/quickreflect.Call hands a zero-length array<byte> to a function taking [32]byte (crypto/internal/edwards25519/field 13 of 16, three rows).

IGoZeroShaped cannot serve any of them: it produces a zero value shaped like a value you ALREADY have, and none of these three has one. Closing the class banks three packages (23 verdicts); each producer is a separate fix and (3) is the reflection chip’s.

An untyped constant SHIFT computed in C# int32 — a silent wrong answer

runtime/internal/math’s TestMulUintptr reports MulUintptr(1, 1) = 1, false want 1, true. The row is Go’s {1 << (UintptrSize / 2), 1 << (UintptrSize / 2), true} with UintptrSize == 64; the converter emitted (uintptr)(1 << (int)((UintptrSize / 2))), and C# masks an int shift count to five bits, so 1 << 32 is 1. The NEIGHBOURING table row folds correctly — 1<<(UintptrSize/2) - 1(uintptr)(4294967296L - 1) — because the shift is then an INNER node whose recorded type is UntypedInt and overflowingConstLiteral’s SIGNED arm folds anything out of int32 range. As the OUTERMOST node the shift carries the CONTEXT’s uintptr, takes the UNSIGNED arm, and that arm folds only values beyond int64. Its stated reason — “a TYPED unsigned constant shift emits with a width-cast operand from the retype path” — is true for a shift the Go SOURCE typed and false for a tree the context typed, which is precisely this case.

Corpus reach, measured: 69 1 << (int)(<symbolic>) sites; the counts are constants and nearly all are below 32, but runtime/mpagealloc_64bit.cs:234 is (uintptr)(1 << (int)(heapAddrBits)) with heapAddrBits == 481 << 16 where Go computes 2⁴⁸. Latent, and the same silent-wrong-answer shape. Deliberately NOT fixed here: overflowingConstLiteral already documents six carefully-scoped rules and a wrong widening drifts the corpus silently, so this wants its own arc with an A/B.

Three roots that each hold a whole package

Two findings that are NOT disclosures, and refuse for the same reason

The differential oracle is not built with the corpus’s build tags. crypto/cipher matches on every row but TestGCMAsm, where Go passes and C# skips with Go’s own message, “no assembly implementation of GCM” — the test’s first act is reflect.TypeOf(asm) == reflect.TypeOf(generic), and under the standing purego ruling the converted corpus genuinely has one GCM implementation, not two. The C# side is right. The oracle is what differs: compareGoAndConvertedTests runs go test -json -count=1 -timeout … . with no -tags, while every conversion applies defaultStdLibBuildTags = {purego, math_big_pure_go}. Go under the corpus’s own tags would skip that row too. It is satisfiable at a layer go2cs owns — one argument on one exec.Command — so disclosing it would launder a comparison defect as an unsatisfiable assert. But it also changes what EVERY roster row claims (“passes Go’s tests” → “passes Go’s tests as Go builds them for the pure-Go configuration”), so it is an arc to design with the user and gate on a full sweep, not something to slip in behind a bank.

AllocsPerRun reports BYTES, and it now blocks a second package. net/http/internal matches 9 of 10; TestChunkReaderAllocs reports mallocs = 640; want 1. r43g root-caused the same shape in log’s TestDiscard (got 424 allocs, want at most 1): the shim measures allocated BYTES per run, not allocation COUNT, because the CLR exposes GC.GetAllocatedBytesForCurrentThread and no object counter. Two packages now stop here, which is the argument for owning it rather than disclosing around it — until the shim reports a count, no alloc-profile disclosure at these sites can claim the CLR provably cannot satisfy the assert, because nobody has measured the number the assert is about.

r56d settled the units question by measurement, and the shim no longer presents bytes as a count. The survey is recorded on the declaration itself (testing.cs, AllocsPerRun): net9.0/9.0.18 x64 exposes byte totals ONLY — GetAllocatedBytesForCurrentThread is exact (40.000 B/object over 1, 10, 1e3, 1e5 allocations of a 40-byte type) but cannot separate count from size, GCAllocationTick is a byte-threshold sample (378 events per 1,000,000 allocations, one per ≈105,820 B), GCSampledObjectAllocation — whose ObjectCountForTypeSample WOULD be a count — raises zero events through an in-process EventListener in every configuration tried (High 0x200000, Low 0x2000000, both, all keywords 0xFFFFFFFFFFFF, Verbose and Informational) with the GC keyword’s own tick count as the live positive control, System.Runtime’s 27 EventCounters offer only alloc-rate (bytes/interval), and runtime events reach an in-process listener asynchronously (zero visible immediately after the loop, settling ≈117 ms later), so no event-derived figure could serve a synchronous call regardless. A nonzero result now notes its unit once on the running test; the zero case is left untouched because there the two units agree exactly, so no passing row’s output moves (verified: 2,195 passing nistec rows carry no note). The disclosure question is now answerable — but it is still the user’s, and it has a third option, below.

r56d-allocdecomp — nistec’s 21,964,011 decomposes, and 100 % of it is the ж<T> box model

The prize was gated on one number, so the number was decomposed the way r39-osalloc decomposed os’s 9,184. Method: a console probe references the converted crypto/internal/nistec + fiat and measures GC.GetAllocatedBytesForCurrentThread deltas — the same instrument the shim uses, so the figures ARE the ones the test sees. Positive control: the probe’s P256 body reads 21,963,547 against the pipeline’s 21,964,011, the 464-byte gap being the rand.Read the probe substitutes. Temporary counters in golib’s ж/array/slice constructors (reverted; instrumentation is temporary by construction) supplied exact per-class counts.

Phase decomposition, P224 body (per run) — sums to within 156 B of the whole, the ibyteseq standard:

Phase B/run Share
ScalarMult(p, scalar) 13,042,167 55.2 %
ScalarBaseMult(scalar) 5,592,992 23.7 %
SetBytes(compressed) 4,556,755 19.3 %
Bytes() / BytesCompressed() 203,226 / 203,194 0.9 % each
NewP224Point().SetBytes(out) 17,681 0.1 %
NewP224Point().SetGenerator() 8,344 0.0 %
make([]byte, 28) 104 0.0 %
whole body (control) 23,624,307 100 %

Unit costs close the bill to the BYTE — three classes, and every field-element operation is exactly (number of field pointers × 128) + (number of address-taken locals × 144):

Operation Measured Closes as
P224Element.Sub 528 3 × 128 + 1 × 144
P224Element.Mul 960 3 × 128 + 4 × 144
P224Element.Add 960 3 × 128 + 4 × 144
P224Element.Square 832 2 × 128 + 4 × 144
P224Point.Add 39,464 ≈43 field ops + 8 @new boxes
P224Point.Double 31,552 same shape

Allocation COUNTS per run (golib counters; Go’s count for all four is zero):

Curve standard ж boxes of which pinnable T[1] field-ref ж boxes array<T> backings total objects bytes
P224 106,472 86,930 66,081 3,373 263,049 23,624,307
P256 97,389 76,513 63,786 3,386 241,077 21,963,547
P384 200,133 168,947 94,993 4,992 469,068 40,754,499
P521 386,667 343,898 129,963 6,783 867,314 72,242,788

Ownership, per class — none of it is established-class waste, and that is the finding:

  1. field-ref boxes, 128 B (of(…), i.e. Go’s &e.x) — a fresh ж<array<uint64>> per call. Go’s &e.x is free and yields the same pointer every time, so memoizing the box per (source, accessor) is semantically faithful — but it is r39 item 1’s territory and changes pinning lifetime, so it is chip-class, design-WITH-user, not a lane fix.
  2. address-taken locals, 144 B (heap(new uint64(), out var Ꮡx)) — Go’s var x uint64; &x handed to p224CmovznzU64, a stack variable there. 144 B = the ж box plus the T[1] pinnable slot its constructor allocates eagerly for an unmanaged T. Removing the eager slot needs the box pinned by handle instead — again the ж<T> architecture.
  3. @new<T>() boxes, 128 B — Go’s comment in ScalarMult says it outright: “The explicit NewP224Point calls get inlined, letting the allocations live on the stack.” The managed model has no inlining that turns a heap box into a frame slot.
  4. array<T> backings, 88 B — Go’s [4]uint64 is inline in the struct; golib’s array<T> is a struct wrapping a heap T[].

The r39-killed classes did NOT reappear — the hot path has zero dead unsafe.Pointer temps, zero GoFunc/defer frames and zero capture boxes (the only closures are one-time sync.Once initializers, outside the measured window). Checked explicitly, because a regression there would have been a lane fix.

So nistec does NOT bank, and the reason is honest: five want-zero rows fail on a real divergence, ruling #1 stands (a want-zero assert is satisfiable in principle, so it is not a disclosure), and no established class remains to fix. Roster unchanged at 110/215. The 2,200 verdicts are gated on the ж<T> box arc — the same arc os’s residual named — which makes that arc’s value 2,200 verdicts larger than it looked.

The third option for the disclosure decision. A true allocation COUNT is obtainable — not from the CLR, but from go2cs’s own runtime. golib allocates essentially every Go-semantic object, so counting there mirrors precisely what Go’s Mallocs already is: a runtime-owned counter, not a platform facility. r56d proved it works (the count column above IS that instrument). It was deliberately not landed: a count that silently omits allocation sites is worse than an honest byte figure — the inverse-of-atomic rule — so making golib the counter requires an audited-total census of its allocation sites and a ruling on what counts as an allocation. Design-with-user.

log and net/http/internal are a DIFFERENT case from nistec — and the difference decides them

Both remaining AllocsPerRun blockers assert a nonzero budget of exactly 1, where nistec asserts zero. Measured through the shim itself (a temporary object-count readout, since these closures resist a standalone probe — the pointer-to-interface conversions go2cs-gen mints are scoped to the declaring assembly, so a hand-written probe cannot obtain them):

Row reported true B/run golib-tracked objects/run Go’s budget
log TestDiscard got 424 allocs, want at most 1 424 ≥ 2 1
net/http/internal TestChunkReaderAllocs mallocs = 640; want 1 640 ≥ 2 1
crypto/internal/nistec TestAllocations/P256 got 21964011.0 21,964,011 241,077 0

The top two are the same order of magnitude as Go — single-digit objects against a budget of one — so their failures are dominated by the unit mismatch, not by over-allocation. nistec is five orders away. That is the line the disclosure decision should follow.

But they are still not disclosable today, and the reason is a result this lane produced against itself. The counter used above covers ж/array/slice only, and 424 bytes cannot be two objects of ~50 B each — so allocations exist on that path which the instrument did not see (@string, object[] varargs, delegates, boxing). The partial counter demonstrating its own incompleteness is the concrete evidence for the caveat above: a golib-derived count is the right mechanism and is NOT trustworthy until its census of allocation sites is audited-total. Until then no site can claim the CLR provably cannot satisfy the assert, because the number the assert is about is still not known exactly — which is precisely the standard r43g set. The lower bound is nonetheless decision-relevant, and it points the opposite way from nistec.

Build roots found in the never-measured tail

Package Verdicts First diagnostic
crypto/tls 3,519 CS0234 'vendor' does not exist — the test half’s vendored import
crypto/internal/nistec 2,200 CS0311, above
runtime 870 build-blocked
go/types 557 CS0839 Argument missing
encoding/json 491 CS0050 inconsistent accessibility · CS1061/CS1739/CS1503 ×8the compile wall is CLOSED end to end (2026-08-16, lanes claude/json-measure then claude/json-unlock: 76 → 8 → 0). The package RUNS: 400 of 491 matching, 91 divergent in four named roots — see encoding/json — the wall is down and the package RUNS at the end of this file
encoding/xml 386 CS0426 ΔToken does not exist in xml_package
crypto/x509 335 CS0102 duplicate definition in x509_package
net/netip 266 CS1525 Invalid expression term '<'
net/http 245 CS1002 ; expected
html/template 243 CS0030 on a test-local named type
sync/atomic 108 CS0103 ᏑᏑX — a DOUBLE address-prefix marker
runtime/pprof CS0103 ᏑᏑsaltsthe same double- root
crypto/ecdsa 82 the nistec family
fmt 63 CS0111 fmt_test_package.SE already defines Append — closed by 14bf20010; five roots behind itthe COMPILE WALL IS CLOSED (2026-08-16, lane claude/fmt-roots): the host builds and 62 of 63 verdicts agree. One verdict (TestSprintf) remains, behind three reflect/fmt-bridge divergences — see fmt’s compile wall is CLOSED at the end of this file
text/template 52 CS0030 on a test-local named type
debug/elf 31 CS8183 cannot infer the type of an implicitly-typed discard
internal/reflectlite 30 CS0016 could not write to output file
database/sql 25 CS0029
flag 24 CS1929 on ж<flag_test_package.URLValue>
os/exec 22 CS0103 The name 'var' does not exist
internal/concurrent 20 CS0426 node<,> — the hand-owned hashtriemap.cs does not declare the internal type its WHITE-BOX test half references
internal/runtime/atomic, internal/syscall/windows/registry, net/rpc/jsonrpc, go/internal/srcimporter, testing/fstest, internal/types/errors build-blocked, first diagnostic recorded

And one hard CONVERTER failure in 108 packages: reflect. go2cs.exe: Failed to convert package tests in "…\src\reflect": convert test file "…\reflect\all_test.go": 1e+06 not an Int — a float-shaped untyped constant reaching a path that demands constant.Int. Every other package in the batch CONVERTED; only the C# build or the run failed. This one has a one-line repro.

Re-baselines this pass owes the board

⚠ Two self-inflicted traps, both worth the next lane’s attention

  1. The corpus is an INPUT to a running batch. Staging the cpu_x86_impl.cs hand-own while the batch was still running made six unrelated packages report CS0111: Type 'cpu_package' already defines a member called 'getGOAMD64level' — the impl implements a partial the CURRENTLY-BUILT converter still emits, and the error is reported against the CONSUMER package, not against internal/cpu. Never stage a converter-paired corpus file until the batch is idle and the converter is rebuilt.
  2. Clean the batch’s untracked artifacts between passes. The re-run then failed wholesale with NuGet.targets(1311,5): error MSB4006: circular dependency … "_GenerateRestoreProjectPathWalk" against internal.syscall.windows.csproj: a package whose run FAILED still leaves a generated <pkg>.tests.csproj on disk, and internal/syscall/windows’s test half imports internal/syscall/windows/registry, which imports internal/syscall/windows — a cycle NuGet’s restore path walk rejects even though the C# compile would be fine. git add the banks, then git clean -fd -- src/core, before re-running anything.

The one-row-away cluster, worked — 3 bank, and syscall’s root is not the one on record (2026-08-09, r56c-onerow)

Worked the ONE ROW AWAY list above in its own order. Three banked (internal/profile 1, net/http/fcgi 12, runtime/internal/math 1); roster 110 → 113 (51.2% → 52.6%), 13,628 → 13,642 matching verdicts, 50 disclosed (unchanged). Every bank came from a converter or generator defect that was producing a SILENT wrong answer — none needed a disclosure, and none was a test-targeted patch.

The three roots

  1. A Go package that spans two assemblies lost its unexported interface methods. internal/profile’s proto_test.go is package profile — an internal white-box test — and it implements the production package’s unexported message interface on its own packedInts. ImplementGenerator emitted the adapter’s members as => default! / { }: a required member satisfied by a NO-OP. marshal() returned an empty buffer, unmarshal decoded nothing, and nothing at any layer said so. The stub is a real mechanism (Go’s package-sealing markers — ast.Expr.exprNode()), but its test was unexported name && declaring assembly != this assembly, a proxy for “there is nothing to forward to” that answers wrongly for the one shape where a single Go package spans two C# assemblies. It now also requires the struct to declare no method of that name in the current compilation. This class is corpus-wide: any white-box test package whose test-local type implements a production unexported interface was silently no-op before this, and the failure mode is invisible — it compiles and it runs.

  2. C#’s \x escape is greedy where Go’s is exactly two digits. net/http/fcgi’s const want = "\x0f\x01" + "FCGI_MPXS_CONNS1" + … folds to one constant with no single BasicLit, so it bypassed convBasicLit’s byte-array diversion and the folded arm asked only utf8.ValidString. The value is pure ASCII, so that test passed it — and \x01F re-parsed as U+001F with the F eaten. TestGetValues compared a correct response against its own corrupted constant. The folded arm now runs the same predicate convBasicLit does. Measured reach: one live site — every other \x-plus-hex-digit run in the emitted corpus is inside a C# verbatim @"…" literal, where \x is two ordinary characters.

  3. uintptr was missing from isWideShiftType. Go’s uint renders as the C# primitive nuint, but Go’s uintptr renders as golib’s uintptr STRUCT — so it was the one wide unsigned type that fell to the narrow arm and got its shift cast on the RESULT, which is exactly what that arm’s own comment says does not help. 1 << (4 * goarch.PtrSize) emitted (uintptr)(1 << (int)(32)), C# masked the count to five bits, and the value was 1. Whole-corpus A/B: eight files, one mechanical family, six sub-int32 reshapes and two live wrong answersMulUintptr’s overflow fast path (guarding at 1, so every uintptr below MaxUint32 “overflowed”) and runtime/mpagealloc_64bit.go’s 1 << heapAddrBits (2^16 where Go computes 2^48, the latent site this board already recorded). Both banked rather than deferred.

syscall 61/62 — the recorded root is WRONG, and the recorded remedy cannot work

This board says the row is “the pipeline’s own path depth … shorten the staging root and 62 verdicts should land.” Both halves are false, and the correction matters because the real remedy is cheap and sits in another lane’s file.

TestGetwd_DoesNotPanicWhenPathIsLong skips on Chdir failed: … The filename or extension is too long. MkdirAll SUCCEEDS — only Chdir fails. The arithmetic refutes the depth story on its own: the test appends two 200-character segments, so it contributes 401 characters whatever the root is. The converted run’s path is ~551; Go’s own is ~488. No staging root gets the total under MAX_PATH (260) — Go is not passing because its path is shorter, it is passing at 488 characters, which is already 1.9x the limit.

Probed directly — same 446-character path, same machine:

binary SetCurrentDirectoryW(plain)
Go (os.Chdir) succeeds
.NET (dotnet run, no manifest) fails, error 206 (ERROR_FILENAME_EXCED_RANGE)
.NET, <ApplicationManifest> carrying <ws2:longPathAware>true</ws2:longPathAware> succeeds

The root is that converted Windows binaries are not long-path aware and every Go Windows binary is. MkdirAll worked because Go’s fixLongPath prefixes \\?\ explicitly; Chdir hands SetCurrentDirectoryW a plain path, and without the opt-in the process is held to MAX_PATH. (\\?\ is no escape hatch here: SetCurrentDirectory rejects the extended form outright — it fails 206 too.)

CLOSED 2026-08-09 (r56e) — syscall banks at 62/62. The diagnosis above held; the MECHANISM attributed to Go did not, and the correction changed the remedy. Go’s linker bakes in no manifest. runtime/os_windows.go’s initLongPathSupport(), called from osinit(), checks for Windows 10.0.15063 and then sets the undocumented IsLongPathAwareProcess bit in the PEB’s bit field itself — which is why every Go Windows binary is long-path aware.

That distinction is not academic, because the two routes are not equivalent: Windows honors a manifest’s longPathAware only when the machine-wide policy HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled is ALSO 1. It is 1 on this machine — which is exactly why the manifest measured as a fix in the row above — so a manifested converted binary would still have diverged from the Go binary on a default install, where that value is 0. Go asks for neither the manifest nor the policy.

So the remedy landed in golib, not the csproj template: builtin.WindowsLongPaths.cs sets the same PEB bit from InitializeGoLib, golib’s analogue of osinit. Probed both ways in one process — without golib the PEB reads 0x04 and a 434-character Directory.SetCurrentDirectory fails 0x800700CE; referencing golib it reads 0x84 before the probe’s own code runs and the same call succeeds. It is also the far smaller footprint: no <ApplicationManifest> property, no per-project manifest artifact, nothing in the emitted .csproj — so CNR stayed byte-identical across all 576 behavioral packages including their .csproj, and none of the banked <pkg>.tests.csproj went stale. internal/syscall/windows.CanUseLongPaths is deliberately left false (golib cannot reference a converted package, and the \\?\ spelling still works with the bit set).

Rooted, not fixed — carried back with evidence

The r56a breadth harvest — the packages with no board row at all; 4 bank, 1 fix, 12 rooted (2026-08-09)

r44a ran 108 packages and left an instruction of its own: the yield is in what nobody has run. This pass took that literally and asked a narrower question than “what is unbanked” — what is unbanked and has never appeared on this board in any form. Of the 106 unbanked testable packages, exactly sixteen had no row, no census, and no mention: the residue r43c’s 58 and r44a’s 108 between them never enumerated. Every one was run.

Roster 110 → 114 (51.2% → 53.0%), 13,628 → 13,645 matching verdicts, 50 disclosed (unchanged).

The pass’s own lesson is a refinement of r44a’s rather than a repeat: three of the four banks came from ONE fix, and that fix was already designed. golib/GoReflect.TypeLayout.cs carried a written deferral — “unifying unsafe.Sizeof onto this rule is deferred pending a named consumer” — and the consumer had been sitting in the unmeasured tail the whole time, three packages deep. A deferred unification with a named trigger is worth re-reading every time the tail is re-scouted; the trigger does not announce itself.

The four

debug/macho (7) · go/internal/gccgoimporter (4) · internal/xcoff (3) · log/slog/internal/benchmarks (3)

The first three are one root. The fourth needed nothing at all.

The one fix — unsafe.Sizeof had two rules and only one of them was Go’s

The converter FOLDS unsafe.Sizeof to a constant wherever go/types can compute one — 283 corpus sites. The folding arc that landed that named what it could not reach: an operand whose type is a type parameter, which Go’s own spec calls variable-size and does not fold either. Seven run-time call sites remain corpus-wide, and they kept riding Marshal.SizeOf<T>.

There the “latent throw” that arc documented was not latent. A type parameter binds at run time to exactly the shapes Marshal.SizeOf refuses — a generic type (“The specified Type must not be a generic type”) or a struct holding a managed reference (“cannot be marshaled as an unmanaged structure”). Three packages died on it through the SAME one line, internal/saferio.SliceCap[E], which asks the size only to choose an allocation chunk:

Package E bound to Reached from
debug/macho the Load interface NewFile, NewFatFile
internal/xcoff ж<Section> NewFile
go/internal/gccgoimporter debug/elf.ΔSection elfFromArelf.NewFile

The run-time form now answers through GoReflect.GoSizeOf — the same Go-layout walk that stamps a descriptor’s Size_ and that reflect.Type.Size() reads — with Marshal.SizeOf retained as the fallback for the shapes GoSizeOf declines, so nothing that resolved before stops resolving. It is also correct where the old rule merely differed: Marshal.SizeOf reports a bool as 4 bytes where Go says 1, so any struct holding one was being measured wrong at precisely the sites folding could not reach. A/B footprint: one method body. Recorded in ConversionStrategies-Reference.md beside the folding subsection.

What the fix moved that did NOT bank — debug/dwarf 7 → 30 of 40, and its residual is ONE root

debug/dwarf opens its fixtures through debug/macho and debug/elf, so the Sizeof fix took it from the board’s recorded 7 of 40 to 30 of 40 with no work aimed at it. All ten residual rows are one panic, at debug/dwarf/type.cs:683:

panic: interface conversion: interface {} is *dwarf.UintType, not dwarf.readType_type

Go’s source asserts to an anonymous interfacetyp.(interface{ Basic() *BasicType }) — which the converter lifts to a package-local [GoType("dyn")] partial interface readType_type. The concrete types (*IntType, *UintType, *CharType, *UcharType, …) satisfy it in Go only through a method PROMOTED from an embedded BasicType, and the value is held as a different named interface (Type) at the assertion site. No witness is minted for that combination, so the assert throws. Ten rows, one root, in the go2cs-gen ImplementGenerator family — the largest single prize this pass leaves rooted, and the reason debug/dwarf is now a near miss rather than a distant one. (debug/elf itself is unmoved: its blocker is the recorded CS8183 implicitly-typed discard at file_test.cs:1195, a build root this fix does not touch.)

The twelve rooted non-validators

Package Census Root
internal/runtime/syscall “build constraints exclude all Go files” on windows/amd64. Joins net/internal/socktest, internal/syscall/unix, log/syslog and runtime/race: in the naive 215 denominator, cannot bank on this target.
runtime/trace 0 of 2 NotImplementedException: getg: external (assembly or cgo) function is not implemented. Both tests enter the tracer through getg; no managed body exists.
log/slog/internal/buffer 1 of 2 TestAlloc: “got 304 allocs, want 0”. Re-measured r58a with the counter live: golib charged NONE of the 304 B/run, so AllocsPerRun fell back to bytes rather than report a zero it could not vouch for. Every object on this path is compiler-emitted or BCL-internal — the structural class no golib census reaches — so it is still not a disclosure candidate, now for a measured reason rather than an assumed one.
internal/trace/internal/oldtrace 2 of 3 TestParseCanned: the pre-1.22 trace parser rejects two of its own canned good traces — “p 3 is running before start (time 369986239)” and “previous sweeping is not ended before a new one”. Parser-state semantics, not I/O.
internal/testenv 3 of 4 TestGoToolLocation looks for <staging root>/bin/go.exe; the converted host’s GOROOT is the pipeline’s exported root, which has no bin. Same shape as internal/godebugs’ GOROOT-relative doc/godebug.md.
internal/fuzz 0 (build) minimize_test.cs(177): CS1003 — a func-literal parameter whose type is an ALIAS to an anonymous struct emits the Go type STRING verbatim: (struct{Parent string; Path string; …} e) => …. CorpusEntry is type CorpusEntry = struct{…}, and production emission handles it correctly (global using CorpusEntry = …CorpusEntryᴛ1), so the lift exists and the func-literal parameter position does not consult it.
internal/trace 75 of 92 — re-measured 2026-08-28, unchanged by the fixture fix batchcursor_test.cs(92): CS0149 Method name expected — a parameter named heap shadows golib’s heap() intrinsic that the same body calls (ref var sb = ref heap(new strings.Builder(), …)). A name-collision rule the analysis does not cover: a local or parameter whose name collides with a golib intrinsic the body invokes. The build wall is gone (38e441cd0, the heap-intrinsic qualification) and the suite runs end to end in 517 s: 92 verdicts, 75 matching, 17 divergent, 0 disclosable, in exactly two roots — and the second -tests run reproduces the first digit for digit, so the republished-fixture deleter was never in this package’s gap. It is nonetheless proven working here: the publish tree held 73 → 73 fixture files across the build publish and the compare publish, and all 21 TestReaderGolden verdicts (which read testdata/tests/*.test by path) match. The two roots: (1) sibling-package fixture staging — 13 verdicts. TestOldtrace globs ./internal/oldtrace/testdata/*_good; testFixturePaths stages *.go plus the whole testdata/ tree plus parent-relative shared fixtures, but not a nested sibling package’s testdata, and testFixtureDirectories creates internal/ EMPTY. The glob matches nothing, the loop never runs, and the parent fails in 15 ms on its own trailing check — “didn’t see expected test case user_task_region_1_21_good” — so its 12 children are never created (the 12 empty C# verdicts are that, not a truncated run: the C# side’s alphabetical tail is complete through TestTraceWaitOnPipe). This is a measured counter-example to testFixtureDirectories’ own comment, “A test that reads INTO a sibling directory would still need its content — none does.” (2) The converted-host WORKING-DIRECTORY class — 4 verdicts. TestTraceCPUProfile shells go run testdata/testprog/cpu-profile.go, and that program alone among the 13 testprogs imports an internal/ package (internal/profile, censused). Under go test the working directory is $GOROOT/src/internal/trace, so Go’s internal-import rule permits it; the converted host runs from its isolated staging root outside $GOROOT/src, so the toolchain refuses: “testdata\testprog\cpu-profile.go:15:2: use of internal package internal/profile not allowed” — one message, four verdicts (parent + Default/Stress/AllocFree). Fourth member of the class after internal/godebugs, io/ioutil and go/build. Neither root is a disclosure: both are satisfiable at layers go2cs owns (the fixture pass; the staging root’s identity), so disclosing either would launder a harness limitation as an unsatisfiable assert — the standing ruling on this class. Fix root 1 and the row banks at 88 of 92; fix both and it banks whole.
crypto/internal/edwards25519 0 of 55 → 52 of 55 with the tuple-spec fix Package-var init ORDER, tuple-spec hole. Go initializes feOne(0) and d(1) before identity(2); C# field initializers run in declaration order, so identity (line 66) reads feOne (line 140) while null, field.Subtract null-derefs, and the package cctor throws before any test runs. The general init-order mechanism already exists and is correct (initOrderOperations.go, landed e39855770 2026-07-11; 36 packages ship a generated package_init.cs) and it flags these two vars correctly — it then declines to act because they are TUPLE specs (var identity, _ = …), warning loudly at visitValueSpec.go:1158. Whole-corpus census: exactly 2 production occurrences (both here) on Windows, 2 latent on darwin (os initCwd/initCwdErr), zero elsewhere; the sibling hoisted-initializer fallback never fires. Hand-simulating the relocation takes the package to 52 of 55; residual = TestAllocations (AllocsPerRun class, 5th member) and TestScalarSetCanonicalBytes/TestScalarSetUniformBytes (one shared new root: testing/quick + reflection bridge synthesizes a zero-length array for a fixed-size [32]byte/[64]byte parameter). Options, costs and recommendation: FINDING-init-order-tuple-specs.md. Option A ratified 2026-08-10 (extend the existing relocation to tuple specs, ~30 lines reusing the landed machinery); implementation sequenced into the post-1.23.1.6 harvest window.
net/smtp 9 of 14 TestNewClientWithTLS fails with loadcert: tls: failed to parse private key; TestSendMail, TestSendMailWithAuth, TestTLSClient and TestTLSConnState infrastructure-error behind it. Shares its root with crypto/rsa below — PEM/ASN.1 private-key parsing.
crypto/rsa BANKED r58a — 559 matching + 1 disclosed = 560 0 of 592; the test package’s own static initializer panics in parseKeyx509.ParsePKCS1PrivateKeyasn1.UnmarshalparseField “sequence truncated”. The cctor panic is GONE, closed by r56f’s reflect.StructField.Tag bridge exactly as that write-back predicted: parseField reaches its asn1:"…" parameters through field.Tag.Get("asn1") (asn1.cs:971, marshal.cs:509/514), so while every converted struct reported UNTAGGED the DER walk read every field as having no optional/explicit/tag: modifiers and desynchronized on the first one that mattered. With tags bridged the whole suite runs: 560 verdicts, 559 matching, 13 excluded (8 benchmarks + 5 examples, Phase-4D). The single mismatch is TestAllocationstesting.AllocsPerRun(100, …) around DecryptPKCS1v15 — and it is the AllocsPerRun-reports-BYTES shim, now its FOURTH member after log’s TestDiscard, net/http/internal’s TestChunkReaderAllocs and log/slog/internal/buffer’s TestAlloc. Measured: 2,851,392,000 bytes over 100 runs = 28,513,920 B/run, reported where Go reports a malloc COUNT. Not banked and NOT disclosable on the standing rule — the shim has never reported the number the assert is actually about, so disclosing it would launder an unmeasured quantity. This is now the largest prize gated on that one decision: 560 verdicts held by a single row, which is the strongest argument yet for the carried AllocsPerRun-ownership item (r56d showed golib’s own ж/array/slice constructors can supply an exact object COUNT — that is the design-with-user path to banking this package). net/smtp’s five and encoding/asn1’s 28-of-38 shared this root and are both worth an immediate re-measure.
go/build 57 of 58 verdicts (34 of 35 top-level) TestLocalDirectory: ImportPath=".", want "go/build". The test calls ImportDir(os.Getwd()); go test runs from the GOROOT package dir, the converted host runs from src/core/go/build, which is not inside a Go source tree. The converted-host WORKING-DIRECTORY class, third member after internal/godebugs (0 of 1) and io/ioutil (27 of 28). Not a disclosure: it is satisfiable at a layer go2cs owns (the staging root’s identity), so disclosing it would launder a harness limitation as an unsatisfiable assert.
crypto/dsa DONE 2026-08-09 (r57a) — 4 of 4, banked. The row’s diagnosis was right and its conclusion was wrong by about ninety seconds. TestParameterGeneration passes in 1,156.8 s (19.3 min), so the 20 m package deadline this row measured at was just UNDER what the package needs end to end — the deadline has to cover conversion, the C# host’s startup and the go test oracle beside it, so it cut a run that was converging. At 30 m it validates first try, and crypto/dsa is now the third $longTimeouts entry beside hash/maphash and index/suffixarray. ⚠ Two lessons worth carrying: “no -test-timeout is enough” is a claim no timeout can ever establish — only a completed run distinguishes a slow suite from a hung one — and this lane opened expecting r56f’s named-numeric shift fix to be the root (a prime search over the converted math/big is precisely the shape that defect corrupted) and it was not: DSA reaches its slowness honestly, every verdict matching Go.  

Two things the next lane should not have to rediscover

  1. A README validation badge can only be refreshed by a -stdlib run. The badge emitter is gated on options.convertStdLib, so a plain single-package conversion does not write README.md at all — and worse, it regenerates the .csproj WITHOUT the validation-pack block (the Exists-guarded VALIDATION.md pack input), an 8-line silent removal that reads as nothing in git status until you diff it. A -tests run does not write the README either. The correct instrument for a rebank is go2cs -stdlib <pkg…> -comments -go2cspath <src>; it also re-copies the six root attribution files (core/LICENSE, core/VERSION, …) as pure CRLF phantoms, which are restored, not banked.
  2. The badge needs BOTH signals present on disk before that run. Green requires the committed <pkg>.tests.csproj and the proof page. The proof page is written at the END of a successful compare, so the ordering is: run the pipeline, THEN the -stdlib regen, THEN commit. Running them the other way around produces an orange badge on a validated package and no error anywhere.

The gate found one pre-existing staleness — time’s implicit-conversion record

The 114/114 sweep reported exactly one CONTENT drift outside the documented 20-file -tests-closure family: src/core/time/package_info_internal_test.cs, one line —

-[assembly: GoImplicitConv<RuleKind, global::go.time_package.ruleKind>(… ValueType = "global::go.time_package.ruleKind")]
+[assembly: GoImplicitConv<RuleKind, global::go.time_package.ruleKind>(… ValueType = "nint")]

Banked at 34f593bf3 (time #73) and stale since some later emission change narrowed ValueType to the UNDERLYING representation. Not attributable to the lane that found it — r56a touched no converter source at all (git diff <base> -- src/go2cs empty, working tree clean there), and unsafe.Sizeof is a run-time golib method the converter process does not even link. Restored, not rebanked, per the standing doctrine; it belongs to the next deliberate test-source refresh. Worth recording because it is precisely what the sweep exists to see: CNR covers behavioral projects and the reconvert-diff covers production .cs, and neither of them can see banked test emission going stale.

r56g — dwarf’s “missing witness” was a missing METHOD; three defects, one family (2026-08-09)

This board left debug/dwarf at 30 of 40 with all ten residual rows on one panic and one attribution: “No witness is minted for that combination… this is the go2cs-gen ImplementGenerator family.” The family was right and the noun was wrong, in a way worth recording because it will recur: no witness CAN be minted for that combination. An anonymous interface asserted from a value held as a different named interface is exactly the shape the compile-time recorders are blind to by construction — convTypeAssertExpr records nothing there deliberately, and says so — which is precisely why golib carries a run-time tier. The tier was present, correct, and answering MISS, because the method it was asked about had never been emitted.

Roster 117 -> 118 of 215 (54.4% -> 54.9%), 13,659 -> 13,699 matching verdicts, 50 disclosed (unchanged). Lane-local arithmetic; totals recomputed by summing the table, whose pre-bank sum reproduces the committed header exactly.

Root 1 — an exportedness gate on a Go method set

TypeGenerator promoted a value embed’s box-receiver (pointer-receiver) primaries only when the embedded type was UNEXPORTED. Go has no such rule: the method set of *S contains every pointer-receiver method of a value-embedded E, because &s.E is addressable, whatever E’s case.

The gate read as a scoping decision, and as one it was defensible — it arrived with the cross-package-reachability shim (testing.T.Errorf, whose Ꮡcommon accessor is internal), and for an EXPORTED embed the accessor is public, so the converter’s own call sites descend inline and need no shim. But the converter’s call sites are not the only reader. golib reconstructs a Go method set at RUN TIME by scanning the emitted extension methods (GetGoMethodSetCandidates, shared by StructurallyImplements and AdapterBinder’s shell binder). An un-emitted promotion is therefore not a missing convenience but an ABSENT Go method, and the type silently stops satisfying interfaces Go says it satisfies.

The transferable lesson: an emission gate that appears to control only “which callers can see this” stops being a scoping decision the moment something reads the emission as a FACT. The method-set reconstruction is such a reader, and it fails silently — MISS, never a diagnostic. Any future narrowing of what gets emitted should be checked against that reader specifically.

Root 2 — a named field the adapter mistook for an embedded interface

With Basic() restored, dwarf reached 37 of 40, and the remaining three exposed something worse than a miss. ImplementGenerator detects an embedded INTERFACE field by NAME — field name equals its interface type’s simple name, modulo the Δ marker — and that test cannot distinguish a Go embedded interface from an ordinary named field whose name equals its type’s. Both emit the same C# field. dwarf carries both shapes in ONE struct:

type PtrType struct {
	CommonType        // a real embed — promotes Common()
	Type       Type   // an ordinary field — promotes nothing
}

Common() was forwarded through the FIELD, returning the referenced type’s CommonType rather than the receiver’s own — a silent wrong answer whenever Type was non-nil, and a null dereference when it was not. Five dwarf structs carry that field shape.

Resolved by precedence, since no new signal exists (the two emissions are identical by construction): marker-backed depth-1 value-embed promotion — public partial ref CommonType CommonType { get; }, a hard converter marker — now resolves ahead of the name heuristic. Legal Go guarantees the two can never both be right at depth 1, because promoting one member from two depth-1 embeds is an ambiguity the Go compiler REJECTS; so a struct where both arms answer is a struct whose “interface embed” is really a plain field. Deeper levels stay below the interface arm, matching Go’s shallower-wins rule. Implemented as two passes of the existing descent (maxDepth 1, then 4) so the “what can bind at this hop” logic is not duplicated and cannot drift from itself.

Root 3 — the shim was emitted, and emitted unreachable

Widening root 1 paid a second package immediately and exposed a third defect doing it. archive/zip was recorded here as build-blocked on “the generated ReadCloser->fs.FS witness binds Open against a ж<Reader> receiver while holding a value ReadCloser. With root 1 fixed the promoted Open(this ж<ReadCloser>) shim existed — and was emitted internal, so the test assembly still could not bind it.

The scope came from the name heuristic, which reduces a return type to its last dotted segment. For a Go MULTI-RETURN that segment is error) — lowercase — so every tuple-returning promoted method read as unexported. The accurate test (ReturnTypeIsPublic, via IsEffectivelyPublicType, which walks tuple elements) already existed but was keyed to the unexported-embed case alone. It now also covers the value-embed box shim, which is the stronger case for it: that shim exists to be reachable across assemblies, since it performs a descent the caller cannot spell, so emitting it internal defeats its own purpose. Every other promotion keeps the conservative heuristic.

archive/zip went from build-blocked (99 errors) to running at 95 of 98 on that one change.

archive/zip — 95 of 98, and the residual is the SLOW class, not a defect

SUPERSEDED 2026-08-09 (r57c-zipperf) — the package BANKS at 98 of 98. Everything measured below stands; the closing paragraph offered two routes and the second one was taken. The “throughput” was an ASYMPTOTE: @string held a bare byte[], so s[i:] copied where Go’s string header slices in O(1). See r57c at the end of this file.

The three residual rows are TestZip64LargeDirectory and its two subtests, and they are not mismatches: the C# verdict is empty, with {"action":"timeout","elapsed":900} and all three still in run state. That is the signature run-validated-sweep.ps1’s own $longTimeouts comment describes — “a timeout with every test up to the cut PASSING, which reads as a failure” — and it now has a third member beside hash/maphash and index/suffixarray.

Measured: Go 13.2 s (go test -run '^TestZip64LargeDirectory$'). The C# side did not complete under a 15 m deadline, nor under 45 m. The test builds a central directory of uint32max-1 and then uint32max BYTES out of ~128 KB records (a 65,535-rune name plus a comment per record) — roughly 4 GiB pushed through the converted writer twice, so it is throughput, not an algorithmic divergence: every other assertion in the package matches, including the zip64 boundary logic these same tests check at smaller sizes.

Not a disclosure. The roster admits only alloc-profile and codegen-liveness — assertions the CLR provably cannot satisfy — and “too slow” is neither; the same call the board already made for crypto/dsa. So archive/zip does NOT bank here, and is left with its blocker rewritten rather than cleared: it is now a performance row, not a build row. Banking it needs either a measured deadline (the index/suffixarray route — add 'archive/zip' = '<N>m' to $longTimeouts once someone measures where it actually lands) or the string/slice throughput work that would make the measurement moot. A lane picking it up should start by timing the C# host solo with no deadline rather than re-rooting anything.

r56f-ecroots — the two EC roots, and a shift-count defect that was a HANG in math/big

Banked: crypto/ecdh 47/47, crypto/ecdsa 82/82, no disclosures. Roster 113 → 115 of 215 (52.6 % → 53.5 %), 13,642 → 13,771 matching verdicts, 50 disclosed (unchanged). Four roots, each isolated to a standalone Go program converted and run against go run before anything moved.

1. An INITIALIZED var never lifted its explicit anonymous declared type. visitValueSpec lifts an anonymous struct/interface DECLARED type — but only on the bodyless arm. var _ interface{ Equal(x crypto.PublicKey) bool } = &ecdh.PublicKey{}, the documented-interface witness idiom Go’s own suites open with, emitted the raw Go text into both the declaration type and the adapter class name: 40 diagnostics from one construct. The lift is named from the Go identifier, not csIDName — a blank _’s C# name is a synthesized temp in no Go scope, so a lift named from it takes the field’s own name (CS0102).

2. The same-assembly pointer-adapter arm composed onto a whole-type alias. A collision-renamed type resolves through global using ecdhꓸPublicKey = …ΔPublicKey, a single identifier; gluing the adapter infix onto it names nothing (CS0246). The FOREIGN arm has carried the rebuild since imageꓸRGBA; the same-assembly arm never got it. ecdh shows both halves side by side — PrivateKey is not renamed, rendered ecdh.PrivateKey, and composed correctly all along.

3. reflect.StructField.Tag had NEVER been read — corpus-wide, and silent. The converter emits [GoTag(…)] at every tagged field and nothing consumed it, so every converted struct reported as UNTAGGED and every tag-driven decoder saw a tagless type. Surfaced as crypto/x509 marshalling an optional nil OID instead of omitting it (“asn1: structure error: invalid object identifier”), which points nowhere near reflection. Behind it, reflect.Copy was still the auto two-header typedslicecopy and NRE’d on the bridge’s empty ptr slot. Both now bridged; Offset/PkgPath/ Anonymous deliberately left unpopulated.

4. The one to carry forward — TestINDCCA/P256/Generic was a HANG, not a performance gap. This board recorded it as a 20-minute timeout with the question open. It is an infinite loop, and the fixed path runs in 0.31 s against Go’s 0.66 s, so slowness was never the answer.

Go’s shift count is unbounded; C# MASKS it. golib’s GoShift guards exist for exactly this and the converter applies them whenever it cannot prove a count in range — but only for an UNNAMED basic operand. A NAMED numeric type resolves through the go2cs-gen wrapper operator instead, which did the native masked shift, so that entire family kept the wrong answer. math/big’s lehmerSimulate reads a2 = B.abs[n-2] >> (_W - h) on Word; for a normalized operand h == 0, so the count is exactly 64. Go yields 0, C# yielded the word. The corrupted Lehmer cosequences make GCD’s for len(B.abs) > 1 loop stop converging — an infinite loop inside math/big, reached from crypto/elliptic’s generic CurveParams path, so elliptic.P256().Params().Double(Gx, Gy) never returned. It is value-dependent, which is why it hid: a garbage a1/a2 that fails Collins’ stopping condition immediately costs only a Euclidean step, so equal-width pairs pass and only pairs that make the condition iterate corrupt anything.

The guard now lives in NumericTypeTemplate’s operator <</>>. That is a corpus-wide runtime semantics change, so it was gated operationally, not just by compile: the full validated sweep is 115/115, 13,771 verdicts, 0 failures. Worth re-reading the board’s own math/big 9-of-226 and crypto/elliptic 4-of-82 censuses against it — both were measured with the masked shift in place.

Escalation — pre-existing drift, not this lane’s. src/core/time/package_info_internal_test.cs flips on every sweep: GoImplicitConv<RuleKind, …ruleKind>(ValueType = …) moves from "global::go.time_package.ruleKind" to "nint". The nint form is the correct one (the VALUE type of type ruleKind int), so a converter fix landed after time was banked and its committed metadata went stale. Confirmed NOT this branch’s by building the converter at the merge base 363e728bb and re-running time’s -tests conversion: the base reproduces the identical flip. It needs a re-bank of that one file by whoever owns the fix, not a restore in perpetuity.

r57a-bignum — the post-fix re-measure: what two corpus-wide fixes were actually worth (2026-08-09)

Banked: crypto/dsa 4/4, crypto/elliptic 82/82, no disclosures. Roster 121 → 123 of 215 (56.3 % → 57.2 %), 13,890 → 13,976 matching verdicts, 50 disclosed (unchanged). No converter change was made in this lane — every delta below is a census that had gone stale against fixes already on master, which is the finding.

Package Board’s census Re-measured Outcome
crypto/dsa 0 of 4, “no -test-timeout is enough” 4 of 4 BANKED — deadline was ~90 s short
crypto/rsa 0 of 592, cctor panic 559 of 560 one row away: AllocsPerRun
math/big 9 of 226 222 of 226 3 roots, 2 of them the alloc model
crypto/elliptic 4 of 82 82 of 82 BANKED — no work needed

The headline: a census taken under a live corpus-wide defect measures the defect, not the package. Three of these four rows moved without a line of code being written. r56f’s named-numeric shift fix alone carried crypto/elliptic from 4 to 82 and math/big from 9 to 222; r56f’s reflect.StructField.Tag bridge carried crypto/rsa from a static-initializer panic to 559 of 560. Both fixes were landed and written up correctly — what was missing was the re-read, and the board had explicitly asked for it. Every census row on this board should be treated as timestamped against the corpus it was taken on, and a lane that inherits one is cheaper re-measuring it than reasoning from it. The r44a lesson (“the yield is in what NOBODY HAS RUN”) has a sibling: the yield is also in what nobody has re-run since the thing that was blocking it got fixed.

crypto/rsa was the campaign’s largest single-row prize — CLOSED r58a. 560 verdicts held by TestAllocations, which was the AllocsPerRun-reports-BYTES shim measuring 28,513,920 B/run where Go reports a malloc count. What unblocked it was not a disclosure ruling but an INSTRUMENT: golib now keeps its own allocation counter (AllocationCounter), the structural mirror of what Go’s runtime.MemStats.Mallocs already is — a counter the runtime keeps at its own sites, not a platform facility. Census, coverage boundary and overhead: DESIGN-allocation-counting.md.

r58a — the AllocsPerRun class, re-measured as a COUNT

Every row below is measured through the counter with the @string census taken (the gap that document’s §5 item 3 deferred to r57c is closed). The count is a lower bound — the C# compiler emits closures, params arrays and interface boxing in CONVERTED code that golib never sees — so each row is reported with that residual named, not laundered into a verdict.

Row Go’s budget Reported BEFORE (bytes) Reported NOW (objects) Outcome
crypto/rsa TestAllocations < 10 28,513,920 340,756 BANKEDalloc-profile, five orders clear
math/big TestNewIntAllocs 0 816 1 not disclosable — see below
log TestDiscard ≤ 1 424 4 ruling; and log has a SECOND root
net/http/internal TestChunkReaderAllocs 1 640 2 ruling
log/slog/internal/buffer TestAlloc 0 304 counter saw none still bytes — not decision-grade

The instrument did its job most visibly on math/big. TestNewIntAllocs reported “wanted 0 allocations, got 816” — a figure no reader could act on, because 816 was bytes. It now reports “wanted 0 allocations, got 1”, seven times, one per operand shape. That is not a disclosure candidate under ruling #1 (a want-zero assert is satisfiable in principle) — it is a tractable engineering target that was previously invisible: one golib object per x.Add(x, NewInt(n)). Whoever takes it next knows exactly what to hunt. (math/big re-measures 224 of 226; the other miss, TestMulUnbalanced, is a memory-VOLUME assert, not an allocation-count one.)

log/slog/internal/buffer is the honest negative. The counter charged NONE of its 304 B/run, so AllocsPerRun correctly fell back to the byte figure rather than reporting a zero it could not vouch for — the false-pass arm working exactly as designed. Every object on that path is compiler-emitted or BCL-internal, the structural class (§5 item 1) no golib census can reach. It stays blocked, and now for a MEASURED reason rather than an assumed one.

log was never one row away, and this re-measure confirms the earlier reading rather than adding to it. TestAll still fails on the runtime.Caller file-name capability already characterized above as an architectural arc: Go asserts ^.*/[A-Za-z0-9_\-]+\.go:(63|65): hello 23 world$ and the converted host emits the absolute path of the .cs file (D:\…\src\core\log\log_test.cs:69). So even a favourable ruling on TestDiscard banks nothing here — 7 of 9 — which is the same call r43f made and the reason log stays off the roster. What the counter adds is the alloc row’s real number: 4 objects/run against a budget of 1, where the shim used to say 424.

crypto/internal/nistec re-measured: still 2,195 of 2,200, and the count CORROBORATES r56d. (Superseded 2026-08-13 — the table below is the PRE-lowering state; the ж-box A3 section at the end of this board carries the post-A2 pinned measure: P256 8,528/run, acceptance met.) The five TestAllocations rows now report objects instead of bytes, against Go’s budget of 0:

Curve objects/run bytes/run
P224 264,540 23,625,160
P256 242,665 21,964,357
P384 471,424 40,755,611
P521 870,534 72,244,419

P256’s 242,665 lands within 0.7 % of the 241,077 r56d derived through a temporary hand-built probe, which is an independent corroboration of that decomposition by a different instrument — and the byte column reproduces r56d’s 21,964,011 to four significant figures. It still does not bank, and the reason is unchanged: ruling #1 holds that a want-ZERO assert is satisfiable in principle, so it is not a disclosure, and the counter does not alter that — it only replaces a modelled number with a measured one. The 2,200 verdicts remain gated on the ж<T> box arc, whose value this measurement re-confirms rather than revises.

io retires a disclosure — the counter satisfied the assert instead of excusing it. TestMultiWriter_WriteStringSingleAlloc asserts EXACTLY ONE malloc. The byte shim measured 406–407 and was disclosed alloc-count-semantics, which was the honest call while nobody had measured the number the assert was about. The counter measures it: 1,024 objects over 1,000 runs = 1 per run, against a want of 1. It PASSES — the first want-exactly-one assert in the corpus the managed runtime has ever satisfied — so the disclosure was DELETED rather than left dormant, a dormant one being a signature-pinned licence to ignore that exact failure if it ever returns. io moves to **60 matched

A roster verdict COUNT can be host-dependent, and path/filepath is the first proven case. The targeted sweep reported COUNT path/filepath 67, banked 61 — not a regression and not an improvement in the corpus, but six symlink tests (TestEvalSymlinks*, TestGlobSymlink, …) that Go itself skips without symlink-creation privilege. On the machine that banked the row both runtimes skipped 20 identically; on the current coordinator box both PASS 16 of them identically. Either way the two sides AGREE, so the package is equally valid on both hosts — only the count differs. The row is deliberately LEFT at 61 rather than raised: banking 67 would false-red every sweep on a host without the privilege, which is the larger population. Worth a general remedy (record privilege-gated skips as such, or normalize the count over identically-skipped tests) before the next roster-wide arithmetic pass — flagged, not fixed here.

encoding/asn1 re-measures 35 of 38 (was 34 at r57b): r58b’s typed-nil packing closed TestMarshalError exactly as predicted. The three that remain are already characterized above — TestCertificate (sequence tag mismatch), TestMarshal #37 (SET emitted where a SEQUENCE tag is wanted) and TestUnexportedStructField (a reflect.setKinded panic on a value obtained through an unexported field). None is an allocation row.

One new root, characterized and escalated rather than half-fixed: Go’s TYPED-NIL interface does not survive the conversion. Detail in the math/big section above. A nil *Int in a slice reaches an interface as a plain null instead of a non-nil interface carrying (type=*Int, value=nil), so .(GobEncoder) takes the wrong arm where Go succeeds and dispatches on the nil receiver that big.Int.GobEncode explicitly handles. Corpus-wide in scope, chip-class in cost — it changes what == nil means for every converted interface — and golib can already represent the state (IsNilStandardPointer), so the narrow starting point is the reflection bridge’s Value.Interface(), which knows the static type at the moment the box is made. Blocks 2 of math/big’s 4 and part of encoding/gob’s 99 of 106.

r58b (2026-08-09) closed the reflection half and A/B’d the rest of that claim, which was wrong. Value.Interface() now packs the typed nil, and it pays math/big exactly (222 → 224 of 226) — but encoding/gob measures 99 of 106 with AND without the fix, the same seven divergent rows. The root has two halves paying two different packages: the reflection READ path (closed; gob reaches math/big’s types through reflect, which is why math/big’s rows moved) and the EMISSION path — a nil pointer VARIABLE boxed into an interface by ordinary converted code, which is what gob’s own TestNilPointerInsideInterface and the mustPanic family need, and which remains chip-class / design-with-user. gob’s current seven: TestBadData, TestEndToEnd, TestIgnoreDepthLimit (infrastructure-error), TestIgnoreRecursiveType, TestIndirectSliceMapArray, TestNilPointerInsideInterface, TestSingletons.

reflect.Value.MapIndex is still the raw converted Go body — a bridge gap, found in passing (r58b, 2026-08-09). CLOSED before it merged: r57b bridged Value.MapKeys and Value.MapIndex in its go/ast arc (bfdb073be), landing on master while r58b was still on its branch — two lanes found the same gap independently, one recorded it and the other fixed it. The claim below is kept struck rather than deleted because its shape analysis was right (the MapRange iterator’s iter.mapValueTypemakeTypedValue machinery is exactly what the fix used): unlike MapRange/SetMapIndex, MapIndex read v.ptr as flat memory and called mapaccess, so it faulted on any Value the managed bridge produced; internal/fmtsort was its first roster consumer and re-validated 3/3 in r57b’s recovered sweep.

crypto/dsa — the negative result, recorded so it is not re-derived. This lane opened expecting the shift fix to be dsa’s root too; a probabilistic prime search over the converted math/big is exactly the shape that defect corrupted. It is not. TestParameterGeneration passes in 1,156.8 s having always been slow-but-correct, and the board’s “no -test-timeout is enough” was a conclusion no timeout can support — only a completed run distinguishes a slow suite from a hung one. It is now the third $longTimeouts entry at 30 m, beside hash/maphash and index/suffixarray.

Two rows the next lane should re-measure immediately, for the same reason: encoding/asn1 (28 of 38) and net/smtp (9 of 14). Both were attributed to the same reflection-driven DER walk that the StructField.Tag bridge just repaired for crypto/rsa, and neither has been run since.

r57b — the near-miss singles, re-measured: five rows were already banked (2026-08-09)

A breadth pass over the board’s smallest-gap rows, run under the r44a doctrine: measure cheaply, bank what clears, characterize what does not. Its most useful product is not the one bank — it is that the ONE ROW AWAY table above is substantially stale, and a lane that trusts it spends its budget re-deriving closed rows.

The stale table — verify before you plan

Five of the eighteen entries no longer exist. internal/profile (roster line 117), runtime/internal/math (143), net/http/fcgi (133) and syscall are banked, and internal/cpu is already struck through. Each was re-measured this pass and each returned a clean Validated N testsnet/http/fcgi at 12/12, whose recorded TestGetValues byte-stream mismatch is gone. Treat every row below as a HYPOTHESIS to re-measure, never as a work item to start from; the roster table in docs/ValidatedTestPackages.md is the authority and the board is a lagging index of it.

go/ast — BANKED 9/9, and the row had two roots stacked

The recorded root (ast.Fprint -> reflect.MapKeys -> mapType.get_MapType()) was right about the family and hid a second defect behind it. Both are closed in this arc’s bank commit; the short form is that the map READ pair was never bridgedMapRange/MapIter.*/SetMapIndex all live in the bridge, Value.MapKeys and Value.MapIndex never joined them — and that with the panic gone, an unnamed struct reported its LIFT’s C# name (ast_internal_test.typeᴛ1) where Go renders it structurally (struct { X int; y int }). The naming arm is corpus-wide and visible immediately: internal/platform’s failure text moved from []platform_test.listEntry to []struct { GOOS string; GOARCH string; ... } in the same pass. Its row does NOT close — the residual is encoding/json refusing to unmarshal an array into a slice whose element is a lifted struct, which is a Kind question about the lift, not a naming one.

net/smtp — the recorded root is CLOSED; what is behind it is the Windows-socket class

The board’s loadcert: tls: failed to parse private key is gone: the PEM/ASN.1 private-key parse now succeeds, which retires the shared attribution with crypto/rsa’s cctor panic for this package (that package is r57a’s and is not re-measured here). All five rows now fail on ONE panic, and it is not a TLS defect at all:

panic: runtime error: index out of range [0] with length 0
  at go.array`1.get_Item ... golib\array.cs:280
  at go.syscall_package.sockaddr(ж`1 Ꮡsa) ... syscall\windows\syscall_windows.cs:881
  at go.syscall_package.Bind(ΔHandle fd, ΔSockaddr sa)
  at net.listenStream -> socket -> internetSocket -> listenTCP -> net.Listen

(*SockaddrInet4).sockaddr does p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) to write the port in network byte order. The emitted form is var p = (ж<array<byte>>)(uintptr)(new @unsafe.Pointer(Ꮡsa.of(...ᏑPort))), and ж<array<byte>> over a raw address materializes default(array<byte>) — a LENGTH-ZERO array — so p[0] panics. array<T> is a managed container, not two inline bytes, so no address reinterpret can produce one.

This is net.Listen on Windows, so it is not one package’s row. net/http/cgi hits the identical stack through httptest.NewServer -> newLocalListener, and every package that listens on a TCP socket will. Note also that fixing the reinterpret alone is not enough: Bind then hands the kernel unsafe.Pointer(&sa.raw), and RawSockaddrInet4’s Addr [4]byte / Zero [8]uint8 are managed references — which is precisely the open syscall STRUCT-PASSING seam already censused above, whose remedy is the established blittable mirror (GetTimeZoneInformation, findFirstFile1/findNextFile1). The board predicted net would be the package that forces it. It has.

RESOLVED 2026-08-11 (lane L10) — and the row does NOT close, because a second wall sits behind it. Both defects above are fixed: the port alias and the struct-passing seam are hand-owned in syscall/windows/syscall_windows_impl.cs, syscall.Bind now succeeds, and the whole encode → kernel → decode round trip is proven value-for-value against go run by the new SockaddrRoundTrip behavioral test on both IPv4 and IPv6. But “unblocks the whole net cluster” was wrong, and the correction is worth more than the fix. With bind working, net.Listen walks on and stops here instead:

System.NotImplementedException: runtime_pollServerInit: external (assembly or cgo) function is not implemented
  at internal/poll.runtime_pollServerInit  (PartialStubGenerator stub)
  at internal/poll.pollDesc.init -> internal/poll.FD.Init  ... fd_poll_runtime.cs:48
  at net.netFD.init -> net.listenStream                    ... sock_posix.cs:216
  at net.Listen                                            ... dial.cs:933

internal/poll declares ten bodyless //go:linkname netpoll entry points (runtime_pollServerInit, pollOpen, pollClose, pollWait, pollWaitCanceled, pollReset, pollSetDeadline, pollUnblock, runtime_isPollServerDescriptor, runtimeNano), every one of them an unimplemented stub. The counterparts EXIST in the converted runtime — runtime/netpoll.cs:217 carries poll_runtime_pollServerInit with its //go:linkname comment intact — but nothing wires a linkname across assemblies. And wiring it would not be sufficient: that body reaches netpollGenericInitnetpollinit, which on Windows is stdcall2(_CreateIoCompletionPort, …) and bottoms out in asmstdcall, itself a PartialStubGenerator stub. So this is an independent seam, one layer deeper than syscall, and the honest remedy is the managed-API-boundary pattern already used for sync’s Mutex and runtime’s traceback surface: hand-own the ten runtime_poll* CONTRACTS against .NET’s own completion-port machinery rather than emulating Go’s poller. That is a design arc with a deadline/unblock story to settle, not a wrapper repair — it wants its own DESIGN doc and a coordinator ruling before anyone starts.

Consequence for the board’s socket-walled rows. net/smtp, net/http/cgi, net/http/httptest, net/http/httputil, net/http/cookiejar and net/rpc stay walled, and their censuses remain walled-not-stale — but the wall they are behind is now the NETPOLL one, not the sockaddr one. Re-measuring any of them before that arc lands will still report the same counts; the L9 item-4 canary reading is therefore already answered here and need not be spent.

The array<T> unshaped-instance class has a sharper root than “producer (N)”

html’s row is a map MISS. Go’s if x := entity2[string(entityName)]; x[0] != 0 reads the ZERO VALUE of [2]rune on a miss and indexes it legally; golib’s indexer returns default(array<rune>), length zero, and x[0] panics. debug/pe is the same class at a different site — its _ [3]uint8 padding field prints [0 0 0 0 0 0 0 0] against Go’s [0 0 0], so there the shape is wrong rather than absent. The class is therefore “an array<T> zero value produced without its Go length”, with several distinct producer SITES, of which the map-miss is one.

golib already carries the contract (IGoZeroShaped / builtin.GoZero<T>), but it recovers shape from a TEMPLATE, and a map miss has none. The natural general fix is the idiom the converter already emits for slices — new slice<ΔValue>(mlen, () => new(nil)) — extended to a map’s miss value, since the declared value type’s Go shape is statically known at the construction site. That is a converter

The converted-host WORKING-DIRECTORY class — why no cheap subset exists

go/build re-measures unchanged at 57 of 58 (TestLocalDirectory: ImportPath="."), and internal/testenv at 3 of 4, now with its exact mechanism: the host’s working directory is <temp>/go2cs-tests/<flat pkg>/<guid>/<last segment> (TestHost.CreateRunDirectory), so ../../../bin/go.exe resolves to go2cs-tests/internal_testenv/bin/go.exe.

All four members of the class (internal/godebugs, io/ioutil, go/build, internal/testenv) want the SAME thing: CWD == $GOROOT/src/<pkg>, which is the working directory go test guarantees. Reproducing it is honest — it is the harness’s job to reproduce go test’s execution environment, and CWD is part of that environment exactly as GOROOT and the env are.

But there is no cheap subset, and the reason is worth recording. Deepening the run directory to <runRoot>/src/<full import path> costs nothing and fixes the SHAPE — and closes none of the four, because every one of them needs CONTENT at the reconstructed ancestor: bin/go.exe for testenv, doc/godebug.md for godebugs, the package’s own .go sources for go/build’s ImportDir, the sibling package’s sources for io/ioutil. So the remedy really is the full synthetic-GOROOT staging the board suspected, it changes the execution contract for all 122 banked packages at once, and it interacts with the staging path that feeds the input-digest manifest. Design-with-user, not a breadth lane’s — and NOT a disclosure, for the reason already recorded: it is satisfiable at a layer go2cs owns.

RESOLVED 2026-08-13 (lane claude/synthetic-goroot-class) — four of the six bank, and the class was TWO roots, not one. Design: DESIGN-package-ancestry-view.md. The remedy is an ANCESTRY view, not a synthetic GOROOT, and the difference was measured rather than argued. PackageAncestry stages GOROOT’s content from its top level down to the package — sibling directories as links, files as hard links, the path to the package materialized, the package’s own directory real copies — under a working directory that gains the src level this section correctly predicted it needed. GOROOT itself keeps pointing at the real installation.

Why not the synthetic GOROOT this section expected. A linked mirror is not walk-equivalent to the real tree: Go reports a junction from Lstat as an irregular file, so filepath.WalkDir steps over it rather than descending. Measured against Go 1.23.1 on a mirrored root — a walk counting *.gz under GOROOT finds 0 where the real tree has 4, and a walk of src/unicode reports 1 entry against the real 19. Two ALREADY-BANKED packages walk GOROOT that way (compress/gzip’s issue14937, path/filepath), so repointing GOROOT would have REGRESSED them. Reads through a junction are faithful and every member of this class resolves against its working directory, so leaving GOROOT real costs nothing here. The feared “changes the execution contract for all 122 banked packages at once” did not materialize: nothing about GOROOT resolution moved.

Banked: go/parser 173/173, io/ioutil 28/28, internal/testenv 7/7, internal/godebugs 1/1 — +209 verdicts, the arc’s ledger paid in full and then some (the deferral price this board tracked was 167 + 12).

Two corrections to this board’s own rows, both from reading the sources rather than the verdict names — the same methodology note §go/parser 6/173 already earned:

The residue is a SECOND root — GOROOT-IDENTITY — and it holds the two that did not bank. Both need the importing/asking file to sit under the GOROOT the toolchain itself uses, which no ancestry view can provide and which repointing GOROOT cannot fix either (the child go resolves its own GOROOT from its executable location — measured: go list returns real-GOROOT paths with GOROOT set to a mirror).

Two host defects surfaced on the way and are fixed here, both general and both previously masked:

And one wall behind the wall. With its initializer working, go/parser ran and died on an uncatchable Stack overflow.TestParseDepthLimit drives Go’s own maxNestLev of 100,001 levels deliberately, ~400k converted frames, which the host’s 256 MB per-test thread served only if every frame fit in 671 bytes. Raised to Go’s own 1 GB ceiling (reservation is address space; pages commit on demand). Any lane hitting a deep-recursion wall elsewhere should suspect this constant before suspecting the conversion.

net/textproto also re-measures unchanged at 25 of 26 — still the want-ZERO canonicalMIMEHeaderKey allocs = 816 against the AllocsPerRun-reports-BYTES shim, still not a disclosure candidate under ruling #1.

Escalation — InterfaceInheritance fails on master, and it is not this lane’s

The full behavioral suite gating this arc came back 554/554 transpile + compile + target, 527 of 528 output, with one failure: InterfaceInheritance, map[:2 :1] against Go’s map[:1 :2].

Confirmed pre-existing, by restoring src/core/golib and src/core/reflect to the merge base (7c7bc7d69) and re-running the project filtered — it fails identically there. It is also deterministic, not flaky: six consecutive runs give byte-identical output. The mechanism is internal/fmtsort.compare’s Interface arm, which orders two keys of differing dynamic type by comparing their type descriptors as VALUES — in Go a Kind Pointer compare of descriptor ADDRESSES, which the linker assigns in declaration order. go2cs’s canonical interned reflect.Type has no such ordering, so the pair sorts by whatever box identity gives. Worth deciding deliberately rather than patching: this is an ordering Go’s own documentation treats as an implementation detail, so the guard may be asserting something go2cs can only match by luck.

encoding/asn1 — the fourth charter row, re-measured: 34 of 38, and the tag root DID close

The board carried encoding/asn1 at 28 of 38 with the standing hypothesis that it shared crypto/rsa’s DER/tag root. Re-measured on this branch it is 34 of 38: six rows closed on their own, which is the hypothesis confirmed — the repaired tag handling reached here too. It is still not bankable, and the converted test artifacts were deliberately NOT committed, per the policy that test sources bank only when a suite validates.

What the hypothesis got WRONG is the shape of the remainder. The four survivors are not one root waiting on one fix; they are four, and three of them belong to areas other lanes already own:

L6 (2026-08-11) closes the last two of those bullets with ONE fix, and it is neither the converter nor makeField. The set field parameter reaches the emitted tag correctly — TestMarshalWithParams, which is the asn1:"set" PARAMETER path, passed throughout. TestMarshal #37 is testSET([]int{10}), the TYPE-NAME path: getUniversalType selects SET over SEQUENCE on strings.HasSuffix(t.Name(), "SET") and nothing else. The bridge’s rtype.Name() gated on GoReflect.ElementType(st) is not null — a proxy for “unnamed composite” that is equally true of a DEFINED container — so every type S []T/[N]T/map[K]V/chan T/*T in the corpus reported no name. PkgPath(), reading the same managed nesting, answered "main" for the same types, which is a pair Go’s model cannot produce and is what named the defect. Fixed with GoReflect.HasGoName, the managed stand-in for the descriptor’s TFlagNamed bit, mirroring GoTypeName arm for arm.

TestCertificate is the SAME root, and is hereby attributed: its RDNSequence is a []RelativeDistinguishedNameSET, so the inner elements were emitted as SEQUENCEs and the RDN came back empty — the “unattributed below the surface message” bullet needs no separate investigation. Measured A/B on one machine, same tree, same GOROOT: 35 of 38 before, 37 of 38 after, the remainder being TestUnexportedStructField alone (L7’s flagRO gap). The board’s projected “36 of 38 when the tag row closes” was one row low for this reason.

Two follow-ons for whoever plans next. (1) abi.Type.HasName() is still false for every synthesized descriptor, so internal/reflectlite.rtype.Name() — the ordinary converted body, which gates on it — answers "" for EVERY type, strictly worse than what reflect had. It is dormant (reflectlite’s consumers context and errors use only String/Kind/Comparable/ AssignableTo/Implements), so it was recorded rather than fixed: populating the bit also changes directlyAssignable’s T.HasName() && V.HasName() short-circuit, currently over-permissive in both packages, which is a corpus-wide assignability change and not a naming one. (2) The measure was taken on the laptop’s Go 1.23.2 GOROOT against the corpus’s pinned 1.23.1; the denominator was verified as 38 test functions, unchanged between the two patch releases, and both sides of the comparison read the same sources — so the per-test agreement is sound and only the absolute count is developmental. Coordinator re-gates on the pinned machine.

The final sweep, recovered after the hardware failure (2026-08-10)

This lane was parked mid-sweep when the coordinator machine died, so the verdict was lost with it. Re-run FILTERED over the lane’s own banked and re-measured rows on a replacement box: 8 packages, 137 verdicts, 8 pass / 0 failgo/ast 9, syscall 62, go/printer 45, net/http/fcgi 12, go/format 4, internal/fmtsort 3, internal/profile 1, runtime/internal/math 1. The last three of those are the rows this pass struck through as already-banked, so the strikethroughs are now gate-backed rather than argued. internal/fmtsort and go/printer were added on purpose beyond the lane’s own list: the bank’s real blast radius is the reflection bridge, and internal/fmtsort is the direct consumer of the MapKeys/MapIndex pair this lane moved into it.

The crash-save wip commit contained NOTHING that belonged. All 22 files classified as standing aftermath and were dropped: nine production .cs in the -tests-closure restore family (the Δio alias and the root-qualification escape), four package_init.cs carrying the initᴛᴛtests hook, three -text-marked compress/testdata fixtures showing a pure CRLF flip — and six log/slog/internal/benchmarks files that were 100% NUL bytes. That last group is a new shape worth naming: NTFS committed each file’s SIZE and lost its DATA in the power failure, and the sizes match the committed content’s CRLF-smudged length exactly, byte for byte, across all six. So the package had no real drift at all — a crash-save git status can be dirty for reasons that are neither a converter change nor a documented phantom, and a size-vs-content check separates them.

r57c — archive/zip banks 98/98; the “performance row” was a WRONG ASYMPTOTE in @string (2026-08-09)

Banked: archive/zip 98/98, no disclosures. Roster 121 → 122 of 215 (56.3 % → 56.7 %), 13,890 → 13,988 matching verdicts, 50 disclosed (unchanged). Lane-local arithmetic against this branch’s base; the coordinator union-recomputes at merge.

The board’s own archive/zip section closed by naming two routes to a bank — “either a measured deadline (the index/suffixarray route) or the string/slice throughput work that would make the measurement moot” — and advised a lane to “start by timing the C# host solo with no deadline rather than re-rooting anything”. That advice was followed exactly, and it is what found the defect: the host, timed solo with no deadline, still had not finished after 45 minutes against Go’s 13.2 s. A constant-factor throughput gap does not do that. Profiling it (dotnet-stack, both worker threads, every sample) put the entire cost in one frame — detectUTF8Buffer._Memmove.

detectUTF8 is the ordinary Go rune walk, and the emission is a faithful 1:1 rendering of it:

for i := 0; i < len(s); { r, size := utf8.DecodeRuneInString(s[i:]); i += size }

The defect was underneath, in the REPRESENTATION. A Go string header is a pointer plus length into shared immutable storage, so s[i:] is O(1) and allocates nothing. @string held a bare byte[], so its range indexer had to materialize the sub-string: O(n), with an allocation. Over a 65,535-byte file name that makes the loop accidentally quadratic — ~2.1 GB copied per call, two calls per record, 32,768 records. Not slowness; the wrong asymptote. @string now carries the header’s real shape (backing array, offset, length) and slices into a window; the backing array is PRIVATE, so a consumer reading it instead of the window is a compile error rather than a wrong answer, which is how the last three raw-array readers were found. Detail in the two signed commits and in ConversionStrategies-Reference.md.

TestZip64LargeDirectory: >45 min (never completed) → 20.2 s, against Go’s 11.3 s.

What this row costs the sweep, and why the deadline entry is still needed

The pipeline builds Debug, where the non-inlined golib window accessors cost ~22x, so the banked suite is minutes rather than seconds and archive/zip joins hash/maphash and index/suffixarray in run-validated-sweep.ps1’s $longTimeouts — authored at '20m', raised to '30m' at merge: the i7-5820K re-measure below left 20m only ~35 % headroom, and a deadline is a safety net against a hung run, never a performance assumption. The 391 s figure was measured on the reference desktop (391 s for the whole suite). Re-verified on the replacement box (i7-5820K 6C/12T, ~3x slower, with two sibling lanes building): the suite ran 792.6 s, of which TestZip64LargeDirectory alone was 774.0 s. Still inside 20 m, but with only ~35 % headroom on a slow loaded box — so if a future sweep reports archive/zip as an empty verdict, suspect the deadline before suspecting the package. Two remedies landed 2026-08-10: $longTimeouts is now a FLOOR rather than an override, so a larger -TestTimeout raises these entries like it raises every other package (a smaller value still loses to the table) — until that fix the table won unconditionally and the flag was silently ignored for exactly the four packages that need it (an i7-5820K sweep reported hash/maphash and crypto/dsa as FAIL … package timeout after 00:30:00 and re-running at 60m died at 30:00 again, while the same package’s pipeline driven by hand at 60m validated its banked 22/22). And the floors themselves were recalibrated to the slow host at merge (maphash/dsa 60m, suffixarray 120m; archive/zip’s 30m stands on its 774 s measurement), so a bare sweep passes on this machine class with tight nets kept on the other 121 packages.

The crash-save classification refines r57b’s NUL rule

r57b found the first instance of crash corruption in a wip(...) snapshot — files that are 100 % NUL bytes, NTFS having committed each file’s SIZE and lost its DATA — and proposed the size-vs-committed-content check as the test that separates it from real drift. This lane’s wip carried five more (go/internal/gccgoimporter/{ar,gccgoinstallation,importer,package_info,parser}.cs) and that test would have MISSED all five: their sizes do not match the committed content, they match the intended new content, because each was mid-rewrite by a -tests-closure emission when the machine died. The reliable discriminator is therefore the content itself — a file that is 100 % NUL is corruption, whatever its size — with the size comparison demoted to a corroborating detail. git diff --stat names them for free: a .cs reported as Bin <old> -> <new> bytes is never legitimate converter output.

The rest of the wip classified into the standing families with nothing unexplained: the -tests-closure production restore family (Δio alias in bufio/bytes/crypto, the global::go.* root escape in crypto/md5, and one initᴛᴛtests package_init hook in crypto/ecdh), three -text compress/testdata CRLF phantoms, and a stray 16 MB src/go2cs.exe build artifact at the repository root — which is worth one line of its own: the converter’s gitignore entry is /src/go2cs/go2cs.exe, so a binary built one directory up is tracked, and a crash-save picks it up.

Handoffs — neither owned by this lane

Coordinator ratifications — the alloc-count rulings, user-confirmed (2026-08-10)

The user ratified the r58a merge’s disclosure rulings, with the honest assessment carried here so the decision and its evidence stay together:

Also ruled in the same pass: the ж-box arc is commissioned chip-class AFTER the near-miss harvest (the counter gives it an exact instrument); the init-ORDER arc starts as a characterization scout; GOROOT-tree-reproduction is DEFERRED past 75% (four packages against a harness-contract change re-validating all 126); r59 runs as the next dedicated lane after the harvest with backlog 24 riding its regen; NuGet 1.23.1.6 is approved after the day’s final consolidated sweep (release push user-owned).

Harvest r60 — the post-1.23.1.6 collection (2026-08-11)

The first release-gated harvest, run across two machines the same day the fixes landed. Every item below supersedes its older census row; the roster is the authority as always.

encoding/asn1 — BANKED 38/38 (roster 127, 74cec76e3). The full arc: 28/38 under one hypothesized DER root → r57a’s StructField.Tag bridge closed six for free → r57b split the remainder into four TRUE roots → r58b’s typed-nil packing took one, L6 took two (TestMarshal #37 AND TestCertificate — via reflect.Type.Name() blanking defined container types, NOT the hypothesized converter SET-tag defect; converter unmodified), L7 took the last (StructField.PkgPath unset on the type side — NOT flagRO; the value side was already refusing writes correctly). The two lanes’ residual sets were exactly complementary and neither could observe the union; the pinned-machine measurement confirmed 38/38.

crypto/internal/edwards25519 — measured 54/55 on merged L4+L7 (was 0/55, a whole-package cctor casualty). L4’s tuple-spec relocation lets the package RUN; L7’s array-dims fix greens both quick.Check rows with real [32]byte/[64]byte values. Sole residual: TestAllocations (109 objects vs want 0 — the ж-box arc’s row; NOT disclosed per the near-budget ruling). NOT banked. ⚠ The fix’s production emission (a new ordered package_init.cs) is deliberately UNCOMMITTED — additive-only drift owed to r59’s queued whole-corpus regen, per the no-casual-regens rule.

math/big (224/226) and nistec (2,195/2,200) — refresh deliberately SKIPPED. Nothing in this harvest touches their residual roots (the want-zero counter rows and TestMulUnbalanced’s truthful performance measurement — all ж-box territory). Their recent measurements stand; a refresh would have measured the same defect-free packages against the same open arcs.

New open items from the lanes’ re-attributions

Machine traps (both cost real time on laptop-1; both now protocol)

Process rulings recorded in passing

Backlog: the AOT full-trim column, deferred with its reasoning (user query, 2026-08-11)

Full trimming (vs the suite’s TrimMode=partial) would shrink the AOT binary and some of its startup/memory floor – but it strips exactly the metadata golib reaches reflectively (fmt’s formatting, sort’s Interface, the bridge's walks), so today it fails Verify rather than producing numbers: a column of n/a at ~25 min of ILC per benchmark on the current coordinator machine. DEFERRED, not declined: when the zh-box arc and a trim-eligibility pass shrink the reflection surface, the fourth column measures a thing that works and shows the payoff. Note also that trim is not the dominant startup lever -- Go package initializers are semantic roots trim can never remove; the larger honest lever is lazy/dead-strippable package inits (Go's linker dead-strips unreachable packages; go2cs loads and inits the whole referenced closure), recorded beside the working-set note in the performance README.

One sharpening from the user (2026-08-11): hoisted string literals materialize at package init (module initializers run eagerly at assembly load), so the hoist cost – deliberately moved to startup to kill per-use allocations and UTF16->UTF8 conversions, and still the right trade by the StringMatch numbers – COMPOUNDS with the eager-closure cost. The lazy-package-init arc therefore recovers both at once: unused packages skip their init() AND their literal materialization. The two items are one lever.

ж-box arc stage A1 — the census is in, the projection HOLDS (lane L3, 2026-08-11)

The zero-emission census the design gated every golden move behind ran corpus-wide, three GOOS targets, on laptop-1 (⚠ go1.23.2 — developmental until the pinned-machine re-derivation, which is ONE command). Full report: CENSUS-zh-box-a1.md. The instrument is -ref-census over the new classification pass (refLoweringAnalysisOperations.go, wired into all three drivers, -debug census per package; CNR byte-identical — analysis only). The headline verdicts, so the next session doesn’t have to open the report to know where the arc stands:

Pinned-machine re-derivation (coordinator, go1.23.1, 2026-08-11): the instrument reproduces. Exact agreement on every aggregate the census itself prints — 25 positions / 5 packages all-already-L3, hand-own 49/41/17, exported return-shaped 3 — with exported candidates 62 vs the laptop’s 64, exactly the GOROOT patch-release shape the developmental flag existed for. A2 is GO on the go-ahead ratifications recorded with its opening.

L9 wave, desktop share — six hypotheses measured, six honest answers (2026-08-11 night)

The stale-census re-measure wave’s items 1–2 plus the seam canary, run on the pinned machine against post-r59 master. One census was stale in each DIRECTION, and the rest attributed:

L9 wave, laptop-G share — item 3’s two toolchain-exec rows, both censuses CONFIRMED (2026-08-11 night)

Item 3 (internal/coverage/cfile, go/internal/gcimporter) measured on laptop G (Ryzen 5 PRO 6650U, 6C/12T, pinned go1.23.1) against dc78eb63c, -tests -test-action all -test-timeout 30m with an explicit -go2cspath. Wall clock: cfile 199 s, gcimporter ~630 s (its C# suite alone 235 s) — both cheap, as the wave promised.

Neither census was stale. Both reproduce their first-census number to the verdict — 4 of 16 and 399 of 583. These two rows are walled, not stale, and nothing in the nine landed fixes touches either wall. The item’s stated expectation — “both exec the Go toolchain, so expect the GOROOT-tree/cwd class in the residue” — is right for one and wrong for the other, which is exactly why the row was measured rather than inherited.

internal/coverage/cfile — 4 of 16 confirmed; the WORKING-DIRECTORY class gains its SIXTH member

Go emits 16 verdicts, the host 7; 4 match (TestAuxMetaDataFiles, TestCoverageSnapshot, TestTestSupport pass/pass; TestIssue56006EmitDataRaceCoverRunningGoroutine skip/skip). Three top-level tests fail — and nine of the twelve mismatches are one root’s shadow: TestCoverageApis t.Fatals in its setup before any of its nine t.Run subtests register, so they compare Go="pass" against C#="" (absent). Read the row as three failures, not twelve.

All three are the established converted-host WORKING-DIRECTORY class (§The converted-host WORKING-DIRECTORY class — why no cheap subset exists), reached because each shells out to the Go toolchain with a path relative to CWD whose MEANING depends on sitting under $GOROOT/src. The host runs in <temp>/go2cs-tests/<pkg>/<guid>/cfile (TestHost.CreateRunDirectory, fixtures staged beside it); go test runs in $GOROOT/src/internal/coverage/cfile. Two facets:

A textbook confirmation of that section’s “no cheap subset” ruling, and it extends the content list: alongside bin/go.exe, doc/godebug.md and sibling sources, a synthetic GOROOT must also reconstruct src/go.mod, and must be a location the toolchain accepts for internal imports. Both facets are satisfied by that one arc and by nothing smaller — a per-test accommodation would be dishonest here, since these tests are measuring the real toolchain’s behavior. +12 verdicts to the arc’s ledger, on top of go/parser’s 167.

go/internal/gcimporter — 399 of 583 confirmed; NOT the cwd class — it is go/types generics

583 verdicts both sides, 399 match, 184 mismatch, and every mismatch is inside ONE top-level test: TestImportTypeparamTests (its parent row plus 183 subtests), all Go="pass" C#="fail". Everything else agrees, including TestImportStdLib’s 303 matching subtests and 82 of the typeparam subtests themselves (68 pass, 14 skip).

The predicted GOROOT-tree/cwd class does not appear, for a precise reason worth keeping: gcimporter’s toolchain execs root their paths at testenv.GOROOT(t) — ABSOLUTE, and the pipeline exports that GOROOT to both sides deliberately (runCommandWithTimeout) — whereas cfile’s are cwd-relative. Same “execs the toolchain” premise, opposite outcome.

The real root sits one layer down, in the test’s own ORACLE half. Each subtest runs compileimportPkgcheckFile in that order (gcimporter_test.go:184-187), and every failure is in checkFile, i.e. go/types.Check over the source file. So compile and importPkg both SUCCEEDED for all 183 — the converted gcimporter read the gc export data fine, and 303 stdlib packages import correctly — and the converted go/types then failed to type-check the generic source handed to it as the reference. The subject of the test is healthy; its yardstick is not.

Shape Count Evidence
Bogus type errors on TYPE PARAMETERS 92 absdiff2.go:70:9: cannot use a.Value_ (variable of type T constrained by orderedNumeric) as T value in return statement — T judged not assignable to T. Also cannot assign map[K]V to map[K]V, S does not implement I[byte], cannot infer S, cannot assign []int to [5]int
Nil-pointer panic inside the checker 91 re-panicked value is runtime error: invalid memory address or nil pointer dereference, surfacing through check.cs:430

The panic stacks are the RE-PANIC, not the fault. check.cs:430 is the default: throw panic(p) arm of handleBailout — a faithful conversion of Go’s own re-panic for a non-bailout recover value — so the frames shown are the bailout path (430 → GoFrame.Run → 435 → 414) and the ORIGINATING frames are gone. Anyone working this must instrument the re-panic or disable the recover; taking check.cs:430 as the defect site will waste the lane.

Whether the 92 and the 91 are one root or two is not established here and must not be assumed. The dominant signature — a type parameter judged not identical to itself — would plausibly produce both, but that is a hypothesis, and this week’s board has a four-for-four record against inherited ones. go/types is unbanked and carries no board row of its own; on this evidence it has earned one, and go/internal/gcimporter’s 184 rows are downstream of it — the row moves when go/types generics do, and not before. Nothing in gcimporter itself is implicated.

The nil-panic is NOT confined to generics — and there is now a minimal reproducer (2026-08-13)

Filed from claude/types-errors-delta-rename, which cleared internal/types/errors’ build blocker and so ran that package’s suite for the first time. Both its top-level tests die on this exact signatureruntime error: invalid memory address or nil pointer dereference re-surfacing through check.cs:430 — and neither is a generics test.

Both fail at the SAME call, codes_test.cs:70 (walkCodes), which is conf.Check("types", fset, []*ast.File{file}, info) over codes.go itself — reached before any Example snippet is ever type-checked. That input is about as small as the checker’s surface gets:

So the 91-count nil-panic class cannot be wholly explained by the generics hypothesis, and the “one root or two” question above now has real evidence on the two side. Anyone taking this on should prefer this reproducer over gcimporter’s typeparam corpus: one import-free non-generic file, two failing tests, ~7 s to the panic, versus 583 verdicts behind a compile+import pipeline.

⚠ The re-panic warning above applies here unchanged — check.cs:430 is handleBailout’s faithful default: panic(p) arm and the originating frames are gone. Instrument the re-panic or disable the recover first; this lane re-derived that independently before finding the note, which is some evidence of how naturally the stack misleads.

ROOT FOUND AND FIXED (2026-08-13, claude/go-types-checker-wall) — an interface MAP KEY compared by wrapper identity

The reproducer above did its job: the fault is one golib defect, it is not generics, not the importer, and not the ж-box or scheduler arcs’ territory.

How it was reached. The re-panic warning is correct and the stack really is useless, but the originating frames were never destroyed — they are snapshotted at the point the .NET exception is ADOPTED as a Go panic (RuntimeErrorPanic.TryAsPanicPanicException.CaptureThrowSite). Dumping PanicTrace there named the site in one run:

at go.ж`1.op_OnesComplement(ж`1 value)            in golib\ж.cs:957
at go.go.types_package.dependencyGraph(map`2 objMap) in go\types\initorder.cs:233
at go.go.types_package.initOrder(ж`1 Ꮡcheck)      in go\types\initorder.cs:33
at go.go.types_package.checkFiles(...)            in go\types\check.cs:515

(Why InheritThrowSite did not already surface this: the re-panic at check.cs:430 is thrown INSIDE handleBailout’s own try, so the emitted catch … when (GoFrame.IsPanic(…)) snapshots its own one-frame site first, and InheritThrowSite’s ??= then declines the origin. Worth a separate diagnostic fix — it is what makes this stack mislead every reader — but it is not the wall.)

The mechanism. initorder.cs:233 is for d := range objMap[obj].deps, and objMap[obj] missed, returning a nil ж<declInfo> that ~ dereferenced one frame later. In Go the lookup cannot miss: every key of M came from ranging objMap. The two differ only in the STATIC interface the key is held in — Object going in, dependency coming back out of obj.(dependency).

Go compares interface values by (dynamic type, dynamic value), and that ONE relation serves both == and map-key lookup. In the conversion they had diverged: emitted ==/!= route through builtin.AreEqual, which unwraps the three adapter tiers, while map<K,V>’s backing Dictionary used the DEFAULT comparer and compared the WRAPPERS — and an interface value’s wrapper is not stable, since asserting to a narrower interface yields a different adapter object over the same receiver box. Equal but unfindable: Object(d) != obj answered correctly (AreEqual unwrapped) while objMap[d] missed. Only the compile-time ImplementGenerator adapters ever carried the unwrap-and-hash contract; the runtime shells go2cs-gen builds for a duck-typed assert (Δ<iface><T>, Δ<iface>ᴛObj) override neither Equals nor GetHashCode.

The fix is golib-only and centralizes rather than duplicates: GoEqualityComparer projects AreEqual as an IEqualityComparer<TKey> and hashes the UNWRAPPED root (the same rule the compile-time adapters already used), installed by map<K,V> only when typeof(TKey).IsInterface or TKey is any — so concrete keys keep EqualityComparer<TKey>.Default’s devirtualized path, the test being a JIT-time constant per instantiation. Restating the relation in each generated shell was rejected: AreEqual is golib’s single definition of Go equality and a per-shell copy is exactly the drift that produced this. Guarded by the InterfaceAssertionMapKey behavioral test; documented in ConversionStrategies-Reference.md under An INTERFACE map key compares by Go equality.

Measured movement. internal/types/errors BANKS at 155/155 (0 mismatches, status: validated) — the row’s full 155 verdicts, from 0 before. The pre-fix host produced two nil-panics and zero verdicts; post-fix it type-checks codes.go and every Example snippet, and the subtests that pass include the generics family (NotAGenericType, WrongTypeArgCount, CannotInferTypeArgs, InvalidTypeArg, InvalidInstanceCycle, MisplacedTypeParam).

go/internal/gcimporter moves 399 → 475 of 583 (+76; mismatches 184 → 108), and the split the row’s census left open — “whether the 92 and the 91 are one root or two is NOT established here and must not be assumed” — is now answered: TWO. The nil-panic class is entirely gone (zero invalid memory address and zero check.cs:430 occurrences across the whole 583-verdict run); every one of the 108 residual mismatches is the OTHER class, the bogus type-parameter errors, unchanged in signature (absdiff2.go:70:9: cannot use a.Value_ (variable of type T constrained by orderedNumeric) as T value in return statement). That second root — a type parameter judged not identical to itself — is still open and is not this fix’s, and gcimporter’s remaining rows stay downstream of it. The row does NOT bank; test sources deliberately not committed.

go/types itself: NOT measured — one BUILD blocker, and it is not the wall. With the wall down, go/types’ own suite was taken through -tests -test-action all -test-timeout 90m for the first time. The conversion fully succeeds — all 34 _test.cs files emit — and the host build produces exactly one error:

check_test.cs(200,53): error CS0839: Argument missing
    defer(ᴛ1 => throw panic(errΔ2), , ref ᒐ);        // Go: `defer panic(err)` (check_test.go:170)

Mechanism. visitDeferStmt.go:62-66 forces the temp-param lambda form for a BUILTIN callee, so paramCount == 1 and lambdaContext.callArgs is sized 1 — but panic is not rendered as a call. It emits throw panic(<expr>) with the ORIGINAL argument expression inlined in the lambda body, so the ᴛ1 substitution never happens and callArgs[0] is never filled, leaving the empty argument slot above. Note the near neighbours are fine: defer delete(w.seen, typ) (infer.go:715, typestring.go:121) converts and compiles today, which is why the corpus never surfaced this — the defect is specific to the one builtin that is a throw, not a call.

Remedy shape. Prefer routing panic through the same temp-param substitution as every other builtin — defer(ᴛ1 => throw panic(ᴛ1), errΔ2, ref ᒐ). The tempting alternative (drop the lambda param and let the body capture the expression) is WRONG: Go evaluates a deferred call’s arguments at defer time, so capturing errΔ2 would report whatever the variable held when the frame unwound. Small and well-scoped, but it is a CONVERTER change and therefore owes its own CNR + full behavioral gate, which is why this lane characterized it rather than folding it into a golib-only commit.

Adjacent, NOT measured — a plausible sibling worth one probe. go2cs-gen’s struct-equality template compares an INTERFACE-typed field with C# == and hashes it with HashCode.Combine(field, …) — e.g. go/types’ own graphNode: this.obj == other.obj. On a C# interface == is reference equality, so a struct carrying an interface field would compare by ADAPTER identity rather than by Go’s (dynamic type, dynamic value) — the same class as this fix, one level up. Unlike the map defect it is at least self-consistent (Equals and GetHashCode are both reference-based), so it produces no equal-but- unfindable split and no nil-panic; it would show as two structs holding the same dynamic value comparing unequal. This is read off the generated template, not observed in a failing test — it may well be masked in practice, and it is stated here as a candidate to measure, not as a finding. The remedy shape, if it reproduces, is the same one used here: route the field through AreEqual and hash GoEqualityComparer.RootOf.

One environmental note for anyone re-running these by hand: four subtests (InvalidPkgUse, UnusedImport, UndeclaredImportedName, UnexportedName) use importer.Default() and fail with could not import fmt … ($GOROOT not set) when the host exe is launched directly. That is the known GOROOT-resolution class, not a checker defect — the pipeline exports GOROOT to both sides, and under it the package is 155/155. Running the host bare gives 150/156.

Scout batch 1 — twelve never-run packages (2026-08-11)

Twelve packages that had never linked a test host were taken end to end through -tests -test-action all on the pinned coordinator (i7-5820K, go1.23.1). Nothing banks, and no row is disclosed — the 2026-08-10 ratification stands, and none of these is an alloc row anyway. The value is the census: five packages produced a verdict map, seven died before producing one, and all twelve now have a named root. (Six produce one since 2026-08-12 — net/rpc/jsonrpc’s build-blocker was fixed and the package runs; its row and §”Five converter defects” item 3 below carry the measured result. The “nothing banks” above has since been overtaken by one row: net/rpc/jsonrpc banked 9/9 on 2026-08-14, three converter defects after this census first named it.)

Package Go verdicts C# matched Outcome Root / attribution
internal/chacha8rand 4 3 near-miss, 1 row array-SHAPE reinterpret (array.cs:280) — the seam L10 works through sockaddr, here with no kernel in it
runtime/metrics 2 1 near-miss, 1 row a //go:linkname PUSHED into a test package is unwired; the implementation exists in converted runtime
internal/singleflight 5 4 BANKED 5/5 the scheduler arc retired the wall — TestDoAndForgetUnsharedRace converges in 1.2 s (was 28.7 min), banked inside the DEFAULT deadline with no $longTimeouts entry (SCHED-S2)
net/http/httptrace 2 2 BANKED 2026-08-29 (2|0)reflect.MakeFunc hand-owned the funcType reinterpret had no managed record behind it (refined root 2026-08-29: funcLayout panics funcLayout of non-func type <nil> on the zero box the reinterpret recovers); MakeFunc is now Value.Call’s exact inverse over the descriptor’s carried System.Type — reflect/makefunc_impl.cs + golib GoReflect.MakeGoFuncDelegate, guarded by the ReflectMakeFunc behavioral test
internal/unsafeheader 6 0 rooted (architectural) the package’s entire subject is the slice/string HEADER LAYOUT that golib deliberately does not have
unique 19 0 ⚠ REGRESSION — flagged, not decided host dies: Fatal error. Internal CLR error. (0x80131506) in System.GC.Collectruntime.GC()drainMaps. Board has this package at 4 of 19 (r43e)
internal/types/errors 155 0 converter defect FIXED → now downstream of go/types the Δ-renamed-imported-type defect is fixed (claude/types-errors-delta-rename); the package now BUILDS and RUNS, and both tests then die on the go/types checker nil-panic — see the sub-row below
internal/fuzz 52 52 BANKED 2026-08-14 — two blockers deep alias-to-anonymous-struct (CorpusEntry) cleared the BUILD (79f2b5e62); the run then died at flag provided but not defined: -json because worker_test.go’s TestMain calls flag.Parse(). The host’s TestFlagBridge now declares its own command line on flag.CommandLine the way testing.Init() declares -test.*, and the suite validates whole
net/rpc/jsonrpc 9 9 BANKED 2026-08-14 — three converter defects deep embedded-pointer promotion (2026-08-12), the non-trailing-default switch lowering (2026-08-13), and a fixed-size array’s LENGTH reaching reflect through a METHOD’s POINTER parameter (2026-08-14). The last was one test, not the three recorded here: TestBuiltinTypes sorts first, its goroutine panic killed the host, and the other eight recorded no verdict — which is also why the host now reports a goroutine panic with its traceback and flushes what it has. Never socket-walled
testing/fstest 7 converter defect a defined type over ANOTHER package’s named map type — the emitted two-hop conversion has only one hop
internal/syscall/windows/registry 6 converter defect the internal-test partial class is emitted non-static, and in this package nothing else declares it
embed/internal/embedtest 7 not a candidate test-only Go package: there is no production package for the host to reference

The comparison JSON’s status string is not a diagnosis. Seven of these read conversion-blocked, but that value only records “no result file appeared”. Five of the seven are C# COMPILE failures of the converted test sources, one (embed/internal/embedtest) is a package that cannot have a production assembly at all, and unique built and ran and then killed its own process. Read the errors array, not the status. (The recorded PS 5.1 ConvertFrom-Json duplicate-key trap did not fire in this batch — a census of all 24 verdict maps found zero case-insensitive duplicate keys — but the raw-text parse was used regardless.)

The address-reinterpret seam owns three of the five packages that ran

internal/chacha8rand, internal/unsafeheader and net/http/httptrace fail at one architectural place: a raw address reinterpreted into a managed shape. They are worth reading together because each names a different facet of it, and fixing the narrowest one fixes none of the others.

runtime/metrics — 1 of 2, and the missing row is a linkname DIRECTION

TestDocs passes. TestNames reports infrastructure-error: System.NotImplementedException: runtime_readMetricNames: external (assembly or cgo) function is not implemented, from a PartialStubGenerator stub. The implementation is not missing — converted runtime/metrics.cs:800 holds internal static slice<@string> readMetricNames() carrying its //go:linkname readMetricNames runtime/metrics_test.runtime_readMetricNames comment, and runtime/metrics/description_test.cs:35 holds the matching bodyless internal static partial slice<@string> runtime_readMetricNames();. The two are never joined, because this linkname PUSHES a symbol from a production package INTO a test package rather than pulling one in. That is a narrower job than the getg-style “external (assembly or cgo)” rows it superficially resembles: both halves already exist in the corpus and the wiring is the whole task, and doing it takes this package to 2 of 2, i.e. to a bank. Cheapest bank-per-unit-work row in the batch.

BANKED 2026-08-12 at 2 of 2 — but “the wiring is the whole task” was measured FALSE, by three layers. The push wiring landed general (a linknamePushTargets row whose key simply spells the _test package path — convertTestVariant already sets currentPackagePath to the external variant’s own PkgPath, so a production package pushing into its own test package needs no new machinery; the registry guard learned that an external test package’s source lives in the base package’s _test.go files). Behind it, in the order the reruns surfaced them:

  1. metricsLocksemacquire1getg — the pushed body’s first call was the runtime sleeping semaphore. Hand-owned at the metricsLock/metricsUnlock boundary (manualConversionFuncs + managed_impl.cs, a SemaphoreSlim(1,1)); the same fix unblocks readMetrics for every future consumer.
  2. readMetricsLocked’s slice-header reconstructmetrics.Read crosses by raw first-element address and the runtime rebuilds a []metricSample over it: the L10 address-reinterpret seam, measured here as garbage @string names out of the fabricated slice. NOT chased through L10: the crossing is re-shaped instead — runtime/metrics/sample.cs is hand-owned and Read marshals names in / computed (kind, scalar, pointer) out through the public runtime.readMetricsManaged shim (the registerPoolCleanup precedent), preserving readMetricsLocked’s batch semantics; the runtime_readMetrics push row is recorded UNHONORABLE with the reason naming the hand-own. The metrics TABLE, initMetrics, and every compute closure stay auto-converted.
  3. Two allm walkers in the compute closuresNumCgoCall and totalMutexWaitTimeNanos both walk the scheduler’s m-list (nil here where Go always has m0). Hand-owned with their honest managed answers (no cgo calls exist → 0; the two real wait-time counters minus the per-m profile walk); consistentHeapStats.read likewise (no Ps, no allocator deltas → the zero snapshot, the ReadMemStats precedent).

The package is therefore no longer an L10 witness — its seam instance is routed around, not fixed — and chacha8rand remains L10’s cheapest kernel-free reproduction.

internal/singleflight — 4 of 5, one test that does not come back

Go passes 5. The host passes 4 and then reports {"action":"timeout","elapsed":1800,"output":"package timeout after 00:30:00"}; TestDoAndForgetUnsharedRace gets no verdict at all, so the emitted JUnit reads tests="4" failures="0", which must not be read as a clean run.

The test (singleflight_test.go:145) loops forever with no iteration bound: it launches n=1000 goroutines that each call g.Do on one key whose function sleeps d, and if calls != 1 — i.e. if the goroutines did not all park inside g.Do before the first call completed — it doubles d and retries. Whether the converted host converges slowly (each retry costs at least d, and d doubles) or never converges is not established here and should not be assumed; distinguishing them needs a run with a much larger -test-timeout and a print per iteration, which this batch did not spend. What is established: the other four rows agree, and the package is one row from a bank behind a question about goroutine parking latency, not about singleflight itself.

Convergence measured 2026-08-12 — it CONVERGES: 20 iterations, 28.7 minutes, and the package validates 5/5

The instrumented run the paragraph above asked for was spent (branch claude/singleflight-convergence, laptop lane — Ryzen 7 PRO 6850U, 16 hardware threads — solo, go1.23.1; NOT the coordinator the scout used, which matters below). Method: the -tests pipeline split at its action seams — convert, then a hand edit to the STAGED converted singleflight_test.cs adding a per-iteration stderr print (iteration, d, calls, shared, ThreadPool.ThreadCount at iteration start/end, pending-queue count, spawn/wait timings), then build, then compare -test-timeout 90m — the same phases -test-action all runs, split only so the edit could sit between conversion and build (the manifest’s input digest covers Go sources, options and runtime sources, not the emitted .cs, so the staleness gate accepts an instrumented staged copy by design). Everything was reverted after: the measurement emitted a real proof page — the comparison genuinely printed Validated 5 tests against go test, 5/5 matched — and that page was deliberately discarded, because a hand-instrumented host banks nothing.

The verdict: converges. TestDoAndForgetUnsharedRace passes at iteration 20 (d=524s), test elapsed 1720.8s; the other four rows total ~0.3s, package wall ≈ 1725s — a 75-second margin under the 30-minute deadline the scout run died at. go test’s whole package on the same machine: 0.040s (the race test itself 0.01s — Go converges on its first iteration; the gap is ~10^5). shared was 0 at the converging iteration, so the assert the loop protects passed: the 28.7 minutes is all scheduling, and none of it is singleflight.

The census (run 2; run 1, without the pool columns, matched it iteration-for-iteration through 15):

iter d calls pool start→end wait
1-3 1-4ms 20, 8, 6 12→258 ~0.02s
4-10 8-512ms 4-9 258→258, flat ~4×d
11-15 1-16.4s 4-7 258→354 (~+0.9/s) ~3-4×d
16 32.8s 4 354→162 98s
17 65.5s 3 162→221 197s
18 131s 2 221→567 262s (=2×d)
19 262s 2 567→75 524s (=2×d)
20 524s 1 75→1002 524s (=1×d)

Three mechanisms, each owning one phase of the table — and none of them is parking LATENCY:

Why the scout run got no verdict: the finish sits at 28.7 min ± the injection rate, and a miss is quantized — the ladder doubles, so slipping one iteration moves the finish to ~55 min (2245s+1049s), two to ~107 min. 1800s lands inside the measured run’s iteration-19/20 window, so any marginally slower gate — the scout’s host was the i7-5820K coordinator, not this laptop, and the rate is a runtime heuristic, not work — pushes convergence one rung up and the deadline eats the test mid-iteration, verdict-less, exactly as recorded. That the same ladder shape produced “4 of 5 + a consumed deadline” on one machine and “5/5 in 28.7 min” on another IS the fragility finding: the two runs differ by one rung, and a rung is a doubling.

Remedy recommendation — two options, priced:

Per the errand’s charter nothing was chased: measurement only, aftermath reverted, no bank.

RESOLVED 2026-08-14 — the scheduler arc landed and the row banks 5/5 in 1.2 s (SCHED-S1/S2)

The recommendation above (“the durable path is the one golib already names”) was taken. Goroutine.Start now creates one dedicated background thread per goroutine instead of queueing a ThreadPool work item, and the min-thread floor retired in the same commit (DESIGN-cooperative-scheduler.md §5.2, OQ1/OQ3). No $longTimeouts entry was ever landed — OQ9 resolved in the ratifying direction, and the bridge is now dead as designed.

Re-measured on the same laptop, solo, go1.23.1 — the machine that produced the 1720.8 s table above, so this is a clean A/B and not a hardware difference. Same method, same instrumentation seams:

iter d calls pool start→end goroutines live (peak) spawn wait
1 1ms 165 3→3 4 141ms 141ms
2 2ms 112 3→3 10 141ms 141ms
3 4ms 83 3→3 20 125ms 125ms
4 8ms 54 3→3 41 125ms 140ms
5 16ms 35 3→3 85 125ms 125ms
6 32ms 30 3→3 45 125ms 157ms
7 64ms 6 3→3 392 125ms 140ms
8 128ms 1 3→3 1001 110ms 172ms

Converges at iteration 8, d=128ms, test elapsed 1.2318 s (was iteration 20, d=524s, 1720.8 s — a 1,397× reduction). Package wall 1.54 s against go test’s 0.040 s, so the residual gap is ~31×, not ~10⁵.

Read the pool column: it is flat at 3 for every iteration. Every mechanism the original table identified is now structurally absent rather than merely faster — there is no queue to drain in waves, no floor to sit at, and no injection-vs-retirement race, because nothing Go-semantic touches the pool at all. What remains is honest work: spawn ≈ 110-141ms is the cost of creating 1000 real threads (~125 µs each), and the ladder now climbs only until d exceeds that spawn window — which is why it stops at 128ms. The live-goroutine count is the new registry’s, and it returns to 2 after every iteration, so 1000 threads are created and retired eight times over with no leak.

The fragility finding retires with the row: the finish no longer sits on a heuristic’s knife edge, so it cannot slip a rung between machines. Banked per the validated-package ritual (roster line, proof page, committed test sources) inside the DEFAULT deadline with ~3 orders of margin.

unique — a REGRESSION against this board’s own record, flagged for a bisect lane

The r43e-weak entry above records unique at 4 of 19 in a “2-minute run with 19 verdicts”, after runtime.GC()’s hand-owned body learned clearpools’ unique arm. Today, against origin/master (5fe713f98), the host produces zero verdicts: every TestHandle subtest starts, and the process then dies with

Fatal error. Internal CLR error. (0x80131506)
   at System.GC.Collect(Int32, System.GCCollectionMode, Boolean, Boolean)
   at go.runtime_package.GC()
   at go.unique_internal_test_package.drainMaps(go.ж`1<T>)
   at go.unique_internal_test_package+<>c__DisplayClass21_0`1[[…testStringStruct…]].<testHandle>b__0

Reproduced twice, per the standing “re-run before believing a crash” rule — identical stack, the naming subtest differing only because the subtests run in parallel. The scout’s own run of the same package instead consumed its 30-minute deadline and was recorded by the pipeline as conversion-blocked/dotnet timed out after 31m0s; a hang and an execution-engine crash are the same “no verdicts” outcome from the pipeline’s point of view, so the recorded status understates this row in both runs.

0x80131506 is COR_E_EXECUTIONENGINE — a GC-state fault, not a managed exception, and this board already ties that exact string to the address model (see the ж<T>/EnsureStableAddress analysis above, where a 4 KB write into freed heap surfaced as ExecutionEngineException and Fatal error. Internal CLR error.). golib has moved materially since r43e — the @string window, the allocation counters, the array-backing materializer — so a plausible story exists in more than one place, and that is precisely why this is flagged rather than attributed. It wants a bisect between r43e and 5fe713f98 with unique’s host as the probe, and it should be treated as higher severity than an ordinary failing row: a memory-safety fault does not stay inside the package that reveals it.

RESOLVED (2026-08-12, unique-bisect lane) — bisected to the @string window; closed by a clone.cs hand-own

The culprit is fc6d8c179 (r57c-zipperf, 2026-08-09): “golib: a Go string is a WINDOW”. Bisect over the 126-commit first-parent range 57abfe9e1..5fe713f98, unique’s pipeline as the probe: r43e anchor GOOD (full 19-verdict map, the recorded 4-of-19-era shape), f2b80a766 (#63) GOOD, 90059385b (#80, r57b) GOOD, 515cea127 (#81, r57c) BAD, and fc6d8c179 — the window commit, probed directly against its own parent’s lineage — BAD. The window is present in every BAD tree and absent from every GOOD one.

Mechanism — one defect, two eras. Converted unique/clone.cs:28 rewrote every string field of a value IN PLACE the way Go does — (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&value)) + offset)) — emitted as a read/write of @string through (uintptr)Ꮡvalue + offset: an interior address of the movable ж<T> heap box plus a Go ABI offset, against a CLR object whose field layout is unrelated to Go’s ABI. For any T whose strings sit at nonzero offsets (testStruct’s b at +8, testStringStructArrayStruct’s s[1].a at +16 — the latter landing entirely OUTSIDE the 8-byte array<T> ref that is the whole CLR value), the access lands on the box’s OWN fields.

Mechanism proven without unique: a ~25-line program against golib alone — ж<TT> over struct { array<SS> s } where SS is struct { @string a }, two @string stores at Go offsets 0/+16 through (ж<@string>)(uintptr), then GC.Collect — dies with the identical 0x80131506-at-GC.Collect stack on window-era golib, and on pre-window golib (7c7bc7d69) survives both collections and only faults when the program itself reads the type-confused slots back. The A/B isolates the window as the escalation and proves the writes were corrupting values all along.

The fix (this lane): src/core/unique/clone.cs is hand-owned ([module: go.GoManualConversion]), the documented S1 managed-referent remedy. clone<T>’s contract — “MAY update string values found in value with a cloned version” — is a retention optimization, never a semantic requirement, so the hand-own clones the T == string case (right-sized copy via stringslite.Clone, no address arithmetic) and returns aggregates unchanged; the only divergence from Go is retention (an interned aggregate’s strings keep sharing their original backing). makeCloneSeq and the builders stay in their converted form — pure descriptor arithmetic, still covered by TestMakeCloneSeq. Post-fix census on the c33b3a67e base: the host runs to completion — 5 of 19 matched (the five TestMakeCloneSeq passes; r43e recorded 4), 13 fail / 2 infrastructure-error, all on the pre-existing roots this section already names (the [GoType] equality gate’s v0 != v1 rows, the eface subtest-naming pair, the nil-vs-empty cloneSeq DeepEqual rows). unique still does not bank; the REGRESSION row is closed.

Residual, for the ж-box arc: the corpus has seven more (ж<@string>)(uintptr) sites, all in converted runtime (map_faststr.cs:487 and iface.cs:461 are the two WRITE sites; alg.cs, error.cs, arena.cs×3 read) — dead or near-dead under the managed model’s own map/iface, but the same shape, and any future caller inherits the same two-era hazard. The general (uintptr)ж<T>-for-managed-T transient-address model is unchanged by this fix; that is the ж-box arc’s charter, not this lane’s.

Five converter defects, each with a named mechanism

None of these five is a wall; all are ordinary emission bugs, listed with the evidence a fix needs.

  1. internal/types/errors — a Δ-renamed IMPORTED type is spelled with its bare Go name. FIXED 2026-08-13 (claude/types-errors-delta-rename). codes_test.cs emitted err._<Error>(ᐧ) (38,42) and new Info(…) (65,22) → CS0246 ×2, against go/typesΔError/ ΔInfo, while the test’s own package_test_info.cs already minted typesꓸError/typesꓸInfo and left them unused.

    The diagnosis above was one step coarse, and the correction is the useful part. It is not “the cross-package path” — the QUALIFIED cross-package spelling was always right (shapelib.Marker{…}new shapelibꓸMarker(…), verified directly). The loss is the bare ident, which only a DOT import produces (codes_test.go has . "go/types"). Type-DRIVEN positions — declaration, parameter, conversion, field — resolve from types.Type through getCSharpTypeName/getScopeCheckedTypeName, both of which already consulted foreignAliasedTypeName; that is why var mu Mutex through a dot import has worked since DotImportRenamedPackage. The two AST-IDENT type positions did not: a type-assertion target and a composite-literal type render through convIdent’s isType arm, which returned the bare sanitized Go name and consulted nothing. That arm now routes through the same lookup. Guarded by DotImportRenamedType; CNR byte-identical across 588 packages.

    Blast radius, for whoever wonders whether a corpus regen is owed: none. The only production (non-test) dot import in the converted corpus is go/typesinternal/types/errors, and that package publishes ZERO collision renames; generrordocs.go’s dot import of go/types is //go:build ignore and is not converted. The fix therefore cannot move production src/core — it reaches -tests conversions only.

    The row did not validate, and its remaining blocker is NOT its own. With the build blocker cleared the host builds and runs; both top-level tests then fail with the go/types checker nil-panic re-surfacing through check.cs:430 — Go 155 verdicts, C# 0. internal/types/errors is now a second dependent of the unbanked go/types row, exactly as go/internal/gcimporter’s 184 rows are. Not banked; test sources and proof page deliberately not committed.

  2. internal/fuzz — an alias whose RHS is an anonymous struct does not reach the test compilation. minimize_test.cs:26 and worker_test.cs:52 emit Func<struct{Parent string; Path string; Data []byte; …}, error> — raw Go syntax in a C# file — producing CS1031/CS1525/CS1003 cascades. Go declares type CorpusEntry = struct{…} (fuzz.go:463), and the production emission handles it correctly by lifting it: fuzz.cs:8 carries global using CorpusEntry = go.@internal.fuzz_package.CorpusEntryᴛ1;. global using is compilation-scoped, and this is a production-reference test project, so the alias never crosses into the test assembly — package_test_info.cs contains no CorpusEntry line at all. Remedy shape: re-emit the lifted alias into the test compilation, or spell CorpusEntryᴛ1.

    FIXED (79f2b5e62, converter) and the package is now BANKED 52/52 (2026-08-14). Clearing the build did not validate it: the host then died before any test on flag provided but not defined: -json, because worker_test.go’s TestMain calls flag.Parse() and nothing had ever declared the host’s own command line on the converted flag.CommandLine. That second blocker is a whole CLASS, remedied host-only by src/core/testing/TestFlagBridge.cs — see the flag-bridge entry above for the class census, the measurements that ruled out a testingflag project reference, and where the other three members stand.

  3. net/rpc/jsonrpc — promotion from embedded POINTER fields is invisible to ImplementGenerator. all_test.go:310 declares type pipe struct { *io.PipeReader; *io.PipeWriter }, whose Read/Write/Close come entirely by promotion. The generated …pipe-global__go.io_package.ReadWriteCloser-ptr.g.cs calls .Read/.Write on ж<pipe>, finds no member, and binds an unrelated extension method instead — CS1929 naming io_package.Read(ref io_package.LimitedReader, slice<byte>) and io_package.Write(ref io_package.OffsetWriter, …). An error message pointing at LimitedReader from a jsonrpc test is the giveaway that this is an overload-resolution fallthrough, not a missing reference.

    FIXED 2026-08-12 (ImplementGenerator + InterfaceImplTemplate, generators only — zero converter-emission change). The hop forwarding was gated to a struct with exactly ONE embedded pointer; with several, no hop was named and every promoted member fell through to the bare m_box.M(…) / this.M(…) receiver. It now indexes the hop path per member, routing each to the UNIQUE embed declaring it — Go’s depth-1 promotion rule, with a name two embeds declare dropped rather than guessed (which is why *pipe.Close, declared on the struct, still wins over the Close both halves of the pipe declare). Each embed’s method set is read from local syntax where it is declared here and from METADATA where it is not, which is the whole jsonrpc case. Guarded by the MultiPointerEmbedPromotion behavioral test; detail in ConversionStrategies-Reference.md.

    This row is NOT socket-walled, contrary to the batch’s expectation. With the host building, the package RUNS end to end: 9 Go verdicts, 6 C# matched, 3 failed, no timeout and no netpoll involvement — because jsonrpc’s own tests never open a socket. They talk over the in-memory pipe above (myPipe() from two io.Pipe()s, “Copied from package net”), which is precisely why that type exists in the file. Being under net/rpc is not the same as reaching the kernel; the walled-list membership was inherited from the parent path, not measured. The three remaining failures share one root and it is a json defect, not an rpc oneTestBuiltinTypes, TestClient and TestServer all report json: cannot unmarshal array into Go value of type [1]interface {}, i.e. encoding/json cannot unmarshal a JSON array into a Go fixed-size array type (jsonrpc passes params as [1]any). The other six pass: TestMalformedInput, TestMalformedOutput, TestServerEmptyMessage, TestServerErrorHasNullResult, TestServerNoParams, TestUnexpectedError. Nothing banked and no roster change — the row moves from “build-blocked” to “one named json root from a bank”, and that root is worth checking against encoding/json’s own suite before anyone spends a lane on it here.

    UPDATE 2026-08-13 — the json root is fixed, and it was WIDER than recorded here; jsonrpc now stands on a second, unrelated root. The defect was not in the reflection bridge or in array<T> at all: it was the converter’s switch lowering for a default clause that Go places before some of its cases (claude/json-fixed-array-unmarshal, guarded by JsonFixedArrayUnmarshal; full mechanism in ConversionStrategies-Reference.md). Scope correction: converted encoding/json could not decode a JSON array into any target except a bare interface{} — every [N]T and every []T took the error arm, not only fixed-size arrays. The same converter bug independently made internal/bisect reject every pattern it was given. jsonrpc’s 3 rows are unchanged in NAME but not in cause: TestBuiltinTypes, TestClient and TestServer now get past json and panic index out of range [0] with length 0. Two things make this worth a fresh lane rather than a footnote: (1) the panic escapes on a goroutine and takes the host process down, so the package now records 0 verdicts where it used to record 6 — a full-suite run of this package reads worse than before even though the corpus is strictly better; run it filtered (-test.run over the other six) to see the 6 passes. (2) The obvious suspect is ruled OUT: a struct-typed field holding a [1]any, left at its zero value by a keyed composite literal that omits it, was probed directly (codec{name:…, id:…}c.req.Params[0] = …, plus the bare-var and local-var shapes) and matches Go exactly — the generated constructor chain does run the = new(1) field initializer. No stack trace is available from the host: it prints panic: {message} with an empty StackTrace, which is itself worth fixing, because a goroutine panic with no frame is the hardest possible diagnostic to act on.

    CLOSED 2026-08-14 (claude/jsonrpc-goroutine-panic) — the package VALIDATES 9/9 and is BANKED. Two landings, and the first one is what found the second.

    (a) The host no longer swallows a goroutine panic’s traceback. The frameless report was not a missing catch — it was golib’s AppDomain backstop doing exactly its job: it prints the panic VALUE and exits 2, which is Go’s own report for a program and useless to a host running many Go programs in one process. Goroutine.ObserveUnhandledPanic now lets a host WATCH a panic cross a goroutine root from an exception FILTER that always declines — so the fatal path stays byte-identical (Go fidelity, and the oracle keeps observing it) while the report is written with the stack still standing. The converted-test host installs one: it attributes the panic to the test whose goroutine it was, reports it as that test’s terminal FAIL with the full traceback, and FLUSHES the result files the fatal path used to discard whole. A goroutine panic now costs the tail of a run instead of all of it. Guards: GolibTests/GoroutineRootPanicTests (5 tests over the root’s whole policy — observed-and-still-escapes, the fault site surviving, a runtime-error panic, containment still taking non-panic exceptions, Goexit taking neither).

    (b) With the trace liberated, the panic rooted in one read — and it was ONE test, not three. The trace named array.get_Item under all_test.cs’s own BuiltinTypes.Array(i int, reply *[1]int), called through reflect.Call from net/rpc’s service.call. net/rpc allocates every reply from the method type alone — reflect.New(mtype.ReplyType.Elem()) — and the [1]int’s LENGTH did not survive the trip, so New built a zero-length array and the callee’s first write panicked. Two hops were missing, both now closed: a METHOD’s func type is built from the method TABLE and never passes through a delegate, so GoReflect.MethodParamDims reads the [GoArrayDims] stamps off the MethodInfo directly; and the array sits behind a POINTER, so the converter stamps a parameter’s POINTEE dims and a pointer descriptor’s dims pass through Elem() unshifted. The converter half had to go into visitFuncDecl’s REBUILT signature path, which is the one a *[N]T parameter always takes — having a pointer parameter is itself what triggers the rebuild — and that same gap had been silently dropping the stamp from VALUE array parameters in heap-boxing functions too. Full mechanism: ConversionStrategies-Reference.md; guarded by the extended ReflectFuncArrayParamDims behavioral test (rpc’s shape in miniature) and by TestGoArrayDimsAttribute.

    The “three failing tests” were one. TestClient and TestServer never failed on this root at all — TestBuiltinTypes sorts first, its panic killed the host, and the other eight recorded no verdict. That is precisely the misreading (a) exists to prevent, and it is worth carrying as a rule: a package that records 0 verdicts has one failure and an unknown remainder, not N failures. Corpus footprint of the converter half, over all 592 behavioral packages: 5 declarations in 5 files, one line each. Gates: behavioral suite 566/566 + 540/540 output, CNR classified, GolibTests 102/102, converter go test ./..., filtered sweep PASS net/rpc/jsonrpc 9.

  4. testing/fstest — a defined type over ANOTHER package’s named map type gets a one-hop conversion. Go has type shuffledFS MapFS where MapFS map[string]*MapFile. The emission declares [GoType("global::go.testing.fstest_package.MapFS")] internal partial struct shuffledFS; and then writes ((MapFS)(map<@string, ж<MapFile>>)fsys).Open(name) — CS0030 at (62,62), because the generated conversion offers shuffledFSMapFS but not shuffledFS→ the raw map the intermediate cast asks for.
  5. internal/syscall/windows/registry — the internal-test partial class is emitted non-static. export_test.cs:9 opens partial class registry_internal_test_package { and then declares internal static error SetValue(this …registry_package.Key k, …) → CS1106. Banked packages get away with the identical non-static opener only incidentally: sort/bytes/strings each have a go2cs-gen RecvGenerator file that re-declares the class as public static partial. Registry’s generated set covers registry_package and registry_test_package only, so nothing supplies the modifier. The emitter should not be relying on a generator for it.

embed/internal/embedtest should be struck from the candidate list

CS0234: embedtest_package does not exist in go.embed.@internal. It never will — $GOROOT/src/embed/internal/embedtest contains embed_test.go, embedx_test.go, testdata/ and concurrency.txt and no non-test source. There is no production package, hence no production .csproj in src/core, and the -tests emitter nonetheless writes global using static global::go.embed.@internal.embedtest_package; plus a production project reference. The tests here exercise embed, not a package of their own. Two follow-ups, both small: teach the emitter the test-only-package shape (no production reference, no _package using), and route the coverage to embed where it belongs. Until then this row is not a measure of anything.

ж-box arc stage A3 — the pinned measure: P256 lands at 8,528, under the wall (lane L3, 2026-08-13)

The §9 A3 row of DESIGN-zh-box-reduction.md, run whole on the pinned laptop R (Ryzen 7 PRO 6850U, 32 GB, go1.23.1 exactly; ambient load one idle two-day-old MSBuild node — effectively solo): the real pipeline on nistec and math/big, the r56d-method probe for the unit targets, the two A2-owed confirmations from the 2026-08-13 sweep’s evidence, and the perf suite with the AOT column. The measured numbers below supersede the r58a four-curve table and the r58b/r60 math/big alloc-row measurements; §7’s table carries the same numbers in its A3 column.

The verdict measure. -tests -test-action all -test-timeout 30m on crypto/internal/nistec (explicit -go2cspath, ~8 min wall): terminal results 2,195 matched / 5 divergent of 2,200 — the identical arithmetic to the standing measure, the five being exactly the want-zero TestAllocations rows. The counter’s numbers, against Go’s want of 0:

Curve obj/run (was, r58a) B/run
P224 8,484 (264,540) 746,678
P256 8,528 (242,665) 733,766
P384 12,572 (471,424) 1,198,070
P521 17,090 (870,534) 1,867,811

P256 8,528 ≤ 10,000: the §7 acceptance is MET on the recommended branch — −96.5 % from the bill, and BELOW A2’s approximate mirror (10,105 on the artifact-laden host; L11’s Once/tmpstring/ const-hoist landings plus the clean pinned host account for the direction). The §3.6 branch question stays closed; nothing re-opens.

The residual decomposes to the named classes and nothing else. The r56d-method probe (a scratchpad console probe over the converted fiat/nistec + the golib AllocationCounter; temporary by construction, reverted with the session) reproduces the pipeline at 8,524 obj/run — Δ4 obj / 466 B, the rand.Read substitution, the same gap shape r56d recorded (positive control passed). Phase decomposition of the P256 body:

Phase obj/run B/run
NewP256Point().SetGenerator() 31 2,970
make([]byte, 32) + fill 1 56
ScalarBaseMult 1,871 160,652
ScalarMult 6,485 558,077
Bytes 25 2,128
NewP256Point().SetBytes(out) 48 4,152
BytesCompressed 25 2,096
SetBytes(compressed) 38 3,168
whole-window control 8,524 733,299

98.0 % of the residual sits in ScalarMult + ScalarBaseMult — the new(fiat.P256Element) / NewP256Point temporaries of point Add/Double/table construction (class 3b) and their lazily-materialized array<uint64> backings (class 4), the two Phase-C classes. The remaining ~168 close to the object at the element level: fiat SetBytes = 12 exactly (3 × 3b minusOneEncoding news + 5 backings + in’s kept box 2 + Bytes-chain out kept box 2), fiat Bytes = 3 exactly (out keep 2 + tmp backing 1) — the §6.3/A1-named wrapper keeps, small and named as priced. Classes 1/2/3a measure ZERO: the fiat five unit targets (Mul/Add/Sub/Square/Select, P224 and the P256 SetBytes/Bytes twins) all read 0 B/op, 0 obj/op (were 960 / 960 / 528 / 832 / ~1,344).

Census note, attributed — NOT the A2 host artifact. go test additionally reports TestP256OrdInverse and TestP256PrecomputedTable ((amd64||arm64) && !purego files: the real toolchain exercises its asm implementation’s tests; the converted purego suite doesn’t declare them). The comparison’s census gate records and excludes them — compared maps have go-only = 0 and the terminal arithmetic is identical to the standing 2,195/2,200. Zero go.std.* namespaces anywhere in the run: the A2 std/-prefix artifact did not recur on the repinned GOROOT.

math/big re-measured through the pipeline: 224/226, both alloc rows unmoved. TestMulUnbalanced reads 20,499,128 B vs the 51× budget of 20,416,320 (51.21× on 400,320 B of inputs; r58b read 20,487,208 — +0.06 %, noise), and TestNewIntAllocs reads exactly 1 obj/run on all seven NewInt shapes (want 0 — class 3b, the escape-analysis elision, Phase-C). As §3.6 forecast: nat’s traffic is slice-backed real allocation on both sides; Phase A promised these rows nothing and moved them nothing.

os probe FINDING. os.File.WriteString measures 2,368 B/op (17 golib objects) against §7’s “3,168 unchanged (±0)” claim — moved −800 B, favorably. The 3,168 stamp is r39-era and predates r41’s inline-defer retirement of that decomposition’s 440 B GoFunc/defer term plus everything since, so the claim’s baseline was stale when written; the per-term re-attribution of the −800 belongs to the next os re-instrumentation (AllocMark), not to this stage’s arithmetic.

The two A2-owed validation items, confirmed from the 2026-08-13 sweep’s evidence:

The perf suite — measured, AOT included, on the dedicated machine (2026-08-13; closes the §9 A3 row’s last obligation via HANDOFF-l3-a3-perf.md). The work laptop’s stand-down transferred the run to a solo, sleep-proofed machine: AMD Ryzen 5 PRO 6650U (6C/12T, 30.8 GB), Windows 11 10.0.26200, pinned go1.23.1 (gate zero verified), .NET SDK 9.0.316, MSVC 14.44 link.exe present — the AOT column is measured, not owed. Full run-performance.ps1 --update-readme (default 5-run medians, MSBUILDDISABLENODEREUSE=1): Verify passed 14/14 — identical timing-filtered stdout across Go binary / C# JIT / C# Native AOT, the gate Measure sits behind — total 14,171 s wall, 13,552 s (95.6 %) of it the fourteen sequential ILC publishes. The table banks in src/tests/Performance/README.md (PERF-RESULTS; mirrored to docs/Performance.md), the first to carry the ж-bound RefLower row: Go 226.3 ms · JIT 660.6 ms (2.92×) · AOT 1,827.8 ms (8.08×). The row is the standing README record, not the arc’s gate — the gate was A2’s paired same-machine A/B per §7’s protocol, and cross-table comparison against the replaced i7-5820K table (2026-08-11, 13 rows, same toolchain) is machine effect by construction. The §7-item-4 recording, from the run’s ILC ok (NNNs) lines and the published Perf*\bin\Release\aot\<proj>.exe sizes — every publish succeeded first-try (the work laptop’s exit-1-then-self-heal quirk did not recur, so no retry-inflated time exists anywhere), each exe carrying the full converted-stdlib closure:

Benchmark ILC wall (s) AOT publish size (MB)
PerfStartup 981 296.4
PerfFib 1,085 296.4
PerfSieve 1,094 296.4
PerfMatMul 1,081 296.9
PerfString 972 296.4
PerfStringView 929 296.4
PerfStringMatch 955 302.3
PerfMap 933 296.4
PerfSort 909 297.0
PerfChannel 923 296.4
PerfIfaceCall 936 296.4
PerfIface 921 296.5
PerfIfaceShell 908 296.9
PerfRefLower 925 296.4

Two run notes for the record: the JIT one-shot batch build reported errors that per-project attribution resolved to 0 failed — the known parallel-build race, exactly as the handoff predicted, not a corpus defect. And an observation the A′/B′ checkpoint should see: on this machine AOT trails JIT on the compute-bound rows (RefLower 8.08× vs 2.92×, MatMul 5.02× vs 1.66×, Sieve 3.28× vs 1.61×) while beating it on Startup and Map — the ref-heavy hot loop is the widest such gap, so ILC codegen of the lowered emission is worth a look when B′’s dual-emission increment prices its own AOT bill.

Scout batch 2 — the never-measured/stale tail, 50 packages re-run; 3 bank, 8 roots MOVED (2026-08-14, lane B)

The derivation first, because the denominator is the part boards get wrong. 305 package directories under src/core carry a production .csproj; 216 of them have a func Test in their Go 1.23.1 GOROOT sources; subtracting hand-owned testing (skip-listed, never queued) reconciles that to the roster header’s 215 exactly, and 215 − 136 banked = 79 unbanked. Of the 79, five have no eligible Test declaration on windows/amd64 (internal/runtime/syscall, internal/syscall/unix, net/internal/socktest, log/syslog, runtime/race) and os/user cannot bank at all (Go’s own TestGroupIds fails in the oracle); the named walls (netpoll sockets, scheduler-walled internal/singleflight, go/types, synthetic-GOROOT go/build) and the censuses taken since 2026-08-09 were left to their owners. Fifty packages were run end to end through -tests -test-action all.

Three bank, forty-six are rooted, and one is NOT MEASURED. Roster 136 → 139 (63.3% → 64.7%), 15,137 → 15,181 matching verdicts, 47 disclosed (unchanged).

The three

crypto/internal/hpke (19) · crypto/internal/edwards25519/field (16) · debug/gosym (9)

None needed a change of any kind — each is a capability that landed in the preceding week finally being looked at, which is r43c’s lesson executed for the third time. crypto/internal/hpke was held by the JSON-into-a-lifted-anonymous-struct root (claude/json-fixed-array-unmarshal); crypto/internal/edwards25519/field by the array<T> unshaped-instance class it was recorded as “producer (3)” of; debug/gosym’s TestPCLine by the converted host’s working directory — it shells out to the real Go toolchain to build testdata/, so it closed when the host learned to reproduce a package’s directory ancestry (40beb776e), not merely its shape.

⚠ The measurement hazard this pass paid for twice — MSB4006 is NOT a build root

r44a’s trap #2 recorded that a failed run’s leftover <pkg>.tests.csproj can produce NuGet.targets(1311,5): error MSB4006: circular dependency … "_GenerateRestoreProjectPathWalk". This pass hit a second, sharper form of the same hazard, and the poisoned file is TRACKED: a -tests run rewrites the production .csproj of packages in its closure, and the runtime run rewrites src/core/runtime/runtime.csproj’s windows-conditional ItemGroup to add internal/syscall/windows — which references syscall, which references runtime. That is a real project-path cycle, and every package whose closure reaches runtime then fails to restore. Four packages (runtime, crypto/tls, go/internal/gcimporter, os) were first recorded as “conversion-blocked” this way, which reads exactly like a build root and is not one.

Two rules follow, and the second cost more than the first:

  1. Restore tracked .csproj drift between pipeline runs, not just untracked artifacts, then re-measure. Proof: with runtime.csproj at HEAD and no prior runtime run, crypto/tls compiles and reaches its own diagnostic in 337 s — no cycle. All four were re-measured this way and the three that could be are reported below on their real roots.
  2. Do NOT “clean” by deleting *.tests.csproj wholesale. 136 of them are committed source under the validated-package commit policy. An over-broad prophylactic delete wiped every banked package’s test project in one command; git checkout -- src/core restores it, but a lane that did not notice would have banked the deletion. Scope such a clean to git clean, which by construction cannot touch tracked files — never to a Get-ChildItem | Remove-Item sweep.

runtime itself remains NOT MEASURED: its own -tests run is what writes the cycle, so the package cannot be measured without first fixing that emission. That is the finding, and it is a converter defect — a test-closure-only reference reaching a production .csproj.

Eight roots MOVED — the board’s recorded first diagnostic is stale for these

Package Census Board had Measured 2026-08-14
archive/tar 0 of 97 97 of 97, BANKED CS1537 ×3, duplicate using alias in one file CS0111tar_internal_test_package.fileOps already defines Append. The alias-dedupe root is CLOSED; behind it is the same defect as fmt — ⚠ and that “CLOSED” was wrong: CS1537 was still live and became this package’s next wall once CS0111 fell. See CLOSED for archive/tar below (2026-08-15)
fmt 0 of 63 62 of 63 CS0111 fmt_test_package.SE already defines Append COMPILE WALL CLOSED (2026-08-16, lane claude/fmt-roots) — two converter roots, not four; the host builds and 62 verdicts agree. TestSprintf is the one open verdict, behind three reflect/fmt-bridge divergences. See fmt’s compile wall is CLOSED below
sync/atomic 0 of 108 CS0103 ᏑᏑX, double address-prefix CS0841 — cannot use local magic64 before it is declared. The double- is closed
internal/reflectlite 0 of 30 CS0016 could not write to output file CS0715 — static classes cannot contain user-defined operators: a generic test-local type’s ==/!= emitted into the static reflectlite_test_package
runtime/debug 0 of 9 CS0264, not taken past the first diagnostic CS0264 + CS0715 — the same static-class-operator defect as internal/reflectlite. Two packages, one root
database/sql 0 of 25 CS0029 CS0121 ambiguous stubDriverStmt(NilType) constructor + CS0117 sql_package has no Δtable
crypto/x509 0 of 335 CS0102 duplicate definition in x509_package CS0234 cryptobyte_package / CS0246 hash_package unresolved in the production files under the -tests closure
internal/platform 0 of 1 JSON array into a slice of a lifted anonymous struct panic: nil dereference in text/template’s errRecoverTestGenerated renders its expectation through a template. The lift question may still sit behind it, but it is no longer the failing edge

crypto/tls — 0 of 3,519, and the vendor root is in GENERATED code

The recorded root (“CS0234 'vendor' does not exist — the test half’s vendored import”) is right about the token and wrong about the file. The errors are CS0246 vendor could not be found in go2cs-gen RecvGenerator output — e.g. go.crypto.tls_package.NonceSize.global__go.crypto.tls_package.prefixNonceAEAD.g.cs(17,7). The generator emits a receiver overload whose parameter type names the GOROOT-vendored root namespace, so this is an emission question in the analyzer, not an import list. It is the largest verdict count on the unbanked list by a factor of six.

Re-baselines this pass owes the board

Reproduced verbatim — no re-measure owed until a relevant capability lands

go/doc 24 of 85 · encoding/gob 99 of 106 · crypto/cipher 13 of 14 · debug/pe 9 of 10 · html 2 of 3 · internal/trace/internal/oldtrace 2 of 3 · internal/weak 1 of 3 · log/slog/internal/buffer 1 of 2 · iter 0 of 28 (newcoro/coroswitch stubs) · runtime/trace 0 of 2 (getg stub) · internal/concurrent 0 of 20 (CS0426 node<,>) · debug/elf 0 of 31 (CS8183 at file_test.cs(1195,5)) · os/exec 0 of 22 (CS0103 var) · text/template 0 of 52 and html/template 0 of 243 (CS0030 SI) · slices 0 of 122 (CS0305/CS0411) · encoding/xml 0 of 386 (CS0426 ΔToken) · encoding/json 0 of 491 (CS0050/CS0053)the compile wall is CLOSED end to end and the package RUNS: 400 of 491 matching, 91 divergent in four named roots (2026-08-16) · net/netip 0 of 266 (CS1002/CS1525) · internal/trace 0 of 92 and runtime/pprof 0 of 174 (CS0149 Method name expected; pprof also still CS0103 ᏑᏑsalts) · internal/runtime/atomic 0 of 15 (CS0103 ᏑᏑx) · flag 0 of 24 (CS1929 on ж<flag_test_package.URLValue>) · crypto/ed25519 0 of 9 (CS0030 PrivateKeycrypto.Signer) · crypto/internal/mlkem768 0 of 11 (CS0315, plus CS0841) · reflect — the converter itself still fails on all_test.go (1e+06 not an Int).

Four packages measured for the FIRST time — no prior board row of any kind

Package Census Root
net/http/pprof 5 of 15 TestHandlers fails and seven of its subtests infrastructure-error (/debug/pprof/{heap,mutex,trace,profile,block,goroutine}); TestDeltaProfile skips where Go passes. Profile collection has no managed body — sibling of runtime/pprof’s and runtime/trace’s stubs
internal/godebug 3 of 5 TestCmdBisect and TestMetrics fail; TestBisectTestCase and the rest pass. The package is fully hand-owned, so this is the hand-own’s own contract being measured for the first time
internal/syscall/windows 0 of 2 TestRunAtLowIntegrity, TestSupportUnixSocket — both fail. This is also the package whose -tests run participates in the MSB4006 cycle above
crypto/internal/boring/bcache 0 of 1 NotImplementedException: registerCache: external (assembly or cgo) function is not implemented — a PartialStubGenerator stub reached from Register[K,V]. The board had only “first divergent verdict recorded; not root-attributed”

net/mail gets its first real census too — 7 of 11, the four TestAddress* rows infrastructure-erroring on System.ArgumentException: Indices low, high and max represent a range outside bounds of the array reference — where the board had only “first divergent verdict recorded”. And go/internal/srcimporter (0 of 7) fails before any test with flag provided but not defined: -json, i.e. the process the host launches is not the go2cs test host; that is an infrastructure root, not the recorded build block. ⚠ The last clause of that diagnosis is CORRECTED (2026-08-14, lane claude/defect-batch-1). The process IS the go2cs test host. internal/fuzz reaches the identical symptom once its build blocker is cleared, and there the cause is exact: worker_test.go’s TestMain calls flag.Parse(), and the converted flag.CommandLine has never been told about the host’s own --json / --result / --junit / -timeout arguments, so parsing rejects them before any test runs. In Go, testing.M registers those flags on flag.CommandLine before TestMain executes, which is what makes the same flag.Parse() legal there. The remedy belongs to the hand-owned src/core/testing host — register its flags on the converted flag.CommandLine — and is a separate, unclaimed item. Every package whose TestMain calls flag.Parse() sits behind it.

REMEDIED 2026-08-14 (lane claude/testmain-flag-bridge) — src/core/testing/TestFlagBridge.cs, host-only, zero converter change. The host now DECLARES its own command line on the converted flag.CommandLine before it invokes a converted TestMain, which is precisely what testing.Init() does for -test.* and precisely what was missing. Four things the implementation had to get right, each measured rather than assumed:

  1. The whole -test.* set is registered, not just the spellings the host was given — with this run’s real values (test.run-run, test.parallel-parallel, test.v, test.short, test.count, test.timeout, test.shuffle; the rest at Go’s defaults), because converted tests READ them: os/exec’s TestMain gates on flag.Lookup("test.run").Value.String() == "" and flag.Lookup("test.list"), and runtime’s gdb tests do flag.Lookup("test.parallel").Value.(flag.Getter).Get().(int). Registering only what appeared on the command line would have traded a parse error for a nil dereference. For the same reason the TYPED registrars are used and not flag.Func/BoolFunc, whose funcValue has an empty String() and is not a Getter.
  2. The flag package is bound LATE, by name, not by project reference. Go’s testing imports flag, so the reference is the obvious mirror — and it was tried and MEASURED and it does not work here. The generated test csproj sets DisableTransitiveProjectReferences=true (load-bearing: CS0576 against the emitted using aliases), so a testingflag reference does not deploy flag.dll beside the 124 of 141 test hosts whose own package does not import flag — an unconditional use would FileNotFoundException every one of them. It also cost every test project’s build +33% (unicode/utf8: 7.5 s warm → 10.2 s). Late binding is the accurate statement of the dependency, not a dodge: the converted flag package is in a test compilation iff the package under test imports it, which is exactly when a converted flag.Parse() is reachable and flag.CommandLine observable at all. Only the flag_package TYPE is resolved by name; every argument type (@string, nint, nuint, time.Duration) is a golib/time type the host already references.
  3. A name the test package already defined is skipped — the converted FlagSet.Var PANICS on redefinition, and the host must not turn a package’s own flag into a crash. Only the host’s unprefixed spellings can collide (Go’s test. prefix exists to make collision impossible), and across all of GOROOT’s non-cmd test sources exactly one such definition exists (-v, in cmd/compile/internal/ssa, which is not converted).
  4. Scope boundary, deliberate: Go’s M.Run also calls flag.Parse() when it is not yet parsed. That is NOT mirrored — no class member needs it (every one’s TestMain parses explicitly), and an unconditional parse would newly reach ExitOnError/os.Exit(2) for the sixteen banked packages that merely reference flag. Registration alone is the minimal change that closes the class.

Class census (GOROOT non-cmd test sources calling flag.Parse(), per package): internal/fuzz, go/internal/srcimporter, os/exec, crypto/tls — four members. (runtime and syscall match a naive grep and are NOT members: runtime/runtime-gdb_unix_test.go’s two hits are inside a Go source STRING literal for a helper program and the file is unix-only; syscall/syscall_unix_test.go’s is in a unix-only re-exec helper. flag/example_test.go and database/sql/example_cli_test.go are Example bodies.) Where the four stand after the bridge:

Package Before After Note
internal/fuzz 0 (died at flag.Parse) BANKED 52/52 the 141st roster row; its TestMain now parses the host’s command line
go/internal/srcimporter 0 of 7 (died at flag.Parse) 5 of 7 not banked; the two failures share ONE root and it is not this class — see below
os/exec build-blocked builds; 22 of 22 match the unnamed-variadic block is FIXED (lane claude/unnamed-variadic, 2026-08-14) — 40 further tests are excluded for want of TB.*, see that section. That exclusion is CLOSED too (lane claude/tb-surface, same day): the 22 became 101 rows, 48 agreeing, and what was behind it was two HOST roots — see the TB.* section at the end of this file
crypto/tls not measured builds; blocked at RUN reached 2026-08-14 by the same lane; two roots, neither this one. Both closed 2026-08-15 (lane claude/crypto-tls) along with a third behind them — the package now compiles and its host reaches the flag bridge, where a different wall stops it: see the section at the end of this file

go/internal/srcimporter — 5 of 7, one root, and it belongs to go/types. TestIssue20855, TestIssue23092, TestIssue24392, TestReimport pass and TestCgo skips identically. The two failures — TestImportStdLib and TestImportedTypes — both die inside the converted go/types checker on the SAME construct: internal/syscall/windows/version_windows.go:87’s call to sync.OnceValue(func() bool {…}) reports “in call to sync.OnceValue, cannot infer T” / “cannot use (func() bool literal) (value of type func() bool) as func() T value”. That is generic type-parameter inference from a func-literal argument, failing in the converted checker where Go’s succeeds — a third dependent of the unbanked go/types row, alongside internal/types/errors and go/internal/gcimporter’s 184. srcimporter type-checks GOROOT from source, so every package whose import graph reaches os inherits it, which is why both failures name a different top-level package and the same innermost cause.

os/exec — still build-blocked, and the root is now exact. Three CS0103: The name 'var' does not exist in the current context, all one shape: an UNNAMED variadic parameter. exec_test.go declares func cmdPipeTest(...string), func cmdStderrFail(...string) and func cmdStdinClose(...string) — Go permits a parameter with no name at all — and the converter emits the variadic unpacking with an empty variable name: internal static void cmdPipeTest(params ꓸꓸꓸstring ʗp) { var = ʗp.slice(); … }. The parameter itself is named fine (ʗp); it is the unpacked local that inherits the absent Go name. A one-line converter fix (emit no unpacking, or a discard, when the Go parameter is unnamed) puts os/exec behind the bridge instead of in front of it. ⚠ FIXED 2026-08-14 (lane claude/unnamed-variadic) — and it was not one line or one site. The prologue is emitted at TWO sites (visitFuncDecl and convFuncLit), and the blank (_ ...T) spelling is a third symptom that COMPILED and so was invisible here. os/exec builds clean now. See the unnamed-variadic build block is fixed below for the fix, the zero-mover census, and where os/exec actually stands.

The prize left on the table

Two packages, one defect: CS0715 — a generic test-local type’s ==/!= operators are emitted into the static <pkg>_test_package partial class, which C# forbids. It holds internal/reflectlite (30 verdicts) and runtime/debug (9). And CS0111 Append holds fmt (63) and archive/tar (97) — 160 verdicts on one duplicate-member emission. The Append half is spent — see the two entries below: 14bf20010 closed CS0111 itself, and claude/dup-append-emission cleared the two walls behind it in archive/tar, which banks 97/97. fmt did not follow; its five remaining roots are censused below. CS0715 is now the cheapest remaining pair on this list.

✅ CLOSED for archive/tar (banks 97/97) — two walls behind the Append root; fmt does not follow (2026-08-15, lane claude/dup-append-emission)

The brief was stale, and that is the first finding. The CS0111 Append root this lane was sent to take had already been fixed by 14bf20010 (2026-08-14) — a named []any slice wrapper’s non-generic ISlice.Append(object[]) became an EXPLICIT implementation, guarded by NamedAnySliceType — but that commit did not update this board, so the “eight roots MOVED” table and The prize left on the table above both still named it. Re-measure before believing a board row; the row above is now struck through rather than deleted so the same mistake is not made twice.

Behind it, archive/tar had two more walls, both general converter defects, both fixed here:

  1. CS1537 ×3 — a function-LOCAL type declaration emitted as a COMPILATION-scoped global using. The board’s “eight roots MOVED” table recorded tar’s alias-dedupe root as CLOSED; the NamedAnySliceType lane reported it as still live, and it was. Root: every local type-declaration kind (struct, interface, slice, map, channel, pointer, named-ident) takes liftLocalTypeDecl — enclosing-function prefix, ᴛN uniquification, liftedTypeMap registration — except the branch that emits a using ALIAS, which is what a real type X = Y and a defined-over-named-interface type X any both take. A global using is scoped to the whole compilation, so type testFnc any in writer_test.go’s TestWriter and TestFileWriter, and again in reader_test.go’s TestFileReader (with fileMaker alongside), claimed one alias name. Fix: the naming half of liftLocalTypeDecl is factored to liftLocalTypeDeclName and the alias branch calls it in-function, with the reference mapping registered only when the declaration’s own object IS the type (liftedTypeDeclaredBy — keying it on the target would rename every Header, or every int, in the file). Zero production emission impact by construction: an AST scan of Go 1.23.1 finds no function-local alias-or-defined-over-interface declaration in any compiled stdlib file — all 50 hits are internal/types/testdata. Guard: LocalTypeAliasScope (the unfixed converter emits five duplicate global using lines).
  2. CS0121 ×9 — an ambiguous one-field nil constructor. testClose{nil} emitted new TestWriter_testClose(default!); the typeless default! converts to both generated one-argument constructors, T(NilType) and T(error field = default!). The argument now carries the field’s type. Narrow by construction — Go requires a positional literal to list every field, so only a one-field struct has the arity, and only nil renders typeless. database/sql’s recorded stubDriverStmt(NilType) CS0121 is the same root and should be re-measured.

archive/tar census: 97 rows, 97 agree, 0 disagree, 0 skipped, 0 disclosed, 3 excluded (the standard Phase-4D Benchmark/Example/Fuzz deferrals). Banked — roster row, proof page, committed test sources.

A third defect this work found and did NOT fix (chip raised, reported here for the record): an alias whose target is an unnamed composite emits its type ARGUMENTS unrooted — type names = []stringglobal using names = go.slice<@string>;, where @string, a nested slice, error, complex64, a same-package Header and a foreign io_package.Reader all arrive bare and do not resolve at compilation scope. It is package-level and pre-existing, unrelated to the scope fix, and no converted stdlib package declares such an alias — but any converted user module would. It surfaced only because the LocalTypeAliasScope guard was first written with such an alias in it.

LANDED 2026-08-16, lane claude/emission-defects. The alias RHS now renders in a ROOTED-NESTING mode: the target and every name it nests carry full qualification, with four qualifiers that are not interchangeable (go. for golib types, System. for the BCL Func/Action, go. again for the variadic Actionꓸꓸꓸ/Funcꓸꓸꓸ family, and go.<ns>.<pkg>_package. for a same-package name), while the csproj-alias names (uint64, any, …) are SUBSTITUTED rather than rooted — they are not members of go at all. Two shapes beyond this row’s census turned out to be in the same class and are fixed with it: a func-type alias (Func/Action need System., which is no more in scope than go) and an alias whose target is itself an alias (a C# using alias may not name another). The census re-verification confirms this row’s “no converted stdlib package declares such an alias” for the type-ARGUMENT arm — the corpus holds exactly four package-level aliases with type arguments (fiat’s p224/p256/p384/p521, each [4]uint64), all four taking a C# keyword as the argument, so that arm moves nothing. The substitution arm did have corpus sites the row did not predict, and they were live CS0234: a csproj-alias name standing as the WHOLE target was rooted (global using _C_int = go.int32;int32 is a <Using Alias=…> for System.Int32, not a member of go, and the existing safe-name sweep skips dot-qualified names by design). Six sites, all cgo _C_* typedefs in darwin-exclusive files (os/user/darwin/, net/darwin/), latent because the default $(GoTargetOS) is windows and nothing compiles them; they now emit int/uint/long. Guard: PackageAliasRootedTypeArgs (25 aliases, output-compared; the unfixed converter fails it with CS0246 and a golden mismatch). Detail: docs/ConversionStrategies-Reference.md §Type Alias Declarations, “The whole RHS is namespace-ROOTED”.

fmt — still 0 of 63, and it is FIVE roots, not one

Measured 2026-08-15 with both fixes above in place. Neither touches fmt: it has no function-local alias declaration and no one-field nil literal. The Append root is genuinely gone; what is behind it is a fan-out, not a queue, and three of the five cluster on one test type:

Diagnostic Site Shape
CS1955 non-invocable map<TKey, TValue> FIXED fmt_test.cs(838) map[int]byte(nil) — a CONVERSION to a map type emitted an INVOCATION, map<nint, byte>(default!), not a cast. LANDED 2026-08-16, lane claude/emission-defects: an untyped-nil operand against a map-underlying type LITERAL is now claimed by isTypeConversion (the identical-underlying guard rejected it, since untyped nil’s underlying is itself) and the ordinary renderer casts — ((map<nint, byte>)default!). The row’s “one emission rule, two spellings” reading of the sibling []int(nil) was measured and does not hold: slice<T>(default!) binds builtin.slice<T>(T[]), golib’s REAL conversion helper — the same one []byte("…") is emitted against, documented as such — and yields the nil slice, so it is correct rather than lucky, and (chan T)(nil) already cast. Both are deliberately left on their existing routes; claiming them would rewrite ~25 corpus sites to no effect. Census, by SPELLING: the BARE map[K]V(nil) — the broken one — has 0 stdlib production sites and 13 test-file sites (fmt, reflect, encoding/json, internal/reflectlite), which is why the corpus never showed it; the PARENTHESIZED (map[K]V)(nil) reached the fork via convParenExpr and was already casting, so its single production site (reflect/type.go:1837) compiled all along and now only re-parenthesizes. Total corpus footprint: one line. Guard: UnnamedMapNilConversion. Detail: docs/ConversionStrategies-Reference.md §Canonical typed-nil pointer boxing, “A nil converted to an unnamed MAP type is a cast”
CS0030 floatrenamedComplex64, doublerenamedComplex128 ×4 fmt_test.cs(941,942,951), scan_test.cs(394) a named type over complex64/complex128 does not admit the real→complex conversion Go allows
CS1729 Scan_type has no 1-arg constructor scan_test.cs(576)
CS0103 Reader does not exist ImplementGenerator output for Scan_typeio.Reader the generated wrapper names the interface unqualified
CS0034 == ambiguous on Scan_type same generated file

The last three are one type’s story and are likely one root; the first two are independent. This is a materially different proposition from the 97-verdict single-wall archive/tar half — the “160 verdicts on one defect” framing above never held for fmt.

Root 1 of the five is closed (2026-08-16, lane claude/emission-defects — see the struck row). fmt’s wall is now FOUR roots; the remaining four are untouched by that lane, so the package’s verdict count is unchanged until they are worked. Whoever picks fmt up next should re-measure rather than assume this row’s sites, since the CS1955 no longer masks whatever follows it in fmt_test.cs.

✅ CLOSED — the unnamed-variadic build block is fixed; os/exec now BUILDS and its next wall is TB.* (2026-08-14, lane claude/unnamed-variadic)

The one-line prediction above was right about the root and wrong about the size: the defect has two emission sites and three symptoms, and the probe found the third.

The fix. visitFuncDecl and convFuncLit each emit a variadic parameter’s unpacking prologue (var <name> = <name>ʗp.slice();). Both now skip it when the Go parameter is UNNAMED or BLANK, via one shared predicate, variadicParamIsUnreferenceable. Either spelling is unreferenceable under Go’s own rules, so the local is dead by construction — and this is the same ruling, for the same reason, that an unnamed/blank POINTER parameter’s deref alias already takes (it would otherwise emit ref var = ref Ꮡ.Value;). The A2 rebank note’s precedent chose skip, so this matches it rather than synthesizing a name. A NAMED variadic still unpacks; the signature is untouched in every case, keeping its params array under its own ʗp name and simply leaving it unread.

Three symptoms, not one. (1) UNNAMED at a top-level func or method — var = ʗp.slice();, the empty identifier the board recorded. (2) UNNAMED inside a FUNCTION LITERAL — worse, and previously unrecorded: the literal’s signature builder normalizes the absent name to _ and declares params ꓸꓸꓸnint _ʗp, while the prologue kept rendering ʗp from the raw name, so the dead local carried an empty name AND a name the signature never declared. (3) BLANK (_ ...T) anywhere — var _ = _ʗp.slice();, which compiles, so it was invisible, but declares a REAL local named _ (a plain var _ = e; declaration is a variable, not a discard) that then hijacks every _ = … discard in the body — the CS0029 class bodyUsesBlankDiscard exists to prevent for a blank parameter name.

Corpus movers: zero, and that is measured, not assumed. An AST census of GOROOT (production + tests, cmd and testdata excluded) finds five sites total. Exactly one is production — syscall/syscall_linux.go’s func cgocaller(unsafe.Pointer, ...uintptr) uintptr — and it is bodyless (a //go:uintptrescapes linkname target, emitted internal static partial uintptr cgocaller(@unsafe.Pointer _Δp0, params ꓸꓸꓸuintptr ʗp);), so it has no prologue to skip on any target, Linux included. The other four are all os/exec test sources: exec_test.go’s cmdPipeTest/cmdStdinClose/cmdStderrFail and exec_posix_test.go’s cmdPwd. CNR agrees: one changed file across 601 behavioral packages, and it is the guard test itself.

Guard: UnnamedParams extended (not a new project — it is already the unnamed/blank parameter guard, and the pointer precedent this follows lives there). It now pins all three shapes — unnamed, blank, and a NAMED control that IS read — at declaration, method and function-literal positions, output-compared vs go run.

os/exec — the build block is GONE; 22 of 22 run, 22 of 22 MATCH, and the next wall is named

The pipeline (-tests -test-action all -test-timeout 30m) now builds os/exec with zero compile errors, and the three helpers emit exactly as Go declares them — internal static void cmdPipeTest(params ꓸꓸꓸstring ʗp) { with no prologue, registered as method groups (["pipetest"u8] = cmdPipeTest,) into the converted map[string]func(...string).

It is not bankable, and the reason is not os/exec and not the host environment:

Class Count What it is
ran and matched Go exactly 22 19 pass + 3 skip; the go and csharp verdict maps are identical, including the three Windows skips (TestExtraFiles, TestPipeLookPathLeak, TestString)
excluded — TB.Fatal unsupported 26 the roster in supportedTestCapabilities carries the whole *testing.T surface but no TB.* member at all; os/exec’s tests funnel through exePath(t testing.TB), which every helperCommand call reaches
excluded — Phase-4D deferral 14 13 ExampleXxx + 1 BenchmarkExecHostname; a standing pipeline policy, not an os/exec fact

The run still exits 1, and that too is not a divergence: with the 26 TB.Fatal tests excluded, the helper commands they drive go unused, and os/exec’s own TestMain self-audit fails a run that leaves a registered helper unused (helper command unused: "echo", …) — on both sides. So the honest verdict is that os/exec has no measured behavioral divergence at all; it has 40 tests that never ran. The process-spawn shapes the board expected to wall on host semantics (TestEcho, TestCatStdin, TestPipes, TestStdinClose, TestExitStatus, TestWaitInterrupt, TestContextCancel, …) are all in the TB.Fatal bucket — excluded, never executed — so nothing is yet known about how they behave. Naming TB.* support in the hand-owned core/testing host is the exact next domino, and it is a host change with no converter component.

crypto/tls — measured for the first time: BUILD-BLOCKED on two roots, neither of them this one

The fourth flag-bridge class member, pipelined once (93 s) to record its outcome class. It does not reach the bridge — it does not build. Four errors, two roots, both new board entries:

No deep chase was commissioned and none was done.

BOTH ROOTS CLOSED, and a third behind them — crypto/tls BUILDS (2026-08-15, lane claude/crypto-tls). Re-measured at master 6dd7547e4 before anything was believed, per the stale-premise rule: both reproduced verbatim, same codes, same four call sites. This board entry was accurate. Its attribution of the first root was not — see the section at the end of this file.

RETRACTED — os’s “REGRESSION” is a HOST CAPABILITY, and the killer is SHARE_INFO_2 (2026-08-14, lane os-av-bisect)

Scout batch 2 flagged os as a REGRESSION — 31 of 679 measured against this board’s 681 of 683, the converted host dying with 0xc0000005 after 32 verdicts — and sent it to a bisect lane under the unique precedent. There is no culprit commit. The access violation reproduces, frame for frame, at a936c8025 (r39-osalloc) — the very commit whose run recorded the 681. What moved is the HOST, not the tree.

The killer, named. One test: TestNetworkSymbolicLink. The goroutine-panic containment from the jsonrpc chip is what makes it nameable — the fault now arrives with a managed stack instead of a bare exit status:

Fatal error. 0xC0000005
   at go.syscall_package.syscalln(UIntPtr, System.ReadOnlySpan`1<go.uintptr>)
   at go.syscall_package.SyscallN(go.uintptr, System.Span`1<go.uintptr>)
   at go.syscall_package.Syscall6(...)
   at go.internal.syscall.windows_package.NetShareAdd(go.ж`1<UInt16>, UInt32, go.ж`1<Byte>, go.ж`1<UInt16>)
   at go.os_test_package.TestNetworkSymbolicLink(go.ж`1<T>)

It is the syscall STRUCT-PASSING seam — the open class this file already carries — reached for the first time by a real suite, and reached in the direction that class had not yet shown: not the kernel WRITING a native record over a smaller managed one, but the kernel READING a managed record as a native one and dereferencing a value field as a pointer. That shape faults AT the call, not arbitrarily far away.

The mechanism, measured rather than argued. internal/syscall/windows.SHARE_INFO_2 holds four ж<uint16> pointer fields and four uint32s. The CLR auto-layouts a struct containing references, so the references are grouped FIRST — dumped by reflection from the built internal.syscall.windows.dll, the record is 48 bytes against the native 56:

native SHARE_INFO_2 (x64) native off C# storage actually at that offset value handed to netapi32
LPWSTR shi2_netname 0 Netname (object reference) a managed reference, read as runes
DWORD shi2_type 8 low half of Remark (nil) 0
LPWSTR shi2_remark 16 Path (object reference) a managed reference
DWORD shi2_permissions 24 low half of Passwd (nil) 0
DWORD shi2_max_uses 28 high half of Passwd (nil) 0
DWORD shi2_current_uses 32 Type 0x40000000
LPWSTR shi2_path 40 MaxUses (=1) then CurrentUses (=0) 0x0000000000000001
LPWSTR shi2_passwd 48 past the end of the 48-byte record whatever follows on the heap

netapi32 dereferences shi2_path — the pointer value 1 — and the process dies. shi2_passwd is a second, independent defect in the same call: an 8-byte over-read past the managed record.

Proven without go2cs. A standalone C# program calling netapi32!NetShareAdd three ways, on this host:

Buffer Result
A — blittable [StructLayout(Sequential)] record with real LPWSTRs rc=0, the share is genuinely created, NetShareDel rc=0
B — object references at the NATIVE offsets survives, rc=123 (ERROR_INVALID_NAME) — an object reference is a readable address, so this alone is not fatal
C — the MEASURED go2cs layout (refs 0/8/16/24, uints 32/36/40/44) Fatal error. 0xC0000005, exit -1073741819

So the fault is not “a managed reference where LPWSTR belongs”; it is the field REORDERING that puts an integer 1 under shi2_path. B is the control that makes C mean something.

Why the board’s 681 held and this host’s run does not. Go’s own test treats exactly two NetShareAdd failures as a skip — ERROR_ACCESS_DENIED and NERR_ServerNotStarted (2114) — and on a host where either fires, netapi32 never reaches the buffer, both sides skip, and the row AGREES. On this host neither fires: the session is elevated, LanmanServer is Running, probe A creates a real share, and Go’s own TestNetworkSymbolicLink PASSES. The buffer is therefore marshalled, and the layout defect becomes fatal. The 681 was measured on the i9 that died 2026-08-09; it cannot be re-measured there, and nothing about that record needs to be doubted — it needs a precondition written down, which is what this entry is.

The record survives — control run, HEAD, one test excluded. With -run '^(?!TestNetworkSymbolicLink$)' and nothing else changed, the host runs the suite to completion: 683 verdicts — 659 pass, 21 skip, 2 test failures, those two being TestUTF16Alloc (the recorded alloc-count-semantics disclosure) and TestWriteStringAlloc (the ONE real residual r39-osalloc rooted and left as an architectural arc). That is the recorded shape exactly. TestNetworkSymbolicLink is the sole host-killer; there is nothing behind it. (C#-side census only — no differential was run, and the skip count differs from the recorded 34 because a more-capable host skips fewer tests, which is the same host-capability fact stated from the other side. Do not read 683 here and 683 on the record as the same denominator.)

Verdict for the roster and the board.

The census this finding corrects. The struct-passing census above is scoped to src/core/syscall and therefore could never have listed this member. internal/syscall/windows is a SECOND package holding the same class, and its own census is:

Wrapper Non-blittable struct Reached by
NetShareAdd SHARE_INFO_2 (Netname, Remark, Path, Passwd) os’s TestNetworkSymbolicLink — the only caller in all of GOROOT; fatal on a capable host
GetAdaptersAddresses IpAdapterAddresses (nine ж<T>, array<byte> PhysicalAddress, array<uint32> ZoneIndices) ROW WITHDRAWN 2026-08-17 — it was never a member of this census. The wrapper is handed a byte BUFFER and fills it, which is what a byte buffer is for; it is correct and stays auto-converted. The defect is entirely in the CALLER, net.adapterAddresses, which reinterprets the filled buffer as the record — the readReparseLink / dir_windows_impl fork, not the mirror-the-wrapper one. Fixed in the net interfaces arc; see the entry at the end of this file
Module32First / Module32Next ModuleEntry32 (array<uint16> Module, array<uint16> ExePath) syscall’s own suite
GetFileInformationByHandleEx FILE_ID_BOTH_DIR_INFO / FILE_FULL_DIR_INFO (array<uint16> names) os’s readdiralready answered, and it is the worked precedent: src/core/os/windows/dir_windows_impl.cs reads the kernel’s buffer at NATIVE offsets instead of reinterpreting it as the managed surrogate
WSASendMsg / WSARecvMsg WSAMsg (ж<syscall.WSABuf>) net’s UDP OOB path
NetUserGetLocalGroups ж<ж<byte>> out-buffer os/user

Why this one is NOT fixed here, and what the candidate remedies cost. The established remedy is a hand-owned wrapper with a blittable mirror and a field-for-field copy at the boundary. It does not reach this member, because the wrapper never sees the struct: os_windows_test.go writes (*byte)(unsafe.Pointer(&p)), which the converter emits as Ꮡp.Reinterpret<windows.SHARE_INFO_2, byte>(), and Reinterpret correctly REFUSES to alias a reference-bearing struct as byte — so it falls to (ж<byte>)(uintptr)box and the wrapper receives a NATIVE-address box with the managed identity already gone. There is nothing left to copy from.

  1. Recover the struct by reading the raw address (Unsafe.Read<SHARE_INFO_2>) inside a hand-owned NetShareAdd. Rejected: it fabricates managed references out of a raw address, which ж.PointerExtensions.cs names as a CLR type-safety break and “strictly worse than the wrong-but-contained read the address route produces”. It would also rest on a pin the address route does not promise.
  2. Hand-own NetShareAdd to fail by name — a manualConversionFuncs placeholder returning a declared “non-blittable struct handed to the kernel” error, the registerCache-style announce-itself stub. Small, zero blast radius (one caller in GOROOT, and it is a test), and it converts a whole-suite process death into ONE loud row. But Go PASSES this test on a capable host, so the row would be a real mismatch rather than a skip, and the stub declares a capability limit — a coordinator ruling, not a lane’s call. Recommended, pending that ruling.

    RATIFIED and LANDED 2026-08-14 (lane claude/defect-batch-1). The coordinator ruled for remedy 2; the wrapper is displaced through manualConversionFuncs and reimplemented in src/core/internal/syscall/windows/windows/zsyscall_windows_impl.cs, throwing a NotSupportedException that names the capability limit, the layout mechanism and this entry. The impl file’s header carries the six same-shape wrappers above as a table, so the boundary of the limit is readable where the limit is declared. The value is measured, on the capable host that produced this entry: os no longer dies at test ~32 of 174 — it runs the suite to completion and the differential is 679 matched of 683, with exactly four divergent rows: TestNetworkSymbolicLink (this declared limit, an honest mismatch — Go passes it), TestUTF16Alloc and TestWriteStringAlloc (the recorded alloc-count disclosure and the r39-osalloc architectural residual), and TestDirectorySymbolicLink, which SKIPS where Go passes and is not attributed here. Compare the pre-fix floor on the same host: 31 of 679. os still does not bank, for the reason it never did.

  3. The durable answer is the ж-box arc’s: have the non-aliasing Reinterpret fallback retain the source object so a hand-owned wrapper can reach it, at which point remedy 1 becomes an ordinary field-for-field copy with no fabrication.

Whichever lands, verify at VALUE level as the class demands: probe A above is the oracle — the share must actually be created and NetShareDel must remove it.

✅ CLOSED 2026-09-02 (lane claude/sub-os-row) — remedy 3, and the root was one level deeper than remedy 1’s rejection

NetShareAdd is implemented. TestNetworkSymbolicLink agrees with Go on this host, and the declared capability limit is retired: os moves from 682 of 685 agreeing + 1 disclosed with TWO real divergences to 683 of 685 + 1 disclosed with ONE. It still does not bank, for the reason it never did — TestWriteStringAlloc, ruling #1 — and that row is now the only thing between os and a bank.

The root, one level under what this entry recorded. The entry above says the wrapper “receives a NATIVE-address box with the managed identity already gone”, and left it there. But the address route HAS a recovery seam — the provenance record — and zsyscall_windows_certchain_impl.cs already uses it to recover a reference-bearing struct at a boundary wrapper (ManagedPointerTokens.Resolve(scalar) is ж<SSLExtraCertChainPolicyPara>). So the obvious first move was to reuse it here, and it does not work — for a reason worth writing down, because it is what separates the two shapes:

Resolve VALIDATES ON READ — alive and still pinned there. RegisterPinned is called by the uintptr operator either way, but EnsureStableAddress can only pin when PinnableStorage is non-null, and a StandardBox of a reference-bearing pointee has no m_slot at all (a reference-containing layout cannot be pinned), so PinnableStorage is null, no pin is taken, IsPinnedAt answers false and Resolve answers null. The certchain scalar resolves because it came from MintOpaque, which registers by the box’s PointerOrderToken — a stable identity, not an address. Two different registration routes; only one of them has a key that survives.

Measured, not argued, in a standalone probe against golib with a reference-FREE control on the same run: a filled reference-bearing StandardBox reports PinnableStorage is null = True, Resolve(own address) = NULL and Resolve(derived address) = NULL, while the reference-free control resolves on both. (Probe hygiene note earned the hard way: the first arm used a default-valued record, whose box reads as a nil pointer, so Reinterpret returned NilBox at address 0 and both NULLs were vacuous. A reading taken through a nil pointer is not a reading — fill the record.)

The remedy — remedy 3, narrowed. PointerExtensions.Reinterpret’s unpinnable arm now REMEMBERS its source box against the DERIVED box, in a ConditionalWeakTable beside s_mintedReferents (ManagedPointerTokens.RememberReinterpretSource / ReinterpretSource). Keyed on the box rather than on a number, so there is nothing to validate and nothing to go stale. It is purely additive: the derived pointer’s numeric value and box kind are byte-for-byte what they were, and only a caller that asks by name sees anything new — asserted as its own guard rather than claimed.

The gate is IsReferenceOrContainsReferences<T>() && !IsReferenceOrContainsReferences<TDst>(), and the DESTINATION half is the load-bearing one: a reference-bearing destination is Go’s prefix-downcast idiom — reflect’s (*structType)(unsafe.Pointer(t)) over an abi.Type, which is unpinnable too and is hot — and it neither needs the source nor should pay for it. A reference-free destination is the boundary idiom (*byte)(unsafe.Pointer(&record)), whose whole purpose is to cross into a syscall. Corpus census at the time of writing: ~30 Reinterpret<…, byte> sites, all in syscall/os/ net boundary code, all cold; reflect’s downcast pairs are not among them.

With the source in hand the hand-own is the ORDINARY mirror-and-copy this class has always used — a [StructLayout(Sequential)] NativeShareInfo2 local, size-asserted against the documented 56, allocUtf16z for each LPWSTR, freed in a finally because SHARE_INFO_2 is input-only. Nothing is fabricated out of a raw address, which is what made remedy 1 unacceptable: the real managed record is in hand before a byte is transcribed. Two paths still THROW rather than guess — a level other than 2 (the buffer’s shape IS the level), and a level-2 buffer with no recoverable source (a genuinely native record; no such caller exists in the corpus, and passing the scalar through — which is right in the certchain wrapper, where an unrecognized scalar IS an address — is exactly the fatal path here).

Verified at VALUE level, as this entry demanded. The oracle is Go’s own test, which does not merely call the wrapper: it Stats the share through its UNC path, requires os.SameFile agreement with the local directory, creates a symlink INTO the share, reads it back with os.Readlink and resolves it with filepath.EvalSymlinks — and its deferred NetShareDel is a t.Fatal. A mirror with wrong offsets cannot pass that; the share must really be created and really be removable.

Gates. No converter source changed, so no CNR and no two-seeded diff is owed. GolibTests 480/480 (474 before, +6 new guards; no Test Run Aborted, and the 485-vs-480 declared gap is the two Linux-only test files, pre-existing). The retention guard’s negative control was run: deleting the destination half of the gate fails exactly AReferenceBearingDESTINATIONIsNotRemembered and nothing else, and the restore is byte-identical (sha256 verified). internal/syscall/windows builds 0 errors. os pipeline re-run at Debug/tiered against a go1.23.12 oracle, on an elevated host with LanmanServer Running and symlink creation available — i.e. a host where Go’s own TestNetworkSymbolicLink PASSES, which is the precondition this entry exists to state.

What this leaves for the class. The four sibling wrappers in the table above are unchanged and still take the ordinary mirror remedy. What has changed is that the byte-reinterpret fork of the class — a record reaching its wrapper as a *byte with no typed pointer anywhere — is now repairable at all, where before it had only a declared limit. That is the ж-box arc’s B1 input satisfied for this shape without the arc: source retention was the missing half, and it turns out to cost a weak table entry at ~30 cold sites rather than a box-layout change.

A measurement rule this leaves behind. A converted suite that dies with a native fault is not automatically a regression, and a bisect is not automatically the right instrument. Root the fault site FIRST — the containment work makes that cheap now — and ask whether the failing call depends on a host capability the previous measurement did not have. Here one probe at the recording commit settled it in six minutes, where a 230-commit first-parent bisect would have found nothing and said so only after eight probes.


RESOLVED (2026-08-14, lane claude/go-types-av) — go/types’ access violation is a reflect POINTER TOKEN converted back to a pointer; 15 → 202 verdicts

go/types’ converted test host builds and runs now that the CS0839 defer panic(err) blocker is fixed, and it died with a bare access violation after 15 verdicts:

Fatal error. System.AccessViolationException: Attempted to read or write protected memory.
   at go.go.types_test_package.testFilesImpl(...)
   at go.go.types_test_package.testFiles(...)
   at go.go.types_test_package.testPkg(...)

Exit 0xc0000005, zero verdicts after, 542 behind it. First-contact territory, so the OS playbook applied rather than a bisect — and it paid the same way: the fault site named the defect outright, with no probing of history at all.

The fault site. testFilesImpl’s FIRST statement after building the Config is

*boolFieldAddr(&conf, "_Trace") = manual && testing.Verbose()      // check_test.go:166

func boolFieldAddr(conf *Config, name string) *bool {              // check_test.go:343
	v := reflect.Indirect(reflect.ValueOf(conf))
	return (*bool)(v.FieldByName(name).Addr().UnsafePointer())
}

There is no frame below testFilesImpl because the faulting store is inlined at the call site; boolFieldAddr itself returns perfectly well. TestCheck is the first test alphabetically that reaches testFilesImpl at all — not one of the nine before it (TestAlias_RhsTestBuiltinSignatures) calls testFiles; they type-check through mustTypecheck or not at all, and neither route uses this idiom — and blank.go is the first file in testdata/check. So the crash is the FIRST EVER EXECUTION of the idiom, not corruption surfacing late. blank.go being a five-line package _ file is a coincidence of alphabetical order and means nothing.

The mechanism. A Go pointer to managed storage has no machine address, so reflect.Value.Pointer and .UnsafePointer both project through reflectPointerToken (reflect/value_impl.cs) and answer with a stable order tokenINilPointer.PointerOrderToken, whose own remarks say tokens “are order keys, never an identity substitute”. The hand-own’s header says what it was written for: “fmt uses it only to test nil-ness and to print an address for %p.” go/types is a new caller with the other need. Emitted, its line is

return (ж<bool>)(uintptr)(v.FieldByName(name).Addr().UnsafePointer());

and ж<T>’s explicit operator ж<T>(uintptr) builds a native-address box over whatever number it is handed. The subsequent .Value store writes a bool at the numeric value of an order token — an access violation where that page is unmapped, silent heap corruption where it is not. Note this is the unique/clone.cs family stated from the other end: there an interior address was fabricated by ARITHMETIC on a movable box; here it is fabricated by a projection that never was an address.

Proven without go/types, in 22 lines — a struct with an unexported bool, string and int field, the boolFieldAddr idiom verbatim, no other package involved. Go prints x true; the converted C# dies System.AccessViolationException at go.main_package.Main(), the identical frame shape (the store inlined into its caller). It is the whole defect, with go/types removed.

The fix — golib remembers what the projection drops. The information was never lost: reflect.Value.Addr surfaces the real aliasing box (addrBox, minted by GoReflect.FieldAliasBox), and only the scalar projection discards it. golib/ж.PointerTokens.cs adds ManagedPointerTokens, a weak token→box table that reflectPointerToken registers into and the uintptr → ж<T> operator consults first; a token that came from there recovers its box and aliases the original storage exactly as Go’s pointer would, and everything else keeps the native-address route unchanged.

Two properties are deliberate, and both exist to keep the blast radius at zero. The token VALUE does not change — minting self-identifying handles from a reserved range would also move what %p prints and what order pointer-keyed maps print in, since fmt and internal/fmtsort read the very same token — so the association is carried out of band instead. Reads are lock-free with an empty fast path: Resolve sits on 875 emitted cast sites corpus-wide (54 in the syscall wrappers), and a program that never asks reflect for a pointer’s scalar form answers from a single volatile load. The type-descriptor path (typeDescriptorOrderToken) returns before registration and is untouched.

Measured movement: 15 verdicts → 202 (169 pass, 33 fail), zero access violations anywhere in the run. The 33 failures are entirely the KNOWN-OPEN type-parameter class — TestCheck/{chans, funcinference,typeinst1,typeparams,map0,map1,slices,issues1}.go, TestExamples/{functions, inference}.go — the same signature as go/internal/gcimporter’s residual 108.

The next wall, named: the SAME open root, in its non-terminating form. The run now dies at TestFixedbugs/issue48951.go with 0xc00000fdSTATUS_STACK_OVERFLOW, an unbounded recursion in validType0:

   at go.go.types_package.validType0(...)      × until the stack is gone
   at go.go.types_package.validType(...)
   at go.go.types_package.processDelayed(...)

issue48951.go is the invalid recursive type testdata (A2[P any] [10]A2[*P]), and Go’s cycle guard is for _, e := range nest { if Identical(e, t) { … } } (validtype.go:104). A converted Identical that judges a parameterized named type not identical to itself never finds the cycle, so the walk recurses forever. That is the board’s already-open second root — “a type parameter judged not identical to itself” — appearing as a hang rather than as a bogus error message. It is a consequence of that root, not a new defect, and it is not this lane’s.

Roster arithmetic: go/types does NOT bank. 202 of 557 with a live process-killer behind it; test sources deliberately not committed, and the pipeline’s churn under src/core/go/types restored.

go/internal/gcimporter re-measured on the same tree: 475 of 583 matched, 108 mismatched — UNCHANGED from its baseline, to the verdict. All 108 are still Go="pass" C#="fail" inside TestImportTypeparamTests, and nothing else moved in either direction (14m20s, under concurrent lane load). That is the expected answer and worth recording as a NEGATIVE result: gcimporter’s residual is the generics root in go/types’ checker, which this fix does not touch, so a package whose failures all sit there should not move — and did not. The two measurements now agree on the same open root from opposite directions.

Gates. GolibTests 111/111. Full behavioral suite PASS — 574/574 transpile, compile and goldens; 548 stdout comparisons, 0 failed, 26 skipped (no package main); 3,769 s (i7-5820K, solo). check-no-regression NO REGRESSION — generated C# and .csproj byte-identical across all 601 behavioral packages, 1,025 s (2 advisory converter warnings, 0 NOT MEASURED). go2cs.slnx build succeeded, 0 errors, 585 s — owed because golib’s public surface gained a type. Guarded by the new ReflectFieldAddrWrite behavioral output test, which faults with an access violation on pre-fix golib. Doctrine: ConversionStrategies-Reference.md, A pointer reflect handed out as an unsafe.Pointer must convert BACK.

What this leaves for the ж-box arc. The arc’s charter item — “have the non-aliasing fallback retain the source object” (remedy 3 of the NetShareAdd entry above) — is the general form of what this table does for one seam. This fix is deliberately narrower: it restores the round trip for pointers that reflect itself handed out, and changes nothing about the transient-address model that (uintptr)ж<T> uses for everything else. A pointer whose scalar form was produced by arithmetic (uintptr(unsafe.Pointer(&x)) + offset) still cannot come back, and still should not.

✅ CLOSED — the TB.* domino: the whole testing.TB surface is supported, and os/exec is measured for the FIRST time (2026-08-14, lane claude/tb-surface)

The unnamed-variadic lane’s closing prediction — “naming TB.* support in the hand-owned core/testing host is the exact next domino, and it is a host change with no converter component” — was right about the domino and inverted about where the work lived. Nothing in the host needed implementing, and the one line of code that changed is in the CONVERTER: the capability ROSTER was the entire defect.

The mechanism, because it is not obvious. A capability name is keyed on the RECEIVER’s named type (analyzeTestingCapabilities): a call written t.Fatal(err) records T.Fatal when t is a *testing.T and TB.Fatal when the enclosing helper declares t testing.TB. Those are two roster entries over ONE implementation, and supportedTestCapabilities listed only the first. So any test whose transitive same-package closure touched a testing.TB-typed helper was gated out wholesale — not for anything it did, but for the type its helper’s parameter happened to name.

Why widening it is honest, and what “supported” mechanically requires. Three things must hold, and for TB all three already did:

  1. core/testing declares the member — its TB interface carries Go 1.23’s full public set (the 18, minus the unexported private()).
  2. Something forwards it for the concrete receiver. The converter already emits [assembly: GoImplement<testing_package.T, testing_package.TB>(Pointer = true)] into every consuming package, and go2cs-gen’s ImplementGenerator mints the testing_TжTB adapter from it — read out of the generated file rather than assumed: void TB.Fatal(Span<object> args) => testing_package.Fatal(m_box, args);, once per member, all 18.
  3. The implementation is real. Each forward lands on the same TestExecution-backed member the T spelling has always used, so a TB.FailNow throws the same TestAbortException and aborts the same way.

So T never needed a base list and no per-suite wiring was ever owed — a standing note on core/testing’s TB predicted that work, and the adapter had already made it unnecessary. That note is now corrected in place.

The one declared limit, and it is a property of B, not of TB: an adapter built from a *testing.B forwards to B’s compile-only no-ops. Benchmarks are never registered or run, so the only path that puts a live B behind a TB parameter is a Test that calls testing.Benchmark itself and hands the b onward. No suite does. If one appears its failure reports would be silently swallowed — a Phase-4D benchmark-execution question, not a reason to withhold the T-backed surface from every test that has one.

Charter §9 discharged — the roster scan, with its positive control. Widening supportedTestCapabilities moves excluded tests into the RUN set of packages already banked, so the rule is to scan every validated package first. Same-package testing.TB helpers exist in 15 GOROOT packages (cmd excluded); the only two non-test files that declare one are internal/testenv’s, and those are consumed cross-package, which the same-package reference graph deliberately does not follow. Four of the 15 are banked, and one of those four — internal/fuzz — does not move, because its newWorkerForTest(tb testing.TB) is reached only from Benchmark declarations already excluded by kind. Positive control: the scan finds os/exec’s exePath, the helper this whole entry is about.

The three that DO move were re-measured, and every one moved by COUNT ONLY — the sweep’s sole complaint in each case, with no verdict disagreeing on either side:

Package Banked Now The tests that were being withheld
debug/gosym 9 10 Test115PclnParsing — pass/pass
internal/zstd 534 536 TestAlloc, TestLarge — skip/skip, both gated on a zstd binary this host lacks
archive/zip 98 100 TestZip64, TestZip64EdgeCase — pass/pass; the first streams 4 GiB through the writer, and is most of that row’s 775 s

strings, bytes and context re-validated at their exact banked counts as capability-regression controls: their exclusion sets are unchanged, which is the property a widening most needs to prove.

One caveat is banked with internal/zstd, because a skip that agrees is not a test that agrees: on a host WITH zstd installed both rows actually RUN, and TestAlloc asserts an exact zero allocations — the alloc-profile class the CLR provably cannot satisfy. Expect it to need a disclosure there. The row COUNT is stable either way, so the sweep’s floor holds; only the verdict would move.

os/exec — measured for the first time: 101 verdict rows, 48 agreeing, and TWO host roots

Before this change the pipeline compared 22 rows and all 22 matched, which read like a nearly-clean package. It was not a measurement: the 26 excluded tests were every process-spawn shape os/exec has, and nothing was known about them. With the surface widened the manifest carries 40 included test declarations and zero capability-blocked ones (13 excluded remain, all Phase-4D: 12 Example, 1 Benchmark), and the comparison covers 101 verdict rows.

Class Rows What it is
agree 48 40 pass + 8 skip, the skips identical on both sides
disagree — root A, helper-command argv 26 every one Go="pass" C#="fail"
disagree — root B, relocatable single-file test executable 27 every one Go="pass" C#="fail"

No verdict disagrees for a reason belonging to os/exec itself. Both roots are properties of the converted TEST HOST, and one of the two is an impossibility this file already declared.

Root A — the host throws on its own test binary’s argv. os/exec drives its helpers by re-executing the test binary with a bare command name (exec.Command(exePath(t), "cat")), and its TestMain dispatches on os.Args[1]. Go’s flag.Parse() stops at the first non-flag argument and leaves the rest for the program. The converted host’s TestOptions.Parse instead has no stopping rule: its default: arm throws ArgumentException("unsupported converted test option: cat"), so every helper child dies at startup with exit 2 before TestMain is ever entered. The parent then reports the downstream symptom — echo: want "foo bar baz\n", got "", ExitCode got 2, want 42, Wait: exit status 2, first output line: EOF — which is why this reads as twenty different failures instead of one. It is a small, closed fix: os.Args is wired to Environment.GetCommandLineArgs() (runtime/goargs_impl.cs) independently of the option parser, so the child already CAN see its argv; the host merely has to stop rejecting it, with Go’s stop-at-first-non-flag rule rather than by ignoring unknown tokens (exe cat -n must leave -n to the child, not parse it as a host flag). This is the next domino.

Root B — the same 0x8000809a this file already names. TestLookPathWindows and TestCommand build their fixtures by COPYING the test binary into a temp directory and running the copy. Every such row fails with exit status 0x8000809a and The application to execute does not exist: ‘…\p1\os.exec.tests.dll’ — byte for byte the LibHostAppRootFindFailure already declared for os_test.TestRemoveAllWithExecutedProcess under the capability “relocatable single-file test executable”: an apphost is a stub bound at build time to a managed assembly of the same base name that must sit beside it. Go’s statically-linked test binary is the only reason the premise holds there.

Deliberately NOT gated. Root B’s 27 rows could be excluded by declaration name the way os’s one test is, but doing it now would be half-work: root A’s 26 rows would still fail, so os/exec would not bank either way, and the next lane would have to revisit the same list. Root A is unimplemented, not impossible, so it must never become a gate entry at all (the standing rule for unsupportedRuntimeCapabilities). os/exec therefore stays unbanked, with both roots named, and its converted test sources were removed rather than committed.

The CLASS this domino was holding — every package with a same-package testing.TB helper

internal/testenv’s TB-typed helpers, which most of the corpus calls, never gated anything: the capability graph closes over SAME-package references only, so a cross-package helper gates through its own package’s conversion instead. What gated was a package’s own test-file helper. The complete GOROOT census (cmd and testdata excluded), with the banked rows re-measured here and the rest recorded:

Package Banked? TB-typed helper Effect of the widening
os/exec no exePath 26 tests unblocked — measured above; the reason this lane exists
archive/zip yes testZip64 re-measured, see the roster
debug/gosym yes read115Executable re-measured, see the roster
internal/zstd yes findXxhsum, bigData, findZstd, zstdBigData re-measured, see the roster
internal/fuzz yes newWorkerForTest none — reached only from Benchmarks, excluded by kind
database/sql no 36 sites, the widest in the corpus pending its own measurement
net no 9 sites pending
net/http no 8 sites pending
crypto/tls no 3 sites pending; the two build roots are CLOSED (2026-08-15) and the package compiles, but its run does not reach these sites — see the section at the end of this file
crypto/internal/mlkem768, crypto/x509, go/types, net/netip, os, runtime no 1 site each pending

The nine unbanked rows are not claims — no run was commissioned for them. They are named so the next lane to reach any of them knows this particular exclusion is already gone.

✅ CLOSED — the helper-argv domino: the host stops at the first non-flag argument, and os/exec’s root A is gone. It does NOT bank: root B holds, and a THIRD root appears underneath (2026-08-14, lane claude/host-argv-stop)

The TB.* lane named root A as the next domino and priced it as “a small, closed fix”. It was: one hand-owned file, src/core/testing/TestOptions.cs, and no converter component at all. 23 of root A’s 26 rows now agree. The other 3 were never really root A — the helper child died at argv before it could reach the thing that was ALSO going to kill it, and fixing the first revealed the second.

The fix, and why it is Go’s rule rather than a leniency. flag.(*FlagSet).parseOne stops at the first token that is not at least two characters long and beginning with -; that token and every one after it belong to the program. A Go test binary IS a program — its TestMain may take arguments — and os/exec drives its entire helper protocol that way: exec.Command(exePath(t), "cat"), with TestMain dispatching on flag.Args()[0]. TestOptions.Parse had no stopping rule; its default: arm threw unsupported converted test option: cat and the host exited 2 before TestMain was entered. Nothing else was owed: the converted os package fills os.Args from the real command line independently of this parser (Windows via syscall.GetCommandLine + commandLineToArgv; unix via runtime.argslice), so the host’s whole obligation is to stop and leave the rest untouched.

Stopping is not ignoring, in both directions, and the guard pins both: exe cat -n must leave -n to the child, while an unrecognized -flag BEFORE any non-flag is the host’s own command line being wrong and still exits 2 — with Go’s wording now (flag provided but not defined: -x), since this host stands in for a Go test binary and its stderr is read beside one. The rest of parseOne is mirrored for the same reason: a lone - is a non-flag, -- terminates and is consumed, ---x/-=x are bad flag syntax, a non-boolean flag takes the next token as its VALUE even when that token looks like a flag (-run -v filters on -v), and one or two leading dashes name the same flag — the --json-json equivalence TestFlagBridge already assumed when it republishes these options undashed. Nothing records the leftover tokens: the program reads its own argv, and an unread property would be machinery this host does not need.

os/exec re-measured: 101 rows, 74 agreeing (67 pass + 7 skip), 27 disagreeing

Class Rows Then Now
agree 74 48 → 71 67 pass, 7 skip
disagree — root A, helper-command argv 0 26 ✅ closed by the host-argv-stop lane
disagree — root B, relocatable single-file test executable 27 27 unchanged, and still a DECLARED limit
disagree — root C, os/signal’s runtime primitives are unimplemented stubs 0 3 ✅ closed by the os-signal-primitives lane

Root C is closed (2026-08-14). The six primitives now forward through the //go:linkname push registry into runtime/sigqueue.cs’s own state machine, and the two dead ends behind the forwarders — nothing armed the Windows console control handler, and notetsleepg threw on getg() before it could block — are closed with it. TestWaitInterrupt/{Wait,Exit-hang} and their parent all pass; cmdHang’s signal.Ignore(os.Interrupt) runs. Detail: ConversionStrategies-Reference, “os/signal’s six primitives”.

The disagreeing set is now exactly root B — the 14 TestCommand/*, 11 TestLookPathWindows/* and their two parents — so os/exec sits at all-agree-except-declared-limit, which is precisely the shape the coordinator ruling below was reserved for. This lane therefore reports and STOPS: no bank.

os/signal’s own suite cannot pay this domino back, and the census says why (2026-08-14). It was re-run end to end (-test-action all) after the primitives landed and it re-validates 1/1, unchanged — because its Windows-eligible RUN surface is exactly one test. signal_test.go is //go:build unix, signal_linux_test.go is linux, signal_cgo_test.go needs cgo, signal_plan9_test.go is plan9, and example_test.go’s two examples carry no // Output: comment, so go test never executes them (they stay excluded on both sides). That leaves TestCtrlBreak, which compiles a native Go child with the real go tool and sends it a console control event — so it exercises syscall.LoadDLL/GenerateConsoleCtrlEvent and os/exec, and never touches a single converted signal primitive. That is why it passed while all six were throwing stubs, and why it still passes now: it is not a regression detector for this area in either direction. The guard that does cover the primitives is the SignalPrimitives behavioral test (Notify/Stop/Ignore/Reset/Ignored transitions compared against go run, which also proves signal_recv really parks — Stop blocks in signalWaitUntilIdle until the watcher goroutine reaches sigReceiving).

Real console-event DELIVERY was measured out of band rather than left to inference: a scratch two-process probe in TestCtrlBreak’s own shape — child started with CREATE_NEW_PROCESS_GROUP, GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, childPid) from the parent — run against the converted child and against a native Go child built from the same source. Both print ready then got: interrupt and exit 0, repeatably. Worth recording from that probe: the FIRST attempt reported exit status 0xc000013a (STATUS_CONTROL_C_EXIT) with no output at all, and it was a cold-start race in the probe, not a defect — a cold Debug child carrying the full converted runtime closure had not yet reached signal.Notify when the 3-second timer fired, so the wanted bit was unset, sigsend correctly returned false and the default handler killed it (buffered stdout dying with it). Raising the delay to 8 s made it deterministic. A probe that sends the event on a timer instead of waiting for the child to announce readiness will keep re-finding this. DELIVERY still has no SUITE guarding it — that fixture is the honest next item here.

One row of the split is unreconciled, and is recorded rather than smoothed. The totals move exactly as root C predicts (101 unchanged; disagree 30 → 27; agree 71 → 74), but the internal split moved by four passes and one skip (63 pass + 8 skip → 67 pass + 7 skip) where root C accounts for three. The likely cause is that a subtest of the previously-FAILING TestWaitInterrupt parent was counted as a skip while its parent failed and now runs and passes; that was not re-derived against the old binary, so it is a hypothesis, not a finding. The 27 disagreeing rows were enumerated by name from the run and are all root B.

Two minor host observations, recorded rather than fixed (neither moves a verdict): the host’s fmt-free TestFormat renders *exec.Cmd and *strings.Builder under %v/%s as raw pointers (0x1fc631592e0), so t.Log(cmd) and t.Logf("stderr:\n%s", cmd.Stderr) print addresses — which is what made root C’s diagnostic invisible in the comparison output and cost a direct child run to see.

⛔ STOPPED for a coordinator ruling — and the ruling is no longer load-bearing for os/exec

The lane was chartered to stop, if os/exec landed at all-agree-except-declared-limit, and ask whether declared-host-limit rows can carry a bank the way alloc-profile disclosures do. It did not land there — root C is undeclared and unimplemented, so os/exec is unbankable on its own merits whatever the answer. Its converted test sources were removed rather than committed, exactly as the TB.* lane left them. The question is still worth answering, so here is the measured shape:

That is the whole question, and it is a policy one: whether “the host provably cannot execute this shape” is bankable at all, and if so whether it should read as an excluded capability (2 gate entries, tests never run) or as disclosed divergences (25 signature entries, tests run and fail visibly). Recorded here for the next lane that reaches a package where it IS load-bearing.

RESOLVED (2026-08-14, lane claude/typeparam-identity) — the type-parameter identity wall is EMBEDDED-STRUCT COPY ALIASING, and the fix is one generator field

The campaign’s last mega-wall — one root, three dependents, ~450 verdicts: go/internal/gcimporter’s 108 TestImportTypeparamTests mismatches (the constrained by signature), go/types’ own 33 failures plus the 0xc00000fd stack overflow at TestFixedbugs/issue48951.go, and go/internal/srcimporter’s 2 cannot infer T failures. None of it is generics, Identical, or the instance caches. It is a struct value copy that was not a copy.

The hypothesis was wrong, and instrumentation said so in one run. The standing suspicion — *TypeParam compared by pointer identity through a conversion that mints distinct wrappers/adapters/boxes, so Identical(t, t) fails reflexivity — is false. Instrumented at predicates.cs’s identical, the two operands of the failing comparison are TWO DIFFERENT *TypeParam objects (ids 2 and 3) and AreEqual answers False correctly; a same-object comparison answers True with the same box on both sides. The Context/instanceHash/lookup caches were instrumented too and behave exactly as Go’s: distinct hashes (00S[T₂] vs 00S[T₃]), zero cache hits, no collapse. Everything the board pointed at was healthy.

The 7-line reproducer. Driving the converted go/types.Check directly over a tiny source (the types/errors lane’s harness shape — build once, then a ~1 s run per probe) minimizes test/typeparam/absdiff2.go’s 13 errors down to:

package main

type S[T any] struct{ V T }

func (a S[T]) M1() T { return a.V }

func (a S[T]) M2() T { return a.V }     // C#: cannot use a.V (variable of type T …) as T value

Go reports 0 errors. The converted checker reports 1, on the SECOND method only — delete M1 and it passes. Spelling the second method’s parameter differently (func (b S[U]) M2() U) prints the tell outright: b.V has type T, the FIRST method’s type parameter.

The mechanism, printed. Named.expandUnderlying substitutes the origin’s underlying per instance. Instrumented:

[EXPAND] targs=[TP#2] origUnder=struct{TP#1} smap={TP#1->TP#2}  ->  newUnder=struct{TP#2}
[EXPAND] targs=[TP#3] origUnder=struct{TP#2} smap={TP#1->TP#3}  ->  newUnder=struct{TP#2}
                                        ^^^^ the ORIGIN was mutated by the first substitution

subst.go’s substVar is copy := *v; copy.typ = typ. go/types.Var EMBEDS object, which carries typ — and go2cs-gen held a promoted embed in a shared ж<T> box, so the C# struct assignment copy = v handed both sides one object and copy.typ = typ wrote into the ORIGINAL. The origin’s struct{V T₁} became struct{V T₂}; the second method then substituted {T₁ → T₃} over a struct that no longer mentioned T₁, kept T₂, and Identical(T₂, T₃) correctly said no. cannot infer T and validType0’s unbounded recursion are the same fact in their other costumes.

Proven at golib level in 12 lines, no go/types involved — a struct embedding another, one plain assignment and one c := *p:

  Go C# (pre-fix)
a.v after b := a; b.v = 2 1 2
a.tag (an ORDINARY field) after b.tag = "b" a a
orig.v after c := *orig; c.v = 99 10 99

The ordinary field copies correctly; only the embed aliases. This is the gap GoValueCloneAttribute’s own remarks had already NAMED and set aside — “embedded-struct copy aliasing is a separate, pre-existing gap” — declared in the array-clone arc and never priced. Its price was this wall.

The fix is one field in StructTypeTemplate: the embed becomes an INLINE field.

private @object ʗobject;                                          // was: private readonly ж<@object> Ꮡʗobject;
[UnscopedRef] internal partial ref @object @object => ref ʗobject;  // was: => ref Ꮡʗobject.ValueSlot;

The box existed only because a struct member cannot ref-return its own instance state (CS8170); [UnscopedRef] states the ref’s lifetime is the receiver’s — exactly the guarantee Go gives, since the selection IS the enclosing value’s storage — and the repo already used that technique in InheritedTypeTemplate. Five emission sites in one file (field, accessor, promoted-field accessor, two constructor initializers). No converter change, no corpus regen, no golden churn: the generator’s output is not committed, so the emitted .cs is byte-identical.

Two things improve for free: a default(T) reached where no constructor runs (a missing-key map read, a freshly maked element) no longer carries a null embed box — the previously documented residual gap narrows to embedded types that need construction in their own right; and the C# struct = now means what Go’s = means for every embedding type in the corpus.

The one residue, named and unchanged. A fixed ARRAY reached only THROUGH an embed is still shared after a copy (array<T> is a struct over a shared T[], and typeNeedsValueClone skips embedded fields when deciding the [GoValueClone] stamp). It was shared before this fix and is shared after, by a different mechanism. Widening the walk is now SOUND — the generated copy.<member> = <member>.ΔClone() lands in the copy’s own inline storage instead of corrupting the source, which is precisely why it was excluded — but it moves converter EMISSION corpus-wide and belongs to a change that owns that footprint.

The unlock, MEASURED across all three dependents

Package Baseline After Verdict
go/internal/gcimporter 475 of 583 (108 mismatches, all TestImportTypeparamTests) 583 / 583, 0 mismatches BANKEDstatus: validated, 14 skipped identically
go/internal/srcimporter 5 of 7 (TestImportStdLib, TestImportedTypes died on sync.OnceValue(func() bool {…})cannot infer T) 7 / 7, 0 mismatches BANKEDstatus: validated, TestCgo skipped identically
go/types 202 verdicts (169 pass / 33 fail), then 0xc00000fd in validType0 at TestFixedbugs/issue48951.go 513 verdicts, 512 pass + 1 skip, ZERO failures not banked — 44 verdicts never produced; a NEW wall, below

go/types is the measurement that settles it: the type-parameter class is entirely gone. Every TestCheck/*, every TestExamples/*, and every TestFixedbugs/* — issue48951 among them, the invalid-recursive-generic that used to exhaust the stack because Identical(e, t) never fired — now passes, and not one produced verdict disagrees with go test. Its 44 absent rows are one process-killer, and it is a different animal (next entry).

The NEXT wall, named: TestSizeof exhausts the stack in golib’s reflect LAYOUT walk

RESOLVED 2026-08-15 (lane claude/gosizeof-recursion) — pre-existing golib defect, exactly as this section suspected but declined to assert; the standalone probe it asked for measured the cycle identically on both golibs. go/types banks at 557. Entry at the end of this board.

go/types’ run now dies at TestSizeof — the first test alphabetically past the ones that pass — with an unbounded recursion whose frames alternate exactly two functions:

   at go.GoReflect.tryStructLayout(System.Type, IntPtr[] ByRef, IntPtr ByRef)
   at go.GoReflect.GoSizeOf(System.Type, IntPtr[])          x until the stack is gone
   at go.internal.abi_package.synthesizeDescriptor(...)
   at go.reflect_package.TypeOf(System.Object)
   at go.go.types_internal_test_package.TestSizeof(...)

It takes TestSizeof and everything alphabetically after it (TestSpec/* and the rest — 44 verdicts) and it is not the type-parameter class: GoSizeOf has no memo and no depth guard, and the walk is reached the moment reflect.TypeOf is asked to synthesize a descriptor for a go/types struct. Deliberately NOT attributed to the embed change on the evidence available: the projection reports the SAME field TYPE for an embed before and after (the old arm unwrapped ж<T> to T; the new one reads the inline field, whose type is T), so the walk sees the same graph — but that is an argument, not a measurement, and nobody has run this test on the pre-change golib because the type-parameter wall stopped the suite ~300 verdicts earlier. Whoever takes it should settle that first, with a standalone GoSizeOf probe over the same types on both golibs; if it is pre-existing it is a golib defect in its own right (a memo plus a cycle guard is the obvious shape), and if it is not, the embed model is implicated and the probe says how.

One coupling the embed change DID break and this lane fixed: GoReflect.collectGoFields recognized a promoted embed by the old box shape (a field named ᏑʗName whose type is ж<T>), so an inline ʗName fell through to the generic arm and reported the Go field under its MANGLED name. The projection now keys on the ʗ marker alone and reports the field’s own type with no pointer hop. It does NOT change the recursion above (measured: identical stack, identical 44 absent verdicts before and after), which is part of the evidence that the recursion is not the embed’s.

Guarded by the new EmbeddedStructValueCopy behavioral output test (assignment, by-value parameter, a two-level c := *p, a slice-element read, and a pointer embed proving both halves of Go’s rule). Doctrine: ConversionStrategies-Reference.md, An embedded struct is an INLINE field, so a value copy copies it.

⛔ MEASURED, DOES NOT BANK — the host-limit GATE is self-defeating on os/exec, and it costs 13 agreeing rows before it even gets there (2026-08-15, lane claude/os-exec-gate-bank)

RULING REVISED on this evidence, same day — the gate preference is WITHDRAWN and root B resolves as a DISCLOSURE. os/exec banks at 74 matched · 27 disclosed (host-limit) — see the disposition at the end of this entry. Everything below stands as measured; it is the evidence the revision rests on, not a live proposal.

The coordinator ruling delivered to this lane was to bank os/exec at 74 agreeing verdicts under the GATE form: two declaration-keyed entries in unsupportedRuntimeCapabilities, tests excluded from the run set, no disclosure. The gate itself works exactly as designed — the keys are right, the two declarations are blocked, the manifest reads 38 included + 2 capability-blocked + 13 Phase-4D excluded, and every surviving row agrees on both sides. It still does not bank, for two reasons the ruling’s evidence could not have contained, both measured here rather than argued.

The keys, since the brief asked them to be verified empirically

TestCommand and TestLookPathWindows both live in lp_windows_test.go, which is package exec_test, so the keys are the EXTERNAL test package’s path:

"os/exec_test.TestCommand":         "relocatable single-file test executable",
"os/exec_test.TestLookPathWindows": "relocatable single-file test executable",

os/exec_test, not os/exec and not exec_test. Both fired on the first attempt.

Finding 1 — a declaration-keyed gate withdraws 40 rows, not 27, and os/exec under it is 61

eligibleTerminalTestResults cuts a verdict row at its first /, so gating a declaration takes every subtest with it — including the ones that were already agreeing. Measured on the same machine, same toolchain, back to back:

  rows go go2cs agreeing
ungated 101 93 pass, 8 skip 66 pass, 27 fail, 8 skip 74 (66 pass + 8 skip)
gated (2 entries) 61 53 pass, 8 skip 53 pass, 8 skip 61, zero mismatches

The 40 withdrawn rows are 18 under TestCommand (parent + 17 subtests) and 22 under TestLookPathWindows (parent + 21). Only 27 of them were disagreeing; the other 13 are passes that the gate discards along with them — 10 of TestLookPathWindows’ 21 subtests and 3 of TestCommand’s 17. The reason a table-driven test splits at all is that not every case reaches a copied executable: for TestLookPathWindows the correlation is exact, every failing case wanting a .exe (which the ground-truth check then RUNS) and every passing case either expecting exec.ErrNotFound — nothing is executed — or wanting a .bat, which installBat writes as text rather than copying. So the choice is not “74 with a gate or 74 with disclosures” — it is 61 with a gate or 74 with disclosures. The 74 figure in the ruling is reachable only by the arm the ruling declined.

The ungated re-measurement also reconciles the split the previous lane recorded as unreconciled: it is 66 pass + 8 skip, not 67 + 7. One row differs, and this run agrees with the arithmetic in both directions (61 gated agreeing + 13 withdrawn passes = 74; 8 skips are all outside the gated set).

Finding 2 — the gate ARMS a TestMain census that only the gated tests could have satisfied

This is the decisive one, and it is a property of gating rather than of os/exec. Under the gate the run reports zero mismatches — and then the converted host exits 1 anyway:

helper command unused: "printpath"

os/exec’s TestMain runs a completeness census after m.Run(): every helper registered with registerHelperCommand must have been marked used. lp_windows_test.go’s init() registers printpath, and the ONLY callers of maySkipHelperCommand("printpath") are the two tests the gate removed. Their init() still runs — a gate excludes declarations, not files — so the helper is registered and never claimed.

Gating the failures is what arms the census. Go guards it with code == 0, so in the ungated run the 27 failures made m.Run() non-zero and the census never executed; that is why no earlier measurement of os/exec ever saw this line. The gate greens the suite, the census fires, and the package fails at the process level with no failing test to point at. Under the gate os/exec does not validate at 61 or at any other count.

Why the precedent looked free. os_test.TestRemoveAllWithExecutedProcess is gated under the same capability name and has never shown this, because os’s TestMain is Exit(m.Run()) with no census — and because os is not on the roster, so nothing measures it. The precedent is therefore evidence that the gate MECHANISM works, not that it is free.

The general shape, and the seam where the gate form could be made viable

A capability gate is invisible to the converted host: the converter simply omits the declaration from the emitted TestRegistry, so nothing at runtime knows a subset is running. Go’s own vocabulary for “a subset ran” is a non-empty -test.run, which is exactly what its census gates on (flag.Lookup("test.run").Value.String() == ""), and TestFlagBridge publishes test.run as options.RunPattern — empty on every pipeline run. So any converted suite whose TestMain asserts that the whole suite ran will answer wrongly while a gate is active. os/exec is the first package in the corpus where a gate and such a TestMain coexist; it will not be the last.

Making the gate arm viable therefore means making the host’s test.run truthful: the converter would have to emit the gated set into the generated host (it currently just drops it), and the bridge would publish a pattern naming the included set. That is honest rather than lenient — it is Go’s own statement of the same fact — but it is a converter change plus a hand-owned core/testing change, so it owes a full validated sweep, and even then the package banks 61, not 74.

The disclosure arm, priced rather than assumed

Measured on the same binary with the gate disabled and a 25-entry signature manifest.

Validated 74 tests against go test (8 skipped identically on both sides,
27 disclosed-divergent (, host-limit), 13 disclosed-unsupported declarations excluded).

It validates, and at exactly the count the ruling named — 74 matched, 27 disclosed, a proof page written. Nothing was banked from it: the artifacts are removed from this branch and the arm is recorded here as a measurement, because admitting the class is the coordinator’s call, not a lane’s.

All 25 leaves carry exit status 0x8000809a (verified: 25 occurrences for 25 leaves) and both parents carry no failure output of their own, so they ride matchTerminalStatuses’ existing disclosed-parent aggregation. Because the tests RUN and fail, m.Run() is non-zero and the helper census never arms — Finding 2 does not exist on this arm. The csErr forgiveness path (goErr == nil && len(disclosed) > 0 && len(mismatches) == 0 && len(csResults) > 0) covers the host’s nonzero exit.

The (, host-limit) in that line is quoted verbatim and is a pre-existing cosmetic defect, not a symptom: the class list is built from disclosures[name].Class over every disclosed row, and the two rows disclosed by the parent-aggregation rule carry no manifest entry, so the empty class joins the set. Any package with an aggregated disclosed parent prints it (encoding/binary’s TestSizeAllocs is the same shape). Recorded, not fixed here.

What it costs is doctrinal, and it is the cost the ruling already weighed: 25 signature entries rather than 2 declaration keys, and a THIRD disclosure class alongside alloc-profile and codegen-liveness — one that is not about a measurement the CLR cannot perform but about a test whose whole premise the host cannot satisfy. docs/ValidatedTestPackages.md’s preamble defines a disclosure narrowly enough that admitting this class is a decision, not an application.

What landed on this branch regardless of the fork

The proof page now enumerates, per capability-gated declaration, every verdict row go test reports underneath it, read from the UNFILTERED Go results (they exist nowhere after eligibleTerminalTestResults) and published under a Gated by a host capability section. This was the ruling’s third annotation requirement, and it had to be a generator feature rather than a hand edit, because a proof page is regenerated on every sweep and a hand-added section would be silently overwritten. It is also what makes Finding 1 visible: without it a gate reads as “2 declarations excluded” on the page while withdrawing 40 rows.

It is owed by the NEXT gated package to bank rather than by this one: three capability entries stand today (syscall.CommandLineToArgv, os_test.createMountPoint, os_test.TestRemoveAllWithExecutedProcess), all in packages not yet on the roster, and os — already measured at 158/178 — carries two of them. Its page would otherwise claim a matched count while saying nothing about the rows those gates take.

Guarded by TestCapabilityGatedDeclarationsEnumerateSubtestRows (the row roll-up, the sorted order, the prefix-vs-segment distinction, and the empty case) and by TestDeclarationKeyedCapabilityEntries, which pins the standing declaration entry by exact key and requires any entry naming a Test to be keyed on an EXTERNAL test package path — a mis-keyed gate fires silently, which is the trap this lane nearly walked into.

The evidence for that feature is split across two halves rather than one end-to-end run, and it has to be, today. The producing half ran on real data — the gated os/exec comparison recorded TestCommand → 18 rows and TestLookPathWindows → 22 — and the rendering half is pinned by the fixture golden. What has never executed is the composition, because the page is written only for a comparison that VALIDATES and no gated package validates yet (Finding 2 is why). The first package to bank with a gate active is the end-to-end proof; until then this is two proven halves and one function call between them.

Not landed: no roster row, no proof page, no committed test sources, no disclosure manifest — and not the two gate entries either. os/exec remains unbanked and, more importantly, stays MEASURABLE exactly as it was: 101 rows, 74 agreeing, 27 rooted failures. Landing the entries would have traded that for one opaque process-level exit, so they live in this entry (verified, above, ready to paste) and as a standing note beside unsupportedRuntimeCapabilities rather than in the map. The branch carries the page feature, the guards, and this record.

What is actually open, in the coordinator’s terms

Three ways forward, each with its measured price:

  1. Gate, made viable — publish a truthful test.run when declarations are gated (converter emits the gated set into the host; TestFlagBridge publishes a pattern). Costs a hand-owned core/testing change and therefore a full validated sweep; os/exec then banks 61.
  2. Disclosure — 25 signature entries land today and os/exec banks 74 matched · 27 disclosed, with no new machinery. Costs a third disclosure class and a preamble that admits host limits alongside CLR-measurement limits.
  3. Neither — drop the gate entries and leave os/exec unbanked but fully MEASURABLE at 101 rows with 27 rooted failures, which is what it is today. The cheapest, and it keeps the 74/27 shape visible for whenever the single-file-publish capability is actually built.

Option 3 is where this branch leaves things, because it is the only one a lane can choose on its own: options 1 and 2 each change doctrine (a hand-owned host contract, or what the word disclosure admits), and those are rulings. The one thing that must not happen is adding the gate entries without option 1’s work — that trades 27 visible, rooted, well-understood failures for a single opaque process-level exit and makes the package measure worse than it does today.

✅ DISPOSITION (2026-08-15, ruling revised, lane claude/os-exec-disclosure-bank) — root B is a DISCLOSURE, os/exec BANKS at 74 + 27

The coordinator withdrew the gate preference on the measurement above and ruled root B a disclosure. The deciding property is one this lane had priced only as a cost: a disclosure keeps every row running, so it retires itself. A gate withdraws the 27 rows from the comparison entirely, which means the day a self-contained single-file test host makes their premise true, nothing happens — no signal, no failure, the entries simply stay forever. Disclosed, those rows keep executing and keep being compared, so on that day they start passing, the disclosed arithmetic stops matching, and the sweep fails loudly until the entries are removed. Add the 13 destroyed passes and the self-defeating TestMain census, and the fork closes.

Banked: 74 matching (66 pass + 8 skip) + 27 disclosed-divergent. 25 leaf rows pinned by signature on exit status 0x8000809a; the 2 parents carry no failure text of their own and ride matchTerminalStatuses’ disclosed-parent aggregation. Roster row 144; docs/validation/current/os.exec.md names all 27 with class and reason.

The third class, host-limit, is authorized and its bar is recorded in two places — docs/ValidatedTestPackages.md’s preamble (the reader-facing definition) and docs/ConversionStrategies-Reference.md’s host-limit — the third disclosed-divergence class” (the full mechanism). The bar: an entry must pin a structural property of the current deployment shape, provable from how the artifact is built, never an unimplemented-but-fixable defect. log remains the negative control on the other side of that line — a Go-source position map would satisfy its .go:63 assert exactly, so it stays unbanked rather than disclosed around.

QUEUED, not closed — the gate mechanism’s truthful test.run. The hazard Finding 2 exposed is real and unfixed: a capability gate is invisible to the running host, so any suite whose TestMain asserts that the whole suite ran will mis-answer while one is active. The hardening is to emit the gated set into the generated host and have TestFlagBridge publish a test.run naming the included set — Go’s own vocabulary for “a subset ran”. It is load-bearing only when a gate meets a completeness-asserting TestMain, and nothing is broken today: the only gated declarations live in os and syscall, and os’s TestMain is a bare Exit(m.Run()). It becomes owed the moment either a gated package with such a TestMain is banked, or os itself banks with its gate entries intact. Cost when it lands: a converter change plus a hand-owned core/testing change, therefore a full validated sweep.

One cosmetic defect left standing, deliberately. The summary line prints 27 disclosed-divergent (, host-limit) — the empty class comes from the two aggregation-disclosed parents, which carry no manifest entry and so contribute "" to the class set. It is pre-existing (any package with an aggregated disclosed parent prints it) and cosmetic. The generated proof page’s own preamble is likewise still worded for the two CLR-measurement classes only. Both are one-line renderer fixes and BOTH were left alone on purpose: touching the renderer restyles all 144 proof pages, and they only rewrite as each package is next re-validated, so the change would dribble a whole-corpus docs diff through unrelated lanes. It belongs in a pass that re-validates the roster.

✅ CLOSED (2026-08-15, lane claude/gosizeof-recursion) — TestSizeof’s stack exhaustion is a PRE-EXISTING classification defect in KindOf, and go/types BANKS at 557

The wall named in the entry above is gone, and it was never the embed change’s. go/types now runs 557 verdicts, 557 agreeing with go test, zero mismatches (1 skipped identically on both sides, 6 disclosed-unsupported declarations excluded) — status: validated. That is exactly the 513 the type-parameter unlock produced plus the 44 the process-killer had been swallowing, and it is the converted Go type-checker itself: TestCheck/TestSpec/TestFixedbugs over the language’s own conformance corpora, TestStdlib type-checking all of GOROOT from source, and TestSizeof — the test that priced this defect — asking reflect for the amd64 size of all 25 type and object nodes.

Attribution, settled FIRST and by measurement — pre-existing, not embed-implicated

The previous entry asked for this explicitly and declined to guess, correctly: the argument that the projection reports the same field type before and after was an argument, not a measurement, and nobody had run the walk on the pre-change golib because the type-parameter wall stopped the suite ~300 verdicts earlier.

A standalone probe settles it. It mirrors GoSizeOf/tryStructLayout exactly — same KindOf classification, same GoFields projection — but carries a path stack, so instead of exhausting the stack it NAMES the cycle. Built twice against the same converted go/types corpus, once against golib at HEAD and once against golib at 48274ae2b (the merge immediately BEFORE the embed change), both runs print the same 30 lines:

  Named        CYCLE  Named -> Mutex -> SemaphoreSlim -> TaskNode -> TaskNode (REVISIT)
  ...
  SUMMARY cases=25 matched=24 mismatched=0 cycle=1 unknown=0 missing=0
  structs=89 cyclic=3     (Named, Context, lazyObject — one distinct cycle tail)

Byte-identical on both golibs. The other 24 of TestSizeof’s 25 cases already produced Go’s exact size on both. So: a golib defect in its own right, older than the embed model, and the embed lane’s decision not to attribute it to itself was right.

Root cause — KindOf called a managed REFERENCE a struct, and Struct is the kind that descends

KindOf’s last line answered Struct for any reference type it did not otherwise recognize. Struct is the one kind whose walks look INSIDE the type, so GoSizeOf/GoAlignOf — and StructFieldsComparable, which synthesizeDescriptor calls on the very next line — enumerated the CLR’s own private fields and descended into the BCL object graph. A Go type graph cannot recurse that way: Go’s layout rule stops at every pointer, slice, map, chan, interface and func, and a Go struct that contained itself by value would be a type Go itself rejects. The BCL graph has no such rule, and SemaphoreSlim’s async wait queue is a linked list — TaskNode.Next is a TaskNode — so the descent alternated tryStructLayout/GoSizeOf frames until the stack was gone.

The corpus entry point is sync.Mutex, hand-owned since 2026-07-11 on a lazily-created SemaphoreSlim gate because Go’s runtime sleeping semaphore has no managed form. Every struct that holds one inherits the cycle: in go/types that is Named (via mu sync.Mutex), Context, and lazyObject (via sync.Once). Nothing about this needed go/typesfmt.Println of any sync.Mutex-bearing struct was on the same cliff.

Why it was a process-killer and not a failure. A StackOverflowException cannot be caught in .NET. It took TestSizeof and every test alphabetically after it, and the run reported 44 ABSENT verdicts rather than one failing test — the worst failure shape the campaign has, because absent verdicts are unmeasurable rather than wrong.

The fix — one classification line, then one memoized walk behind it

1. The classification (the root cause). go2cs emits every Go struct as a C# VALUE type — the corpus carries exactly seven [GoType] partial class declarations and all seven are named-POINTER types (type P *T), already classified Pointer structurally. So a reference type reaching that final line is never a Go struct: it is an opaque managed handle, the backing object a hand-owned shim holds in place of Go’s own representation, and in the Go model a handle is one pointer word. KindOf now says Pointer, the descent stops at the handle, and the answer is Go’s own — a Go sync.Mutex is 8 bytes and so, now, is the converted one. Named computes to 112, which is what TestSizeof wants.

2. The durable shape on top. Offsets, size and alignment now come out of ONE memoized pass (structLayoutOf) instead of two walks that could disagree, and alignment is accumulated over every field even after a size becomes unknowable, because the two questions are independent. A depth cap (128) sits underneath as a safety net that answers “size unknown” — the r39d rule, a descriptor field that cannot be read truthfully stays unpopulated — rather than overflowing. It is unreachable by construction: only Struct and Array recurse, Struct is now answered for value types alone, and C# forbids a value type from containing itself transitively (CS0523). Tripping it would mean the CLASSIFICATION is wrong again, and the point is that the next such defect costs a wrong number instead of a dead process and 44 unmeasurable verdicts.

A cycle guard proper was deliberately NOT added. The brief’s requirement — a guard must produce CORRECT sizes for legal self-referential graphs, not merely avoid crashing — is met by the classification rather than by detection: a struct holding a pointer to itself terminates at the pointer and answers 24, not “unknown”. Guarded by GoStructLayoutTests.SelfReferentialThroughPointer_IsFiniteAndCorrect.

Guards

Three new tests in src/tests/GolibTests/GoStructLayoutTests.cs, the golib home of this walk (114/114, was 111/111): a managed reference is one word and not a struct to descend into (SemaphoreSlim included, so the real BCL graph is in the assertion); a self-referential managed class terminates; and Go’s own legal self-reference through a pointer is finite AND correct. The first two are guards against a stack overflow, which no assertion can catch — reaching the assert at all is the guard, and the value proves the walk stopped at the handle rather than merely stopping.

Doctrine: ConversionStrategies-Reference.md, A managed reference is a Go POINTER, not a Go struct — the reflection bridge’s descent rule.

💡 CAPTURED (2026-08-15, user, from the live NuGet page) — README badge layout + a partial-progress badge idea

1. Badge-line wrap on NuGet — ✅ LANDED (lane claude/badge-break, merged 2026-08-15). The four badges emit as one markdown line (readmeValidationBadge.go), and NuGet’s narrow README pane wraps them raggedly. User ruling: hard line break between the docs badges and the source badges — line one carries Tests + Docs (the Tests badge is the variable-width one and gets the room), line two carries Source·Go + Source·.NET. Sequencing: land AFTER the in-flight second leveling regen merges (the emitter change re-levels the whole README family at the next regen; published NuGet pages pick it up at the next version push). Update readmeValidationBadge_test.go’s expectations in the same change.

2. Partial-progress badge for not-yet-validated packages — DEFERRED, captured verbatim. User: “does it make sense to carry partial test results, as a badge link to the ‘not yet validated’ so users can get a sense of how close things sit?” Honest constraints, recorded so the future design starts from them: the roster’s integrity principle is validated-only rows (a package that almost passes never appears — that is what keeps the denominator honest); partial counts have no machine-maintained data source today (the sweep records validated packages only, and this board’s census entries are prose); and many unvalidated packages do not yet COMPILE their -tests closure, so “how close” is not a number for them at all. A future shape would need a sweep mode that records per-package partial verdicts and a badge/proof surface that cannot be mistaken for validation. Revisit after the 75% terminal marker, possibly with the queued proof-renderer pass.

⛔ MEASURED FOR THE FIRST TIME, DOES NOT BANK — database/sql builds and runs at 133 of 139; the two compile roots are CLOSED and three divergence roots are named (2026-08-15, lane claude/database-sql)

The board carried database/sql as 0 of 25, CS0121 + CS0117 and the TB.* census listed it as “36 sites, the widest in the corpus — pending its own measurement”. Both compile roots are gone and the suite now runs end to end: 139 rows, 133 agree, 6 disagree, 0 skipped, 0 disclosed, 8 excluded (the standard Phase-4D Benchmark/Example deferrals). It does not bank — four of the six are genuine divergences — but it is no longer build-blocked, and every remaining root is named.

The recorded CS0121 was already closed, exactly as the tar lane predicted. stubDriverStmt(NilType) was the typed-nil constructor argument (08afdd267); it emits new stubDriverStmt(want) today and never appeared in this lane’s build. The prediction was worth acting on — but the roots BEHIND it were two, not zero.

Compile census — two roots, both general converter defects, both fixed here

Diagnostic Sites Root
CS0117 'sql_package' does not contain a definition for 'Δtable' 6 A box-field accessor (Type.Ꮡfield) qualified its owner with the PRODUCTION package class even when an internal _test.go declares the type. fakedb_test.go’s type table collides with func (db *fakeDB) table(string), so the type is Δ-renamed — and Δ-renamed always qualifies — putting all six t.mu.Lock()/Unlock() sites in a class that has no such member. packageScopeClassName already drew the production/bridge line for package-level VALUE references; the accessor now uses it for the TYPE. Provably inert outside -tests: with no class override the helper returns the production class
CS0029 cannot convert ж<fakeStmt> to fakeStmt 1 func (s *fakeStmt) QueryContext walks s = s.next. Go’s pointer receiver is an ordinary local, so repointing it is legal; the converter deref-aliases the receiver to a value var, which cannot be repointed. visitAssignStmt’s repoint-and-re-alias arm (Ꮡs = s.next; s = ref Ꮡs.DerefOrNull();) has existed as long as the pointer-PARAMETER twin, but it is reachable only through the box — so it needed a direct-ж trigger the pre-pass did not have. bodyReassignsReceiver adds it, matched by object identity so a shadowing inner := does not promote

The receiver-repoint trigger is corpus-invisible by construction, and that is why it surfaced in a test file. All ten production sites in the converted scope are also carried by a neighbouring predicate — container/ring’s Move and go/typesLookupParent return their receiver; math/big’s fmtX, net/http’s addBytes and time’s two (*Location) lookups already emit this ж<T> Ꮡx — verified against the committed corpus before the change and re-verified by the whole-stdlib A/B after it. (internal/concurrent and testing also match the shape and are hand-owned, so they never convert.)

Verdict census — 6 disagreeing rows, 4 distinct roots

Rows Root Class
TestGrabConnAllocs, TestRawBytesAllocs testing.AllocsPerRun want-zero asserts (grabConn 4 objects/run; RawBytes 15). The existing measured-allocation gap alloc-profile — disclosable under the existing class if the other three roots ever close
TestConversions, TestUserDefinedBytes abi.Type.Elem() nil — see below real defect, reflect/internal/abi
TestNullByteSlice an untyped nil in a variadic slot vanished — fixed here, see below was a converter defect
TestConnRaw conn.Raw(func(…) { panic(…) }) must leave the connection CLOSED: Raw’s deferred cleanup sets err = driver.ErrBadConn while a panic is in flight and calls release(err). The converted run panics correctly (the test’s own recover() assertion passes) but conn.dc is still non-nil afterward, and closeDB then reports 1 connections still open. Not rooted further by this lane — a defer-during-panic + named-result + capture question inside the converted sql.cs real defect, unrooted

TestNullByteSlice was a CONVERTER root and is fixed. exec(t, db, "INSERT|t|id=10,name=?", nil) emitted exec(…, insertTId10Nameˢ, default!) against params ꓸꓸꓸany argsʗp. A typeless default! converts to the params ARRAY as readily as to its element, and C# prefers a call’s normal form over its expanded one — so the argument bound as a null any[], the callee saw len(args) == 0, and the fake driver answered sql: expected 1 arguments, got 0. A silent behavioral divergence, not a compile error, which is what makes it worth naming: the emission is valid C# that means something else, and nothing but a differential run would have caught it. Every trailing argument of an expanded variadic call that is the predeclared nil now states the element type — (any)(default!). A spread call is excluded (it passes the slice whole, so describe(none...) still yields 0).

TestConversions/TestUserDefinedBytes are ONE root, and it belongs to reflect, not to database/sql. Both die in the same stack: convertAssignRowsrtype.ConvertibleToconvertOphaveIdenticalUnderlyingTypehaveIdenticalTypenameFor → the hand-owned reflect.Name, which reads Ꮡt.Value.t.sysType and nil-dereferences. The nil comes from abi.Type.Elem(): it reaches its element through the prefix-downcast idiom (Ꮡt.Reinterpret<Type, SliceType>() and the Array/Chan/Map/Pointer siblings) — the exact route internal/abi’s hand-owned type_impl.cs already documents as unusable under the managed bridge (“there is nothing behind a ж<abi.Type> to downcast to”), and which it replaced with SYNTHESIS from the descriptor’s carried System.Type for StructType() and ArrayType(). Elem() and Key() were never routed through that synthesis, so they answer nil for a slice/pointer/chan/map descriptor, and any reflect path recursing through them nil-derefs. TestUserDefinedBytes is the minimal shape: convertAssign(&u, v) with u userDefinedBytes (a local named []byte) and v []byte reaches sv.Type().ConvertibleTo(dv.Type()), both Kind Slice, and the slice arm immediately recurses on Elem().

This is a shared root worth its own lane, not a database/sql fix. The remedy is the pattern type_impl.cs already establishes — synthesize SliceType/PtrType/ChanType/MapType from sysType and route Elem()/Key() through them, populating nothing that cannot be honored (the r39d rule). It gates every reflect.ConvertibleTo/AssignableTo recursion in the corpus, not just these two rows.

Timing — a $longTimeouts floor is owed IF this package ever banks

The converted run took 1,712.7 s (28.5 min) under -test-timeout 30m, i.e. it cleared the deadline with about 90 seconds to spare on a quiet i7-5820K. At the sweep’s default it would false-red. Nothing is owed today (the package has no roster row), but the entry that banks it owes 'database/sql' = '60m' in run-validated-sweep.ps1’s $longTimeouts in the same change.

What this lane banked

The two compile fixes and the variadic-nil fix, each with a guard and a reference-doc entry — bodyReassignsReceiver (guard: PointerReceiverRepoint, a receiver-repointing list walk written to avoid every other trigger), the bridge-qualified box accessor (guard: TestTestVariantBoxAccessorNamesBridgeDeclaringClass, asserting both directions), and the variadic untyped-nil cast (guard: the VariadicSlotInterfaces extension, arity in four positions against three controls). Both compile-root guards were neuter-verified: reverted, each fails with the exact diagnostic censused above. database/sql’s converted test sources are NOT committed — the validated-package commit policy covers packages that validate, and this one does not.

crypto/tls BUILDS — both recorded roots closed, a third behind them, and the RUN wall resolved into four named roots (2026-08-15, lane claude/crypto-tls)

Both recorded roots reproduced verbatim at master 6dd7547e4 before anything was believed — same codes, same four call sites. That entry was accurate; the campaign’s stale-premise rule cost one 93-second pipeline run to confirm and was worth paying. What was NOT accurate was its attribution of the first root, and what nobody had seen was a third root standing behind the first two, which is the reason a build-blocked package is only ever measured one wall at a time.

Four errors → zero. The package compiles for the first time, and its host runs.

Root 1 — CS0012 ×3 is NOT a missing reference; it is the Phase-4D exclusion being too conservative

Recorded as “a fresh instance of the -tests reference-closure family”. It is not: adding the reference cannot fix it and makes the diagnostic worse. crypto/tls selects the whitebox-reference model and then FALLS BACK to recompile (recordsRequireProductionMutation), which compiles the production .cs into the test assembly. example_test.go is the package’s ONLY black-box file, and its Examples reach http.Transport{TLSClientConfig: …}, http.Server{TLSConfig: …} and httptest.Server.TLS — fields whose type is tls_package.Config in the production assembly, while the recompile has just made a second, distinct local copy. The field is therefore unnameable. Referencing crypto.tls as well would let the compiler name the type and then reject the assignment between the two copies: CS0012 becomes CS0029. The file simply must not be compiled.

Which is what the Phase-4D file-exclusion ruling exists to do — go/token’s example_test.go is the recorded precedent, the same failure in the same model. crypto/tls’s file differs in one respect: its Examples need an io.Reader to hand Config.Rand, so it declares type zeroSource struct{} and one Read method, and condition (1) disqualified the whole file for it. Condition (1) now admits pure TYPE declarations and METHODS — they have no run-time behavior, nothing executes at package init, and any use by a retained file is a reference condition (2) already resolves. The type and method objects are now recorded in declared, without which the widening would have silently disarmed condition (2) for exactly the declarations it just admitted. var/const (initializer side effects) and plain funcs (init) stay disqualifying. Doctrine + the two new unit guards: ConversionStrategies-Reference.md, An Example/Benchmark-ONLY test file is dropped from the compile set.

Phase-4D widening confirmed by coordinator ruling 2026-08-15; the 2026-07-24 conservatism’s safety property is preserved by the declared-object fixpoint. The lane flagged this as a ruled boundary being moved rather than assuming it. The original ruling’s “conservative by design” existed to guarantee one property — nothing a test file declares can vanish from the compilation while anything still references it — and the widening RETAINS it, because the admitted type and method objects are recorded in declared, so condition (2)’s fixpoint still forces the file back in whenever a cross-file reference exists. What it removes is a false positive the alternative cannot fix at all (the recompile model’s duplicate type: adding the reference turns CS0012 into CS0029, never into a fix). A conservatism whose protective content survives the refinement intact is not weakened by it, only made precise. The three conditions the ruling attached are met and deliberately kept visible: the fixpoint protection is stated in the code where the widening lives (classifyTestFileForExclusion), the two unit guards (TestSelectCompileExcludedTestFilesDropsExampleWithHelperType, …KeepsHelperTypeUsedByRetainedTest), and the reference-doc section.

Root 2 — CS1503 is a delegate-VALUED comparator, and the first fix silently did nothing

isBetter := func(a, b uint16) int { …; return -1; …; return +1; …; return 0 } passed to slices.IsSortedFunc. All-INT-literal arms emit bare, so C# infers Func<ushort, ushort, int> against a Go int (nint) result. Every call of the variable accepts that (int converts to nint) — only the delegate-valued use rejects it, delegate types being invariant. convFuncLit now states the declared return type for that arm set.

Two things this cost, both worth carrying:

  1. numericBasicLit stripped unary - but not unary +. The comparator returns -1 AND +1, so the predicate saw a non-literal arm, suppressed itself, and the first fix emitted nothing at all — a green build of a converter that had not changed its output. Go writes an explicitly positive literal precisely where it pairs with a negative one, which is the comparator shape this arm exists for. Both signs are stripped now.
  2. Two successive cuts OVER-applied, and only the emitted C# showed it. The natural rule — “any numeric literal whose natural C# type differs from the declared result” — is wrong, because the converter does not emit every literal bare: a declared FLOATING result carries its width into the literal (func() float32 { return 0.5 }0.5F; func() float64 { return 3 }3D), so those already infer correctly. Only a declared INTEGER width other than int32 leaves the literal bare. A predicate about C# inference must be measured against emitted output, never derived from the Go-side type. All five shapes are pinned side by side in the extended FuncLitUntypedConstReturn behavioral guard.

Root 3 — CS7013: the hoisted-literal slug budget never bound the first word

Behind the other two, and previously unseen. key_schedule_test.go carries a 2,176-character hex test vector; the hoist slug is built from the literal’s own content and truncated at a word boundary within a 24-character budget — but the truncation only applied once the slug was non-empty, so the leading word was written whole. A literal that is ONE long word became an identifier of exactly its own length: CS7013: Name '…' exceeds the maximum length allowed in metadata.

The committed corpus was already past the design’s intent without failing — 33 of 5,928 hoisted names exceed 24 characters, the longest 256 — so this was luck, not a boundary case. Raising the number would not close the class; making the budget total does, and len(literalSlug(v)) ≤ 24 is now an invariant. Those 33 inline instead of hoisting; zero behavioral goldens move.

⚠ The A/B footprint splits 11 / 22, and the second half is LATENT — expect it, do not re-diagnose it. A seeded whole-corpus reconvert (single run, version.props + docs/validation seeded, marker gate 52 marked / 0 violations) emitted 1,960 files and changed 352, and the accounting closes exactly:

That is 10 of the 33 names. The other 23: one is crypto/x509/internal/macos/darwin/security.cs, darwin-exclusive and correctly not emitted by a windows-target run; the remaining 22 live in committed *_test.cs sources across ~14 banked packages (regexp, bufio, bytes, context, compress/flate, crypto/{dsa,ecdh,rsa}, encoding/base32, go/scanner, internal/cpu, mime/multipart, os/exec, path/filepath, strings), which a -stdlib run never re-emits. They will re-level at each package’s next -tests run and show up in a future sweep or leveling regen as unexplained-looking test-source churn. It is this fix, it is expected, and it is benign.

The host RUNS — 23 of 184 top-level tests before the deadline, and the roots are named

A live-diagnosis correction, recorded because the method failed, not just the guess. This lane first reported the host as “blocked from the start — 8.3 s CPU across twenty minutes, flat across eleven samples, I/O-blocked”. The CPU reading was accurate and the inference from it was wrong. The host runs the suite alphabetically and got through 23 top-level tests in roughly the first minute, most of them failing fast; it then blocked inside TestConnCloseBreakingWrite and spent the remaining ~38 minutes of the deadline parked there. The first CPU sample was taken about nine minutes in, by which time the interesting part was already over — so a flat sample series was read as “never started” when it meant “already finished the part that moves”. Flat CPU dates a stall; it cannot date its beginning. Sample from t=0 or read the verdict stream; do not infer a run’s shape from its tail.

The measured outcome (converted host, -test-timeout 40m): 26 pass, 12 fail, 1 infrastructure-error, 1 package timeout over 23 top-level tests reached. Go’s own run of the same suite on the same host executes 184 top-level tests (1,251 pass / 2,381 skip / 12 fail). Per the jsonrpc rule this board already applies, a package that dies mid-run has one failure and an unknown remainder — the 26 is a floor, not a census, and no differential is claimable from it.

Four distinct roots, in order of weight:

  1. localPipe: dial tcp 127.0.0.1:…: failed to find ConnectEx: An invalid argument was supplied. ×9 — the dominant one, and far more specific than “the net stack”. localPipe() dials a loopback listener, and the converted Windows socket layer cannot resolve the ConnectEx extension function pointer (WSAIoctl / SIO_GET_EXTENSION_FUNCTION_POINTER). Every handshake-driving test dies on it in ~2 ms. This is S2b netpoll + syscall territory and is the priority signal this lane hands over.
  2. TestConnCloseBreakingWrite blocks indefinitely and consumes the entire remaining deadline — the reason nothing past TestConn… was reached. A separate defect from #1 (it does not fail fast; it never returns) and the single highest-leverage fix for coverage, since the alphabet beyond it is unexplored.
  3. A golib REFLECT gap, not a net oneSystem.InvalidOperationException: Not a pointer box type: go.sync_package+RWState in GoReflect.slotAccessorShape (golib/GoReflect.FieldAccess.cs:125), reached reflect.DeepEqualdeepValueEqualdeepValueEqualBoxedreflect.Elem, from TestCloneNonFuncFields. sync.RWMutex’s RWState box is not recognised as a pointer box, so DeepEqual cannot descend a struct containing one. Independently actionable, unrelated to sockets, and it will bite any package that DeepEquals a struct holding a sync primitive.
  4. TestCertCache: “timed out waiting for expected ref count” — weak-reference/GC observability, the internal/weak family. TestBogoSuite fails against the external BoGo shim and is not a conversion signal.

One result worth stating: TestCipherSuites PASSES. That is the exact test whose comparator produced the CS1503 above — so root 2’s fix is validated by execution, not merely by compiling.

Go itself fails 4 top-level tests on this hostTestResumption, TestVerifyConnection, TestResumptionKeepsOCSPAndSCT, TestCrossVersionResume (12 verdicts with subtests). Whoever takes the differential must treat those as host-environmental until proven otherwise, exactly as the os SHARE_INFO_2 retraction demands.

Not banked, and not bankable on this evidence. Builds-and-partly-runs is precisely the partial result the roster’s validated-only integrity principle excludes — no roster row, no proof page, no disclosures. The three converter fixes and their guards are the deliverable; crypto/tls returns to the board as a package whose build question is answered, whose run question is now four named roots rather than one vague one, and whose next move is ConnectEx plus the TestConnCloseBreakingWrite hang.

✅ CLOSED (2026-08-15, lane claude/reflect-bridge-gaps) — both named reflect-bridge gaps, and one root that stood behind them

Two entries on this board named two independent defects. Both reproduced verbatim before anything was believed, both are fixed at the classification/routing principle rather than at the symptom, and a third root of the same family stood behind the first — the pattern this board keeps recording: a wall is only ever measured one layer at a time.

Gap 1 — abi.Type.Elem() / Key() (and Len()) reached their answer by prefix downcast

Recorded as “Elem()/Key() were never routed through the synthesis path, so they return nil and reflect.Name nil-derefs”. Accurate, and the reproduction is sharper than “returns nil”: a program whose first statement is reflect.TypeOf([]byte(nil)).ConvertibleTo(reflect.TypeOf(myBytes(nil))) panics before printing a single line.

That is the whole point of this root. StructType()/ArrayType() also answer nil when a layout is unknowable, and every Go caller of those tests the nil. Nothing tests Elem(). Go’s haveIdenticalType recurses straight into nameFor(t), which reads the descriptor’s carried System.Type — so a nil element is not a wrong answer that propagates, it is a nil dereference that ends the process. ConvertibleTo/AssignableTo over any operand that is not a scalar was unreachable corpus-wide.

Both are now hand-owned in internal/abi/type_impl.cs (Type.Elem / Type.Key in manualConversionFuncs) and synthesized from the carried System.Type over GoReflect.ElementType / KeyTypethe same golib resolution reflect’s own hand-owned rtype.Elem/rtype.Key already use one layer up, so the descriptor layer and the reflect layer cannot disagree about what an element type is. The array-dims cargo threads by the rule rtype.Elem already applies: an array’s element takes the tail of [outer]…[inner], a pointer’s dims are the pointee’s and pass through unshifted.

The third root, found by the guard rather than by the census. With Elem()/Key() fixed the new behavioral test still disagreed with go run on the ARRAY rows, and Type.Len() is why: it is the same downcast, and its failure is the nastiest of the three, because it does not answer nil — it reads a uintptr out of the memory following the descriptor’s value slot. Two [3]byte descriptors therefore read two DIFFERENT numbers and haveIdenticalUnderlyingType reported [3]byte and [3]byte as different types. Type.Len is hand-owned too, over the same carried dims; a length no source knew still answers Go’s 0, so two dimension-less array descriptors compare equal rather than randomly unequal.

Gap 2 — slotAccessorShape threw because pointer KIND was being read as pointer BOX

Recorded as a golib descent-rule sibling of the KindOf fix, and that attribution is exactly right. KindOf classifies every managed REFERENCE it does not otherwise recognize as Pointer — the fix that stopped the layout walks descending into the BCL object graph. What that settled was the LAYOUT question. It left a second, different question for the VALUE walks: is there a slot behind this handle at all?

For an opaque handle there is not — a hand-owned shim’s backing object (sync.Mutex’s SemaphoreSlim gate, sync.RWMutex’s RWState, sync.WaitGroup’s WaitGroupState) stands in for a representation Go has and the CLR does not, and nothing behind it has a Go form. So “one word wide, do not descend into it” is also “no slot, do not read through it” — one rule at two layers, of which only the first half had been stated.

reflect.Value.Elem asked the wrong one: it resolved a pointee with GoReflect.ElementType and, on null, fell through to a “detached read” through ReadPointerSlot, which classifies the box shape itself and threw Not a pointer box type: go.sync_package+RWState. The classification now lives in ONE place — GoReflect.TryPointerBoxElement, which slotAccessorShape is refactored onto — so “can I read through this?” and “what will I read?” can never be answered by two different probes. reflect.Value.Elem and its internal/reflectlite twin ask first and answer the invalid Value for a handle, which is what they already answer for a nil pointer.

The resulting blindness is Go’s own answer, not a concession. Go’s sync.RWMutex is state words, and a used-then-released lock is back at its zero state, so two of them are deeply equal — which is what two handles now compare as, whether or not the shim has lazily created one of them (crypto/tls’s TestCloneNonFuncFields is the measured consumer). Real Go state beside the handle is still seen: a sync.Once that has run differs from a fresh one, because done is an ordinary field and not part of the handle. The DeepEqual behavioral test asserts both directions.

Three adjacent roots this lane measured and deliberately did NOT chase

The new guard was written to cover every element-bearing kind and was then TRIMMED to what the corpus can truthfully produce, because a guard that asserts an answer the corpus cannot give is a standing false red, and one that asserts the current answer pins a defect as a contract. Each row removed is named here instead:

Surface Root Why not here
AssignableTo (all kinds) reflect’s rtype.AssignableTo is hand-owned as identity-on-the-managed-type plus interface-implements A recorded deferral — and as of this lane it HAS a measured consumer, database/sql’s TestUserDefinedBytes (see below). Still a of Go’s unnamed↔named underlying rule, and retiring it is not the one-liner it looks like: a synthesized descriptor never sets TFlagNamed, so HasName() is false for every type and directlyAssignable’s first gate would call two DISTINCT named types with one underlying type assignable — which Go rejects. Needs HasName before it needs the hand-own removed
STRUCT identity haveIdenticalUnderlyingType’s struct arm downcasts ж<abi.Type> to structType DIRECTLY rather than through the synthesized StructType() Reads zero fields, so any two structs of equal field count compare identical — a silent FALSE POSITIVE, measured: struct{B []byte; M map[string]int; …} and the same struct with M map[string]int64 are reported convertible. The fix is a reflect-level hand-own, not an abi one
CHAN identity its arm compares ChanDir() first, the same downcast The only one of the family with no synthesis waiting for it: <-chan int and chan int are both channel<nint>, so a direction is not recoverable from the managed type for an unnamed directional channel. This one needs a ruling, not a fix

MapType(), FuncType() and InterfaceType() remain on the same list for the reason they always were — each awaits a measured consumer, and a synthesized ΔMapType would have to populate runtime-map fields (Hasher, KeySize, the indirect-key/elem flags) that have no managed answer.

Guards

Doctrine: ConversionStrategies-Reference.mdabi.Type’s SPECIALIZATIONS are synthesized, not downcast (extended with Elem/Key/Len) and A managed REFERENCE is a Go pointer, not a Go struct (extended with a new subsection, The VALUE side of the same rule: pointer KIND is not pointer BOX).

Corpus footprint, measured

A seeded whole-stdlib reconvert A/Bs to exactly one regenerated file, internal/abi/type.cs (three Go bodies replaced by placeholders). Two families of difference the same A/B reports are pre-existing regen debt at master, not this lane’s and deliberately not carried: 300 package README.md files (the merged badge-line break awaiting its re-level) and seven .cs files (crypto/elliptic/nistec.cs, go/parser/parser.cs, image/ycbcr.cs, os/exec/windows/lp_windows.cs, runtime/windows/os_windows.cs, syscall/windows/syscall_windows.cs, testing/slogtest/slogtest.cs — string-literal hoisting and the variadic untyped-nil cast, both banked without a corpus regen).

database/sql — measured again after the fixes: 135 of 139, and the last non-alloc row is now ROOTED

Re-run end to end (-test-action all -test-timeout 60m; 3,423 s under concurrent lane load, vs the 1,712 s the previous lane measured solo — the machine, not the package). The census moves from 133 agreeing to 135, and the change is exactly what this lane predicted plus one it did not:

Row Before Now
TestConversions abi.Type.Elem() nil → reflect.Name nil-deref passes
TestUserDefinedBytes the same nil-deref fails DIFFERENTLY, and the new failure is rooted below
TestGrabConnAllocs, TestRawBytesAllocs AllocsPerRun want-zero unchanged — the standing alloc-profile class
TestConnRaw conn.dc non-nil after a callback panic unchanged, still unrooted

It does NOT bank, and the reason is worth more than the row would have been.

TestUserDefinedBytes is now a MEASURED CONSUMER of the AssignableTo deferral. The test does convertAssign(&u, v) with u userDefinedBytes (a named []byte) and v []byte, then asserts &u[0] != &v[0] — “got potentially dirty driver memory”. convertAssignRows has two arms in sequence:

if (sv.IsValid() && sv.Type().AssignableTo(dv.Type())) {
    case slice<byte> b: dv.Set(reflect.ValueOf(bytes.Clone(b)));   // arm 1 — CLONES
}
if (dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type())) {
    dv.Set(sv.Convert(dv.Type()));                                 // arm 2 — SHARES the array
}

Go takes arm 1: []byte is an unnamed type and userDefinedBytes is named with the identical underlying type, so Go’s assignability rule admits it, and the []byte case clones. The converted run takes arm 2, because rtype.AssignableTo is hand-owned as identity-on-the-managed-type and the two managed types are distinct — so Convert hands back a view over the driver’s own array and the assertion fires.

Two things follow. First, arm 2 is reachable at all only because this lane’s Elem() fix made ConvertibleTo answer: before it, the same row died in nameFor(nil). The fix moved the test from a panic to the wrong arm, which is progress and is also how the root became visible. Second, the AssignableTo entry in the table above should now be read as has a named consumer, not “awaits one” — and the sequence it needs is fixed: HasName() must become truthful on a synthesized descriptor before the hand-own can retire, and the struct and chan arms of haveIdenticalUnderlyingType must be fixed with it, or retiring the hand-own trades one wrong answer for a wider one (every two structs of equal field count would become assignable). That is a second arc of this same family, not a tail of this one.

The $longTimeouts floor is still owed if it ever banks'database/sql' = '60m', per the previous lane’s note. Nothing is added today: the package has no roster row, and a floor for a package the sweep never visits would be dead configuration.

Converted test sources are not committed, per the validated-package commit policy — that policy covers packages that validate, and this one does not.

⛔ RE-MEASURED, DOES NOT BANK — crypto/tls reaches 127 of 180 Go-passing top-level tests; three of the four named roots are CLOSED and the whole remaining divergence is ONE converter defect class in two shapes (2026-08-16, lane claude/tls-remeasure)

The 2026-08-15 entry left four roots and said the next move was ConnectEx plus the TestConnCloseBreakingWrite hang. Both landed (netpoll S2b), the reflect-bridge lane closed a third, and this lane re-measured the package on the post-poller tree. The three fixes all hold, and none of the remaining divergence is a poller defect.

The headline, and the number that went DOWN

Measure 2026-08-15 now
top-level tests the ONE-PROCESS run reaches before parking 23 7
top-level tests that PASS when each is run on its own not measured 127 of 180
C# verdicts agreeing with Go 26 (floor, uncensused) 127 top-level / 274 incl. subtests
divergences rooted or named 4 roots, remainder unknown 53 of 53, zero unrooted

The single-process floor got WORSE, and that is progress, not regression — do not read the 23 → 7 as a loss. Under the old ConnectEx failure every handshake-driving test died in ~2 ms, so the alphabet advanced fast and then parked. Those same tests now do real work, and one of them parks forever, so a single package deadline buys fewer completed tests than a fast-failing suite did. Once a suite contains a hang, “how far the one-process run gets” stops measuring capability and starts measuring where the first hang sits. The per-test census below is the measurement that answers the question the floor was standing in for; the floor number is kept only because it is what the previous entry recorded.

Method. Each of the 190 top-level Test* functions in the staged _test.go set was run in its OWN process (-run '^Name$' -timeout 20s), so one hang costs 20 s instead of the whole package deadline. 6 of the 190 are //go:build boringcrypto and are absent from Go’s run — 190 − 6 = 184, which is exactly the count Go executes, so the enumeration is confirmed rather than assumed. Go’s own run on this host reproduces the previous entry’s numbers exactly: 184 top-level (180 pass, 4 fail); 1,251 pass / 2,381 skip / 12 fail at all levels, 46.5 s.

C# per-test: 127 PASS, 34 FAIL, 13 HANG, 9 infrastructure-error, 1 process CRASH = 184, and the cross-tab against Go closes with no remainder: 127 agreeing passes + 53 real divergences (Go passes, C# does not) + 4 both-fail + 6 build-tag-excluded = 190.

The four roots, re-measured

# (2026-08-15) verdict now evidence
1. failed to find ConnectEx ×9 CLOSED zero occurrences of the string across all 190 test outputs; ESTABLISHED loopback pairs observed live (127.0.0.1:42281 ↔ :42159); full TLS 1.2 handshakes complete end-to-end (TestClientAuth/TLSv12, TestConnectionState/TLSv12, …)
2. TestConnCloseBreakingWrite blocks forever CLOSED PASSES, 3.5 s. S2b’s second acceptance anchor holds at suite level, not just at golib level
3. Not a pointer box type: sync_package+RWState CLOSED zero occurrences corpus-wide in the outputs; TestCloneNonFuncFields PASSES
4. TestCertCache ref-count timing still open timed out waiting for expected ref count — unchanged, 1 divergence

The 53 divergences, every one rooted

count root
30 AticketKey.aesKey is length 0 → tls: failed to create cipher while encrypting ticket: crypto/aes: invalid key size 0. Every TLS 1.3 session-ticket path
10 Bnet/netip.As16 slices a zero-length array → System.ArgumentException, full stack captured
7 B-shaped — silent hang, zero verdicts: server parked in pollBlock reading the ClientHello while the client goroutine is gone. Same stack shape as B; the killing exception is NOT captured (see “why it hangs”)
2 F — hang after partial progress (TestConnectionState, TestHandshakeKyber: TLS 1.2 subtests pass, the 1.3 subtest parks)
1 DTestCertCache weak-ref timing (root 4 above)
1 ETestBogoSuite against the external BoGo shim; not a conversion signal
1 GTestQUICHandshakeError: panic: runtime error: invalid memory address or nil pointer dereference at golib/ж.cs:957 ж<T>.op_OnesComplement
1 HTestVerifyHostname: process access violation 0xC0000005 in syscall.GetAddrInfoW

A and B are the SAME converter defect, and together they are 47 of the 53.

The root: the zero value of an array-bearing type is emitted as default!

C# default produces the all-zero value and does not run field initializers — those run only through a constructor. go2cs’s array<T> is a struct carrying its own m_length, so a default one has length 0, not N.

Shape 1 — a [N]T named return. net/netip’s func (ip Addr) As16() (a16 [16]byte) becomes (src/core/net/netip/netip.cs:718):

public static array<byte> /*a16*/ As16(this ΔAddr ip) {
    array<byte> a16 = default!;                      // ← length 0, not [16]byte
    byteorder.BePutUint64(a16[..8], ip.addr.hi);     // ← throws here

Shape 2 — a struct whose fields are fixed arrays. crypto/tls’s ticketKey declares internal array<byte> aesKey = new(16);, and ticketKeyFromBytes opens with ticketKey key = default!; (src/core/crypto/tls/common.cs:747) — which skips exactly that initializer, so copy(key.aesKey[..], …) copies nothing and aes.NewCipher is handed 0 bytes.

The converter already knows the right emission — three lines from the broken one, src/core/net/ip.cs:529 builds the same zero value correctly as new byte[]{}.array(16) in composite-literal position. Only the zero-value declaration path emits default!.

Census (Windows target, committed corpus — this is a SHIPPED defect, not something this run emitted; git status on src/core/net/netip is clean).

Why it HANGS instead of failing, which is the expensive half. The throw is a plain ArgumentException from slice<T>’s constructor (golib/slice.cs:227), not the RuntimeErrorPanic.SliceBoundsOutOfRange that the reslice path raises. A non-panic exception satisfies Goroutine.CanContain, so the test host contains it and records it on the TestExecution. If the test then completes, the record flushes and you get the INFRASTRUCTURE-ERROR line with the full stack (the 10 B rows). If the dying goroutine was the one another goroutine was waiting on, the test never completes, the record never flushes, and the whole package deadline burns with no output at all (the 7 B-shaped rows). A Go-visible panic: here would have failed 17 tests loudly in milliseconds instead.

The one-line reproducer for whoever takes this: TestHostnameInSNI hangs — a pure table test with no sockets and no TLS, whose table contains IP literals. It reaches hostnameInSNI → net.ParseIP → parseIP → As16 and dies. As16 is only reached when the string genuinely parses as an IP, which is why hostname-SNI TLS 1.2 handshakes pass while everything touching 127.0.0.1 dies. The second reach path is server-side and equally common: x509.VerifyHostname → net.ParseIP → As16 (captured in TestFallbackSCSV’s stack).

Not fixed here, deliberately. It is a converter emission change with a corpus-wide blast radius (34 candidate sites across 5+ packages), so it owes the converter gate set plus a seeded reconvert — its own lane, with a behavioral guard pinning both shapes (a [N]T named return and a zero-valued struct with an array field) and the slice<T> constructor’s panic-vs-exception question decided alongside it.

Two singletons worth their own lines

Go’s own 4 failures are EXPIRED TEST FIXTURES, not a host defect

The previous entry flagged TestResumption, TestVerifyConnection, TestResumptionKeepsOCSPAndSCT and TestCrossVersionResume as “host-environmental until proven otherwise”. They are, and the reason is now named: the certificates those tests carry expired on 2025-01-01, and this host’s clock is 2026-08-16

x509: certificate has expired or is not yet valid:
current time 2026-08-16T02:57:52-05:00 is after 2025-01-01T00:00:00Z

Both languages fail them identically, so they are correctly excluded from the differential (C# turns them into hangs rather than failures, which is root B’s containment behavior again). This will worsen with time on any machine, and it caps crypto/tls at 180 of 184 no matter what go2cs does — whoever eventually banks this package needs that fact before reading a differential, and it is a Go-toolchain-version property, not a go2cs one.

Not banked, and not bankable

Builds-and-partly-runs, exactly as the previous entry. No roster row, no proof page, no disclosures — none of the 53 divergences is a disclosed-divergence candidate under alloc-profile, codegen-liveness or host-limit: every one is either a real defect go2cs owns or an environmental fixture expiry. Converted test sources are not committed, per the validated-package commit policy.

Next move, in dependency order: the default! zero-value emission (closes 47 of 53 at one stroke), then GetAddrInfoW, then the two singletons, then TestCertCache. The 7 B-shaped silent hangs should be re-measured after the emission fix rather than investigated now — the cheapest way to learn whether they were the same root is to remove the root.

Method notes worth keeping

⛔ STILL DOES NOT BANK — the default! zero-value emission LANDED and crypto/tls goes 127 → 163 of 184; the remaining wall is ONE pre-existing root worth 10 tests (2026-08-16, lane claude/zero-value-arrays)

The previous entry’s “next move, in dependency order” opened with the default! zero-value emission and priced it at 47 of 53. It landed (claude/zero-value-arrays), and the re-census on the same per-test method — one process per top-level Test*, -run '^Name$' -timeout 20s, the same 184 non-boringcrypto functions — measures 163 PASS, 0 FAIL, 4 HANG, 14 CRASH, 3 infrastructure-error in 691 s.

Measure 2026-08-16 (pre-fix) now
top-level tests that PASS run on their own 127 of 184 163 of 184
real divergences (Go passes, C# does not) 53 17
distinct roots behind them 8 5

Four of the 21 non-passing are Go’s OWN expired-certificate failures (TestResumption, TestResumptionKeepsOCSPAndSCT, TestVerifyConnection, TestCrossVersionResume) — unchanged, both languages fail them, still the 180-of-184 ceiling this host cannot beat. 184 − 163 − 4 = 17.

The roots that closed

Roots A (30 tests, ticketKey.aesKey length 0 → aes: invalid key size 0) and B + B-shaped (17 tests, netip.As16 slicing a zero-length array) were the same defect and are both CLOSED. Every TLS 1.3 session-ticket path now passes — TestQUICSessionResumption, TestQUICEarlyData, TestQUICPostHandshakeKeyUpdate and the rest of the QUIC family are green — and the 7 silent B-shaped hangs are gone, which answers the previous entry’s open question: they were the same root, and removing it was indeed the cheapest way to learn that. Root F (hang after partial progress) is also gone as a category.

The root that was HIDING behind them — 10 of the remaining 17

TestHostnameInSNI, the previous entry’s one-line reproducer, no longer hangs: it drives a real handshake and dies further down, in sha3.copyOut, with a fatal AccessViolationException reading d.storage[..rate] — i.e. ref state d does not address a valid managed state. Reach: mlkem768.NewKeyFromSeed → kemKeyGen → sha3.Sum512, which every TLS 1.3 ClientHello performs, so it now claims 10 tests: TestDialTimeout, TestHandshakeKyber, TestHostnameInSNI, TestKyberDecapsulate, TestKyberEncapsulate, TestSCTHandshake, TestServerSelectingUnconfiguredApplicationProtocol, TestServerSelectingUnconfiguredCipherSuite, TestTLS13OnlyClientHelloCipherSuite, TestVersion.

It is PRE-EXISTING, and that was proven rather than assumed. The suspicion is natural — this lane changed sha3’s four Sum* functions, whose digest [N]byte named results were four of its 14 sites — so the four sites were reverted to default!, the host rebuilt, and the test re-run: the identical stack reproduces. The in argument plays no part in the faulting path either (Sum fills a freshly make-d buffer and only touches in in the closing append). It was simply unreachable behind the netip wall.

The remaining 17, by root

count root status
10 sha3.copyOut AccessViolation via mlkem768 key-gen — ref state not addressing managed storage NEW HEAD OF THE QUEUE; pre-existing, newly reached
3 hangs with no output: TestCipherSuitePreference, TestConnectionState, TestDialer unrooted — first census with no output at all to root them
1 TestVerifyHostname — process AV 0xC0000005 in syscall.GetAddrInfoW unchanged; the open non-blittable-syscall class
1 TestQUICHandshakeErrorж<T>.op_OnesComplement nil-deref unchanged singleton
1 TestCertCache — weak-ref timing unchanged (old root 4)
1 TestBogoSuite — external BoGo shim not a conversion signal

Next move, in dependency order: the sha3/mlkem768 ref state AccessViolation (10 at one stroke, and it is a POINTER-materialization defect, so it likely reaches well past crypto/tls), then re-census the 3 unrooted hangs after it — the same “remove the root before investigating what sits behind it” that just paid off here — then GetAddrInfoW, then the two singletons.

Still builds-and-partly-runs; no roster row, no proof page, no disclosures, converted test sources not committed.

⛔ STILL DOES NOT BANK — the sha3 AccessViolation closes and crypto/tls goes 163 → 176 of 184; real divergences 17 → 4, and every one of them was already on this board (2026-08-16, lane claude/sha3-copyout-av)

The previous entry named the sha3.copyOut AccessViolation the new head of the queue, priced it at 10, and said to re-census the 3 unrooted hangs after removing it rather than investigating them first. Both calls were right, and the second paid better than the first: the same per-test method — one process per top-level Test*, -run '^Name$' -timeout 20s, the same 184 non-boringcrypto functions — now measures 176 PASS, 7 FAIL, 0 HANG, 1 CRASH, 0 infrastructure-error in 728 s.

Measure 2026-08-16 (pre-default!) after default! now
top-level tests that PASS run on their own 127 of 184 163 of 184 176 of 184
real divergences (Go passes, C# does not) 53 17 4
distinct roots behind them 8 5 3
silent HANGs 13 4 0

184 − 176 = 8 non-passing, and four of those are Go’s OWN expired-certificate failures (TestResumption, TestResumptionKeepsOCSPAndSCT, TestVerifyConnection, TestCrossVersionResume) — unchanged, both languages fail them, still the 180-of-184 ceiling this host cannot beat. So the real divergence count is 4.

What closed

All 10 of the tests the previous entry attributed to this root now PASS: TestDialTimeout, TestHandshakeKyber, TestHostnameInSNI, TestKyberDecapsulate, TestKyberEncapsulate, TestSCTHandshake, TestServerSelectingUnconfiguredApplicationProtocol, TestServerSelectingUnconfiguredCipherSuite, TestTLS13OnlyClientHelloCipherSuite, TestVersion.

And so do all 3 of the unrooted silent hangs — TestCipherSuitePreference, TestConnectionState, TestDialer. They were the same root, exactly as the “remove the root before investigating what sits behind it” heuristic predicted for the second time running. The package now has zero hangs, which is worth recording on its own: a suite that contains a hang cannot be measured by a single-process run at all (the 23 → 7 floor the 2026-08-16 entry had to explain away), and this one no longer does.

The root, named exactly — and it was not about sha3, or about crypto, or about the OS

The previous entry read the fault as “ref state not addressing managed storage”. That is the symptom, not the defect, and the receiver is fine. Reproduced in 25 lines with no crypto in it:

type state struct{ a [4]uint64 }
var d state
ab := (*[32]byte)(unsafe.Pointer(&d.a))
copy(b, ab[:])                            // Fatal AccessViolationException

which emits, identically to sha3’s xorIn/copyOut:

var ab = (ж<array<byte>>)(uintptr)(new @unsafe.Pointer(d.of(state.a)));
copy(b, (~ab)[..]);

A byte[] view over a uint64[] has no managed spelling, so pointerReinterpretManagedSource correctly excludes pointer-to-ARRAY targets and the site keeps the raw-address route. That box is a perfectly good address. The defect is the dereference: ~ab reads an array<byte> STRUCT — a backing-store reference plus bounds — out of the keccak state’s own DATA, fabricating a managed reference. The stack the previous entry recorded is the fabricated reference being used, one frame removed from the reinterpret that built it:

System.AccessViolationException
   at go.slice`1[Byte]..ctor(Byte[], IntPtr, IntPtr, IntPtr)
   at go.array`1[Byte].Slice(Int32, Int32)
   at go.array`1[Byte].get_Item(System.Range)

⚠ The previous entry’s “reading d.storage[..rate]” is a misattribution worth correcting: that is the argument at the call site, and it evaluates fine. The fault is inside copyOut’s own ab[:].

The two fixes, at the two layers that have one

No general fix exists, and that is the finding rather than a shortfall: slice<T>/array<T> are windows on a real T[], and a U[] view over a V[] cannot be constructed. This is the raw-metal fork the S1/CS0030 ruling already governs, and crypto/subtle’s xor_generic.cs is the same case one type-pair over (a uintptr[] view over a byte[]).

The site is src/core/vendor/golang.org/x/crypto/sha3/xor.cs, which takes [module: GoManualConversion] and crypto/subtle’s remedy: MemoryMarshal.AsBytes over the state array’s own span is a genuine ALIASING view, so the absorb’s XOR lands in the real state and the squeeze reads it. Go’s cpu.IsBigEndian branch is left exactly as converted. Marker census 58 → 59.

The layer fix was BUILT, MEASURED, and REJECTED — and the reasons are the most transferable thing in this entry. An AccessViolation is the worst available failure mode (uncatchable, no diagnostic, names the innocent consumer), so ж<T> was made to refuse the read with a contained panic instead. Two counter-examples killed it, both found by gates rather than by reasoning:

  1. RuntimeHelpers.IsReferenceOrContainsReferences<T>() alone is too WIDE. time.syncTimer is return ~Ꮡc.Reinterpret<channel<Time>, unsafe.Pointer>(), and unsafe.Pointer is a CLASS, so it lands on the same address route — but reading it yields the REAL channel object, type-CONFUSED rather than fabricated. That is the managed-referent model the corpus is built on. The wide form took down time.NewTimer, and with it every crypto/tls test that opens a pipe, on the first host run. Adding a typeof(T).IsValueType term fixed that one.
  2. The narrowed form still regressed ArrayCastDerefClone — the behavioral guard for this very fork, caught by the full suite (Output exit code mismatch: C# 2 vs Go 0). Its *(*Row)(unsafe.Pointer(&r)) over a ZERO-valued array reads an array<nint> whose fabricated backing reference is null, and array<T>’s null-safe zero value absorbs it — so the site produced garbage harmlessly, which is exactly the “compiles and does not crash” bar the S1 ruling sets for raw-metal stubs. The refusal converted a tolerated stub into a panic.

The distinction the remedy actually needs is not the pointee’s TYPE but whether the fabricated reference comes out NULL — benign when it does, fatal when it does not. That cannot be tested without first materializing a T with a wild reference in a stack slot, which is itself unsafe (a GC scanning that slot is the same crash). So the class keeps the AccessViolation, and the sketch above is the starting point for whoever revisits it. Anyone tempted to re-add the refusal should read this paragraph first: the wide form and the narrow form have both already been tried.

Census of the class, and of the siblings

The emitted shape appears in 43 corpus files. Almost all are either address-only (never dereferenced) or in runtime raw-metal paths nothing reaches — runtime and its per-GOOS folders hold 26 of the 43. The live remainder is internal/syscall/windows/registry’s SetDWordValue/SetQWordValue, reflect/runtime’s name.pkgPath, internal/reflectlite, internal/poll/windows, syscall/windows and go/types. Each is now a named panic instead of a process kill, and each gets fixed when a suite reaches it — not speculatively.

Sibling vendored crypto is CLEAN. sha3’s two lines are the only (*[N]T)(unsafe.Pointer(…)) in all of vendor/golang.org/x/crypto: chacha20, chacha20poly1305, internal/poly1305, curve25519, hkdf and cryptobyte use none (the only other unsafe.Pointer uses are internal/alias’s address COMPARISONS, which are not reinterprets), and blake2b is not vendored into GOROOT at all. The suspicion that “they use similar state tricks” does not survive the census.

The 4 that remain, by root

count root status
1 TestVerifyHostname — process AV 0xC0000005 in syscall.GetAddrInfoW unchanged; the open non-blittable-syscall class. Now the head of the queue
1 TestQUICHandshakeErrorж<T>.op_OnesComplement nil-deref on a goroutine unchanged singleton
1 TestCertCache — weak-ref timing unchanged (old root 4)
1 TestBogoSuite — external BoGo shim not a conversion signal

Next move: GetAddrInfoW (the last root shared with another package — net and crypto/x509 both want it), then the op_OnesComplement singleton. TestCertCache and TestBogoSuite are not worth an arc: one is a GC-timing assertion the CLR cannot be made to satisfy on demand, the other is an external binary. At 4 divergences, the question crypto/tls now poses is a banking question, not a debugging one — what a roster row and a disclosure manifest would have to say about the four.

Guarding, honestly

No behavioral guard is available for the sha3 fix, and the reason is structural rather than an omission: the vendored package has no _test.go in GOROOT, and a behavioral test cannot import golang.org/x/crypto/sha3 (the converter resolves it to core/golang.org/…, not core/vendor/golang.org/…). What guards it instead:

Neutered-fix control, run rather than asserted. Restoring the auto-converted xor.cs does not merely fail the four vector tests — it KILLS the test host with an AccessViolationException inside slice<byte>..ctor. That is the defect’s whole character, and the reason the layer fix above was attempted at all: expect a dead host, not a red test, if you re-run that control.

crypto/internal/mlkem768 is the natural operational guard and it is BLOCKED, on two defects that have nothing to do with any of this. Its suite is the direct consumer of sha3 (TestRoundTrip, TestPQCrystalsAccumulated and the field/compress vectors all drive SHAKE), and the -tests pipeline converts it but cannot build it:

Fixing those two would bank mlkem768 as a roster package AND retire this guard gap in one arc; it is the cheapest available roster growth in the crypto tree.

CLOSED 2026-08-16 by lane claude/mlkem768. Both defects are fixed at their real layer, the package banks 12/12 · 0 disclosed as roster row 147, and the guard gap this paragraph opened is retired. One correction to the bullets above: the second defect is not an emission-ordering defect — nothing is emitted out of order. It is a package-level const taking neither half of the local-shadow defence, and it produces CS0165 as readily as CS0841 depending only on whether the C# declaration says var. See the entry at the end of this board.

Method notes worth keeping

crypto/internal/mlkem768 BANKS 12/12 — sha3’s only available operational guard, and the two defects that stood in front of it (2026-08-16, lane claude/mlkem768)

The sha3 entry above closed an AccessViolation and then recorded a gap it could not close itself: crypto/internal/mlkem768 is the only operational guard the hand-owned sha3 fix can have — the vendored golang.org/x/crypto/sha3 ships no GOROOT test suite and is not importable from a behavioral test — and the -tests pipeline converted mlkem768 but could not build it, on two defects that had nothing to do with sha3, with crypto, or with each other. Both are now fixed at their real layer, both proven failing-first by behavioral guards, and the package banks 12 matched · 0 disclosed as roster row 147 (16,573 verdicts, 68.4%).

Defect 1 — CS0315: a named numeric was ordered by OPERATORS but was not IComparable<T>

min(a-got, got-a, a-got+q, got-a+q) over type fieldElement uint16 (TestDecompressCompress) did not compile: “no boxing conversion from fieldElement to System.IComparable<fieldElement>.

Ordering has two surfaces in .NET and the generated [GoType num:] wrapper carried only one. IComparisonOperators<T,T,bool> — which the wrapper declares — serves a constraint lifted from cmp.Ordered, and golib’s TWO-argument min/max bind it. The N-argument forms cannot: a params ReadOnlySpan<T> element has to compare through a member, so they bind IComparable<T>, which the wrapper did not implement. So a named numeric bound min(a, b) and failed min(a, b, c, d) — an arity cliff, which is why nothing had tripped it before.

Fixed in go2cs-gen, not at the call site and not by relaxing golib’s constraint: InheritedTypeTemplate declares IComparable<T> on the same kind-gate as IComparisonOperators (every numeric kind except complex, which Go orders no more than C# does) and NumericTypeTemplate emits its one member inside the same gated block, CompareTo forwarding to the UNDERLYING value’s:

public int CompareTo(fieldElement other) => m_value.CompareTo(other.m_value);

Forwarding rather than re-deriving from the wrapper’s own </> is deliberate — it keeps a named float on the BCL total order (NaN below everything), which is what makes min yield NaN when any argument is NaN, as Go’s does. The wrapper was already IEquatable<T>; it is ordered now too, matching the golib uintptr and @string structs, which are both.

Why this layer and not golib. Relaxing the params overload to IComparisonOperators was the alternative, and it is the wrong general fix: the two overloads differ only in constraint, so it would have to REPLACE the IComparable<T> one, silently withdrawing N-argument min/max from every type that is comparable without carrying operators. The wrapper’s missing interface was the actual incompleteness, and filling it also buys Array/List.Sort, SortedSet<T> and Comparer<T>.Default over every named numeric in the corpus.

Defect 2 — CS0841/CS0165: a package-level CONST had NEITHER half of the local-shadow defence

q := big.NewInt(q) (TestZetas, TestGammas, over const q = 3329). Legal Go — a short variable declaration’s scope begins after its own ValueSpec, so the initializer reads the constant — and illegal C#, where the local owns the name for the whole block and its own initializer binds to it.

The converter already defends this shape twice over, and both defences are keyed to *types.Var:

A *types.Const fell between them. Fixed by giving convIdent a const arm beside the var arm, with one deliberate difference: it consults funcScopeVarNames (every variable declared ANYWHERE in the function) rather than funcLevelDecls (declarations directly in the function body), because the same shape inside an if/for init is not function-level and the const has no rename to fall back on. Qualifying a reference no local actually shadows costs verbosity and never changes meaning, so the wider set is the safe side to err on.

The board’s characterization of this one was wrong and is corrected in place above. It reads as an “emission-ordering defect”; nothing is emitted out of order. It is also not reliably CS0841: that diagnostic appears only when the C# declaration infers var (which big.NewInt does). Declare the type and the identical Go source produces CS0165, “use of unassigned local variable” — one root, two diagnostics, which is why the guard carries both shapes.

Guards, proven failing-first

Both fixes extend an EXISTING behavioral project rather than adding one — each defect is a new member of a family that project already documents.

Guard Extension Pre-fix verdict
MinMaxBuiltin min/max at 2 and 4 arguments over named unsigned (fieldElement uint16, including mlkem768’s own four-argument call), floating (ratio float64) and signed (delta int8) underlyings CS0315 ×10 across all three kinds
GlobalShadowedByLocal a self-referencing initializer at function level, the same shape in an if init, the var-inferred form that reproduces mlkem768’s exact CS0841, and an unshadowed control that must keep the bare name CS0165 ×2 (explicit-type shapes); the var shape adds the CS0841

Both were run with the fixes reverted and the binaries rebuilt, not asserted.

Corpus footprint: measured at ZERO

A seeded -stdlib reconvert (5,055 files seeded incl. version.props + docs/validation; 59 [module: GoManualConversion] files, 0 marker-gate violations; 1,664 files emitted) produced 50 CRLF phantoms and exactly ONE real content diffcrypto/internal/mlkem768/README.md, whose Tests badge flips to 12/12 validated because the package now has a proof page. That is the bank’s own artifact, not the converter’s. So the const arm changes no stdlib package’s emission: no converted function both references a package const and declares a same-named local. The generator change is compile-time and changes no emission at all; its gate is the corpus BUILD, which is clean.

The census, honestly

12 of 12 top-level tests match, 0 disclosed, 0 skipped, 4 declarations excluded on both sides (BenchmarkKeyGen, BenchmarkEncaps, BenchmarkDecaps, BenchmarkRoundTrip — the standard Phase-4D benchmark deferral). No third defect appeared behind the two: the suite built and ran first try once they were fixed, which is worth recording against this week’s pattern.

C# runtime, i7-5820K, solo: 434.7 s, and it is one test —

Test C# elapsed
TestPQCrystalsAccumulated 417.3 s
TestFieldReduce 7.4 s
TestFieldSub 3.2 s
TestFieldMul 2.8 s
TestFieldAdd 2.6 s
the other seven < 1 s each

TestPQCrystalsAccumulated runs 10,000 full key-gen/encapsulate/decapsulate rounds and accumulates them into a SHAKE-128 digest. 435 s clears the sweep’s 10 m default by only 1.4x — inside the spread a loaded sweep produces — so this package is owed a $longTimeouts floor and gets 30m (4x headroom). That is the sixth entry in that table; the rule it follows is unchanged (a floor, not an override — a larger -TestTimeout still raises it).

The guard gap closes

mlkem768’s production code drives sha3.Sum512, New512, Sum256, New256, NewShake256 and NewShake128 — six entry points across key generation, encapsulation, decapsulation and the sampling PRFs — and every one of them is on the path of the banked verdicts. The sha3 entry’s hand-owned xor.cs (the array-reinterpret fix) therefore now has a committed suite that exercises it on every sweep, which is exactly what that entry said it lacked. crypto/tls’s 176-of-184 remains the wider witness; this is the repeatable one.

Run, not asserted — the neutered-fix control. Restoring the auto-converted xor.cs over the hand-own (its .cs.auto sibling is committed beside it) and rebuilding kills the mlkem768 test host outright, exactly as the sha3 entry predicted for crypto/tls: a fatal AccessViolationException, exit 0xC0000005, on the stack

go.slice<byte>..ctor  ←  array<byte>.Slice  ←  sha3.copyOut  ←  padAndPermute  ←  Read
                      ←  sha3.Sum  ←  Sum512  ←  mlkem768.kemKeyGen  ←  generateKey
                      ←  GenerateKey  ←  TestBadLengths

The host dies at the FIRST test that generates a key, so the comparison reports Go="pass" C#="" for all 12 rows — the documented dead-host signature, not twelve failures. Restore the hand-own and all 12 pass again. That is the guard doing its job, measured on the committed suite.

A restore-from-backup trap, worth one line because it cost a run. Copy-Item PRESERVES the source file’s LastWriteTime, so copying a backup back over xor.cs gives the restored file an older timestamp than the neutered build’s assembly — MSBuild judges the project up to date and keeps the NEUTERED dll. The re-run then reproduces the AV with clean, HEAD-matching source and a git status that shows nothing at all. Touch the restored file (or build --no-incremental) before believing any verdict that follows a hand-own swap.

🔎 SCOUT — golib’s name surface leaks into every converted package, and ONE shape of collision compiles and lies (2026-08-16, lane claude/golib-name-scout)

FIX IS QUEUED, NOT PRESSING (user ruling at the time this was raised). Nothing in the corpus is broken today; the exposure is user-code- and -recurse-facing. This entry is the census, the measured resolution matrix, the defense recommendation and the guard spec, so the fix lane starts from measurement rather than from re-derivation.

The question (user-raised)

golib publishes helper names into the scope every converted file compiles under — builtin members (mapclone, tmpstring, subslice3, …), the runtime types (slice, map, @string, error, …), extension methods. End-user Go code may legally declare identifiers with those exact names, and Go even permits shadowing its own predeclared identifiers (func len(...) at package scope is legal Go — verified, not assumed). What happens in the emitted C#?

The scope converted code compiles under — measured from the emission, not from doctrine

Three channels, and only the first is obvious:

  1. <Using Include="go.builtin" Static="True" /> in csproj-template.xml (every converted .csproj carries it) — every public static member of go.builtin is a bare simple name in every converted file.
  2. namespace go; — converted code is emitted inside golib’s own namespace, so every public golib type there (slice<T>, map<K,V>, channel<T>, array<T>, @string, error, sstring, uintptr, comparable<T>, complex64, ж<T>, …) is a bare type name, and the nested namespace go.golib is a bare namespace name.
  3. The alias <Using> itemsany, uint8uint64, int8int64, float32/64, complex128, rune, GoBigConst, GoTagAttribute, GoInitAttribute — plus using System; and using System.Numerics;. package_info.cs adds using go; + using static go.<pkg>_package;.

src/core/GlobalUsings.cs is DEAD and misleads a reader into thinking it is the mechanism. There is no .csproj at src/core/; golib.csproj has no explicit <Compile> and SDK globbing is rooted at its own directory; every converted csproj does <Compile Remove="**/*.cs" /> then <Compile Include="*.cs" /> — current folder only. Nothing compiles it, and it has drifted (it still declares a GoTag alias no live csproj uses). Live global usings come exclusively from csproj-template.xml. Deleting the file, or wiring it, is a separate small cleanup.

Census of the exposed surface

Bucket Count Names
(a) all-lowercase ASCII 46 append array builtin cap channel clear close comparable complex complex64 copy defer delete error fallthrough fatal heap i imag iota len make map mapclone max min nil nonnil panic print println range real recover select slice sslice sstring str subslice subslice3 tmpstring type uintptr widen zero
(a′) lowercase-initial, mixed +4 _ errorExtensions initPackage trySelect
(b) glyph-bearing 9 ᐧᐧ ꓸꓸꓸ ᐸꟷ ж makeǃ goǃ
(c) @-escaped 1 @string — the collision that matters, see p17
(d) PascalCase 11 + ~60 types AreEqual ConvertToType ConvertToUInt64 GetGoTypeName GoZero Implements StackAllocThreshold ToUTF8Bytes TryTypeAssert (+ InitializeGoLib/ZeroIsDefault, internal), plus the public type/attribute/interface surface
extension-method names in scope 16 _ array DerefOrNil DerefOrNull i Lsh OrTypedNil PrintPointer Reinterpret Rsh slice sslice ToGoString ToSlice ToUTF8Bytes type

golib’s internal members are not in scope for converted packages — ж.cs:18-19 grants InternalsVisibleTo only to unsafe and GolibTests. go2cs.Symbols’ 15 constants are not bare names either (no using static go2cs.Symbols; in any converted csproj); only the namespace simple name go2cs leaks.

Three C# resolution rules, MEASURED — this is what decides each row

The measured resolution matrix — 21 probes, transpiled, compiled, run and diffed against go run

Probes were behavioral-test-shaped single packages built against the worktree’s live src/ (-p:go2csPath=<worktree>/src/ -p:UseSharedCompilation=false). Every probe’s Go program runs clean first, so a divergence is the conversion, never the source.

# Scenario C# result Class
p01 pkg func len([]int) int (predeclared shadow) + slice exprs, range compiles, matches benign
p02 pkg func append(a, b int) int compiles, matches benign
p03 pkg func heap(int) int + &T{} and an escaping local compiles, matches benign (R2)
p04 pkg func mapclone(int) int + maps.Clone compiles, matches benign (call emits qualified maps.Clone<…>)
p05 pkg func str([]byte) string compiles, matches benign (no builtin.str site emitted here)
p06 pkg func nonnil(int) int, no trigger compiles, matches benign
p07 pkg func tmpstring(int) int + m[string(b)] CS1503 cannot convert from 'go.slice<byte>' to 'nint' LOUD
p08 type error struct{}, unused compiles, matches benign
p09 type string struct{}, unused compiles, matches benign
p10 type slice struct{} + real slices compiles, matches — emitted Δslice benign, existing defense
p11 pkg var append = 11 compiles, matches benign
p12 pkg func subslice3(int) int + s[1:3:4] compiles, matches (emits s.slice(1,3,4)) benign
p14 pkg func tmpstring(b []byte) string + m[string(b)] compiles; Go 11 ZZ vs C# 0 ZZ SILENT WRONG
p15 LOCAL heap := 5 in a function with a heap-promoted local compiles, matches benign (R2)
p16 pkg func nonnil(int) int + setOne(&e.x) on a pointer param CS1615 Argument 1 may not be passed with the 'ref' keyword LOUD
p17 type string struct{} + a real string in the package CS0029 Cannot implicitly convert 'ReadOnlySpan<byte>' to 'go.main_package.@string' LOUD
p18 type error struct{} + a real errors.New value compiles, matches benign
p19 pkg func len + copy/map/range-over-string/3-index/variadic sweep compiles, matches benign
p20 LOCAL vars str, zero, nonnil, subslice, tmpstring compiles, matches benign
p21 type error struct{} + dynamic interface{ Error() string } assert compiles, matches benign
p13 type array + type channel alongside real arrays/channels compiles, matches — Δarray/Δchannel benign, existing defense
p23 -recurse=module app with a package named golib CS0576 Namespace 'go' contains a definition conflicting with alias 'golib' LOUD, -recurse-only

Verdict census: 1 compiles-and-WRONG, 4 compile errors, 16 benign.

p14 is the whole finding

Go source says table[string(b)]. The converter emits table[tmpstring(b)]builtin.tmpstring is the zero-copy map-index-key optimization, a name the Go source never spells. A user who happens to declare func tmpstring(b []byte) string gets @string tmpstring(slice<byte>) in main_package, which by R1 hides the golib helper and by luck matches its signature. The map lookup silently routes through the user’s function, misses, and returns the zero value. No warning, no error, exit 0, wrong answer. The converter’s transpile step exits 0 and prints nothing.

The same shape is live for every helper the converter emits implicitly and without explicit type arguments. Derived from the census and the emission sites (convIndexExpr.go:337 tmpstring(%s), refLoweringEmissionOperations.go:530 nonnil(ref %s), visitFuncDecl.go:1788 maps.clone → mapclone, and the str/trySelect/fatal/i/widen sites), the dangerous class is 12 names:

_ fatal i initPackage iota mapclone nil nonnil str tmpstring trySelect widen

The other 34 of the 46 fall out for three reasons, and the first is free immunity worth naming:

Two of the twelve are not function-call shaped and route through different emission paths, so they need their own attention in the fix: nil (a static readonly field the converter emits in every == nil / = nil, and var nil int is legal Go) and iota (a const, likewise legal as a Go package-level name). Neither was probed; both belong in the guard project.

Extension-form emissions (i, _, type, slice, array, sslice, ToUTF8Bytes) were NOT probed for silent capture. Extension-method lookup runs only after instance lookup fails and is not hidden by a same-named static member, so they are low-risk by construction — but that is reasoning, not measurement, and the fix lane should probe one before dismissing the bucket.

Live corpus near-misses — 5 packages already do this, all benign TODAY

The corpus compiles and 130 packages validate, so nothing here is broken. But the stdlib is already one emission away from p14:

Package Declaration Shadows Why it survives
runtime map.cs:1656 internal static any mapclone(any mʗp) builtin.mapclone(any)identical signature runtime never emits a maps.Clone call. This is p14’s exact shape, sitting in the tree.
math fma.cs:11 internal static uint64 zero(uint64 x) builtin.zero<T>() R2 — zero<T>() always carries explicit type args
runtime, sync panic.cs:1103, mutex.cs:35 fatal(@string) builtin.fatal(string, nint = 1) no builtin.fatal site in either package (mutex.cs is hand-owned, so this one is deliberate)
log/slog value.cs:354 str(this Value v), plus local funcs str in level.cs:65 and json_handler.cs:201 builtin.str no builtin.str site in slog
runtime debuglog.cs:191 i(this ж<dlogger> Ꮡl, nint x) builtin.i (extension) extension-vs-extension on different receivers

That is the honest answer to “does the corpus trip this today”: no — but it holds five loaded guns, one of them (runtime.mapclone) with a signature that matches exactly.

What defends this today, and the exact gap

Five hand-maintained lists, none of them derived from golib:

List File Size What it does
keywords identifierNaming.go:33 65 @-escape — this is what turns Go string into @string, i.e. it creates p17’s collision
reserved identifierNaming.go:85 52 Δ-rename (Δfoo, or Δfooᴛ when also collision-flagged) — holds array, builtin, channel, slice, sstring, GoFrame, NilType, PanicException, AreEqual, GetGoTypeName, ToUTF8Bytes, type, and 4 marker glyphs
emitterSpelledTypeNames nameCollisionAnalysisOperations.go:37 4 (any, rune, nint, nuint) package-scoped Δ-rename, TYPES only
goBuiltinNames packageGlobalState.go:234 18 a package-level func/method with a Go universe name → the converter’s own builtin calls emit qualified as builtin.X(…) (packageBuiltinShadows). This is the mechanism that covers the universe-name family — p01/p02/p11/p19.
csharpKeywordCastTypes convCallExpr.go:26 15 cast parenthesization, not naming

The gap, stated exactly: nothing in the converter enumerates golib’s builtin member surface or its type surface. reserved covers 4 of the ~30 public builtin statics and misses mapclone, tmpstring, str, subslice, subslice3, nonnil, heap, zero, fatal, range, select, trySelect, initPackage, widen, i, Implements, TryTypeAssert; on the type side it misses error, comparable, complex64, uintptr, sslice, and the go.golib namespace. goBuiltinNames is correct but scoped to the 18 universe names and to func/method declarations only.

The symbol table does not help and was checked because it looked like it should. src/core/go2cs/symbols.jsongensymbolssymbols.go + Symbols.cs is purely the glyph and marker vocabulary (27 entries: ж, , Δ, , ʗ, ˢ, _package, …). It carries no reserved-name data, and nameCollisionAnalysisOperations.go does not read it at all — the only seam is identifierNaming.go:94, which drops four marker glyphs into reserved. check-symbol-sync.ps1 verifies only that the two projections match the JSON, and is wired into no gate (not CNR, not the behavioral runners, not go test; there is no CI workflow). So the generator precedent exists and is good — it simply was never pointed at this problem.

No unit test guards any of the five lists. sanitization_test.go covers import-path segments only. The 46 behavioral projects matching Collision/Shadow/Reserved/Keyword/Builtin cover the machinery well — ReservedNameShadows is the closest — but every one of them tests names already in the lists. None tests the gap, by construction.

Recommendation — one generated list, feeding two mechanisms that already exist

The durable fix is not to grow reserved by hand. Two reasons, and the second is a trap:

  1. Hand-maintenance is exactly what drifted; golib gains members freely and nothing notices.
  2. reserved is the WRONG mechanism for these names. Its own doc comment already warns that names the emitter itself spells must never go in it, because legitimate emissions flow back through the same string-based sanitizers (slice<rune> corpus-wide would corrupt to slice<Δrune>). mapclone, tmpstring, nonnil, str are precisely emitter-spelled names. Adding them to reserved would corrupt the converter’s own output.

So: generate the list, and feed it to the mechanisms already proven for the universe builtins.

Guard spec

Three behavioral projects, output-compared against go run ([GoTestMatchingConsoleOutput]), all currently FAILING — so they land with the fix, never before it:

  1. GolibHelperShadows — the p14/p07/p16 family in one package: func tmpstring(b []byte) string with a m[string(b)] map index; func nonnil(a int) int with a setOne(&e.x) pointer-field call; func mapclone(...) with a maps.Clone; func str(b []byte) string; func fatal(s string). Each helper’s Go-visible answer must survive AND the converter’s own helper call must still do its job. This is the project that proves the qualification path.
  2. GolibTypeShadowstype string struct{} used alongside real strings (p17), plus type error, type comparable, type uintptr, type complex64, type sslice each exercised next to the real golib type. ReservedNameShadows already owns any/rune/nint/builtin/sstring/ GoFrame; this is its missing half and could reasonably be folded in rather than added beside it.
  3. RecursePackageNamedGolib — p23. -recurse-shaped, so it may not fit the behavioral runner’s single-package mold; if not, a converter integration test in moduleConverter_integration_test.go is the right home.

Plus the drift test above (golibSurfaceIntegrity_test.go), which is the guard that actually prevents recurrence — the behavioral projects prove today’s fix, the drift test prevents tomorrow’s golib member from re-opening the hole silently.

Neutered-fix control the lane owes: revert the qualification helper and confirm GolibHelperShadows reports an OUTPUT divergence (not merely a compile failure) — p14’s whole character is that it compiles.

Size estimate

One lane, medium — roughly a day of work plus a CNR pass. Generator + generated list + drift test ~250 lines across 3 new files; collision-analyzer extension ~40 lines across two existing files; the emission-site qualification helper plus ~10–15 call-site swaps; ~5 lines for the import-alias set; 2–3 behavioral projects with goldens and .slnx registration.

The lane owes a CNR run and must not assume zero corpus drift. Five stdlib packages already declare shadowing names (table above); the moment packageBuiltinShadows learns about them, any implicit helper call inside those packages starts emitting builtin.X(…). runtime in particular declares three of them (mapclone, fatal, i) and is a large package. Expect a small, explainable emission diff in runtime/math/sync/log/slog, verify each hunk is exactly a qualification, and budget for a targeted regen rather than assuming none.

Two side findings this scout tripped over, both worth their own chips

What cut against the brief

Reproducing

The 21 probe packages were built under the session scratchpad and deliberately not committed — they are all currently-failing or currently-passing-by-accident, so committing them would either break the behavioral suite or bank the wrong baseline. Every probe’s Go source is reproduced in the matrix descriptions above and each is 10–25 lines; the guard spec is the durable form. Method: go2cs.exe -go2cspath <worktree>/src <dir>, then dotnet build <dir>.csproj -c Debug -p:go2csPath=<worktree>/src/ -p:UseSharedCompilation=false, then run and diff against go run ..

fmt’s compile wall is CLOSED — two roots, not four; 62 of 63 verdicts agree (2026-08-16, lane claude/fmt-roots)

The census above was stale in the usual direction, and the usual way. It recorded four remaining roots after the CS1955 map-nil row closed. Re-measured on master (9c64f0a44) the compile wall is two roots — the census’s last three rows were, as it predicted, “one type’s story”, and they are indeed ONE root. Both are general converter defects; neither is fmt-specific; both now carry failing-first behavioral guards.

Root 1 — a cast’s operand asks TWO questions, and the arm asked only one

CS0030 float→renamedComplex64 / double→renamedComplex128 ×4 (8 diagnostics across the internal and external test variants). The named-numeric identity-constant arm of convCallExpr (reached because go/types gives a constant operand the target type, so the conversion reads as an identity) consulted only castOperandNeedsParens — a leading-sign TEXT test that answers the cast-vs- subtraction parse ambiguity. It never asked the precedence question: a C# cast binds tighter than every binary operator, so a constant operand rendering as a top-level binary expression has the cast claim its left operand alone.

((renamedComplex64)3F + 4F.i())   // was; the cast applies to 3F alone, CS0030
((renamedComplex64)(3F + 4F.i())) // now

The compile error is the mild symptom. The same emission is silently value-changing on every named int/float type, where the [GoType] wrapper supplies an operator for the mis-bound first leg and the code compiles clean:

Go emitted (before) Go value C# value
rf(3 / 2) ((rf)3 / 2) 1 1.5
rf(7 / 2) ((rf)7 / 2) 3 3.5
rf(1 / 3) ((rf)1 / 3) 0 0.3333333333333333

Go folds the constant expression in exact arbitrary precision — untyped INTEGER division — and then converts; the mis-bound cast converts first and divides in the target’s own float arithmetic. Measured end to end, not argued: a four-line probe printed 1 3 0 1 under Go and 1.5 3.5 0.3333333333333333 1.25 under the transpiled C#. A named complex target has no float→named-complex conversion at all, which is the only reason the class ever surfaced as an error rather than as a wrong answer.

Keyed on the AST (*ast.BinaryExpr), not the rendered text — only the written expression says whether a binary operator is left exposed. Unary operands are deliberately excluded: a cast and a unary operator share precedence and associate right, so (T)~0 already means (T)(~0). Guard: NamedConstConversionPrecedence (output-compared, so the silent value divergence fails the gate and not merely the CS0030). Detail: docs/ConversionStrategies-Reference.md §Named Numeric Types and Constant Contexts, “A cast’s operand asks TWO questions”.

Root 2 — inFunction does not mean “there is a function DECLARATION”

CS1729 + CS0103 + CS0034 — the census’s last three rows, and one root exactly as predicted. convFuncLit sets inFunction for a literal’s body (correctly — its locals are function scope), but currentFuncName and currentFuncPrefix, the lift’s name prefix and its declaration sink, are allocated together by visitFuncDecl. For a literal in a package-level initializer they therefore held whatever the PREVIOUS function declaration in the file left behind. Every type-lift site keys on lifted && inFunction and writes into that prefix, so fmt scan_test.go’s

{"ReaderOnly", func(s string) io.Reader { return struct{ io.Reader }{strings.NewReader(s)} }},

lifted a type named after an unrelated preceding function and wrote its declaration into that function’s already-flushed buffer. The declaration vanished; only the use site survived — new Scan_type(…), CS1729 (no one-argument constructor), plus CS0103/CS0034 in the ImplementGenerator wrapper generated for the phantom type from its surviving [assembly: GoImplement] record.

And it has a second, worse symptom the census never saw. With NO preceding function declaration the buffer is not stale but nil, and the lift panics (nil receiver in strings.Builder.copyCheck). That panic is recovered per file, so the entire FILE is skipped with only a visit file error warning — a silent, total loss of one file’s conversion. Which symptom appears depends solely on declaration order within the file, which is why fmt showed the compile errors while a minimal standalone reproduction showed the panic.

A package-level literal now gets its own sink, flushed at package scope — where a lifted type belongs anyway, and exactly where the sibling package-level lift (readersᴛ1) already goes — with its name seeded from the declaration being initialized (readers_type, unique per var as readersᴛ1 already is). Guard: PackageVarFuncLitTypeLift, whose two files cover both symptoms (main.go puts a function declaration before the var; varfirst.go declares the var first). Detail: docs/ConversionStrategies-Reference.md §Struct Types, “A lift inside a PACKAGE-LEVEL func literal”.

Root 3 (bridge) — reflect.Value.Bytes had no Array arm

Behind the compile wall the first run threw InvalidCastException: go.array<byte> → go.slice<byte> out of the hand-owned core/reflect/value_impl.cs. Go’s bytesSlow accepts an addressable byte array as well as a byte slice, and fmt’s printValue calls Bytes() whenever f.Kind() == Slice || f.CanAddr() — so Sprintf("%s", &[3]byte{'a','b','c'}), whose pointer deref IS addressable, reached a function that handled only slice shapes. CanAddr() was right; Bytes was incomplete. It now takes Go’s Array arm, aliasing the array’s storage through array<T>.Slice exactly as Go’s unsafe.Slice(p, n) does (a copy would silently break writers), and panics with Go’s own text on an unaddressable byte array. Zero regression risk by construction: that shape previously always threw.

fmt census — 63 verdicts, 62 agree, 1 open, 0 disclosed

   
Go 62 pass, 1 skip (TestCountMallocs, GOMAXPROCS>1)
C# 61 pass, 1 skip, 1 infrastructure-error
Divergences 1TestSprintf
Excluded 42 (standard Phase-4D Benchmark/Example/Fuzz deferrals)
Disclosed 0

Not banked, and deliberately: banking wants genuine closure, and TestSprintf is a real divergence rather than a disclosure candidate (the existing disclosure classes are alloc-count asserts the CLR provably cannot satisfy; a formatting mismatch is not one).

What TestSprintf is standing on — three co-located bridge divergences

All three live inside that one table-driven test, so none can move the verdict alone. Each is measured, not inferred; the first two are reported by the test itself before the third aborts it.

# Entry (fmt_test.go) Go C# Root
1 {"%#12.5g", 1230000 - 0i, …} (line 565) +0.0000i -0.0000i The converter emits Go’s constant complex expression as RUNTIME arithmetic (1230000D - 0D.i()), and .NET’s double - Complex computes -right.Imaginary, turning +0 into -0. Go folds the constant exactly, and an exact zero has no sign. Isolated in a probe: only the SUBTRACTION form diverges — complex(1230000.0, 0.0) and 1230000D + 0D.i() are both correct
2 {"%#v", TestFmtInterface, "(func(*testing.T))(0xPTR)"} (line 717) (func(*testing.T))(…) (Action`1)(…) GoReflect.TypeNaming.cs has no delegate handling at all — rendering a Go func signature from a CLR Action/Func/Actionꓸꓸꓸ/Funcꓸꓸꓸ is an unimplemented bridge feature, not a defect. Sized as its own arc
3 {"%x", []renamedUint8{…}} and five siblings (lines 734–735, 839–842) 68656c6c6f throws Bytes() on a slice whose element is a NAMED uint8 type. Go permits it (Elem().Kind() == Uint8) and ALIASES through the slice header; the bridge holds slice<renamedUint8> of wrapper structs, which is not layout-compatible with byte[], so no safe aliasing conversion exists. Deliberately NOT half-fixed here — a copy would silently break writers of a core reflect API. A representation-level question

Fix 1 was measured and deliberately NOT landed. Folding complex constant expressions is the right general answer (the established precedent is foldedNamedFloatConstLiteral, which folds float constant conversions so Go’s exact evaluation rounds only once). But the census says the subtraction form has zero production sites corpus-wide — all 115 are test files in fmt, math/cmplx, strconv and internal/fmtsort, three of which are BANKED — so landing it would churn banked packages’ committed test sources and owe them a re-sweep, while moving no verdict on its own (2 and 3 would still fail). It belongs with the TestSprintf arc, not ahead of it.

Recommendation for whoever takes TestSprintf: it is one lane, and #2 is its centre of gravity — #1 and #3 are each contained, #2 is a bridge feature. All three must land together for the verdict to move, and fmt then banks at 63/63. That bank is worth more than its own row: the behavioral suite’s ~520 stdout comparisons all run through converted fmt, so fmt’s own %v/%T table is the deepest test the reflection bridge has, and every Printf-comparing behavioral test is strengthened by it.

⛔ STILL DOES NOT BANK — crypto/tls holds at 176 of 184, but the four divergences are now four DIFFERENT things, and only one of them is a disclosure (2026-08-16, lane claude/tls-endgame)

The sha3 entry above ended by saying “the question crypto/tls now poses is a banking question, not a debugging one”, and named the four remaining divergences as GetAddrInfoW, an op_OnesComplement singleton, TestCertCache and TestBogoSuite. This lane took all four. The headline number does not move — the same per-test method (one process per top-level Test*, -test.run '^Name$', 25 s cap, raw stdout saved) measures 176 PASS, 7 FAIL, 1 process-AV in 853 s — and that is the finding rather than a disappointment: GetAddrInfoW is FIXED and the test that measured it still fails, because a second wall of the same family stood behind it.

Measure after default! after sha3 now
top-level tests that PASS run on their own 163 of 184 176 of 184 176 of 184
real divergences (Go passes, C# does not) 17 4 4
distinct roots behind them 5 3 4
of those, disclosable under an existing class 0 1

Go on this host, re-measured rather than carried: 184 top-level, 180 pass, 4 fail, 79.9 s. The four failures are the same expired-fixture set (TestResumption, TestResumptionKeepsOCSPAndSCT, TestVerifyConnection, TestCrossVersionResume — the test certificates expired 2025-01-01), and the converted host fails exactly those four, with the same x509: certificate has expired text, so they are AGREEING rows, not divergences. The cross-tab closes with no remainder: 176 agreeing passes + 4 agreeing failures + 4 divergences = 184.

1. GetAddrInfoW — FIXED, and the class gained its LINKED-OUTPUT member

The open non-blittable-syscall class CLAUDE.md has carried since the Timezoneinformation fix (“net and crypto/x509 will” reach it) now has its DNS member closed. Native ADDRINFOW is 48 bytes of scalars and raw pointers where the converted AddrinfoW holds Canonname, Addr and Next as managed references, so the hints Windows read were garbage and the *ADDRINFOW it wrote landed in a reference slot — Fatal error. 0xC0000005 inside Syscall6, killing the process.

What makes this member different from every earlier one, and what the next member of the class should read first: copying the top-level record would not have been enough. net reads the sockaddr THROUGH the result ((*syscall.RawSockaddrInet4)(unsafe.Pointer(result.Addr))), and RawSockaddrInet4.Addr [4]byte is an array<byte> — a backing-array REFERENCE plus bounds — so reading that struct out of a native sockaddr_in fabricates managed references from address bytes. That is the fork the sha3 entry proved has no general fix. So the hand-own transcribes the WHOLE chain into managed boxes, sockaddr included, typed by ai_family; frees the native chain eagerly (which makes FreeAddrInfoW a hand-owned no-op — nothing native escapes the call); and carries the managed sockaddr pointer across the unsafe.Pointer field through golib’s ManagedPointerTokens, whose only previous minter was the reflection bridge. Two properties worth carrying:

Guarded by the new LookupServicePort behavioral output test: net.LookupPort for tcp/udp/tcp4/tcp6 services is the one reach into this pair needing neither DNS nor a network, and it exercises the hints mirror, the chain copy, the token handoff and BOTH sockaddr flavors at VALUE level (a byte-order slip prints a swapped port rather than failing). Proven failing-first: with the hand-own removed the guard reports exit code mismatch: C# -1073741819 vs Go 0 — the access violation itself. Marker census: the two entries join manualConversionFuncs["syscall"], and TestWindowsOnlyEntriesAreScopedToWindows now pins their platform scope.

2. …and TestVerifyHostname STILL fails, one layer further out

It now dies in net.adapterAddresses, which is dnsReadConfig’s only source of DNS servers on Windows:

Fatal error. System.AccessViolationException
   at go.ж`1[IpAdapterAddresses].op_Equality(ж`1<IpAdapterAddresses>, NilType)
   at go.net_package.adapterAddresses()
   at go.net_package.dnsReadConfig(string)      <- via getSystemDNSConfig, from lookupIP

Same family, bigger structure, and NOT a wrapper defect: adapterAddresses asks GetAdaptersAddresses to fill a managed slice<byte> (that part is legitimate) and then walks it as Ꮡ(b, 0).Reinterpret<byte, IpAdapterAddresses>(). IP_ADAPTER_ADDRESSES is a linked record with three raw string pointers, a [8]byte physical address, a [16]uint32 zone-index array and six nested linked lists; reading it out of a byte buffer fabricates a managed reference on the first field touched — here the Next comparison itself. The remedy is the same transcription shape this lane just built for ADDRINFOW, one structure size up, and it belongs to a net interfaces arc (adapterAddresses also backs interfaceTable, interfaceAddrTable and Interfaces()), not to crypto/tls. It is what stands between the corpus and any name resolution at all on Windows.

3. The op_OnesComplement singleton is a SECOND syscall class — **T OUT-parameters arrive NULL, censused at 13

The brief for this lane (and the entry above) read TestQUICHandshakeError as “a golib operator on a nil box … make it a Go-shaped panic, not an NRE”. Both halves are wrong, and the correction is the most transferable thing here. The panic already IS Go-shaped — golib’s ~ raises RuntimeErrorPanic.NilPointerDereference, printed as panic: runtime error: invalid memory address or nil pointer dereference — and golib is the innocent frame. The nil comes from three frames out:

crypto/x509 systemVerify -> (*storeCtx).Store        <- storeCtx is nil, and err was nil
             createStoreContext -> CertAddCertificateContextToStore(handle, leafCtx, ADD_ALWAYS, &storeCtx)

&storeCtx is a ж<ж<CertContext>>, and golib’s ж<T> → uintptr answers 0 for it: IsNull is the VALUE-PEEKING question, and a heap-boxed POINTER legitimately holds null before the call. That answer is deliberate and correct for the case it was written for (syscall.Write hands writeFile a nil *Overlapped; uintptr(unsafe.Pointer(nil)) is 0 in Go) and silently wrong for the shape every out-parameter takes: the wrapper tells the kernel “no output wanted”, ppStoreContext is documented OPTIONAL, the call SUCCEEDS, and the caller reads back the nil it started with. Note the 0 is accidentally the SAFER outcome — a real address would have had the kernel write a native pointer into a slot the collector reads as an object reference — so the remedy is a per-wrapper hand-own, never a change to the operator.

Census of the emitted wrappers taking a **T: 13. CertAddCertificateContextToStore, CertGetCertificateChain, ConvertSidToStringSid, ConvertStringSidToSid, DnsQuery, _DnsQuery, GetFullPathName, getQueuedCompletionStatus, GetQueuedCompletionStatus in syscall; plus CreateEnvironmentBlock, NetGetJoinInformation, NetUserGetInfo, NetUserGetLocalGroups in internal/syscall/windows. Two more of the shape are already hand-owned for other reasons (GetAddrInfoW, GetAcceptExSockaddrs). Not fixed speculatively, on the standing fix-it-when-a-suite-reaches-it rule.

Closing TestQUICHandshakeError is therefore a crypto/x509 Windows system-verifier arc: the out-parameter is only the first defect, because CertContext, CertChainContext, CertSimpleChain and CertChainElement are all read back through raw addresses afterwards — the fabricated-reference fork again, over six CryptoAPI wrappers.

4. TestCertCache IS codegen-liveness, and it is the only disclosable row

The test nils its own local, calls runtime.GC(), and polls for 4 s waiting for a finalizer to decrement a ref count. Measured, not argued:

That is the roster’s existing codegen-liveness class verbatim — “a test asserts, from inside its own frame, that an object it just stopped referencing is collectible”. The likely rooting slot is the same one sync’s TestOnceXGC disclosure names: var (certA, err) = cc.newCert(...) materializes an address-exposed tuple temp, and an address-exposed slot is not lifetime-tracked. That last step is INFERRED from the class’s known mechanism, not read out of the JIT — the three bullets above are the measurements. No new class, so no coordinator ruling is owed; the row is disclosable whenever the package can bank.

5. TestBogoSuite is NOT “external infrastructure” — it is a fixable test-host defect

The previous entry recorded it as “an external binary … not a conversion signal”. Measured, that is wrong. Go passes it on this host, so BoGo downloads, builds and runs here. It drives the shim as -shim-path=os.Args[0] -shim-extra-flags=-bogo-mode, i.e. it re-executes the test binary itself. Run the converted host that way and it answers:

> tlsendHost.exe -bogo-mode
flag provided but not defined: -bogo-mode

bogo-mode IS registered by the converted source (handshake_test.cs:50, a flag.Bool package-var initializer) — but testing’s host parses args itself in TestOptions.Parse and throws on any name it does not recognize, before the package’s own flag.Parse() in TestMain ever runs. Go’s test binary cannot behave that way: testing.Init() merely DEFINES the -test.* flags on flag.CommandLine, and one flag.Parse() then covers the host’s flags and the package’s together. TestFlagBridge already does the first half (publishing the host’s flags into flag.CommandLine); the missing half is tolerating the package’s. So every BoGo case fails instantly, the runner exits 1 without writing results, and the test reports bogo failed: exit status 1.

Sizing it honestly: fixing the pass-through is small and general (any package with its own test flags benefits), but it lives in the shared hand-owned testing host, so it owes the full validated sweep as a gate — and it must defer the unknown-flag ERROR to flag.Parse() rather than dropping it, or the host stops rejecting typos. And even fixed, TestBogoSuite becomes a long external TLS interop measurement, not a pass: it is its own arc. Deliberately not taken here.

One more thing the bogo failure printed, unrelated and worth a look

t.Fatalf("bogo failed: %s\n%s", err, out) printed the second operand as 0x19c45d7c4f8. out is a *strings.Builder, whose pointer-receiver String() makes it a fmt.Stringer in Go; the converted fmt reached its p.arg._<Stringer> assertion and missed. Reproducible on every run of that test. Whether this is general (any %s/%v on a pointer whose String() was never cast to Stringer in converted source) or specific to strings.Builder is UNMEASURED — recorded with its exact site so whoever looks does not have to find one.

Where crypto/tls stands

Four divergences, four different owners, and only one of them is crypto/tls’s to answer. Two are Windows syscall arcs that other packages want more than this one does (net’s interfaces, crypto/x509’s system verifier); one is a testing-host gap; one is a disclosure. Nothing here is a TLS defect — the protocol work has been green since the sha3 fix, TLS 1.2 and 1.3, QUIC, session tickets, ECH and the whole handshake matrix. The expired-fixture ceiling deserves repeating for whoever eventually banks it: 180 of 184 is the most this host can score, both languages fail the same four rows, and it worsens with time — a Go patch release or regenerated fixtures changes the shape, and a proof page written today must say so.

Still builds-and-partly-runs; no roster row, no proof page, no disclosures, converted test sources not committed.

encoding/json — the wall is down and the package RUNS: 400 of 491, four roots (2026-08-16, lanes claude/json-measure + claude/json-unlock)

First measurement of the package. The board’s prior rows — encoding/json | 491 | CS0050 inconsistent accessibility on a test-local return type and encoding/json 0 of 491 (CS0050/CS0053)reproduce exactly, and the diagnosis behind them was right about the symptom and one word off about the cause: the offending types are not “test-local”, they are function-local, and that distinction is the whole fix.

The wall: 76 errors, four codes, one cause — CLOSED

Code Count Shape
CS0053 35 property type less accessible than property
CS0050 20 return type less accessible than method (the generated -accessors)
CS0051 19 parameter type less accessible than method (the generated constructors)
CS0052 2 field type less accessible than field

A type declared inside a function body has no Go exportedness. The export convention governs PACKAGE-LEVEL identifiers; a function-local S8 is exactly as unreachable from outside its function as embed2 is, and Go draws no distinction between them. go2cs hoists both to package scope as <Func>_<name>, and at that point an accessibility rule reads a case out of the name — by either of two routes, which is why the failure looked like several defects:

  1. the converter’s bridge arm (visitTypeSpec, under testInlineTypeAccess) asked generatedTypeScope for the LOCAL name, so the siblings one function declares split public/internal — [GoLocalName("S8")] public partial struct TestUnmarshalEmbeddedUnexported_S8 holding a field of [GoLocalName("embed2")] internal partial struct TestUnmarshalEmbeddedUnexported_embed2;
  2. a lifted ANONYMOUS struct carries no modifier at all, so go2cs-gen’s own rule read the HOISTED name and inherited the case of the enclosing functionTestEncoderSetEscapeHTML_type is public because the Test… function is, and its exported fields over the package-level unexported strMarshaler/strPtrMarshaler are the two CS0052.

Fix (landed): a function-local type is emitted internal, always — localTypeAccess in typeAccessibilityOperations.go, consumed at the three finalization points (visitTypeSpec’s bridge arm, visitStructType’s and visitInterfaceType’s lift defaults). internal is both faithful and sufficient: no Go consumer outside the function can name the type, and every emitted C# consumer compiles into the same test assembly. Writing it inline is what makes the generator follow — measured, not assumed: the generator reproduces a modifier the declaration already carries (internal partial struct TestUnmarshalEmbeddedUnexported_embed2 appears verbatim in its output) and falls back to its name rule only for a bare one. Guarded by TestFunctionLocalTypesShareOneAccessibility (converter go test), which pins all three shapes and fails without the fix (A/B verified — TestLocals_S8 reverts to public).

Deliberately scoped to the bridge. The production path leaves the modifier empty and lets recordTypeAccessibility pin generatedTypeScope of the mangled name, which gives every local type of one function the SAME modifier — uniform, and consistent for that reason rather than by design. ⚠ The identical latent mixture exists in production (a function-local struct with an exported field of a package-level unexported type) and no corpus package exhibits it today; flipping production local types to internal would also move a public value adapter’s operand out from under it. Measured and left, not changed speculatively — if a production package ever presents the shape, this is its root.

Behind it: two roots, 8 errors, and the package still does not run

Re-running the pipeline on the fixed converter took the wall from 76 to 8, in two families that were masked, not causedrenamedByte, strMarshaler and strPtrMarshaler are package-level types the fix does not touch, and they error now for the first time.

R2 — a hoisted function-local type’s EMBEDDED-field member keeps the hoisted name, while use sites spell the Go field name (CS1061 ×2, CS1739 ×1). The converter is internally inconsistent about one name. type S struct{ *myInt } inside TestAnonymousFields emits the member as TestAnonymousFields_myIntᴛ1 (declaration and go2cs-gen’s promotion and constructor), while the converted body says s.myInt and the composite literal says new TestUnmarshalEmbeddedUnexported_S3(embed1: …). The Go field name of an embedded *myInt is myInt, and [GoLocalName] already records it for %T — the promotion simply does not use it. Naming the member by the Go local name is collision-free (it lives inside the struct), but the fix spans the converter’s embedded-field emission and go2cs-gen’s promotion generator, so it is its own arc.

R3 — a string↔byte-slice conversion loses its footing when either side is a DEFINED type (CS1503 ×5), in two directions of one idea:

Direction Emitted Sites
[]byte(namedString) slice<byte>(v) where v is [GoType("@string")] marshaledValue, strMarshaler, strPtrMarshaler
[]NamedByte(string) slice<TestSliceOfCustomByte_Uint8>("hello"), slice<renamedByte>((@string)"abc"u8) TestSliceOfCustomByte, TestEncodeRenamedByteSlice

Overload resolution lands on slice<T>(T[]) and reports cannot convert from '…strMarshaler' to 'byte[]'. golib’s string constructor is reachable only for the exact @stringslice<byte> pairing; a named type on either side is not routed through it. The emitted FORM is not the problem — positive control: the identical slice<byte>(s) conversion appears in the banked suites of archive/zip, bytes and compress/zlib and compiles there, because the operand is a plain @string. What is missing is the unwrap of a DEFINED type to its underlying at the conversion site (or the participation of the generated implicit conversion in overload resolution) — a golib/converter boundary question that wants a measurement of its own. The shape is ordinary Go, so it is very likely NOT confined to this package.

Where encoding/json stands

0 of 491, still compile-blocked — but the block is now 8 errors in two named roots rather than 76 in one, and the converter fix that closed the first is general (any package whose tests declare types inside a function body). No roster row, no proof page, no disclosures, converted test sources not committed. The suite converts cleanly end to end — every _test.go file emits, and the production encoding.json.dll builds — so the remaining distance is exactly R2 and R3, and neither is a reflection-bridge question. Nothing here touches the descriptor/assignability semantics the claude/assignableto-arc lane owns; no cross-reference is owed.


UPDATE 2026-08-16 (lane claude/json-unlock) — both roots closed; FIRST measurement: 400 of 491

R2 and R3 are fixed, the test half compiles with 0 errors, and encoding/json ran for the first time. It does not validate: 400 of 491 verdicts match, 91 diverge. The reflection-heavy body is now measured rather than guessed at, and the divergences cluster the way this week’s other packages did — four roots and one loose end, none of them an emission defect.

R2 — an embedded field is named by GO, not by the C# rendering of its type. visitStructType derived the member name from the rendered type, which coincides with the Go field name for every ordinary embed and parts from it the moment the converter RENAMES the type: a function-local type myInt int hoists to TestAnonymousFields_myIntᴛ1, and the declaration (plus go2cs-gen’s generated constructor and promotion accessor, both read off it) then spelled that while every use site spelled s.myInt / S3{embed1: …}. The name now comes from the Go OBJECT the embed resolves to — the field *types.Var for a same-package embed, the embedded type’s TypeName for a selector embed — which is the Go field name by definition and is already unqualified and type-argument-free, so it REPLACES the bracket/dot stripping rather than adding to it. It also settles the field’s EXPORTEDNESS, which the hoisted name silently flipped (embed1 is unexported; the TestUnmarshalEmbeddedUnexported_ prefix made the member public — the opposite of what that test asserts). The generator followed: its promoted-struct accessor scoped by TYPE name where every sibling accessor already scoped by MEMBER name, so the corrected declaration met the opposite modifier (CS8799). Guarded by LiftedLocalTypes (extended), proven failing-first.

R3 — a string ↔ byte/rune-slice conversion with a DEFINED type on either end. Two ends, two remedies, one rule: the STRING end spells the (@string) hop explicitly (a [GoType("@string")] wrapper needs wrapper→@stringbyte[], two user-defined conversions, and C# chains at most one); the ELEMENT end projects element-wise through the wrapper’s own operator with golib’s widen (slice<byte> and slice<myByte> are unrelated instantiations with NO conversion between them). Go’s string↔slice conversion always materializes fresh storage, so the element-wise copy is its cost model, not a concession. Corpus census (type-aware, go/packages over all of std with tests): FIVE sites in the entire Go 1.23.1 standard library, production and test, all in encoding/json’s own suite — which is exactly why the corpus compiled clean without them; the string([]myByte) direction has zero stdlib sites and is emitted by the same rule. Zero committed corpus files move. A pre-existing sibling hole closed with it: a named byte-slice type converted from a string VARIABLE (plainByteSlice(s)) was a bare cast, CS0030 — only the LITERAL form had ever been fixed. Guarded by the new DefinedElemStringConversion, proven failing-first (23 errors without the fix).

The 91 divergences

# Root Verdicts Evidence
A An embedded field is invisible to the reflection view as an EMBEDreflect does not report the field as anonymous, so encoding/json never flattens it ≈31 every embed marshals as a NAMED object instead of promoting: {"S1":{"X":2},"S2":{"X":4}} for want {}, {"S":"B","BugA":{"S":"A"}} for want {"S":"B"}; DisallowUnknownFields then reports unknown field "Level1b" where Go reports "extra". Covers TestAnonymousFields, TestUnmarshalEmbeddedUnexported, TestEmbeddedBug, TestDuplicatedFieldDisappears, TestTaggedFieldDominates, TestMarshalEmbeds, TestUnmarshal/#56#63/#89/#91/#93/#139/#140. R2 was the naming PREREQUISITE for this; the anonymous FLAG is a separate seam
B reflect.DeepEqual(ptr, reflect.New(T).Interface()) is false for a fresh zero ≈41 TestUnmarshal’s own precondition — unmarshalTest.ptr %#v is not a pointer to a zero value — fires before the subtest’s real assertion runs, so these 40-odd verdicts are UNREACHED rather than wrong. One fix would re-open them all
C The reflection view of a slice whose ELEMENT or whose SLICE TYPE is DEFINED 3 reflect.Value.Bytes() (core/reflect/value_impl.cs:616) ends in (slice<byte>)other! and throws InvalidCastException for slice<Uint8> / renamedRenamedByteSlice (TestSliceOfCustomByte, TestEncodeRenamedByteSlice, both surfacing as infrastructure-error); TestByteKind instead marshals a local type byteKind []byte as a generic array, so its Kind is not seen either. ⚠ Note the asymmetry with R3: Go’s Value.Bytes() ALIASES the storage, so this fix must project a view, not a copy
D Cycle detection recurses forever → StackOverflowException kills the host 13 unrecorded TestUnsupportedValues marshals a self-referential map; Go returns UnsupportedValueError: encountered a cycle, the converted encoder recurses interfaceEncodermapEncoder without bound and the process dies with 0xc00000fd. Per the jsonrpc rule this is ONE failure plus an unmeasured tail: TestValid ×7, TestUnsupportedValues ×4, TestIndentBig, TestMarshalFloat recorded no verdict at all
E A recovered panic value’s boxed type 2 TestMarshalPanic/TestUnmarshalPanic panic with 0xdead and compare reflect.DeepEqual(recover(), 0xdead); the C# side reports the right VALUE (57005) and still fails the compare — the untyped-int panic argument boxes at a different width than the comparand
loose end 1 TestLargeByteSlice round-trips a PLAIN 2000-byte []byte and diverges at byte 0. No defined type anywhere in it, so it is not root C; unclassified, and worth one look because it is the only base64 round-trip failure

Roots A, B, C and E are all reflection-bridge seams; none is a converter emission defect. Root A is the biggest single unlock (a third of the divergences) and it is the classic Go-embedding contract that encoding/json, encoding/xml, encoding/gob and text/template all read. ⚠ Root B is adjacent to the descriptor semantics claude/assignableto-arc owns — cross-referenced, deliberately not touched here.

No roster row, no proof page, no disclosures, converted test sources NOT committed: 400 of 491 is a measurement, not a validation.

Corpus footprint of both fixes: zero. A seeded stdlib reconvert on the fixed converter (304 packages, 0 failed, 13m32s; marker gate 60 marked / 0 clobbered) emitted 1,681 artifacts, of which 1,629 are byte-identical to the committed tree and 51 of the remaining 52 are the documented CRLF phantom (identical once CRs are stripped). ⚠ The one REAL difference is not from this lane and is owed by another: vendor/golang.org/x/text/unicode/bidi/core.cs emits ((level)(maxDepth + 2)) where the committed file has ((level)maxDepth + 2) — the residue of the cast-precedence fix that landed in claude/fmt-roots (93ef5abaa, guarded by NamedConstConversionPrecedence) without its corpus regen. It is the arc’s ONLY stdlib site; whoever next levels the corpus should expect exactly that one file.

fmt BANKS at 63/63 · ⛔ encoding/json reaches 482 of 491 — the REFLECT-BRIDGE CLOSURE arc (2026-08-17, lane claude/reflect-closure)

The union of the roots holding encoding/json (400/491) and fmt (62/63) turned out to be one subsystem with nine roots, and they came out in dependency order exactly as the brief predicted: descriptor cargo first, then the value operations, then the consumers. Two of the nine were on no list at all — each stood behind one that was, which is this board’s recurring shape.

Package Before After
fmt 62 of 63 63 of 63 — BANKED, roster row 149
encoding/json 400 of 491, 13 unrecorded (process death) 482 of 491, 0 unrecorded

encoding/json’s whole suite now runs in 24 s, where before it either exhausted a 20-minute deadline or died of stack exhaustion partway through.

The nine roots

# Root Where Verdicts
1 StructField.Anonymous was never populated, so no embed was an embed to any Go encoder reflect/value_impl.cs ~31
2 An embed’s [GoTag] sits on the emitted partial PROPERTY while the backing FIELD is generated — so every embedded field read as untagged GoReflect.FieldAccess.cs with #1
3 new(T) ran golib’s ALLOCATING parameterless constructor, so new([]T) / new(map[K]V) pointed at a non-nil EMPTY container where Go points at nil builtin.cs ~41
3b A POINTER descriptor never carried its POINTEE’s array dims, so reflect.New over TypeOf(new([3]int)).Elem() allocated a ZERO-length array internal/abi/type_impl.cs, GoReflect.TypeLayout.cs with #3
4 Three identity rules were each too FINE for a cycle detector: unsafe.Pointer compared by BOX, a map/slice UnsafePointer tokened the HEADER copy, and a struct’s generated Equals compared an interface field with C# == unsafe.cs, golib/ж.cs, reflect/value_impl.cs, go2cs-gen 13 + the process
5 Value.Bytes threw for a DEFINED byte element, and Value.SetBytes wrote through the never-populated Go data word — silently, for EVERY byte slice reflect/value_impl.cs, GoReflect.ValueMarshalling.cs, slice.cs 3 + 1 (fmt)
6 An unnamed FUNC type had no Go rendering at all — GoReflect.TypeNaming had no delegate arm GoReflect.TypeNaming.cs 1 (fmt)
7 A COMPLEX constant expression was emitted as RUNTIME arithmetic, and .NET’s double - Complex negates +0 converter, convBinaryExpr.go 1 (fmt)
8 (on no list) An unexported EMBED took flagStickyRO where Go gives it flagEmbedRO — only the sticky bit inherits, so every field promoted through an unexported embed was read-only reflect/value_impl.cs 7
9 (on no list) rtype.FieldByName searched DIRECT fields only, so a promoted name read as ABSENT — and Value.FieldByName then handed the EMPTY index to FieldByIndex, which answers the struct ITSELF reflect/value_impl.cs with #8

Two more surfaced as consequences of the fixes above and are closed with them: a NaN map key compared equal to itself (BCL collection semantics, not Go’s ==) — fmt’s own %v of map[float64]int{NaN:1, NaN:1} reads it back — and a variadic tail was detected by the delegate FAMILY NAME, which C#’s natural delegate type does not carry, so func(string, ...int) reported non-variadic and rendered func(string, Span'1).

Root 4 is the one worth reading twice. Go’s cycle detectors are written on identity — e.ptrSeen[v.Interface()] for a pointer, [v.UnsafePointer()] for a map, and [struct{ptr any; len int}] for a slice — and each of the three keys took a different route to being unfindable. The third is a go2cs-gen defect with nothing to do with reflection: the generated memberwise Equals compared an INTERFACE-typed member with C# ==, i.e. by reference, where Go compares interface values by dynamic type and value. Since a struct’s Equals is also what a map LOOKUP calls, such a struct could never be found under a key it had itself stored. The consequence was not a wrong answer but a dead process: Marshal of a self-referential value recursed until 0xc00000fd, which is uncatchable and took every verdict the run had not yet produced with it.

Doctrine: ConversionStrategies-Reference.md gained seven sections — An EMBEDDED field is an embed to reflect, An UNNAMED func type renders STRUCTURALLY, reflect.Value.Bytes/SetBytes are defined over the element KIND, and they ALIAS, new(T) is Go’s ZERO value, An unsafe.Pointer is compared BY ADDRESS, A NaN map key is never equal to anything, and A COMPLEX constant expression must be FOLDED.

Where encoding/json still stands: 482 of 491, ONE root, and it is NOT rooted

TestUnmarshal rows #56–#63 — eight subtests of one table — plus the aggregate row. Each decodes into the 40-field All fixture and compares the result against the table’s literal with reflect.DeepEqual. gotJSON and wantJSON are byte-identical, so the decode itself is right; the only structural difference the failure message shows is one element:

got  … [0x…  <nil>  0x…] …          // SliceP, the decoded side
want … [0x…          0x…] …         // SliceP, the table literal — middle element prints EMPTY

allValue.SliceP is []*Small{{Tag:"tag22"}, nil, {Tag:"tag23"}}, emitted as new ж<Small>[]{Ꮡ(…), default!, Ꮡ(…)}.slice(), so its middle element is a C# null where the decoder writes the canonical typed-nil box. That is the obvious hypothesis and it is measured WRONG: a minimal reproduction of exactly that shape — a nil hole in a []*T, built once as a literal and once through reflect.MakeSlice into a struct field, compared with DeepEqual and printed with %v — agrees with go run on both counts, and it is committed as the ReflectBridgeClosure behavioral test’s nil element / decoded vs literal rows. Normalizing a null pointer read to the canonical nil at Value.live was tried, moved nothing, and was reverted rather than banked as speculative machinery.

So the root is something in the larger value, not the nil encoding alone. The next lane should start by finding which of All’s forty fields DeepEqual actually separates — %v of the whole struct is too lossy to say — and the cheapest instrument is a hand-written probe that walks the two values field by field with reflect.DeepEqual rather than the test’s single top-level call.

No roster row, no proof page, no disclosures, converted test sources NOT committed: 482 of 491 is a measurement, not a validation.

Adjacent, boarded rather than taken

Corpus footprint, measured

A seeded whole-stdlib reconvert (304 packages, 0 failed, 9m00s; marker gate 60 marked / 0 clobbered, full-file line-anchored scan) emitted 1,664 artifacts, of which 1,609 are byte-identical and 49 of the remaining 55 are the documented CRLF phantom. Six real differences, all owed: encoding/gob/{encode,enc_helpers}.cs (the complex fold renders 0+0i as the bare 0D.i() — value-identical, Go’s own spelling), encoding/json/{decode,encode}.cs (restored to the clean emission after this lane’s own pipeline runs), fmt/README.md (the validation badge fmt earns by banking), and vendor/golang.org/x/text/unicode/bidi/core.csthe debt the claude/json-unlock entry above predicted by name, the residue of claude/fmt-roots’ cast-precedence fix landing without its corpus regen. It is paid here. reflect/value.cs also moves, by one Go body replaced with a placeholder (Value.SetBytes joins the hand-owns).

A caution for the next regen census: scan the WHOLE file for the hand-own marker. A first pass here read only each file’s first 40 lines and reported 35 marked files against the real 60, which would have made the clobber gate vacuous for 25 hand-owns. The rule is already written as line-anchored; it must also be whole-file.

encoding/json BANKS at 491/491 — ONE root, and it was a map ENTRY read without its type (2026-08-17, lane claude/json-lastroot)

The entry above left encoding/json at 482 of 491 with the remainder unrooted: TestUnmarshal rows #56–#63 plus the aggregate, all comparing a decoded 40-field All fixture against the table literal with one top-level reflect.DeepEqual, and Marshal of both sides producing byte-identical JSON. It also left the right next step — find which of All’s forty fields DeepEqual separates — and that instrument cost about two minutes and answered on the first run.

Package Before After
encoding/json 482 of 491 491 of 491 — BANKED, roster row 150

491 matched, 0 disclosed, 0 skipped, 41 Benchmark/Example/Fuzz declarations excluded under the standard Phase-4D deferral. The whole suite runs in ~24 s, so it needs no $longTimeouts floor.

The leaf, and the root behind it

A field-by-field probe — walk both structs with reflect, DeepEqual each field, recurse, print the first path that separates — named All.MapP immediately, and named it in the informative way:

DIFF All.MapP: DeepEqual FALSE but every child compares equal (kind map)

All.MapP is map[string]*Small{"19": {Tag:"tag19"}, "20": nil}. Reduced, the divergence is four lines and does not involve encoding/json at all — it needs only that ONE side was written through reflect and the other declared as a literal:

   
two literals equal ✓
two decoded equal ✓
decoded vs literal false

Root: deepValueEqual’s map arm built each entry’s Value from the STORED OBJECT, not from the map’s declared element type. Every other read in the bridge is slot-derived — Field, Index, Elem, MapIndex, MapIter.Value all type by the declaring slot — but the DeepEqual map arm walks the backing Dictionary directly (it must: golib keeps a nil KEY in a side slot no iterator can see) and used makeReflectValue. A slot physically holding C# null therefore came back as the invalid zero Value instead of a valid nil pointer. The two sides spell nil differently and always have — a literal’s nil element is stored as raw null, while a reflective write stores the canonical ж<T>.NilBox that packInterfaceValue needs so a typed nil survives being handed out as an interface — so one side read valid and the other invalid, and the map compared unequal. The same blindness made a nil element compare EQUAL to a missing key (both invalid), which is the more alarming half and had no consumer.

The fix is mapElemValue: type each entry by Elem() of the map’s own type, exactly as MapIndex does. Two nil elements then meet at the kind’s nil rule; a missing key still fails on Contains; a nil element still separates from a present non-nil one. 13 lines in reflect/deepequal_impl.cs, no converter change, no golib change.

Why the visible-difference hypothesis missed it. The previous lane read the failure text, saw SliceP’s nil element print differently, reduced exactly that shape, and measured it AGREEING — a correct measurement of the wrong object. SliceP is a slice, and the slice arm has always used v1.Index(i), which is typed. Only the map arm was blind. The lesson is the one the entry above already wrote down and this lane simply executed: when %v is too lossy, bisect with a probe rather than reason from the rendering.

And the signature is reusable. A container reporting unequal while every one of its elements compares equal is what a lost slot type looks like from outside, because a walk that re-boxes each element through Interface() re-enters the dynamic path on both sides and cannot see the difference. The identical discrepancy named this function’s FUNC arm on 2026-07-31 (compress/flate’s TestWriterReset, ten levels failing while every field matched). Two arms of one function, four months apart, found by the same tell.

What the bank proves

encoding/json is not the roster’s biggest suite by verdict count — at 491 it is sixth, behind go/doc/comment (10,059), go/internal/gcimporter (583), crypto/rsa (559), go/types (557) and internal/zstd (536) — and the claim worth making is a different one: it is by some distance the most reflection-dense suite on the roster. Nearly every row of it is a reflection walk checked against Go’s own answer: struct-tag naming, embedding under Go’s full dominance rules, Marshaler/Unmarshaler/TextMarshaler dispatch at depth, MakeMap/MakeSlice/ SetMapIndex writes into every container kind, cycle detection keyed on UnsafePointer, and DeepEqual over the results. The reflect-closure arc alone rooted nine defects, seven of them credited to encoding/json verdicts, and this lane’s map-element root is the tenth and last. Banking it means the managed reflect is no longer assumed adequate for the encoding packages — it is differentially proven against the standard library’s own hardest consumer of it.

Roster: 150 / 215 (69.8%), 17,264 matching verdicts, 76 disclosed — header recomputed from the table itself.

Carried forward, unchanged

Everything the entry above boarded rather than took is still open and still unrooted-by-choice: the handler(nil) methodless-func conversion (converter emission), a methodless defined func type having no managed identity, projected field ORDER putting every embed last, and the NaN-in-a-composite map key. None of them is reachable from any measured consumer, and this arc found no new one behind the map-element root — the census is honest at one root, not one root plus a remainder.

⛔ The **T OUT-PARAMETER class is CLOSED at the boundary — and crypto/x509 is walled twice more behind it (2026-08-17, lane claude/x509-cryptoapi)

The entry above censused a second syscall class at thirteen wrappers and named its root: ж<T>uintptr answers 0 for a ж<ж<T>> whose held pointer is still null, so an out-parameter tells Windows “no output wanted”. That reading is right and incomplete, and the missing half decides the fix layer. Measured directly against golib on current master:

the box IsNilPointer IsNull (uintptr)
&p before the call (held pointer null) false true 0
&p after anything fills it (held pointer non-null) false false a live MANAGED address

So the operator has two wrong answers here, and the second is far worse than the first: a real address of a slot the collector reads as an OBJECT REFERENCE, handed to a kernel that writes eight raw bytes into it. The board already suspected as much (“the 0 is accidentally the SAFER outcome”); this measures it. EnsureStableAddress does not even pin such a slot — GCHandle pins only reference-free storage — so the address is transient as well as wrong.

The layer, decided by measurement rather than by preference

Nothing golib can return would serve the boundary. The kernel’s representation is eight raw bytes; the managed one is an object reference; no single address is both. Reconciling them needs a SYNC POINT — the moment the raw word becomes a pointer box — and that moment is “after the call returns”, which only the wrapper knows. The remedy is therefore per-wrapper, but the mechanism is one shape for the whole class: a native cell local to the call, its address handed over, and a publish through ValueSlot afterwards (never Value, whose nil guard value-peeks and would panic on the very write that fills the slot in). A zero report publishes the nil pointer with no special case, because ж<T>’s native constructor already treats address 0 as nil.

ж.cs is unchanged. The operator’s answer is correct for the case it was written for, and the contract is now pinned from the other side by two GolibTests cases (AddressOfAPointerToPointerIsNotAKernelWritableSlot, PublishingANativeAddressThroughValueSlotIsTheBoundaryRemedy) so a future lane cannot “fix” it into the dangerous answer.

Class disposition: 13 wrappers, 5 taken, 8 left for stated reasons

The census re-measures at 13, and one attribution on it is wrong: NetGetJoinInformation and NetUserGetInfo are in syscall, not internal/syscall/windows — so the split is 11 + 2, not 9 + 4. Taken (syscall/windows/zsyscall_windows_ptrout_impl.cs, five entries in manualConversionFuncs):

Wrapper Out-param Why taken
ConvertSidToStringSid **uint16 SID.String(); half of a round trip
ConvertStringSidToSid **SID StringToSid; SID is Go’s EMPTY struct — an opaque handle nothing reads through — so a native box is not merely safe but exactly right
NetGetJoinInformation **uint16 a THIRD DLL (netapi32) with a different free routine (NetApiBufferFree) — what makes the guard evidence for a CLASS rather than one advapi32 accident
CertAddCertificateContextToStore **CertContext crypto/x509’s measured consumer
CertGetCertificateChain **CertChainContext crypto/x509’s measured consumer

Left, each for a reason rather than for lack of effort: DnsQuery / _DnsQuery (**DNSRecord) — the pointee is a LINKED native chain whose converted record holds managed references, so publishing the address alone would replace a silent nil with a fabricated-reference landmine; it wants the whole-chain transcription zsyscall_windows_addrinfo_impl.cs gives ADDRINFOW, in a net DNS arc. getQueuedCompletionStatus / GetQueuedCompletionStatus (**Overlapped) — an OVERLAPPED’s identity belongs to the netpoll arc’s per-operation record, and a bare native box would mint an identity that arc does not know. GetFullPathName, NetUserGetInfo, and internal/syscall/windowsCreateEnvironmentBlock / NetUserGetLocalGroups — the same safe shape, no corpus consumer, therefore no value-level proof available (Go’s own syscall.FullPath passes nil for fname, so even its one caller does not exercise it).

The guard, and what failing-first actually printed

New behavioral output test PointerOutParameter: the four well-known SIDs round-tripped string→*SID→string, a malformed SID that must still be REJECTED, a stability check, and NetGetJoinInformation whose returned buffer is WALKED to its NUL rather than merely tested for nil — reading through the published pointer is what proves the address is the one Windows wrote.

Proven failing-first by neutering the cell address. The output is worth recording because it is not a crash:

StringToSid error: The parameter is incorrect.      (x4)
malformed SID rejected: true
stable: true false                                   <- silently wrong, and self-consistent
NetGetJoinInformation error: The parameter is incorrect.
exit=0

advapi32 REQUIRES its out-parameter (unlike ppStoreContext, which is documented OPTIONAL), so the defect surfaces there as a plausible-looking ERROR_INVALID_PARAMETER rather than as silence — and stable: true false is the quiet-wrong-answer shape this class’s history keeps warning about: two empty strings agreeing with each other and with nothing else.

crypto/x509 census: the suite cannot be measured, and the verifier is walled twice

The pipeline does not reach a verdict. go2cs -tests -test-action all converts every _test.go file cleanly and the production assembly builds, but the test host fails to compile with 5 errors in 3 roots — far smaller than the board’s older CS0102 duplicate definition row implies, and none of them about CryptoAPI:

Root Errors What it is
A name minted by the TEST half collides with one the PRODUCTION half already emitted CS0111 ×2 x509.go and x509_test.go both blank-import crypto/sha256 and crypto/sha512; each emits initᴛᴛblankImportꓸcryptoꓸsha256 into the same partial class. General to any package whose tests repeat a production blank import
The same collision through the BLANK-IDENTIFIER counter CS0102 ×1 pem_decrypt.cs has _ᴛ1ʗ (a blank const in an iota block) and oid_test.cs re-mints _ᴛ1ʗ for var _ encoding.BinaryMarshaler = OID{} — the counter restarts for the test half
hash_package unresolved in the PRODUCTION files under the tests closure CS0246 ×2 the tests csproj sets DisableTransitiveProjectReferences, and hash reaches x509.cs / pem_decrypt.cs only TRANSITIVELY (via crypto/sha256); the production csproj does not reference it either and does not need to

All three are -tests emission defects, all three are general, and together they are what stands between crypto/x509 and any operational number at all. Deliberately not taken here — they are a test-pipeline arc, not a CryptoAPI one.

So the verifier was measured directly instead, with an offline probe: a self-signed ECDSA leaf, Verify with Roots == nil (which on Windows routes through systemVerify). Go reaches UnknownAuthorityError immediately. The converted program, with this lane’s fix:

created der: true
parsed cn: go2cs.example
<hangs>

It no longer nil-dereferences at (*storeCtx).Store — that defect is gone — and dotnet-stack names the new wall exactly:

[Native Frames]
syscall!go.syscall_package.Syscall9(...)
syscall!go.syscall_package.CertGetCertificateChain(...)
crypto.x509!go.crypto.x509_package.systemVerify(...)

Blocked INSIDE the kernel call, ~1.7 s of CPU across minutes of wall time. The mechanism is the OTHER, established class: CertChainPara is handed to the kernel BY ADDRESS while holding RequestedUsage.Usage.UsageIdentifiers as ж<ж<byte>> and CacheResync as ж<Filetime> — managed references — and systemVerify writes para.Size = 80, the NATIVE size, into a much smaller managed object. Every field past the first therefore reads from the wrong offset, dwUrlRetrievalTimeout among them, which is a blocking network budget. ((~storeCtx).Store, passed as additionalStore in the same call, is read at a wrong managed offset for the same reason.)

The full remaining CryptoAPI wall, so the next lane needs no census:

Direction Structs Reference-bearing fields
PASSED by address (kernel READS) CertChainPara, CertChainPolicyPara, SSLExtraCertChainPolicyPara, CertChainPolicyStatus ж<ж<byte>> UsageIdentifiers, ж<Filetime> CacheResync, Pointer ExtraPolicyPara, ж<uint16> ServerName, Pointer ExtraPolicyStatus
READ BACK through raw addresses (kernel WROTE) CertContext, CertChainContext, CertSimpleChain, CertChainElement, CertRevocationInfo ж<byte> EncodedCert, ж<CertInfo>, ж<ж<CertSimpleChain>> Chains, ж<ж<CertChainContext>> LowerQualityChains, ж<ж<CertChainElement>> Elements, ж<CertContext>, ж<CertEnhKeyUsage> ×2, …

⚠ The read-back half cannot be answered the way GetAddrInfoW was. That hand-own transcribes the whole chain into managed boxes and makes the free a NO-OP, which works because nothing native has to survive the call. Here CertVerifyCertificateChainPolicy and CertFreeCertificateChain both need the ORIGINAL native pointer back, so any remedy needs a DUAL identity — a managed view that still remembers its native address. That is the arc, and it is not a small one.

Adjacent, found while guarding: (*[N]T)(unsafe.Pointer(p))[:] over a NATIVE pointer is the fabricated-reference fork again

The guard’s first draft read NetGetJoinInformation’s result with Go’s classic pre-unsafe.Slice idiom and died:

Fatal error. System.AccessViolationException
   at go.slice`1[UInt16]..ctor(UInt16[], IntPtr, IntPtr, IntPtr)
   at go.array`1[UInt16].Slice(Int32, Int32)

array<T>.AliasPointer windows a real backing array when the pointer HAS managed element storage, and otherwise falls to (ж<array<T>>)(uintptr)element — a native-address box over array<T>, which is itself reference-bearing. Dereferencing it fabricates a T[] reference out of address bytes. Live corpus sites over genuinely native pointers: net/windows/lookup_windows.cs lines 395, 465 and 500 (the DNS answer walk — SRV Target, NS Host, TXT StringArray) and reflect/type.cs:1887 (t.t.GCData). The syscall/internal/syscall/windows uses are over MANAGED element pointers and are fine. Note where the DNS three sit: directly behind the DnsQuery member this lane deferred, so that arc owns two walls, not one.

Cross-package effect on crypto/tls, recorded rather than measured

TestQUICHandshakeError — the one crypto/tls divergence rooted in this class — is expected to still fail, because the nil it dies on is only the first of three defects on that path; the chain para and the chain walk stand behind it. TestVerifyHostname is likewise still expected to fail on net.adapterAddresses, which this lane does not touch. The full tls census was deliberately NOT re-run: a sibling lane is live on shared paths, and re-measuring 184 tests to confirm two predictions is not worth the contention. Owed at post-merge: one crypto/tls per-test census, whose honest prediction is 176 of 184, unchanged, with TestQUICHandshakeError’s failure having moved from a nil-pointer panic to whatever the chain para produces.

Gate verdicts

Converter go test ./... ok (207 s) · GolibTests 148/148 · PointerOutParameter PASS on all four phases, proven failing-first · syscall package builds clean · solution integrity 622/622 · full CNR and the go2cs-stdlib.slnx windows build recorded with the commit.

Still builds-and-partly-runs for crypto/x509: no roster row, no proof page, no disclosures, converted test sources NOT committed — the package cannot yet produce a verdict to commit.

✅ WINDOWS NAME RESOLUTION WORKS — net.adapterAddresses transcribes the adapter chain, and the wall the tls-endgame lane found was never a wrapper defect (2026-08-17, lane claude/net-interfaces)

The tls-endgame entry above ended by naming net.adapterAddresses as “what stands between the corpus and any name resolution at all on Windows”, and filed it under the syscall struct-passing class with GetAdaptersAddresses as the censused wrapper. The wall is down, and the filing was wrong — that correction is the most transferable thing here.

The row is WITHDRAWN, not marked fixed

internal/syscall/windows’s GetAdaptersAddresses is handed a byte BUFFER and fills it, which is exactly what a byte buffer is for. The wrapper is correct, it stays auto-converted, and hand-owning it would have fixed nothing. The defect was entirely in the CALLER — net’s own adapterAddresses, which walks the filled buffer AS the record:

for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next

IpAdapterAddresses is the corpus’s most reference-dense converted struct (nine ж<T>, an array<byte> PhysicalAddress, an array<uint32> ZoneIndices) where the native record has raw pointers and inline storage, so golib rightly declines to alias the byte run as it, the reinterpret falls to a native-address box, and the loop’s OWN nil test fabricates a managed reference out of adapter bytes — ACCESS_VIOLATION in ж<IpAdapterAddresses>.op_Equality. So this belongs to the readReparseLink / dir_windows_impl fork (a byte buffer the kernel filled, reinterpreted by its consumer), not the mirror-the-wrapper one. The row is struck from the internal/syscall/windows census above and from that package’s own hand-own table, which had asserted it was “repairable by the ORDINARY mirror remedy, because each receives the struct as a typed pointer rather than through a byte reinterpret” — precisely backwards for this member.

The lesson for the rows that remain: which struct is non-blittable does not tell you where the repair goes. Who OWNS the memory it is read out of does.

The remedy, and three properties that generalize

core/net/windows/interface_windows_impl.cs holds the buffer in NATIVE memory that never escapes the function, transcribes the whole chain into managed boxes, and frees it eagerly in a finally.

  1. The output is a chain OF CHAINS. Each adapter record carries six nested linked lists (unicast, anycast, multicast, DNS-server, prefix, WINS-server and gateway — five sharing one native shape), and every consumer reaches THROUGH a record into them, so a top-level copy would have moved the fabrication one hop out rather than removing it.
  2. Whether a managed pointer needs a ManagedPointerTokens handoff is decided by the GO DECLARATION, not by the data. AddrinfoW.Addr is an untyped syscall.Pointer the consumer casts by hand, so it needs a token; SocketAddress.Sockaddr is a TYPED *syscall.RawSockaddrAny that converts to a field carrying a managed box directly — no unsafe.Pointer round trip to survive, so no token, no weak table, no anchor. Reaching for that machinery here would have been ceremony, and the brief for this lane expected it.
  3. The transcription writes the image an EXISTING hand-own reads. Consumers call .Sockaddr(), which is syscall’s hand-owned decode; it flattens the managed RawSockaddrAny back to its 116-byte native image, so this is the same inverse-flattening GetAcceptExSockaddrs performs (Family at 0, Addr.Data 2..15, Pad 16..115).

The copy is FAITHFUL rather than minimal: Length, AdapterName, DnsSuffix, Description, Flags, ZoneIndices, FirstPrefix, the link speeds and the WINS list are carried although no consumer reads them today, because this record is the public shape behind net.Interfaces and a declared field left nil is a SILENT divergence for the next consumer rather than a loud one.

The payoff, measured

A probe run under Go and under the converted corpus, output byte-identical:

LookupIP(localhost) err: <nil> count>0: true
LookupHost(localhost) err: <nil> count>0: true
LookupPort(tcp,https): 443 <nil>
LookupHost(dns.google) err: <nil> count>0: true
Dial(dns.google:443): connected

The last two lines are the result: a LIVE DNS resolution through the server list dnsReadConfig builds from adapterAddresses, then a TCP connection to the resolved address. It is deliberately NOT promoted to a behavioral guard — it needs a network and an external host — so the suite keeps the network-free IpAdapterAddresses guard instead and this stays a recorded probe.

crypto/tls’s TestVerifyHostname was NOT re-measured this lane; the probe answers the same question more directly and an hour-plus census was not spent to restate it. Its verdict stays open.

net’s FIRST pipeline census — it runs, and it does not bank

go2cs -tests -test-action all -test-timeout 60m over net (57 Windows-eligible _test.go files). The suite CONVERTS and COMPILES, and the host RUNS — where before this arc anything reaching adapterAddresses killed the process.

Measure Value
Go on this host 474 verdicts, 695.6 s, FAIL (network-dependent)
converted host 25 verdicts, then killed at the 61-minute safety net
matched / excluded / errors 1 / 53 (benchmarks) / 461

Every one of the 461 “errors” is Go="pass" C#="" — a test the host never REACHED, not a conversion failure. The 25 it did reach ran TestAcceptErrorTestClosingListener, with TestAddrList, TestAddrListPartition, TestCancelAfterDial, TestCloseError and the accept-path tests passing. So the root is a severe SLOWDOWN, not a correctness wall, and net needs a poller/performance arc before its census is even measurable. ⚠ This is exactly the mass-empty shape that reads like total conversion failure; the 25 real verdicts are what prove it is not one.

Gates

Converter go test ./... ok (216.8 s) · full CNR byte-identical across 620 behavioral packages, nothing NOT MEASURED · seeded full reconvert 304/304, and the overlay is 0 content differences / 0 file-set differences with the marker gate at 61 marked files, 0 clobber violations (18 .cs.auto emitted) — so the hand-own is stable under regeneration, including its L3 routing into net/windows/ and the AllowUnsafeBlocks flip its [module: GoRequiresUnsafe] causes · full behavioral suite 593/593 across all four phases, 567 output comparisons, 0 failures (3,077.4 s) · solution integrity ok (622 projects). The guard is proven failing-first: with the auto body restored it reports exit code mismatch: C# -1073741819 vs Go 0, the access violation itself.

⚠ Two process traps paid for here, both already in CLAUDE.md and both re-encountered: PowerShell’s Start-Process -ArgumentList ARRAY form does not quote a path containing a space, so the first pipeline launch died with Failed to access input file path "C:\Program" — which reads exactly like a missing GOROOT and is not (use the single-string form with embedded quotes); and an until ! powershell -Command "exit (…)" wait-loop reported a still-running go test as finished, the documented exit $true trap, caught only against a positive process count.

⛔ HARVEST — the pure-compute tail re-measured; nothing banks, net/netip’s SYNTAX wall falls, and three “reproduced verbatim” rows re-earn that phrase (2026-08-17, lane claude/harvest)

A ranked breadth pass over the unbanked pure-compute candidates, run on the r43c/r44a doctrine and returning the least fashionable answer this board records: six candidates measured, zero banked. That is worth writing down precisely because every previous breadth pass here banked something — the cheap rows are spent, and what is left in the pure-compute tail is walls with names. The pass is still worth its cost twice over: two converter defects that emit UNPARSEABLE C# are closed, and a root the board has recommended twice as “the cheapest remaining pair” turns out to be spent.

The enumeration, and what it excludes

305 converted package directories under src/core carry a production .csproj (306 counting the hand-written golib); 216 have a func Test in their Go 1.23.1 GOROOT sources; minus hand-owned testing that is the roster header’s 215, and 215 − 150 banked = 65 unbanked. Of those, the following are excluded from a pure-compute harvest rather than measured: the socket-walled net family (net, net/http, net/http/{cgi, cookiejar,httptest,httptrace,httputil,internal,pprof}, net/rpc, net/smtp, net/mail, net/internal/socktest, log/syslog) behind the poller-throughput arc; the runtime/OS internals (runtime, runtime/{debug,pprof,race,trace}, internal/runtime/*, internal/poll, internal/syscall/*, internal/trace*, internal/coverage/cfile); the lane-owned crypto/tls, crypto/x509 and reflect; the packages a ratified ruling already blocks (math/big 224/226 and net/http/internal 9/10, both held by the undisclosed near-budget alloc counts; log 7/9 behind the runtime.Caller arc; os/user, whose TestGroupIds fails in Go’s own oracle); the working-directory class (go/build 57/58, internal/platform, internal/godebugs); and the architectural rows (internal/unsafeheader 0/6, iter 0/28 on the newcoro/coroswitch stubs, slices 0/122 on element-type deduction for constrained slice generics).

The six, in the order they were run

Package Verdicts Board had Measured 2026-08-17
html 3 2 of 3 2 of 3, unmoved. TestUnescape — the map-MISS producer of the array<T>-without-its-Go-length class. The default! zero-value emission (claude/zero-value-arrays) does NOT reach it: that fix covers declared zero values, and a map miss has no declaration to read a shape from. Still the converter+golib arc r57b priced
internal/chacha8rand 4 3 of 4 3 of 4, unmoved. TestBlockGeneric panics index out of range [0] with length 0 at chacha8_generic.cs:64 — and the producer is NOT a declaration either: it is the unsafe.Pointer array-SHAPE reinterpret ([32]uint64[16][4]uint32) that hands back a length-0 outer array<T>. Same seam as debug/pe’s, with no kernel in it
debug/pe 10 9 of 10 9 of 10, unmoved. TestReadCOFFSymbolAuxInfo — the byte-level struct pun across surrogate layouts, exactly as recorded
encoding/gob 106 99 of 106 100 of 106 — one row, from the reflect-bridge closure. TestSingletons closed. The six that remain are four roots: the array<T> length class (TestEndToEnd, TestIndirectSliceMapArray), gob’s own decoder IGNORE path (TestBadData, TestIgnoreRecursiveType), reflect.Value.IsNil on an INTERFACE asking the pointee (TestNilPointerInsideInterface), plus TestIgnoreDepthLimit infrastructure-erroring on the reflect.ArrayOftypelinks stub
net/netip 266 0 of 266, CS1002/CS1525 The SYNTAX wall is CLOSED — two converter defects, both fixed below — and three semantic roots stand behind it. Still 0 of 266
internal/reflectlite 30 0 of 30, CS0715 0 of 30, and the root MOVED. CS0715 is gone; two different diagnostics now: CS0030 and CS8130, below

The two converter defects net/netip was the first thing in the corpus to reach

Both emit C# that does not PARSE, so nothing downstream of the compiler could have caught them, and both are shapes the production corpus happens not to contain — which is why they survived to be found by a Phase-4 measurement rather than by a build. Both are fixed, each with a failing-first guard in src/go2cs/mixedKeyedComposite_test.go.

1. Go’s all-or-nothing keying rule is a STRUCT-literal rule. An ARRAY or SLICE literal may MIX positional and keyed elements — []byte{0xfe, 0x80, 15: 0x01} is a SIXTEEN-byte value — and every keyed path in convCompositeLit decided from Elts[0] alone (compositeLitIsKeyed’s comment stated the wrong rule as its justification). A mixed literal therefore took the plain positional emission while its keyed elements still rendered through the key/value arm, whose sparse form wants a target ident that does not exist in an expression position: new byte[]{0xfe, 0x80, <nil>[15] = 0x01} — CS1525. The fix normalizes the POSITIONAL elements to the indices Go gives them (first element 0; after a keyed element at k, continue at k+1) so the literal is all-keyed and the existing SparseArray machinery renders it — new slice<byte>(16){[0] = 0xfe, [1] = 0x80, [15] = 0x01}, which also recovers the length a wrong emission gets silently wrong. An all-positional or already-all-keyed literal is untouched by construction, which is why the whole corpus is byte-identical. A literal whose keys will not fold to constants is left exactly as it was: an index the converter cannot compute is one it must not invent.

2. Rooting a global using RHS was not idempotent. renderCSFullTypeName’s default arm prefixes the root namespace unconditionally, and a WHITE-BOX test conversion hands it names the test-alias qualifiers (testAliasShadowOperations) have already rooted with an explicit global:: — so export_test.go’s type Uint128 = uint128 emitted global using Uint128 = go.global::go.net.netip_package.uint128;, CS7000 “unexpected use of an aliased name”. global:: IS the root; prefixing it can only produce a name that is not one.

What stands behind them — net/netip, three roots, all semantic

The package now COMPILES past the parser and stops on eight diagnostics in three families:

Root Sites Note
An EXPORTED test declaration over an UNEXPORTED production type is emitted more accessible than the type CS0050 ×2, CS0052 ×3 in export_test.cs func MakeAddrDetail(…) addrDetail and var Z0 = …Handle[addrDetail] are exported Go names, so they emit public; addrDetail/uint128 are unexported, so they emit internal. Legal Go, illegal C#. A test assembly’s public has no external consumer, so clamping an internal-test declaration’s accessibility to its type’s is the shape of the remedy
A global using alias is assembly-scoped, not a namespace member CS0426 in netip_test.cs netip_package.AddrDetail — the class importOperations.go:894 names, whose CROSS-package two-hop form is already fixed; this is the SAME-package (white-box test) instance of it. encoding/xml’s recorded blocker reads identically (ΔToken does not exist in xml_package, 386 verdicts) and html/template’s CS0030 sits next door — whether one fix serves all three is a hypothesis this pass did NOT measure, but it is the cheapest one to test
A generic test helper’s constraint renders non-generically CS0305/CS0308 in fuzz_test.cs comparable<T> used with no type argument, and netipTypeCmp used with one

internal/reflectlite — the recorded root is spent, and the two behind it are named

CS0715 (a generic test-local type’s operators emitted into the static test partial) no longer appears, which retires the board’s “cheapest remaining pair” framing — the pair is now one package (runtime/debug, 9, unmeasured this pass) plus two fresh roots here:

The ranked queue this pass hands on

  1. encoding/xml (386) and net/netip (266) — the assembly-scoped-alias CS0426 root, measured in both and identical in shape. html/template (243) and text/template (52) sit behind a CS0030 that may or may not be the same family; measure encoding/xml first, since its diagnostic is the one that matches.
  2. sync/atomic (108) — CS0841 (use of a local before its declaration); debug/elf (31) — CS8183 (implicitly-typed discard); flag (24) — CS1929; crypto/ed25519 (9) — CS0030; internal/concurrent (20) — CS0426 node<,>. Five one-diagnostic build blocks, each unmeasured since scout batch 2 and each therefore a candidate for having MOVED, as reflectlite’s just did.
  3. encoding/gob (106) — three roots, of which Value.IsNil on an interface is a five-line bridge fix the board has carried since r39.
  4. internal/reflectlite (30) + runtime/debug (9) — behind the two roots above.
  5. crypto/internal/edwards25519 (55) — the ratified Option A tuple-spec init-order fix is still unimplemented; the residual after it is one AllocsPerRun row.

Deliberately NOT re-measured: unique (4/19), log/slog (153/213), go/doc (24/85) — each has a current census and a named owner, and r44a’s lesson is that the yield is in what nobody has run.

The measurement hazard this pass paid for

MSB4166 “Child node exited prematurely” is NOT a build root. debug/pe first measured as a hard build failure with eleven MSB4166s and no CS diagnostic at all; re-run with MSBUILDDISABLENODEREUSE=1 it reached its real 9-of-10 in 45 s. The pipeline shells out to dotnet run, whose MSBuild leaves node-reuse workers alive BETWEEN pipeline runs, and a reused node that dies takes the next package’s build with it. Set MSBUILDDISABLENODEREUSE=1 for any queue that runs several -tests conversions back to back — it is also the isolation flag a lane owes its siblings.

Gates

Converter go test ./... ok (274.3 s) · full CNR byte-identical across all 621 behavioral packages, nothing NOT MEASURED, 2 advisory warnings (1,440 s) · seeded full corpus reconvert (307 projects, 549 s) 1,665 emitted artifacts, 1,665 identical, 0 differing, 0 new — the zero-corpus-churn claim measured rather than argued, and self-validating on the hand-own marker gate (a failed seeding would have emitted the hand-owns as plain .cs and they would have shown as differing) · both guards proven failing-first (<nil>[15] = 0x01 and go.global::go.net.netip_package.uint128 each reproduce with its fix neutered). No roster row, no proof page, no disclosures, no converted test sources committed: six measurements, zero validations.

⛔ The three -tests roots were SIX, they are all RECOMPILE-MODEL roots, and crypto/x509’s compile wall is now ONE FILE behind a model-selection arc (2026-08-17, lane claude/x509-unlock)

The entry above censused crypto/x509’s test host at 5 errors in 3 roots and called all three general -tests emission defects. The count was right, the reading was right, and it was half the depth: closing those three exposed three more of the same family, and closing all six took the package from 5 errors to 6, in one file — a 97 % reduction that ends at a different class entirely. Every one of the six is fixed in the converter with a failing-first guard.

One principle, five supplies: under the RECOMPILE model the test half CONTINUES the production pass

Two supplies were already pinned this way (productionLiftedTypeNames, productionHoistedConstOrdinals). Three more owed it, and the fix collects all five into one productionSeed struct captured before the first variant’s resetPackageState:

# Supply Emitted name crypto/x509’s collision Errors
1 blank-import force hooks initᴛᴛblankImportꓸcryptoꓸsha256 x509.go and x509_test.go both blank-import sha256 and sha512 CS0111 ×2
2 the blank-identifier counter _ᴛ1ʗ pem_decrypt.go’s blank iota const vs oid_test.go’s var _ encoding.BinaryMarshaler CS0102
5 func init() ordinals init / initΔ1 windows/root_windows.go’s init vs x509_test.go’s CS0111

None of these is exotic Go — a test repeating a production blank import is what a test exercising those registrations does. The blank-import hook is the one whose OWNERSHIP is worth stating and not merely its uniqueness: exactly one hook per (assembly, imported package), and the production half owns it whenever its file is in the compilation, because that file is the one a -tests run cannot rewrite.

Three more: a recompile-model test project compiles the production sources, so it owes their references and their per-GOOS half

# Root crypto/x509’s symptom Errors
3 the B2c alias scan read only the TEST-emitted files x509.cs/pem_decrypt.cs emit using hash = hash_package; (crypto.Hash.New() RETURNS hash.Hash, so hash is in no import list and in no production reference) — and DisableTransitiveProjectReferences hides it CS0246 ×2
4 productionCSFiles was FLAT-only, and layout L3 is not flat the whole Windows verifier (windows/verify.cs, windows/root_windows.cs) fell out of the test compilation 187
6 the production static-ctor probe was FLAT-only a SECOND static x509_package() emitted beside the real one in windows/package_init.cs CS0111

Root 3’s omission hides in the ordinary case, because a production file’s aliases are usually its own package’s direct imports, which the import-derived set already carries; it bites only where the alias names a package reached transitively. Roots 4 and 6 had never been exercised because crypto/x509 is the corpus’s only L3 package on the recompile model — every other L3 suite takes a reference model, where the production ASSEMBLY carries its per-GOOS half. Note root 4’s shape: 187 errors, every one of them reported against a TEST file, none of them naming the missing folder.

Unlock breadth: measured, and NARROW — because all six are recompile-model-only

Worth stating plainly, because the honest answer is smaller than the brief expected. All six roots live on the path taken only when the test half emits into the production class, i.e. the recompile model, which is a deterministic FALLBACK rather than a selection. Measured on this branch:

So: general in FORM, crypto/x509-specific in PRACTICE today, and free for any future package that falls back to recompile. That is the whole breadth; there is no larger unlock hiding behind it.

crypto/x509’s census: 5 → 6 errors, ONE file, and the wall is the one the reference model exists to prevent

The remaining six errors are all in hybrid_pool_test.cs, and they are the recompile model’s signature defect, already written down in this repo’s own doctrine (testProjectReference’s declaration comment): a referenced assembly whose API mentions a production type names it in the PRODUCTION assembly, while the test assembly’s recompiled copy is a DISTINCT type.

googChain := c.ConnectionState().PeerCertificates      // c is a crypto/tls Conn — a REFERENCED assembly

CS0012: The type 'x509_package.Certificate' is defined in an assembly that is not referenced … 'crypto.x509' ×4, plus CS1929 … the best extension method overload 'Verify(ж<Certificate>, VerifyOptions)' requires a receiver of type 'go.ж<go.crypto.x509_package.Certificate>' ×2. No compile-set or reference adjustment repairs an identity split; the remedy is to stop needing the fallback.

Why x509 falls back, censused exactly — ONE record. Instrumenting recordsRequireProductionMutation on this branch prints exactly one offender:

CENSUS implicit: global::go.crypto.x509_package.Certificate -> ж<global::go.crypto.x509_package.Certificate>

That is the shared Go pointer-boxing route T → ж<T>, and the adjacent indirectImplicitConversions loop already exempts precisely this shape, with the reasoning spelled out in its own comment (“the generator intentionally emits no type-owned operator for a foreign T, so it does not mutate production”). The exemption is simply absent from the DIRECT implicitConversions/invertedImplicitConversions loop above it. That is the next lane’s move and this lane deliberately did not take it: it changes MODEL SELECTION, whose blast radius is the three recompile packages above — two of them banked and validated — so it owes their re-proof plus a sweep, which is a different arc from six naming/closure fixes and must not be smuggled in behind them.

The census with that one line applied is MEASURED, not predicted — run on a throwaway binary, with the branch’s source unchanged and the experimental binary deleted afterwards. It is the most useful number this lane produced:

   
verdicts agreeing 264 of 335
divergent 71
— of those: Go=pass C#="" (no verdict produced) 51
— of those: Go=skip C#="" 16
— of those: real C# failures 4

The host compiles clean and RUNS: zero C# errors, and 264 of the suite’s own verdicts agree with go test — parsing, marshalling, name constraints, PEM, SEC1, CRL creation, path building, EKU enforcement. The run then times out at 1h1m (the safety net past -test-timeout 60m), and the 67 empty verdicts are dominated by TestSystemVerify/* and everything sequenced after it — exactly the stack-proven CertChainPara wall the entry above censused, now visible as a HANG rather than as a nil dereference. TestHybridPool is among the empty rows.

The four genuine divergences are small, named, and independent of every wall above: TestParsePKIXPublicKey (and its X25519 subtest), TestOIDMarshal, TestPKCS8.

So the ordering for the next lane is settled by measurement rather than by guess: land the model-selection exemption (with crypto/ecdh and text/tabwriter re-proved), and crypto/x509 becomes a package with four ordinary divergences and one big wall, not a build-blocked one.

Gate verdicts

Converter go test ./... ok · full CNR byte-identical · the two other banked recompile-model packages re-converted byte-identical · encoding/json canary re-swept (491) · six guards proven failing-first by neutering each fix in turn (the neutered runs print the real diagnostics: _ᴛ1ʗ re-minted, windows/verify.cs missing from the compile set, hash absent from the reference set).

⚠ Process note, both already in CLAUDE.md and both re-paid here: the Start-Process -ArgumentList array form does not quote C:\Program Files\… (died as Failed to access input file path "C:\Program"), and the Bash tool’s timeout caps at 600 s regardless of the value passed — a 20-minute pipeline run passed 3000000 and was killed at exactly 10 minutes with exit 143, orphaning the dotnet run child that then held runtime.dll locked. Long runs go through detached Start-Process with a PID poll, and the orphan sweep must be scoped by COMMAND LINE (Win32_Process … CommandLine -like '*<lane>*'), never by process name — three sibling lanes were live on this machine throughout.

crypto/tls STILL does not bank — but its endgame collapses from FOUR owners to ONE, and BoGo was never the wall it looked like (2026-08-17, lane claude/tls-finish)

The tls-endgame entry above left four divergences with four different owners and predicted where each would land once the syscall arcs merged. This lane re-measured all four. Three of the four predictions were right and the fourth was wrong in the useful direction: TestBogoSuite’s host defect is fixed, and BoGo does not merely start — a real BoringSSL interop case passes end to end through the converted host as its TLS shim. What is left is one arc, not four.

The census, re-measured today

Same method as the tls-endgame lane (one process per top-level Test*, -run '^Name$', 25 s cap, raw stdout saved, classified offline against the CONVERTED host’s PASS<pad><Name> format). TestBogoSuite is measured separately below, because a 25 s cap cannot hold it on either runtime.

Measure after sha3 after the syscall arcs now
top-level tests that PASS run on their own 176 of 184 (not re-run) 176 of 184
real divergences (Go passes, C# does not) 4 4 predicted 3 measured + BoGo
distinct roots behind them 3 2
of those, disclosable under an existing class 1 1

Go on this host, re-measured rather than carried (go test -json -count=1, 56.9 s): 184 top-level, 180 pass, 4 fail, and the four are the same expired-fixture set — TestResumption, TestResumptionKeepsOCSPAndSCT, TestVerifyConnection, TestCrossVersionResume (the test certificates expired 2025-01-01). The converted host fails exactly those four, so they are AGREEING rows. The cross-tab closes with no remainder: 176 agreeing passes + 4 agreeing failures + 3 divergences + TestBogoSuite = 184.

⚠ The expired-fixture ceiling is re-confirmed rather than carried: 180 of 184 is the most this host can score in either language, it worsens with time, and a Go patch release or regenerated fixtures changes the shape. Any proof page written for this package must say so.

1. TestVerifyHostname — the net wall is DOWN, and what it uncovered is not tls’s

The net-interfaces entry above declined to re-measure this test, on the ground that its own probe answered the question more directly. It did, and the census now confirms it from the consumer side: Dial("tcp", "www.google.com:https", nil) resolves the name AND the service, connects, and completes a TLS 1.3 handshake through the server’s Certificate message before failing. Everything the two syscall arcs bought is exercised on that one line — GetAddrInfoW for LookupPort’s https, adapterAddresses for the DNS server list, the poller for the connect and the record reads.

It then dies one layer further out, and NOT in net:

INFRASTRUCTURE-ERROR TestVerifyHostname — System.Runtime.InteropServices.SEHException (0x80004005)
   at go.syscall_package.Syscall9(...)
   at go.syscall_package.CertGetCertificateChain(...)      <- zsyscall_windows_ptrout_impl.cs:222
   at go.crypto.x509_package.systemVerify(...)             <- root_windows.cs:272
   at go.crypto.x509_package.Verify(...)
   at go.crypto.tls_package.verifyServerCertificate(...)
   at go.crypto.tls_package.readServerCertificate(...)     <- handshake_client_tls13.cs:692

That is the wall the x509-cryptoapi entry named: CertChainPara is handed to the kernel BY ADDRESS while holding RequestedUsage.Usage.UsageIdentifiers as ж<ж<byte>> and CacheResync as ж<Filetime>, so systemVerify’s para.Size = 80 writes the NATIVE size into a much smaller managed object and every field past the first is read from the wrong offset. Note where the frame is: inside the hand-owned zsyscall_windows_ptrout_impl.cs wrapper, i.e. the out-parameter fix is doing its job and the defect is the other class, exactly as that lane predicted.

2. TestQUICHandshakeError — the prediction was right, and the shape is a HANG

The x509 lane predicted this row would still fail, “with its failure having moved from a nil-pointer panic to whatever the chain para produces”. Measured: it produces nothing — the process is still inside CertGetCertificateChain when the census’s 25 s cap fires (TIMEOUT — package timeout after 00:00:25), the same blocked-in-the-kernel signature that lane’s offline probe recorded with dotnet-stack. The corrupted CertChainPara includes dwUrlRetrievalTimeout, a blocking network budget, read at the wrong offset.

So TestVerifyHostname and TestQUICHandshakeError are ONE root, not two — the same CryptoAPI structure-passing wall, reached by two different tls paths (a real server’s chain, and a QUIC handshake against an untrusted test certificate). Neither is disclosable: this is an unimplemented-but-fixable defect, which is exactly what the host-limit bar excludes.

3. TestCertCache — disclosed, codegen-liveness, signature-pinned

Re-measured and unchanged: FAIL TestCertCache — timed out waiting for expected ref count, at the FIRST check (refs 2 → 1), 8.1 s in the per-test census. Argued from the test’s own assertion: it nils its local, calls runtime.GC() and polls four seconds for a finalizer to decrement a count, while certA, err := cc.newCert(p.Bytes) is a two-result call whose results materialize an address-exposed frame temp — a slot the CLR reports live for the whole method, so the activeCert cannot be collected until TestCertCache RETURNS. The finalizer bridge itself is proven working by sync’s banked TestPoolGC. Committed as src/core/crypto/tls/go2cs_test_disclosures.json ahead of a bank the package cannot yet make, so the row is pinned by exact signature the moment it can.

4. TestBogoSuite — the host defect is FIXED, and the converted TLS stack talks to BoringSSL

The tls-endgame entry read this as “a fixable test-host defect”, and beyond it, “a long external TLS interop measurement, not a pass: it is its own arc”. The first half was right. The second half is wrong, and the correction is the most encouraging thing in this entry.

The host defect, and its real mechanism. BoGo re-executes the test binary as its shim (-shim-path=os.Args[0] -shim-extra-flags=-bogo-mode), and -bogo-mode is a package-level flag.Bool in handshake_test.go. The board read the defect as “the host parses args itself and throws on any name it does not recognize, before the package’s own flag.Parse() runs” — true, but the fix is NOT to tolerate unknown flags. Go’s test binary reaches exactly ONE flag.Parse(), by which time testing.Init() has defined -test.* and the package’s own package-level flag variables have initialized, so both vocabularies live in one flag set and an unknown name is still an error there. What the converted host lacked was the ORDER: nothing had run the package’s initialization at the moment the host had to decide what its command line meant.

The fix, in the shared hand-owned host (three files under src/core/testing/). An unrecognized flag name no longer throws — TestOptions.Parse records it and STOPS, the way a non-flag token stops the parse, because nothing there can know a foreign flag’s arity (-port 5000’s value is indistinguishable from a program argument). TestHost.Run then does what Go does before main: it runs the package’s own initialization — RuntimeHelpers.RunClassConstructor over the declaring types of the registry’s delegates, which are exactly the converted package’s classes — registers the host’s own flags on top, and only then asks the converted flag.CommandLine whether the recorded name is defined. Undefined — or no flag package in the compilation at all, which is 124 of 141 test projects — is still flag provided but not defined: -x and still exit 2. The rejection moved; it did not go away. Deliberately NOT done: a host-side flag.Parse() mirroring M.Run’s if !flag.Parsed(), which the reference already rules out as newly reaching ExitOnError for packages that merely reference flag.

One measured surprise worth carrying. Creating a delegate over a static method does not run its declaring type’s static constructor — ldftn+newobj is neither a static-field access nor an invocation — so the generated test host’s registry.Add("TestX", pkg_test_package.TestX, …) lines leave the package uninitialized until the first test BODY runs. That is what makes the forcing load-bearing rather than belt-and-braces, and it is proven failing-first: with the InitializePackageUnderTest call neutered, the new guard reports flag provided but not defined: -harness-package-mode and exit 2 — the BoGo failure verbatim.

Guarded by TestingRuntimeTests.APackageRegisteredFlagParticipatesAndAnUndefinedOneIsStillRejected, which stands a class whose STATIC CONSTRUCTOR declares a flag in for the package under test (written with an explicit static ctor, not a field initializer, so the CLR’s precise non-beforefieldinit rules apply and a runtime that chose to initialize early could not fake a pass). It pins all four claims: the package’s flag participates, a flag BEFORE it is still the host’s, a flag AFTER it belongs to the program, and an undefined name is still exit 2. This is the one test that needs the converted flag package present, so BehavioralTests.csproj now references it — testing.csproj still must not, and does not, which is the whole point of the late binding.

What BoGo then did. Two measurements, both value-level:

> tls.test.exe             -bogo-mode -is-handshaker-supported   ->  No     (0.038 s)
> crypto.tls.tests.exe     -bogo-mode -is-handshaker-supported   ->  No     (3.30 s)

— byte-identical output from the shim entry point, network-free. And then the real thing, the converted host driven by BoringSSL’s own runner:

> crypto.tls.tests.exe -run "^TestBogoSuite$" -v -timeout 14m -bogo-filter Client-Verify-ECDSA-TLS1
RUN                  TestBogoSuite
RUN                  TestBogoSuite/Client-Verify-ECDSA-TLS1
PASS                 TestBogoSuite/Client-Verify-ECDSA-TLS1
PASS                 TestBogoSuite

12.1 s, including go mod download of the pinned boringssl module, building the BoGo runner with the Go toolchain, and a TLS 1.0 client handshake with certificate verification against BoringSSL as the peer. The converted TLS implementation interoperates with a foreign, adversarial TLS implementation — which nothing in the corpus had shown before.

Why the full suite was still not run. Go’s own run here is 3,242 BoGo cases (861 pass, 2,381 skip, 0 fail) in 31.8 s; BoGo spawns the shim once per case, and the converted host’s shim invocation costs 3.30 s against the Go binary’s 0.038 s — ~87x, and ~3 hours of process startup alone before any TLS work. That is a cost multiplier, not a correctness wall, it is the same managed-startup number every converted test host pays, and it is not on crypto/tls’s critical path, because the package cannot bank on the x509 root regardless. Left deliberately unmeasured-in-full and named for whoever wants it: the honest prediction is that it PASSES, given roughly a four-hour deadline.

Where crypto/tls stands

One arc — crypto/x509’s Windows system verifier — is now the entire distance between crypto/tls and a roster row. Everything else is settled: the protocol work has been green since sha3 (TLS 1.2 and 1.3, QUIC, session tickets, ECH, the whole handshake matrix, and now BoringSSL interop), the four expired-fixture failures agree with Go, TestCertCache is disclosed and committed, and TestBogoSuite’s host defect is closed by a general fix the whole roster now carries. The remaining root has a full census in the x509-cryptoapi entry above (four structures passed by address, five read back through raw addresses, and the dual-identity problem that makes the GetAddrInfoW transcription shape inapplicable) — and note that crypto/x509’s own suite still cannot be measured at all, on three unrelated -tests emission defects, so that arc owes a test-pipeline arc in front of it.

Still builds-and-partly-runs: no roster row, no proof page, converted test sources not committed (the disclosure manifest is, as the one hand-owned artifact that outlives the measurement).

Gates

Full validated sweep — the gate every core/testing change owes, and the canaries fmt, database/sql and os/exec are rows inside it · TestingRuntimeTests 26/26 including the new guard, proven failing-first · solution integrity 623/623 · go2cs.slnx build, the only gate that compiles BehavioralTests.csproj, whose reference set changed. No converter change, so neither go test ./... nor CNR is owed — src/go2cs is untouched.

✅ THE CRYPTOAPI CHAIN WALL IS DOWN — and the finding is that its two halves were never separable (2026-08-18, lane claude/x509-verifier)

Three entries above named this wall from three sides: the x509-cryptoapi lane censused it (four structures passed by address, five read back through raw addresses, “the dual-identity problem that makes the GetAddrInfoW transcription shape inapplicable”), the x509-unlock lane measured it as a 1-hour hang swallowing 67 verdicts, and the tls-finish lane measured it as the single arc standing between crypto/tls and a roster row. crypto/x509’s Windows system verifier now runs end to end, offline, with every value agreeing with Go.

What the brief predicted, and the one prediction that was wrong

The lane was scoped in two phases: fix the INPUT (CertChainPara by address) and measure whether the call RETURNS; only then consider the read-back. That sequencing turned out to be the most useful thing measured, because phase 1 alone changes nothing, and the reason is general:

baseline (no fix)          SEHException 0x80004005 out of Syscall9
                             at CertGetCertificateChain   <- zsyscall_windows_ptrout_impl.cs:222
                             at systemVerify              <- root_windows.cs:272

parameter mirror ONLY      SEHException 0x80004005 out of Syscall9   (identical)
                             at CertGetCertificateChain   <- zsyscall_windows_certchain_impl.cs:183

parameter + read-back      all fourteen lines agree with `go run`, 1.44 s

root_windows.go calls CertGetCertificateChain(0, storeCtx, verifyTime, storeCtx.Store, para, …). storeCtx.Store is a field the CALLER reads out of a native CERT_CONTEXT before the wrapper is ever entered — at the converted struct’s offset, which under the CLR’s reference-first auto-layout is where the native record keeps cbCertEncoded. The store handle crypt32 receives is a certificate LENGTH. When one wrapper’s argument is a field of another wrapper’s result, the input fix and the read-back fix are one change, and no amount of work on the parameter can show progress alone. That is worth carrying to the remaining members of both syscall classes.

(The input half is real and does have to be fixed. CERT_CHAIN_PARA is 80 native bytes against a managed record roughly a third that size, and the field that decides the SYMPTOM is dwUrlRetrievalTimeout at native offset 56 — a blocking network budget read from arbitrary managed bytes, which is why the same root shows as a multi-minute hang from crypto/tls and as an SEH from the offline verifier. RequestedUsage.Usage.UsageIdentifiers is an array of C string pointers into NUL-terminated managed byte slices — no native form at either level — and is transcribed into one native block for exactly the duration of the call.)

The dual identity, and why it needed no golib change

The read-back could not take the GetAddrInfoW shape, exactly as the census said: that hand-own transcribes a native chain and makes the free a NO-OP, which works only because nothing native has to survive the call. Here the original pointer must go back to crypt32 three more times — as the next call’s leaf, to CertVerifyCertificateChainPolicy, and to the two CertFree* routines, which release reference-counted memory. A managed view alone leaks a chain per verification; a native box alone reads every field from the wrong offset.

So each returned pointer becomes a managed view that remembers its native identity: a real ж<CertContext> / ж<CertChainContext> whose fields the converted Go code reads as an ordinary struct, with the address it was built from recorded beside it in a weak ConditionalWeakTable. Wrappers that must hand a pointer back ask that table first and fall back to the box’s own address — which is why CertCreateCertificateContext and CertEnumCertificatesInStore stay generated and keep working: they produce plain native boxes nothing reads a field through.

ж.cs is untouched, and ManagedPointerTokens was not needed either. The table is a syscall-local seam on purpose: ж<T> has no business knowing that one pointee is reference-counted by crypt32, and the sync point (“the moment a raw word becomes a pointer box”) is again something only the wrapper knows — the same argument the **T out-parameter entry made for its own remedy. The corpus now has THREE answers for a native pointer coming back, and the question that selects between them is not the struct’s shape but who else needs the pointer: publish a native box (ptrout, for opaque handles), transcribe and free eagerly (addrinfo, when nothing native survives), or transcribe and remember (here, when it must).

The guard, and what failing-first printed

New behavioral output test SystemCertVerify. A self-signed ECDSA leaf, generated in-process (so it never expires and never depends on the host’s certificate stores), verified with Roots == nil — which on Windows routes to systemVerify — for an untrusted-root verdict every Windows host agrees on with no network. Then the same CryptoAPI sequence driven DIRECTLY, because a verdict alone cannot carry this evidence: a misread trust status produces the same “unknown authority” answer. The round trip is what proves it.

created der: true                          leaf der round-trips: true
parsed cn: go2cs.example                   store handle round-trips: true
verify chains: 0                           store context der length: true
verify error: x509: certificate signed by unknown authority
unknown authority: true                    chain count: 1
verify with dnsname error: … unknown authority
hostname mismatch: true                    chain reports untrusted root: true
                                           simple chain elements: 1

store handle round-trips is the dual identity read back by value (the handle inside the context equals the handle CertOpenStore returned); leaf der round-trips walks CertChainContext → Chains → Elements → CertContext → EncodedCert and compares the recovered DER byte for byte against the original, which exercises the transcribed pointer arrays and unsafe.Slice over them. Proven failing-first: the pre-fix binary prints the first two lines and dies with the SEHException above.

Deliberately NOT taken, with the reason: CertVerifyCertificateChainPolicy

Its CERT_CHAIN_POLICY_PARA carries pvExtraPolicyPara as Go’s opaque syscall.Pointer, minted in crypto/x509 as unsafe.Pointer(sslPara) over an SSLExtraCertChainPolicyPara whose ServerName is itself a managed reference. What reaches the boundary is therefore a transient managed address with no recoverable box behind it — golib’s uintptr operator declines to pin reference-bearing storage, so there is nothing for the wrapper to resolve and nothing to copy from. This is a MINT-SITE problem, not a boundary one, and it has two priced remedies, neither of them this arc:

Remedy Cost Note
Register a ManagedPointerTokens entry when ж<T> → uintptr cannot pin golib, corpus-wide changes what 875 emitted conversion sites hand out; the token table was written for exactly this round trip, but making the operator a MINTER is a model change and wants its own ruling
Hand-own crypto/x509’s checkChainSSLServerPolicy ~80 lines, one package builds the two policy mirrors itself and calls crypt32 directly; puts a native mirror in a non-syscall package, which no hand-own does today

It is reached only when a chain is TRUSTED and the caller supplied a DNS name.

What this means for crypto/tls and crypto/x509 — predicted, not measured

Neither package was re-censused here: crypto/x509’s suite still cannot build (three -tests emission defects the x509-cryptoapi entry names), and a crypto/tls census is 184 processes for a prediction. Recorded honestly as predictions, for whoever runs them:

Footprint and gates

One new hand-owned file (syscall/windows/zsyscall_windows_certchain_impl.cs), two members MOVED into it from zsyscall_windows_ptrout_impl.cs with the reason stated at both ends, two new manualConversionFuncs entries (CertFreeCertificateContext, CertFreeCertificateChain) and their two generated bodies replaced by placeholders. No golib change, no converter behavior change beyond the registry, no crypto/x509 change at all.

Converter go test ./... ok (185 s) · full CNR · seeded reconvert with the path-precise marker gate · run-validated-sweep.ps1 -Filter syscall canary · go2cs-stdlib.slnx windows build · SystemCertVerify PASS on all four phases, proven failing-first · solution integrity 625/625.

crypto/tls BANKS at 400 + 2 — the mint-site closes at the EMISSION, not at either priced remedy, and the flagship row is one suite over the whole Windows networking stack (2026-08-18, lane claude/tls-mint-site)

The entry above left one named, priced question between crypto/tls and a roster row: pvExtraPolicyPara, minted in crypto/x509 as unsafe.Pointer(sslPara) over a record whose ServerName is itself a managed reference. This lane took that question by MEASUREMENT, and the measurement rejected BOTH priced remedies for a third shape cheaper to prove than either — after which the package validated on the first full pipeline run that could hold it.

The remedy decision — why both table rows lost

What landed (commit e4d4aeb9b)

The converter’s opaquePointerMintEmission (convCallExpr.go): T(unsafe.Pointer(p)) where T’s underlying type is *struct{} — an opaque pointer BY CONSTRUCTION, nothing to dereference — now emits golib’s ManagedPointerTokens.MintOpaque(p). The mint keeps the numeric route byte for byte for every pointee that route already answered exactly (nil → 0, native → its address, reference-free → pinned stable storage) and diverges only for the reference-bearing class: the scalar becomes the box’s own pointer-order token, Registered so the boundary wrapper recovers the box with Resolve — the table’s THIRD minter, the round trip its design doc names — and the minted box holds the referent reachable for its own lifetime through a ConditionalWeakTable, because the referent is otherwise reachable only through a local the JIT may retire before the syscall that consumes the token. CertVerifyCertificateChainPolicy joins the certchain hand-own as the RESOLVER (native mirrors both directions, nativeIdentityOf for the chain, the server name transcribed for exactly the call’s duration). crypto/x509 itself is untouched beyond the regenerated mint line — no new hand-own anywhere, and ж.cs is untouched again.

Guarded failing-first by SystemCertVerify’s three policy rows, all values: with CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG waiving the fixture’s untrusted root, a MATCHING server name answers 0 and a MISMATCHED one answers CERT_E_CN_NO_MATCH — an answer crypt32 can only give if the name crossed the boundary intact — and with nothing waived the same chain answers CERT_E_UNTRUSTEDROOT. Pre-fix, the first policy call dies 0xC0000005 inside Syscall9’s successor frame after all 14 existing rows pass. The golib liveness contract is separately pinned by ManagedPointerTokenMintTests (referent survives a forced collection with only the mint held).

The census, and the two predicted flips — confirmed

Same documented method (one process per top-level Test*, 25 s cap, TestBogoSuite separate): 178 of 183 exit 0, and the five failures are exactly the four expired-fixture rows (same x509: certificate has expired text as Go) plus TestCertCache’s pinned disclosure signature. Zero hangs, zero timeouts, zero infrastructure errors. TestVerifyHostname PASSES — a real server’s TRUSTED chain with a DNS name, i.e. the mint and the resolver working end to end against live traffic — and TestQUICHandshakeError PASSES (the hang is gone). Go re-measured on this host: 184 top-level, 180 pass, 4 fail. Cross-tab closes with no remainder: 178 agree-pass + 4 agree-fail + 1 disclosed + TestBogoSuite = 184.

TestBogoSuite — the serial estimate was wrong, and the truth is a different wall

The tls-finish entry priced full BoGo at “~3 hours of process startup”, a SERIAL estimate. Measured: the runner parallelizes 16 shim workers — and the run still dies at exactly 600 s, twice, because the runner is itself a go test package carrying Go’s DEFAULT 10-MINUTE test-binary deadline, which neither crypto/tls’s code nor the host controls (the test passes no -timeout in either language). At its wall the runner had reached ~267 of 5,481 cases (~3.4 h projected — the serial estimate was accidentally about right for the wrong reason), then exits without writing its results JSON, selecting the “bogo failed” arm whose text the disclosure now pins. Go passes inside the same wall in 32 s. Disclosed host-limit: the ~87x managed shim startup (3.30 s vs 0.038 s) is what a framework-dependent apphost IS — the same self-retiring texture as os/exec’s single-file entries (publish fast-startup hosts and the row starts passing until the entry is removed). The interop CAPABILITY is proven by value in the same suite: the filtered Client-Verify-ECDSA-TLS1 case completes a full handshake with certificate verification against BoringSSL as the peer, 14 s end to end. Also observed on the way: the host printed a *strings.Builder %s operand as a raw pointer (0x…) where Go prints its contents — a real fmt/Stringer-dispatch divergence on the diagnostic path, named here for whoever takes it; it does not affect any verdict.

Two oracle duals the bank required — both arms that already existed, mirrored

  1. Go-side exit forgiveness on AGREED failures. crypto/tls is the first package whose honest go test baseline is red (fixtures expired 2025-01-01), and the compare treated any nonzero Go exit as fatal — so the package could never validate however perfectly the rows matched. The mirror of the existing C#-side arm: both exits forgiven only when BOTH sides exited nonzero, zero mismatches, both runs produced results, and at least one agree-fail row exists to attribute the exits to. A red Go baseline beside a green converted run stays fatal.
  2. Downward disclosure withdrawal. The oracle aggregated disclosed LEAVES up to their parent but had no arm for a disclosed ROOT’s Go-side children — TestBogoSuite’s 3,242 case rows exist only on Go’s side because the disclosed failure precedes the case fan-out, and every one read as Go="skip" C#="". The dual rule: a Go-only row under a signature-matched disclosure root is WITHDRAWN — published in the comparison record and on the proof page (count by root), subtracted from the matched count, never silently dropped. It never widens: a root failing with the wrong signature withdraws nothing, and a two-sided divergent child under a disclosed root is still a strict mismatch. Both rules unit-tested beside the existing aggregation guard.

The proof-page generator also gained hand-owned manifest notes (go2cs_test_disclosures.json "notes"), because the brief requires the expired-fixture ceiling note VERBATIM on the page and the page is regenerated on every re-validation — a hand edit would not survive one. The note rides the same hand-owned manifest as the disclosures.

What the flagship row proves

crypto/tls banks at 400 matched + 2 disclosed — and the row is one suite over everything the Windows arcs built: the managed netpoller and the WSA submit family (every handshake is real loopback sockets), name resolution (GetAddrInfoW + adapterAddresses), the CryptoAPI chain verifier end to end (CertGetCertificateChain, the SSL policy check, the opaque-pointer mint), sha3 key schedules, session tickets/resumption/QUIC/ECH, and BoringSSL interop by value. The four agree-fail rows carry the expired-fixture ceiling note on the proof page; the sweep floor is 30m (TestBogoSuite deterministically burns its child runner’s 600 s deadline every run — evidence comment in $longTimeouts).

Also measured, not taken

The full-reconvert control surfaced SIX files of accumulated intended drift inherited from master’s escape/box-copy and dyn-struct arcs (encoding/xml ×3, go/constant/value.cs, runtime/mstats.cs + its windows package_info.cs) — the committed corpus is stale relative to master’s converter for exactly those files (last regen 489a32e19, converter moved at 0428c3161). Left for the owning arc or the next leveling regen; named so the next full regen does not read them as its own.

Gates

Converter go test ./... ok twice (211.9 s / 212.4 s, including the new oracle guards proven in both directions) · full CNR twice — ONE changed file, the intended SystemCertVerify golden · full seeded reconvert 304/304, path-precise marker gate 63/63 marked files 0 violations, corpus footprint exactly syscall + crypto/x509 + internal/poll · go2cs-stdlib.slnx 0 errors · go2cs.slnx 0 errors · GolibTests 152/152 · full behavioral 597/597 transpile+compile+target, 571/571 output (2,162 s) · SystemCertVerify 4/4 phases, failing-first · pipeline -test-action all -test-timeout 60m: Validated 400 tests (2 disclosed-divergent: codegen-liveness, host-limit), twice, consistent · full validated sweep: 149 of 151 rows PASS at their exact banked counts in 340 min (laptop R, solo), crypto/tls PASSing at 400 through the sweep machinery itself — the two not-green rows are archive/zip and go/parser, BOTH proven full-sweep-load floor shortfalls, not regressions: each timed out at its floor+grace with one-sided-row truncation signatures under the 151-row sweep’s disk pressure, and each PASSES solo the same day on the same machine well inside its old floor (zip 100 verdicts in 850 s, parser 173 in 836 s). Floors raised to the loaded case (60m / 90m) with the evidence in $longTimeouts.

⛔ The assembly-scoped-alias CS0426 was ONE root in name only — encoding/xml goes 0 → 353 of 386, net/netip sheds three of four roots, and the lost write behind xml’s last dozen is MEASURED (2026-08-18, lane claude/xml-netip-alias)

The harvest entry handed on encoding/xml (386) and net/netip (266) as one shared root — “the assembly-scoped-alias CS0426 root, measured in both and identical in shape”. Measured from both ends, the DIAGNOSTIC is identical and the ROOT is not: they are two different aliases, minted by two different mechanisms, and neither fix reaches the other package. Both are closed, along with four more roots the two packages were hiding behind them, and encoding/xml runs for the first time.

Six converter fixes, each with a failing-first guard. Neither package banks — xml is 353 of 386 with five named residual roots, netip is still 0 of 266 behind a structural wall — so there is no roster row, no proof page and no committed test sources.

The two CS0426s are two roots

Both are the same PRINCIPLE — a global using alias is assembly-scoped and is a member of no C# class, so nothing may qualify it through one — and that is where the resemblance ends.

  encoding/xml (36 sites) net/netip (1 site)
The alias ΔToken, from type Token any AddrDetail, from type AddrDetail = addrDetail
Declared in a PRODUCTION file (xml.go) a TEST file (export_test.go)
Why it is an alias a DEFINED type over an interface has exactly that interface’s method set and can carry no methods of its own, so visitTypeSpec emits global using ΔToken = object; rather than a class member an ordinary Go type alias
Who qualifies it wrongly the INTERNAL test half, through the production class it cannot see into (global::go.encoding.xml_package.ΔToken) the EXTERNAL test half, through the package’s using alias (netip.AddrDetail), because Go says netip.AddrDetail during a test build
Fix seed the alias from the production package_info.cs into the test compilation — the NAME into productionAliasLiftedTypes so every renderer spells it, the TARGET into importedTypeAliases so the global using is re-emitted render it BARE: the internal half’s global using is compilation-scoped and the external half lands in the SAME compilation

xml’s fix reuses seedProductionAliasLifts’s two-halves-together shape verbatim and follows the same TWO-HOP published chain a cross-package consumer follows (GoTypeAlias("Token","ΔToken") then GoTypeAlias("ΔToken","object")), so the name seeded is the one the production compilation actually declares. It is gated OFF for the recompile model, and for the reason that matters rather than for safety: there the production .cs are compile items, the alias is already declared, and re-declaring it would be the defect. The predicate needs the production files’ AST and cannot be recovered from go/types — type X any and type X interface{} are the same *types.Named over the same empty *types.Interface, and only the right-hand SYNTAX says which one emits a class member.

encoding/xml: 0 → 353 of 386, and the second root was a residual its own comment had named

Closing the CS0426 left ONE error, six times: CS1503, cannot convert from 'TestUnmarshalEmptyValues_type' to 'Child_G'. read_test.go declares type Child struct{ G struct{ I int } } — package-level, lifted Child_G — and then writes the very same anonymous type as a composite literal inside a function, which minted a SECOND C# type. Go says those are one type and assigns one to the other.

visitStructType already deduped structurally identical anonymous structs, and its own comment recorded the wider case as open: “Function-scoped: the cross-function/package-level anonymous split is a recorded residual.” The package-level registry registerDynamicTypeName is the authority for closing it — package-scoped, so an unexported field name can only mean this package’s, and keyed by the full types.String() including field tags, which is exactly what Go’s struct identity compares. Reuse is one-directional: a function-local lift adopts a package-level name, never the reverse.

Corpus effect, measured by CNR: two behavioral packages, both intended, both the same collapse. TypeConversionInterfaceParam had three C# types for one Go type (DataProcessor_data, Process_data, main_data) — and its own Go source comments them as “Define the SAME anonymous struct inline”, which is the invariant the emission now honors; NewAnonStructIfaceEmbed had two (reservedᴛ1, main_type). The residual that remains is ORDERING, not scope: the package-level declaration must already have been visited, which holds within one file and not across files.

xml’s differential: 353 of 386, five roots, and one of them is a SHIPPED LOST WRITE worth 12

386 verdicts, 8 excluded (3 benchmarks, 5 examples), 353 agree, 33 diverge.

Root Verdicts Note
A type-switch binding whose address is taken is not heap-boxed 12 measured, below
reflect.canonType assertion — “abi.Type has no System.Type (synthType was bypassed)” 15 structType.FieldByIndex reaches toType(Ꮡt.of(structType.ᏑType)) on a Reinterpret<rtype, structType> result; the process TERMINATES, so every later test in read_test.go is absent rather than failed. A reflect root, reached from xml.getTypeInfo
CopyToken(CharData) / (Comment) “uses same buffer” 2 the copy is not copying its backing bytes
reflect.Type.Name() on a GENERIC type returns the CLR arity-mangled name 1 TestMarshal/47: marshals a Generic type under its Generic + backtick-1 CLR name where Go writes Generic
embedded-struct field ORDER in marshal output 1 TestMarshal/64: promoted fields emit in a different order than Go’s

(plus TestToken and the two parent verdicts TestMarshal/TestUnmarshal.)

The lost write, measured — and the two wrong turns on the way to it

Token()’s case StartElement: arm is emitted

d.pushElement(t1.Name);
d.translate(Ꮡ(t1).of(StartElement.ᏑName), true);          // element name
foreach (var (i, _) in t1.Attr) {
    d.translate(Ꮡ(t1.Attr, i).of(Attr.ᏑName), false);     // attribute names
}
t = t1;

Ꮡ(t1) is golib’s Ꮡ<T>(in T) — its own doc says “Pointer to a new heap allocated COPY” — so translate writes the resolved namespace into a copy and t = t1 re-boxes the untouched local. The ATTRIBUTE line is fine: Ꮡ(t1.Attr, i) aliases the slice’s real backing array, which is the element-field-address form a previous arc already fixed. Go writes through &t1.Name and then assigns t = t1, so Go’s semantics require the local to BE the storage — i.e. escape analysis must heap-box the type-switch binding, and does not.

Measured A/B, hand-boxing the local and reading it back: encoding/xml 353 → 365 of 386, +12 verdicts, every namespace row (TestUnmarshal/46,61,62,70,71,87,106–109, TestIssue7113).

Census of the emitted shape across the whole production corpus: a bare-local address-of followed by a field select occurs at 6 sites in 4 filesencoding/xml/xml.cs:325, net/netip/netip.cs:385 and :391, net/rpc/debug.cs:99, runtime/symtab.cs:1102 and :1146. Only xml’s is a WRITE; the other five read. So exactly ONE shipped lost write today, and a correspondingly small blast radius for the fix.

Two wrong turns worth recording, because both are traps this repo’s own doctrine warns about and both were paid anyway:

net/netip: 13 errors in 4 roots → 7 in 2, and the wall behind it is the x509 split seen from the other side

Three roots closed, all general:

Root Errors Fix
An EXPORTED test declaration over an UNEXPORTED production type CS0050 ×2, CS0051 ×2, CS0052 ×3 the downgrade-to-internal machinery already existed; its predicate looked through pointer/slice/array/map/chan and not through a generic type ARGUMENT (unique.Handle[addrDetail]), an ALIAS (AddrDetail), or a SIGNATURE. All three are positions C# accessibility-consistency looks through exactly as it looks through a pointer
The same-package white-box CS0426 1 above
A constraint interface embedding comparable CS0305, CS0308 Go’s built-in comparable is not expressible in C# — the bare-constraint arm has emitted nothing for it since golib’s comparable<T> CRTP turned out to be implemented by nothing. An EMBEDDED one inherits that fact, and the two sides disagreed: the declaration dropped to arity-0 while the constraint took the generic CRTP form netipTypeCmp<P>. Now comparable is dropped from the C# base list AND discounted when deciding whether an interface is a method set. Zero production-corpus sites — censused before the change

A fourth root, an [assembly: GoImplement<AddrPort, P>] record naming a type PARAMETER (CS0246 ×3, plus CS8785 taking the whole generated adapter set down with it), splits into two halves that must move in opposite directions: the RECORD belongs against P’s CONSTRAINT (the only one of the two with a C# spelling, and the one C# checks), while the EMISSION must pass the value UNCHANGED (C# infers P from the argument, so an adapter wrap there is CS1503 ×5 — measured, in that order, one fix at a time).

What remains — and the first of the two is structural.

What this lane did NOT do

No roster row, no proof page, no disclosures, no committed test sources: encoding/xml does not validate at 353 of 386 and net/netip still produces no verdicts. The corpus is restored — the two packages’ -tests artifacts are removed and the initᴛᴛtests() package_init.cs hook (the fourth -tests-closure phantom shape) restored — so the only tracked changes are the converter, its guards, two intended behavioral goldens, and this entry.

The corpus effect, isolated by a CONTROL reconvert

Two of the six fixes reach PRODUCTION emission (the anonymous-struct lift and the type-parameter slot), and CNR only samples the shapes the behavioral corpus happens to contain — so the stdlib was measured too, twice, under identical seeding:

Seeded full-corpus reconvert (304 packages) identical differing new
this branch 1,611 53 0
CONTROL, master’s converter 1,613 51 0

Marker gate on both runs: 62 marked files, 0 violations — so neither seeding failed, and the differing sets are comparable. The set difference is exactly TWO files, both only in this branch and nothing only in the control:

The other 51 are the documented CRLF phantoms — CR-stripped identical to the committed tree — plus one that is not, and is not this lane’s either: crypto/internal/edwards25519/field’s README emits the not_yet_validated badge where the committed file carries 16/16 validated. It differs identically in the control, so it is a pre-existing badge-emission anomaly (every other banked package’s README reproduced byte-for-byte under the same seeding). Worth someone’s attention; not this one’s.

Gates

Converter go test ./... ok (138.5 s), with all six guards, each proven failing-first by neutering its own fix · full CNR byte-identical across all 621 behavioral packages except the two intended ones, 0 NOT MEASURED, 0 advisory warnings — and both intended packages re-baselined and green on all four phases (Transpile/Compile/Target/Output), so the new emission matches go run as well as its golden · seeded full-corpus reconvert plus a control reconvert against master’s converter, above · runtime built clean with the one corpus change applied. No golib change, so no GolibTests/behavioral-suite/go2cs.slnx gate is owed; no corpus regen banked, so no go generate is owed.

Guards

All six fixes carry a guard in src/go2cs/assemblyScopedAlias_test.go, each proven failing-first by neutering its own fix:

Guard Pins
TestSeedProductionInterfaceAliasesFollowsPublishedChain the AST predicate (exactly the named-RHS declarations, never the inline interface{…} beside them), both seeded halves, the two-hop chain resolving to the alias the production compilation DECLARES, the recompile-model gate, and the _test.go-declared negative control
TestFunctionLocalAnonStructAdoptsPackageLift the literal constructs the field’s lifted type, the package-level lift is declared exactly once, no second lift is minted — and a DIFFERENT signature keeps its own, so the reuse is identity-driven and not a blanket collapse
TestPublishedAliasChainTerminates the chain’s four ends, including a self-referential published set (read from a file this run did not necessarily write)
TestUnexportedProductionTypeReachedThroughWrappers the three new positions (generic type ARGUMENT, ALIAS, SIGNATURE) alongside the pointer that already worked, plus both negative controls
TestComparableConstraintInterfaceEmitsMethodSetForm both sides of the embedded comparable agreeing, and the type-parameter slot’s record-vs-emission split in the same fixture
TestTestDeclaredAliasSpelledBare the external-variant rule and each of the four clauses that must hold it back

One of those is a PREDICATE guard rather than an emission guard, and the reason is worth carrying: the qualified spelling netip.AddrDetail depends on which file-local package aliases a real conversion happens to register, and three successive fixtures (same-package generic, foreign generic, bare var, each with a dot import) all emitted the bare name for reasons unrelated to the fix — the guard passed with the fix neutered every time. The arm’s decision was extracted to testDeclaredAliasSpelledBare so the rule itself could be pinned; the EMISSION is covered by the measured net/netip run. A guard that passes while its fix is neutered guards nothing, and noticing that is the only reason this one exists in the shape it does.

MEASURED — the five one-diagnostic build blocks re-measured: ONE moved and its wall FELL, four stood, and crypto/ed25519 turns out to be internal/reflectlite (2026-08-18, lane claude/one-diagnostic-remeasure)

The harvest entry’s queue item 2 listed five packages “each unmeasured since scout batch 2 and each therefore a candidate for having MOVED, as reflectlite’s just did.” All five are now re-measured on current master (eda03743e). One moved; four are reproduced verbatim. The one that moved, sync/atomic, moved because its recorded diagnostic was never its only one — and closing the two defects behind it takes the package from build-blocked to running, 65 of 108 verdicts agreeing, stopped by a single named behavioral root rather than by the compiler.

Nothing banks. The honest summary is one build wall closed, two general converter defects fixed with failing-first guards, and four walls re-priced with their current owners named.

The five, in the order they were run

Package Verdicts Board had Measured 2026-08-18
sync/atomic 108 CS0841 MOVED, and the wall FELL. CS0841 is gone entirely; the build stopped instead on CS0103 ×12 + CS0201 ×2, both fixed below. The package now COMPILES and RUNS: 65 of 108 agree, 43 diverge on ONE root
debug/elf 31 CS8183 CS8183 ×1, unmoved. file_test.cs(1195): _ = net.ResolveIPAddr — a Go blank assignment from a bare func reference. C# cannot infer the type of an implicitly-typed discard from a method group
flag 24 CS1929 CS1929 ×10, unmoved — and the owner is NOT the converter. Every one is inside a go2cs-gen GENERATED adapter (Generated/go2cs-gen/go2cs.ImplementGenerator/go.flag_test_package.<T>-global__go.flag_package.Value-ptr.g.cs)
internal/concurrent 20 CS0426 node<,> CS0426 ×1, unmoved. Plus a finding the board did not have: the converter CRASHES on this package’s hand-owned file
crypto/ed25519 9 CS0030 CS0030 ×2, unmoved — and it is internal/reflectlite’s root, exactly. crypto.Signer(private) emits ((crypto.Signer)@private)

The two converter defects sync/atomic was the first thing in the corpus to reach

Both are shapes the production corpus happens not to contain, which is why each survived to be found by a Phase-4 measurement. Both are fixed, with failing-first guards in src/go2cs/escapedArrayElementAddress_test.go.

1. The address of an element of a heap-escaped array LOCAL composed two box spellings. An array local that a closure captures escapes to the heap and owns an identity box — ref var X = ref heap<array<int32>>(out var ᏑX). C# cannot capture the ref alias that names its value, so every reference inside the closure renders through the box instead. The element-address arm then prefixed the address operator onto THAT already-boxed render, naming a box of a box that was never declared: &X[me] inside a goroutine literal emitted ᏑᏑX.Value.at<int32>(me) — CS0103 — and &ack[me][i%3] the same one hop deeper, because the nested-index router recognised only a SELECTOR inner base, never a bare escaped ident. This is the same naive-prefix hazard the slice branch and the pointer-to-array branch above it each already record, arriving at the one base class neither covered. The fix renders the base in POINTER context to get the box and aliases the element through it — ᏑX.at<int32>(me), Ꮡack.at<array<int32>>(me).at<int32>(k) — which is also what keeps writes through the returned pointer landing in the escaped storage rather than in a copy: golib’s at materializes the backing on the real storage and returns a pointer over the SHARED array.

2. A DISCARDED unsafe.Pointer result kept the cast that types a CONSUMED one. Any call whose result type is unsafe.Pointer takes a (uintptr) construct prefix so the value converts. In an expression STATEMENT nothing consumes the value, and a statement slot is the one place C# admits a call but not a cast: Go’s func() { SwapPointer(nil, nil) } — a row in sync/atomic’s nil-deref table — emitted (uintptr)SwapPointer(nil, nil);, CS0201. The suppression is keyed on AST-NODE IDENTITY, not a boolean, so a call nested inside the same statement whose value IS consumed keeps its conversion; the guard asserts both directions.

What stands behind them in sync/atomic — ONE root, and it is the copy-box

The 43 divergences are not 43 problems. Every one is a lost write through a boxed COPY of a non-escaped struct local:

delta=1 i=0 j=1 k=1        // TestAddInt32Method

x.i.Add(delta) returns the right value (k=1) and then x.i reads back 0. The emission is Ꮡ(x).of(TestAddInt32Method_x.Ꮡi).Add(delta) — the one-arg Ꮡ(in T) overload boxes a COPY of the local struct x, so the atomic mutates a temporary and the next occurrence makes a fresh copy. x has no identity box because escape analysis never marked it, even though its field’s address is taken. That accounts for all 35 *Method rows, TestAutoAligned64, TestHammer32/64, TestLoadPointer/TestStorePointer/TestSwapPointer and TestValue_CompareAndSwap. The seq-consistency tests the two fixes above were found in — TestStoreLoadSeqCst32/64 — PASS, which is the positive control that the element-address fix aliases rather than copies.

This is the documented Ꮡ(value) limitation, and the remedy is an ESCAPE-ANALYSIS change (mark a local whose field address is taken through a method call), whose blast radius is the whole corpus. It is not a bounded single-package fix and this lane deliberately did not take it.

crypto/ed25519 and internal/reflectlite are ONE root, and the remedy is already written down

The harvest entry named reflectlite’s CS0030: “a Go conversion-to-interface written in CALL syntax takes a raw C# cast … T(x) where T is an interface must route through convertToInterfaceType.” crypto/ed25519’s two errors are that root verbatim — crypto.Signer(private) on type PrivateKey []byte.

The gap is precisely locatable. convCallExpr.go’s interface-target conversion branch already routes two source shapes through convertToInterfaceType: a POINTER source, and a FOREIGN named VALUE source (pkg != v.pkg, the crypto.SignerOpts(sigHash) precedent). A LOCAL named value source falls through to the plain cast, on the stated reasoning that a local type can be partial’d to declare the interface. That reasoning holds for a local interface and fails for a FOREIGN one: nothing records the [assembly: GoImplement<PrivateKey, crypto.Signer>] pair, so no adapter exists and the cast has nothing to bind to.

Deliberately NOT taken here, for the same reason the x509-unlock lane declined model selection: the “no churn” boundary on local value sources is load-bearing and the change owes a corpus-wide re-proof, which is a different arc from two emission fixes. Two lanes have now converged on it — it is the cheapest unbanked root on this board that nobody owns.

flag’s owner is go2cs-gen, not the converter

Worth stating because it re-prices the row. All ten CS1929 are inside GENERATED adapter sources: the ImplementGenerator adapter for a TEST-package type implementing the PRODUCTION flag.Value interface cannot find the type’s own String method on the box, and binds bytes_package.String instead. No converter emission is at fault, so the row belongs to whoever owns the generator’s handling of test-package types — a different arc from every other row in this batch.

internal/concurrent — the recorded diagnostic stands, and there is a crash behind it

CS0426 node<,> reproduces exactly (hashtriemap_test.cs(406,145)). The finding the board did not have is in the same run’s stderr:

WARNING: visit file error: runtime error: invalid memory address or nil pointer dereference
in "hashtriemap.go" (auto-conversion sibling skipped)

internal/concurrent is one of the three hand-owned-by-consequence packages (its entire single Go file is hand-owned), so the crash is confined to the .cs.auto review sibling and cannot reach the corpus — which is exactly why it has gone unnoticed. It is a converter nil-deref on a real stdlib file and should be root-caused before the .cs.auto backlog (CleanupBacklog item 18) is levelled, since that item’s whole purpose is refreshing the siblings this crash silently skips.

The measurement hazard this lane paid for

A -tests batch that runs CONCURRENTLY reports CS2012 as if it were a package diagnostic. The first pass of this batch launched all four remaining packages at once (a PowerShell capture bug: the launcher’s PID= line goes to the INFORMATION stream, so 2>&1 missed it, the wait was skipped and the loop ran on). debug/elf and flag came back with CS2012 “cannot open file … being used by another process” and no other error — which reads exactly like a real one-diagnostic build block, in a batch whose entire premise is one-diagnostic build blocks. Re-run sequentially they report CS8183 ×1 and CS1929 ×10. Two lessons, one already in CLAUDE.md and one not: capture repo-script output with *>&1, never 2>&1; and treat CS2012 as contention, never as a root — it is the build-lock analogue of the MSB4166 hazard the harvest entry recorded, and it fabricates precisely the finding this kind of batch is looking for. All five results above are from sequential runs.

Gates

Converter go test ./... ok, 205.2 s, zero failures (includes projitemsIntegrity_test.go over the newly registered guard file) · full check-no-regression.ps1 byte-identical across all 621 behavioral packages, nothing NOT MEASURED, 2 advisory converter warnings (1,060 s); solution integrity 623/623 and path casing 4,478/4,478 in its preflight · both guards proven failing-first by neutering each fix in turn: the doubled prefix reproduces and (uintptr)swapPtr( reappears in a statement slot. No golib change, so GolibTests/behavioral/go2cs.slnx are not owed. No roster row, no proof page, no disclosures, no converted test sources committed: five measurements, zero validations.

✅ THE ESCAPE/BOX-COPY ROOT IS CLOSED — an observed address always aliases; sync/atomic 65 → 99 of 108, encoding/xml 353 → 366 of 386, and the residuals belong to other owners (2026-08-18, lane claude/escape-box-copy)

Three entries named this root from three sides: the one-diagnostic-remeasure entry measured sync/atomic’s 43-divergence residual (Ꮡ(x).of(…Ꮡi).Add(delta) mutating a temporary), the xml-netip-alias entry measured the shipped Token() lost write (Ꮡ(t1).of(StartElement.ᏑName), hand-boxed A/B +12) and censused the emitted shape at 6 sites in 4 files. The root is one principle with two blind spots. Go’s answer to “when must a local’s address alias its storage rather than copy” is ALWAYS — and go2cs already has both conforming mechanisms (Phase-A ref-lowering aliases in place when every consumer lowers; the identity box serves everything else), but two address-taking forms were invisible to the promotion analysis, so emission fell to the documented last-resort Ꮡ(value) copy-box, which reads right and drops writes.

The predicate the xml lane could not identify, identified

That lane recorded three failed reductions and left “what forces the ж box form" open, warning "do not assume type switch is the whole predicate." It isn't — the answer is one line in the ref-lowering charter: **`Decoder.translate` is a METHOD, and Phase A never lowers a method's pointer parameters** (§10.1; refLoweringAnalysisOperations.go, "Methods do not lower in Phase A"). Every reduction used a standalone `fix(n *Name)` — a Phase-A candidate, duly lowered to `ref Name`, which aliases without any box. Make the consumer a method and the parameter keeps `ж` forever. The type-switch half of the shape matters only because such bindings were never escape-analyzed at all: the guard ident's object is NIL (go/types: *"symbolic variables t in t := x.(type) … the corresponding objects are nil"*; the real per-case `*types.Var` lives in `info.Implicits`), so `performEscapeAnalysis` bailed at ObjectOf for every one — and the A2 locals census (origins from `info.Defs`) never tracked them either.

The two blind spots, closed at the analysis

  1. A capture-mode method called on a value-field CHAIN rooted at a local or value parameterx.i.Add(delta) is Go’s implicit (&x.i).Add(delta), and the escape trigger recognized the capture-mode call only on the var ITSELF (i.Store(10)). bodyCallsCaptureModeMethodOnObject now accepts a chain via selectorChainRootsAtIdent — the explicit-& arm’s root walk, whose Selection.Indirect() gate keeps pointer-crossing chains excluded — with the pointer-typed- operand exclusion mirrored from selectsPointerMethodOn, the method-VALUE analogue of this call form. The analysis trigger and the emission-side re-verification (paramBoxReasonHolds) read the SAME function, so value parameters take the widening in the same motion. A non-capture-mode pointer-receiver call stays untouched: it binds this ref on the field in place, and promoting for it would heap-box every local that calls any pointer-receiver method.

  2. Type-switch bindings join both analyses — the object-keyed performEscapeAnalysisForObject (which performEscapeAnalysis now delegates to) runs the standard walk for each case clause’s implicit var, and censusFuncLocals tracks the same category, so a binding whose every address-connected use feeds a lowered position still REVERTS to a plain stack local — the lane’s reduction shapes emit byte-identically. Narrowed to non-inherently-heap bound types: the multi-type and default arms always bind at the guard’s interface type, already a reference, whose no-entry state is load-bearing for the capture analysis. On the emission side a C# pattern variable cannot be a ref local, so an escaping binding binds the pattern to a uniquely-numbered temp and opens the clause with the entry-time box pattern proven by the escaping-parameter preamble and selectCommBindingcase StartElement t1ᴛ1: { ref var t1 = ref heap(t1ᴛ1, out var Ꮡt1); — gated on identHasHeapBox, the exact predicate the body’s &-emission consults, so the box is declared iff it is referenced.

Measured

Guards

TypeSwitchBindingAddress — the xml shape through a ж-parameter METHOD, a held p := &t1.n pointer, the direct &t1 form, and the already-correct slice-element control &t1.attr[i] — and CaptureModeFieldAddress — local, value parameter, type-switch composition, lifted anonymous struct, and the non-capture-mode control w.c.inc(). Both proven failing-first against the pre-fix converter: a x/z w where Go says urn:a x/reset w; local: 5 0 0 0 / param: 0 / switch: 0 / anon: 1 where Go says 5 7 0 0 / 7 / 9 / 8; controls identical on both sides. Doctrine recorded in ConversionStrategies-Reference as two new sections beside the explicit-& selector arm’s.

Gates

Converter go test ./... ok twice — 144.0 s pre-merge with the fix, 142.8 s after both feeder merges, all guards included · full CNR byte-identical across all 624 behavioral packages except ONE intended mover, StructPointerPromotionWithInterface — itself the param-arm of this fix catching a latent copy-box (probeRig(r rig) calling capture-mode Tag() on r.dev; reads only, so its output matched by luck) — re-baselined and re-verified on all four phases · seeded reconvert-and-build above · full behavioral suite 597/597 Transpile+Compile+Target, 571/571 Output (26 skipped, no package main), 0 failures, 4,070 s. No golib change, so GolibTests/go2cs.slnx are not owed. This branch carries both feeder lanes as merges (8f63c1dde, dd11e1e35) — their entries above land with it.

✅ BANKED — crypto/ed25519 validates 8/8 (+1 disclosed) behind the local-value cast fix; internal/reflectlite’s compile wall was THREE roots, all closed, and the package now RUNS into the reflection mini-bridge (2026-08-18, lane claude/local-iface-cast)

The cheapest unowned root two lanes converged on — a LOCAL named value source with an interface target falling through convCallExpr’s conversion branch to a plain cast that records nothing — is closed the way the one-diagnostic-remeasure entry priced it, and the boundary it retired turned out to be misreasoned rather than load-bearing: a local type CAN be partial’d to declare the interface, and that is exactly why the route matters, because the partial is go2cs-gen’s, minted from the [assembly: GoImplement<T, Iface>] record the plain cast never wrote. Framed by SYNTAX it is one rule: Iface(x) and var i Iface = x are the same Go conversion and now take the same route. For a local non-func value source the route is RECORD-ONLY — the corpus re-proof measured the claim rather than arguing it (seeded reconvert, 1,668 emitted, 0 real differences, 0 new; 63 marked hand-owns, 0 clobbered) — and the pairs it newly records are precisely the two shapes the speculative recorder declines: a FOREIGN interface (recordSamePackageImplements pairs two locals only) and an UNEXPORTED local one (its exported gate). Guards failing-first at both levels (TestLocalValueIfaceCallConversion; behavioral LocalValueIfaceCallConversion, all four phases).

crypto/ed25519 — banked, 151/215 (70.2%)

First attempt at the wall: the suite converts, builds and runs 8 of 9 verdicts matching go test -json — sign/verify plain/ph/ctx, crypto.Signer through the interface surface (the cast that named the defect), RFC 8032 golden vectors, the extended edge-case set, equality, malleability. The ninth is TestAllocations, want-zero AllocsPerRun over a loop that derives keys and signs — disclosed alloc-profile, the bytes/strings precedent verbatim. Roster header recomputed from the table: 151/215, 17,272 matching, 77 disclosed; proof page converter-generated at 0f3495688.

internal/reflectlite — the wall was three deep; the compiler is no longer the owner

Closing CS0030 exposed the harvest entry’s CS8130 as TWO defects, and a third behind both. All three are general, all guarded failing-first (packageLevelAnonStructLift_test.go):

With the wall down the package RUNS: 5 of 30 agree (TestImplicitSetConversion, TestNilPtrValueSub, TestPtrSetNil, TestTypeOf, TestUnaddressableField), 2 skip-parity, and all 23 remaining failures stop in the reflectlite reflection CORE — packEface nil-deref (value.cs:125), descriptor field walks (Field index out of bounds/out of range through export_test’s Field/TField), bad indir (value.cs:116). That is the mini-bridge the board already records as “only ever landed Len/Swapper” — the reflection-bridge arc owns this package now, not a converter lane. Nothing banks; no test artifacts committed.

A third call-syntax gap, measured and deliberately not taken

The behavioral guard’s negative case measured the INTERFACE-source twin of the same family: valued(d) with d an interface emits a plain cast that throws InvalidCastException at runtime where var dv valued = d builds the describedᴠvalued adapter — same call-syntax-skips-the-route class, but the route is recordableInterface’s adapter-wrapping emission, a wider change owing its own re-proof. The guard pins the position UNTOUCHED so a later widening cannot move it silently; whoever takes it inherits a measured repro.

The measurement hazard this lane paid for

A stale machine-global ~/go2cs deploy root hijacks the canonical two-arg -tests form. Self-location defers to a CONFIGURED root that exists, and the default -go2cspath (~/go2cs) IS one wherever deploy-core ever ran — on this laptop that root carries its own circular-restore defect (MSB4006 in its unsafe.csproj), and the pipeline reported it as the mass-empty Go="pass" C#="" verdict, reading exactly like total conversion failure. The harness rule generalizes: pass an EXPLICIT -go2cspath <repo>\src on every pipeline invocation from a repo with work in flight; the two-arg form is only canonical on a machine with no deploy root.

The ranked queue, updated

  1. debug/elf (31) — CS8183 ×1 (_ = net.ResolveIPAddr, implicitly-typed discard of a method group), reproduced verbatim 2026-08-18 by the sibling lane and untouched here: the next bounded one-diagnostic item.
  2. encoding/xml (386) / net/netip (266) — the assembly-scoped-alias CS0426 root, ranked ahead on verdict mass by the harvest entry, unmoved.
  3. internal/reflectlite (30) — behind the reflection-bridge arc now; runtime/debug (9) still unmeasured since the CS0715 retirement.

Gates: converter go test ./... ok twice (219.3 s / 193.2 s) · full CNR byte-identical across all 623 behavioral packages twice (0 NOT MEASURED both runs) · TWO seeded whole-stdlib reconverts, 63 marked / 0 clobbered both, every real mover named · reflect single-package build 0 errors · solution integrity 625/625, path casing 4,485→4,492. Banked: 0f3495688 (cast fix + guards), ed25519 bank, e61758549 (reflectlite’s two remaining roots + reflect adoption).

internal/reflectlite VALIDATES 27+3/30 — the mini-bridge was seven roots, five of them SHARED machinery; encoding/gob 100 → 103 of 106 and its census halves (2026-08-18, lane claude/reflect-minibridge)

The reflect mini-bridge pair, taken as one lane so no parallel work touched the bridge. Both prompt targets measured; one banks.

Base note — the lane merged its own dependency

The prompt expected master > d1ed1f7c1 (the local-iface-cast merge). That commit was never pushed: remote master stood at b5a82df19 and the reflectlite unlock lived only on origin/claude/local-iface-cast-747795. This lane merged that branch onto master itself (36d06ab20) — and the merge surfaced a REAL two-lane conflict: the local-iface-cast lane’s anon-struct scope-dedup (“scopes never unify”) and the xml-netip-alias lane’s package-registry ADOPTION path compose in git but contradict in one case, the function-local literal of a package-lifted anonymous struct. The composed behavior (adoption wins) is the more Go-faithful one — anonymous-struct identity is scopeless — so the guard’s scope-separation assertion was amended to assert adoption (d8f71750e). Coordinator: when merging local-iface-cast, take this lane’s reconciliation with it (or merge this lane, which contains it).

internal/reflectlite — banked, 152/215 (70.7%)

5 of 30 at handoff; 27 matched + 2 skip-parity + 3 disclosed (chan-direction) = 30, status validated, proof page converter-generated. Roster header recomputed from the table: 152/215, 17,299 matching, 80 disclosed. The 23 mini-bridge failures decomposed into SEVEN roots — five landed in shared machinery both reflection layers (and every bridge consumer) inherit:

Root Fix home Guard
GoFields walked CLR METADATA order — an embed’s generated backing field lands LAST where Go declared it first; offsets pair by index and reorder with it golib GoReflect.FieldAccess (reorder by the generator’s all-fields ctor, exactly when an embed is present) GoStructLayoutTests.FieldOrder_IsGoDeclarationOrder_NotMetadataOrder, failing-first
The structural implements probe lacked Go’s UNEXPORTED-METHOD package rule — *reflectlite_test.notAnExpr “implemented” ast.Expr through its own private exprNode golib TypeExtensions.GoMethodSets + new GoReflect.GoPackageClassPath GoUnexportedMethodPackageTests (3 rows), failing-first
A nil FUNC into interface space erased its type (delegate nil IS null) golib CanonicalNilFunc/NilFuncValue, minted only at the eface boundary, resolved away on every read-back PointerNilPredicateTests.CanonicalNilFuncCarriesItsTypeAndResolvesAwayOnEveryReadBack
reflectlite’s IsNil lacked the generated == nil operator probe (nil slice/chan fields answered NOT nil) isNilGoValue moved to golib as GoReflect.IsNilGoValue; one nilness, three readers reflectlite TestIsNil (operational)
TryTypeAssert’s dynamic-struct copy arm: public-only GetField NRE’d on unexported fields, and SetValue on the unboxed struct wrote into a transient box (zeros) golib builtin.cs reflectlite TestBigUnnamedStruct (operational)
Two converter emission defects: a PARENTHESIZED array field type dropped = new(N) (missing ast.Unparen), and the delegate lowering ate a bare chan parameter as a NAME (func(chan *integer, *int8)Action<ж<integer>, ж<int8>>) converter visitStructType.go / typeSignatureParsing.go (the leading token is a name only when it is a plain identifier that is not a type-leading keyword — now shared with convertToCSResultList) structFieldEmission_test.go
The reflectlite MIRRORS: valueInterface (mirrors packInterfaceValue), rtype.PkgPath (over GoPackagePath), the AssignableTo retirement to Go’s literal body over bridged implements+haveIdenticalUnderlyingType, and the export_test surface Field/TField/Zero reflectlite type_impl.cs/value_impl.cs + export_impl_test.cs — the first TEST-file hand-own, carried by the new *_impl_test.cs convention (_test.cs suffix rides the existing production exclusion; testConversion globs it into the tests project and digest) the banked suite itself

Naming gains shared by both layers (GoTypeDefinednessTests rows, failing-first): generic instantiations render Go’s bracket form with IMPORT-PATH-qualified args (B[internal/reflectlite_test.A]), an anonymous-INTERFACE lift renders structurally, and a pointer descriptor’s dims thread to the pointee (*[10]int).

The gob IsNil root closed on the way (it is the same bridge): reflect.Value.IsNil on an INTERFACE-kind value answered about the POINTEE — IsZero for an interface IS IsNil, so gob’s !sendZero && v.IsZero() skipped the field and its “nil pointer inside interface” error path was unreachable (carried since r39). The interface arm now answers the interface itself; guarded by the ReflectTypedNilInterface behavioral shape (both directions + nil-interface control), proven failing-first.

The three disclosed rows are ONE representational limit, and it now has measured repros

Chan DIRECTION is not in the managed type (channel<T> whatever the direction — the reference doc’s standing ruling). TestAssignableTo’s <-chan int → chan int row and TestTypes/TestSetValue’s chan<- string stringification are pinned as class chan-direction. Whoever takes the direction-as-descriptor-cargo arc the doctrine names inherits three measured repros and retires the class.

encoding/gob — 100 → 103 of 106; the census HALVES, and it does not bank

Re-measured with everything above in (-test-timeout 12m; the first attempt’s default-deadline run timed out under concurrent load and reported the tail as mass-empty verdicts — reread the CLAUDE.md hazard before believing one). Of the four recorded roots, TWO closed: TestNilPointerInsideInterface (the IsNil arm) and — unowned on the board, closed by this arc’s shared fixes — BOTH decoder-IGNORE rows (TestBadData #8, TestIgnoreRecursiveType). The two that survive are the already-recorded owners:

Root Tests State
the array<T> LENGTH class (“gob: length mismatch in decodeArray”; “wrong type ([3]int) for received field Indirect.A”) TestEndToEnd, TestIndirectSliceMapArray reflect bridge — chip, standing
reflect.ArrayOf → the typelinks stub TestIgnoreDepthLimit infrastructure-error, standing

No gob artifact is committed (100→103 is measurement, not a bank).

Gates

Converter go test ./... ok, 145.9 s, zero failures (post-merge guard subset re-proven against the coordinator’s reconciliation) · full check-no-regression.ps1 byte-identical across all 625 behavioral packages, 0 NOT MEASURED, 2 advisory warnings, exit 0 · seeded whole-stdlib reconvert: 63 marked / 0 clobbered, SIX movers — all intended consequences of the two merged converter arcs composing (xml ×3 + go/constant take the escape-box heap form at case-var addresses the cast recorder now observes; runtime retires readmemstats_m_bySize for the package-level MemStats_BySize under the reconciled adoption rule), zero movers from this lane’s own emission fixes, all three packages build clean · GolibTests 155/155 · stdlib-metadata in sync · solution integrity 627/627, path casing 4,506 · full behavioral suite 598/598 Transpile+Compile+Target, 572/572 Output (26 skipped, no package main), 0 failures, 4,473 s — run AFTER the master merge, so it re-proves the union tree · go2cs.slnx Build succeeded, 0 errors (the golib-change gate) · sweeps, all -SkipBuild against the merged tree’s binary: internal/reflectlite PASS 27 (37 s — the bank’s closure), fmt PASS 63 + internal/fmtsort PASS 3, encoding/json PASS 491, go/types PASS 557 — every banked bridge consumer named as a canary re-validates. Guards proven failing-first where stated (field order, unexported-method rule, both naming arms, the behavioral IsNil shape, both emission guards).

The ranked queue, updated

  1. encoding/gob (106) — 103 of 106 behind the array<T>-length class (2 tests) + the typelinks stub (1); the closest unbanked package on this board.
  2. The chan-direction cargo arc — three pinned repros in reflectlite’s disclosure manifest; retires a disclosure class.
  3. debug/elf (31) — CS8183 ×1, unmoved, still the next bounded one-diagnostic item.

MEASURED — both template packages’ CS0030 wall is GONE; text/template RUNS at 38 of 52 behind ONE named bridge stub, html/template’s new wall is two roots and this lane closes one (2026-08-19, lane claude/row-harvest-2)

The harvest entry’s queue listed html/template (243) and text/template (52) behind “a CS0030 that may or may not be the same family” as encoding/xml’s. It was the same family: the local-iface-cast merge closed it in both, unmeasured, exactly as this lane’s brief predicted. Both are re-measured on current master (e01780c28). Neither banks, and the honest summary is one compile wall gone in two packages, one general converter defect found and fixed with a failing-first guard, and the residual in each priced with its owner named.

text/template — the wall fell, and what was behind it was a ONE-LINE datum loss

52 verdicts. CS0030 is gone entirely; the package compiles and RUNS. First measurement: 36 of 52 matching, 16 divergent — and all 16 were ONE panic, at funcs.cs:95:

panic: 0x211163e3340
   at go.text.template_package.addValueFuncs(map`2 out, FuncMap in)

That is addValueFuncs’s throw panic(err)goodFunc REJECTING a function Go accepts. The message could not be read because the panic value rendered as an ADDRESS (see the second finding below); instrumented, it says:

GOODFUNC-DIAG name=die     msg=function die has 0 return values; should be 1 or 2
GOODFUNC-DIAG name=doPanic msg=function doPanic has 0 return values; should be 1 or 2

Go’s die is func() bool { panic("die") } — one result. The emission was ["die"u8] = () => { throw panic("die"); }, and a parameterless C# lambda with no reachable return NATURAL-TYPES to Action. The Go result type was gone, so the reflection bridge answered NumOut() == 0 — truthfully; the datum was missing from the emission, not from the bridge — and text/template’s own goodFunc rejected every FuncMap holding such a literal, panicking as the map was registered.

The defect is general, and the argument position had already been fixed

A func literal in an empty-interface slot has no delegate target, so C# derives the delegate from the body. CallExprContext.emptyInterfaceArgsLambdaContext.untypedInterfaceTarget already stated the declared Go result type explicitly for the ARGUMENT position (testing/quick’s TestFailure #3). The KEYED COMPOSITE forms — a map[K]any value, an any struct field, a sparse-[N]any element — are the same slot reached through convKeyValueExpr instead of convExprList, and were never marked. Two arms, both fixed:

Arm Symptom Fix
SINGLE result, body never completes normally no return to infer from → C# infers Action, result type lost outright convKeyValueExpr.go marks the slot once valueSlotType is resolved, so all three keyed forms are covered by one predicate
MULTI result every arm a C# tuple with a typeless element ((i - 1, default!), (default!, fmt.Errorf(…))) → no arm fixes the delegate, CS8917 + CS1662/CS8716 per return convFuncLit.go states the declared result tuple through generateResultSignature, the helper the generic-inference arm already used

The multi-result arm retires the reference doc’s standing “no demonstrated consumer” caveat — html/template’s escape_test is the consumer it was waiting for.

A slot with a CONCRETE func type (map[string]func() bool) HAS a delegate target and is deliberately untouched; that control is asserted in the guard and stayed green under both neuterings.

text/template after the fix: 38 of 52, and the residual is ONE stub

36 → 38 of 52. The fix’s own yield is only TWO verdicts, and that is worth stating plainly: the registration panic blocked all 16, but a SECOND wall stands immediately behind it, so thirteen of the sixteen simply advanced from one root to the next. The 14 remaining divergences census to two owners, and thirteen of them are one:

Root Verdicts State
reflect.Value.Call: variadic func values are not implemented 13 the stub’s own message names text/template as its “next consumer” — that is now this measurement
TestIssue43065 1 a different panic, unexamined

The variadic stub is NOT a bounded member fix and this lane deliberately did not take it. The obstacle is structural: a converted Go variadic lowers to a params Span<TArg> delegate (golib variadic.cs, 18 closed shapes across the Funcꓸꓸꓸ/Actionꓸꓸꓸ families), Span<T> is a ref struct, and a ref struct cannot be boxed into the object?[] that Delegate.DynamicInvoke and MethodInfo.Invoke both require. Implementing it means typed dispatch — one trampoline per family arity, plus rebinding a natural-typed variadic delegate onto its family type via Delegate.CreateDelegate — which is a feature arc with golib gates, not a member fix.

Second finding, recorded and NOT fixed: panic(err) prints an ADDRESS where Go prints the message

PanicException renders its state as state?.ToString(), so a panic carrying a converted Go error prints panic: 0x211163e3340. Go’s runtime does not: preprintpanics replaces an error panic value with v.Error() and a Stringer with v.String() BEFORE printing. This cost this lane a diagnostic round-trip on the only defect it was chasing, and it degrades every panic traceback in the corpus that carries an error. The remedy is small and has an exact Go precedent, but it is a golib change (GolibTests + full behavioral + go2cs.slnx) with corpus-wide output reach, so it is recorded here rather than taken alongside a converter fix.

html/template — CS0030 also gone; the new wall is TWO roots and this lane closes one

243 verdicts, still BUILD-BLOCKED, but on a different and much shorter wall: 9 errors in 2 roots.

Root Diagnostics Owner
A. FuncMap could not be found CS0246 ×6 the board’s ASSEMBLY-SCOPED-ALIAS class. type FuncMap = template.FuncMap emits as a production-file global using FuncMap = go.text.template_package.FuncMap;, which is scoped to ONE compilation; the tests project is reference-model (its <Compile> set is *_test.cs only), so the name does not cross. seedProductionAliasLifts exists for exactly this and is deliberately NARROW — it seeds only aliases whose RHS is an ANONYMOUS struct/interface, on the stated reasoning that “a named RHS already renders through its own qualified name”. This alias has a NAMED RHS and that reasoning does not hold for it: the test half renders the bare FuncMap, not the qualified templateꓸFuncMap the test’s own metadata file does declare. Whether the fix is widening the seed (the doc warns of test-local collisions) or making the renderer honor the stated invariant is a real choice, NOT taken here
B. the multi-result any-slot literal CS8917 ×1, CS1662 ×2, CS8716 ×2 closed by this lane (the arm above)

Root A is unrelated to Root B, so html/template stays build-blocked — but the wall is now ONE root instead of two, and that is re-measured rather than argued: with the fix in, the build reports 6 errors, all of them CS0246 FuncMap (clone_test 277, escape_test 938, exec_test 799/1491/ 1519/1949). Root B’s five are gone. The residual behind Root A is unmeasured — but html/template wraps text/template, so the variadic-Call stub is predicted to stand behind it as well.

encoding/gob’s array<T>-length root, READ but NOT RUN — the zero-value ladder does not reach it

This lane did not get a gob pipeline run in, so nothing below is a measurement. What it is is a located shape, and it corrects the standing assumption that the root is a map-index miss the zero-value ladder can close.

The array-valued map in TestEndToEnd is T1.Marr map[[2]string][2]*float64 — key AND value are arrays. But gob never INDEXES that map on the decode path: decodeMap calls allocValue(mtyp.Elem()), i.e. reflect.New over the ELEMENT DESCRIPTOR, and decodeArray then takes its length from t.Len() (decode.go:856). So the loss is in the descriptor, not in map<K,V>’s default! miss:

// abi type_impl.cs, Elem() — a MAP descriptor carries no arrayDims, so its element gets none
nint[]? dims = t.Value.arrayDims;
nint[]? elemDims = kind == Pointer ? dims : dims is { Length: > 1 } ? dims[1..] : null;

A map descriptor has no dims cargo of its own, so Key()/Elem() hand down null and the array descriptor answers Len() == 0 — hence “length mismatch in decodeArray”. The zero-value ladder (GoZero<T>(T template), IGoZeroShaped) cannot help because it recovers shape FROM a live value, and at decode time the target map is empty: there is no template anywhere. The durable remedy is the one the func-parameter case already established — carry the dimension as descriptor CARGO ([GoArrayDims]/funcParamDims’s map twin: key and elem dims stamped by the converter), which is a converter + golib + abi arc, not a member fix.

The ranked queue, updated

  1. reflect.Value.Call variadic dispatch — now the single named owner of 13 of text/template’s 14 residual verdicts, and predicted to stand behind html/template’s 243 as well. A feature arc, not a member fix: the tail is params Span<TArg>, Span<T> is a ref struct, and no reflective invoke path (DynamicInvoke, MethodInfo.Invoke) can box one. Needs typed dispatch across the 18 Funcꓸꓸꓸ/Actionꓸꓸꓸ shapes plus Delegate.CreateDelegate rebinding for natural-typed variadics. Two packages, ~295 verdicts, one owner.
  2. html/template’s Root A — the named-RHS type alias across the reference-model test boundary; seedProductionAliasLifts’s narrowness is the decision to revisit, and it is 6 errors from a 243-verdict measurement.
  3. panic(err) renders an address, not the message — golib PanicException; Go’s preprintpanics is the exact precedent. Small, with corpus-wide traceback reach.
  4. encoding/gob (106) — unchanged at 103 of 106; still the closest unbanked package.

Gates

Converter go test ./... ok, 155.2 s, exit 0, zero failures (includes the new projitemsIntegrity registration and both guard arms) · full check-no-regression.ps1 byte-identical across all 625 behavioral packages, nothing NOT MEASURED, 2 advisory converter warnings, exit 0, 720 s; preflight solution integrity 627/627 and path casing 4,506/4,506 · seeded whole-stdlib reconvert 63 marked / 0 clobbered, 1,661 emitted artifacts identical, ONE movergo/internal/gcimporter/gcimporter.cs, the multi-result arm firing on sync.Map.LoadOrStore(pkgDir, func() (string, error){…}), an any ARGUMENT slot rendering (@string, error) () => …; verified compiling, 0 errors 0 warnings, then RESTORED per corpus-regen policy for the next leveling regen. A second reported difference, html/template/package_init.cs, was an artifact of seeding from this lane’s own -tests-dirty tree (the initᴛᴛtests hook), not a mover · guards proven failing-first by neutering each arm in turn — the single-result arm reproduces ["die"u8] = () => and the multi-result arm reproduces the untyped (params ꓸꓸꓸany aʗp) =>, with the concrete-slot CONTROL staying green under both · operational proof beyond emission: the converted probe prints 1 1 / 2 1 1, identical to go run, so NumOut() agrees with Go for the panic-only literal, the normal-return literal, the multi-result variadic, the any struct field and the concrete-slot control alike.

No golib change, so GolibTests/behavioral/go2cs.slnx are not owed. No roster row, no proof page, no disclosures, no converted test sources committed: two measurements, one converter fix, zero validations.

SHIPPED — the variadic reflect.Value.Call stub is GONE; text/template goes 38 → 49 of 52, and a panic value stops printing its address (2026-08-19, lane claude/variadic-call)

The queue’s #1 and #3 rows, taken together because they are the same layer and share one gate set. Both landed with failing-first guards; neither package banks, and the reasons are measured rather than assumed. Zero converter changes in this lanegit status src/go2cs is empty — so no emission moved, no CNR or converter go test is owed, and every diagnostic quoted below is master’s own behavior.

Row 1 — reflect.Value.Call’s variadic path, via typed dispatch

The board priced this as “a feature arc, not a member fix”, and that reading held: the obstacle is structural, not a missing descriptor read. A converted Go variadic lowers its tail to params Span<TArg>; Span<T> is a ref struct; Delegate.DynamicInvoke and MethodBase.Invoke both marshal through an object?[] a ref struct cannot enter. The third exclusion is the one the board’s plan did not name and it removes the obvious fallback: System.Linq.Expressions rejects a byref-like type outright, so increment 3’s Expression.Lambda receiver binder — the machinery that already builds arbitrary delegates in this bridge — cannot serve here either.

So the call is made in TYPED code (GoReflect.InvokeVariadic, golib GoReflect.TypeLayout.cs, beside TryFuncShape whose header already claimed Value.Call’s marshalling): eighteen small generic trampolines, one per family arity, closed over the delegate’s own parameter types by MakeGenericMethod and cached as ordinary delegates — the elementBoxViaAt idiom GoReflect.FieldAccess.cs already uses, so this EXTENDS the bridge’s existing machinery rather than forking a second dispatch tier. Inside a trampoline the tail is a TArg[] and its conversion to Span<TArg> is ordinary, so nothing is boxed and the tail ALIASES the caller’s array.

The rebind is where the plan needed correcting, and the correction is what makes it total. The board’s design said to rebind a natural-typed variadic onto its family with Delegate.CreateDelegate. That is right, but binding the delegate’s own Target/Method — the obvious form — throws ArgumentException: MethodInfo must be a runtime MethodInfo object for a delegate the BRIDGE ITSELF built: Value.Method binds a receiver by compiling an expression lambda, and a compiled lambda’s Method is not a runtime MethodInfo. A variadic method value is exactly that shape, and it is in the corpus. Retargeting through Invoke (Delegate.CreateDelegate(familyType, del, "Invoke")) has no such blind spot, needs no special case, and carries a multicast invocation list intact — so it is the ONLY rebind, not a fallback.

Three delegate identities therefore all work: the golib family type (a declared variadic func type), C#’s natural type (a variadic literal in an any slot — the FuncMap shape), and an expression-compiled one (a bridge-built method value). A fixed prefix beyond the family’s eight throws a named NotImplementedException rather than mis-indexing; a non-variadic delegate arriving here is refused by name.

text/template: 38 of 52 → 49 of 52. The stub owned 13 verdicts; eleven now match, and the other two (TestComparison, TestExecute) advanced to roots that were standing behind it. The residual three verdicts census to four roots, none of them this arc’s:

Root Where State
Value.Index / Value.Slice over a Kind-STRING Value TestComparison, part of TestExecute Go supports both on a string; the bridge answers reflect: call of reflect.Value.Index on string Value. Bounded and unowned
chan DIRECTION lost in the descriptor TestIssue43065, part of TestExecute surfaces as panic: reflect: recv on send-only channel in walkRange — the board’s already-RATIFIED fourth disclosure class, self-retiring on descriptor cargo
typed-nil method dispatch part of TestExecute (*W)(nil).Error() renders -<nil>- where Go renders -nilW-; html typed nil renders &lt;no value&gt; for &lt;nil&gt;
three-index Value.Slice3 part of TestExecute <slice .SICap 6 10 10>invalid memory address or nil pointer dereference

TestIssue43065 is no longer “a different panic, unexamined” — it is the chan-direction class, which means the board’s own disclosure ruling already owns it.

CallSlice’s named next consumer is retired as measured WRONG. Its stub named text/template; text/template’s safeCall reaches Go through fun.Call(args) (funcs.go:375) and never CallSlice, and a GOROOT-wide census finds no other caller at all. The machinery it would need now exists; it stays a stub for want of a consumer, not for want of a way.

Row 2 — a panic value renders through Go’s preprintpanics rule

Recorded by row-harvest-2, fixed here. PanicException rendered state?.ToString(), so a converted panic(err) holding a pointer-held error printed its ADDRESS. The guard reproduces the defect exactly as that lane reported it — neutered, the behavioral test’s first stderr line is panic: 0x25bf089bd28 against Go’s panic: open final.txt: code 13.

Go’s rule substitutes BEFORE printing: an error panic value becomes its Error(), a Stringer its String(). Both arms are implemented; the Stringer arm is not redundant with ToString(), because a Go named type’s generated ToString() forwards to its UNDERLYING value (go2cs-gen’s InheritedTypeTemplate), so panic(2 * time.Second) would print 2000000000 where Go prints 2s. The method is found through the extension-method registry — where a converted Go method lives — the way golib’s error<T> already finds Error.

Computed on FIRST READ, not at construction, because that is when Go computes it: preprintpanics runs only once a panic has gone unrecovered and is about to print. Eager substitution would call user Error()/String() on every recovered panic in the corpus (fmt’s catchPanic, text/template’s errRecover, every defer func(){ recover() }()), which Go does not do. recover() still hands back the value itself — the substitution is a printing rule, not a value rewrite. Both readers of a panic value now go through it: the unhandled-exception backstop, and debug.Stack’s panic line in runtime/managed_impl.cs, which carried its own copy of the old rendering.

html/template — still build-blocked, and the wall is 7 errors in 2 roots, not 6 in 1

Re-measured on this tree: 243 Go verdicts, 36 excluded, 0 C# verdictsconversion-blocked. Root A is unchanged and was not taken here (it is the board’s own “real choice”, and this lane had banked its two rows). Root C is NEW, and it is a genuine census finding rather than a regression:

Root Diagnostics Owner
A. FuncMap could not be found CS0246 ×6 unchanged — the named-RHS assembly-scoped alias across the reference-model test boundary. [assembly: GoTypeAlias("FuncMap", "go.text.template_package.FuncMap")] IS published, so seedProductionAliasLifts’s “published” precondition is already met; only its anonymous-RHS filter blocks it. Widening that filter vs. making the renderer qualify is still the open choice
C. defer of a VARIADIC func literal CS0411 ×1 examplefiles_test.cs:113. defer func(dirs ...string){…}(dir1, dir2) emits defer((params ꓸꓸꓸstring dirsʗp) => {…}, dir1, dir2, ref ᒐ), which cannot infer against builtin.defer<T1,T2>(Action<T1,T2>, T1, T2, ref GoFrame)

Root C is the first demonstrated consumer of a residue ConversionStrategies-Reference already recorded — “defer/goǃ of a call through a variadic func value would need to capture the Span tail, which a ref struct cannot be — no stdlib occurrence”. A census of the whole Go 1.23 tree finds exactly one such site, html/template/examplefiles_test.go:90. The “no stdlib occurrence” claim was true and is now retired: the original A/B was over PRODUCTION sources, and the shape lives only in a _test.go, so nothing before the Phase-4 -tests pipeline could see it. The doc is corrected in place. Remedy (not taken): pack the arguments into a slice at the defer site and defer a zero-argument closure over it — a visitDeferStmt change, bounded, one site in the entire corpus.

Gates

GolibTests 168/168 · full behavioral suite PASS at 600 projects, 3,877.8 s — Transpile 600/600, Compile 600/600, Target 600/600 byte-identical, Output 574 compared / 0 failed (26 skipped, no package main) · go2cs.slnx Debug --no-incremental 0 errors (282 warnings, 672 s) · canaries all green and unmoved: fmt 63/63, internal/fmtsort 3/3, encoding/json 491/491, internal/reflectlite 27/27 · marker census 63 marked files / 51 *_impl.cs companions (line-anchored via git grep; the UNANCHORED count is 101, the false-alarm number the ritual warns about) · a seeded reconvert is not owed — zero converter changes means zero emission movement, and both corpus files touched (reflect/value_impl.cs, runtime/managed_impl.cs) are *_impl.cs companions the converter never re-emits · converter go test and CNR not owed for the same reason. The Target phase is the independent confirmation of that “zero emission movement” claim: 600 goldens byte-identical, so nothing the converter writes moved.

Guards proven failing-first by seven separate neuterings, each reproducing its own defect and nothing else: removing the family rebind (only the natural-typed row fails), defensively copying the tail (only the aliasing row), throwing from InvokeVariadic (the four call rows, the refusal control staying green), rebinding via target+method instead of Invoke (only the expression-compiled row), transposing two entries of the Func family table (only the arity row), disabling the error arm (the behavioral panic test reproduces panic: 0x25bf089bd28 verbatim, the exact shape row-harvest-2 reported), and disabling the Stringer arm and the memoization in turn.

Arities 3–8 have no consumer in the corpus and are guarded rather than asserted. The behavioral test reaches 0, 1 and 2 fixed parameters through real Go source; the remaining six family arities are driven directly through InvokeVariadic in GoReflectBridgeClosureTests, with fixed argument i set to 1 << i so a dropped or duplicated argument lands on a different sum. (Two arguments TRANSPOSED still sums the same — addition is commutative — so that is deliberately not claimed.) A mis-indexed table entry fails loudly whenever it is first used, never silently; the row exists so “first used” is now, not in some later package.

-tests tree churn classified and RESTORED per the standing rule, nothing unclassified: CRLF phantoms (text/template/doc.cs, html/template/doc.cs, fmt/doc.cs, five encoding/json *_test.cs), the initᴛᴛtests hook (html/template/package_init.cs, +7 real lines), the global::go.* root escape (three internal/reflectlite files) and a seeded ΔToken alias (encoding/json/package_test_info.cs).

The ranked queue, updated

  1. html/template’s two roots — 243 verdicts behind 7 errors. Root A (the named-RHS alias) is unchanged and coordinator-adjacent; Root C (defer of a variadic func literal) is newly located, one site in the whole corpus, and bounded.
  2. reflect.Value.Index / Value.Slice over a Kind-STRING Value — 2 of text/template’s 3 residual verdicts, and the only unowned root among the four. Go indexes and slices a string; the bridge refuses both.
  3. encoding/gob (106) — unchanged at 103 of 106; still the closest unbanked package.
  4. typed-nil method dispatch(*W)(nil).Error(); part of text/template’s TestExecute, and a shape with reach well beyond templates.

No roster row, no proof page, no disclosures, no converted test sources committed: one golib feature, one golib fix, two behavioral guards, thirteen GolibTests rows (seven variadic-dispatch, six panic-text), two measurements, zero validations.

✅ BANKED — debug/elf validates 31/31; the wall was a DISCARDED method group, and the same fix’s other half was a discard that had been emitting a DECLARATION (2026-08-19, lane claude/row-harvest-3)

The harvest entry’s queue item 3 — debug/elf (31) — CS8183 ×1, unmoved, still the next bounded one-diagnostic item” — closes. The diagnostic reproduced exactly as the one-diagnostic-remeasure entry recorded it, the remedy is one general converter rule, and the package validates on the FIRST run behind it: 31 matched, 0 disclosed, one skip-parity, two benchmarks excluded. Roster 154/215 (71.6%), header recomputed from the table.

The wall is one line, and it is only ever a COMPILE wall

file_test.go:1159 is _ = net.ResolveIPAddr // force dynamic linkage, inside TestNoSectionOverlaps — a test that t.Skipfs on Windows four lines earlier and never reaches it. Go writes the shape to make the linker keep a symbol. C# infers a discard’s type FROM its right-hand side, and a method group has no type to give: CS8183, “cannot infer the type of implicitly-typed discard”. Nothing executes the line; it merely has to compile, and until it did, the whole test assembly did not.

Worth stating plainly, because it prices the row honestly: closing a 31-verdict package here bought no behavioral change whatsoever in the package’s own code. Everything else in debug/elf — the section and program-header walk, symbol tables, DynValue, compressed sections in both the GCC .zdebug and gABI SHF_COMPRESSED spellings, the >65280-section overflow path, and DWARF relocation application across twenty compiler/architecture objects — already worked.

The fix is two rules, and the second was the worse defect

1. A discarded func value takes a CAST. The two C# forms with no type of their own are the method group and the lambda (a func literal, or the method VALUE that converts to one). Both are CS8183 in a discard, and discardTargetTypeName supplies the target: a package named func type whose underlying signature matches, else the structural Func<…>/Action<…> render iifeDelegateType already provides. A variadic signature takes the golib family (Funcꓸꓸꓸ<@string, any, @string> for fmt.Sprintf). The parentheses around the RHS are load-bearing for the lambda form — (Func<…>)(nint p1) => … does not parse as a cast. A func-typed VARIABLE is left alone: it already has a C# type.

2. A blank LHS is a discard, never a declaration. The := path one statement form over faces the same typeless RHS and answers it by DECLARING the local with the matching package named delegate (stateFn state = lexText;, the self-referential state machine). A blank LHS reached that branch too and emitted stateFn _ = lexText; — a local literally named _. Everything downstream then degrades: every other _ = x in the scope becomes an ASSIGNMENT to that local (CS0841 before it, CS0123/CS0029 after), and a second named-func-type discard collides outright (CS0128). The blank test is now decided ONCE, above every declaration arm, rather than inside the var arm where it lived.

The asymmetry that makes this a separate rule rather than the := rule again

C# 10 gives a method group a natural type, so var f = pair; compiles and needs nothing. A discard is specifically excluded from that inference. So two statement forms one line apart in the same emission block, both facing an untyped func RHS, need OPPOSITE treatments: the declaration gets a TYPE on the left, the discard gets a CAST on the right. Recorded in ConversionStrategies-Reference beside the := section it mirrors.

Corpus footprint: zero, measured rather than assumed

Both arms move emission only for a blank LHS. A census of the committed corpus for a declaration literally named _ returns three hits, all of them Go source inside go/types’ test string literals (api_test.cs, resolver_test.cs), never an emitted declaration; and a bare _ = <method group> cannot exist in the compiling corpus, because it would be CS8183. The seeded whole-stdlib reconvert confirms it: zero movers from this fix.

Guard

BlankIdentifierCollision already owned this family — it guards the _, _, _, _ = a, b, c, d split-discard CS0128 and the blank-func _() rename — so the shapes were added there rather than in a new project. blankFuncValues covers all seven RHS forms (unnamed signature, no-results, no-params, named func type twice in one scope, variadic, cross-package variadic, method value, func literal) plus two controls: a func-typed VARIABLE discard, which must stay uncast, and state := lexText; state = lexNumber, which must still declare the named delegate. Proven failing-first against the neutered converter at 11 diagnostics in 7 classes — CS0841 ×3, CS0029 ×2, CS0123 ×2, CS0128, CS1661, CS1662, CS1678 — and green on all four phases with the fix in.

The measurement hazard this lane paid for

$ErrorActionPreference = 'Continue' at SCRIPT scope let a failed SEED proceed into a conversion. The reconvert ritual prescribes 'Continue' around the converter CALL (under 'Stop', the converter’s stderr WARNINGs become a terminating NativeCommandError). Setting it for the whole script is the easy over-application, and it is not safe: seeded under the session scratchpad, Copy-Item died mid-tree on MAX_PATH — the source-generator output names under Generated\ are long enough to exceed it from a deep root — every failure was non-terminating, and the script converted into a HALF-SEEDED root, which is exactly the state whose false clobber report the marker gate exists to catch. Two lessons: scope 'Continue' to the converter invocation and leave seeding under 'Stop'; and seed into a SHORT root with bin/obj/Generated excluded, which is both immune and far faster (5,297 files). The abandoned run was killed by verified PID, not by name.

A banking trap nobody had hit yet: .gitignore ate twenty testdata fixtures

TestDWARFRelocations reads twenty compiler/architecture objects from testdata, and Go names a fixture for what it CONTAINS — so they are *.obj. The root .gitignore’s “Files built by Visual Studio” block matches by EXTENSION across the whole tree, and *.obj is in it. git add staged seven of the twenty-seven fixtures and reported nothing wrong; the row would have banked with a suite that cannot reproduce from a clone, which is the one promise the roster makes.

debug/elf is the first banked package to hit this — debug/dwarf’s equivalents are .elf/.elf4 and were never matched — and the census closes at exactly these twenty (git status --ignored=matching -- src/core now reports 0 ignored testdata files, was 20). The fix is a trailing !src/core/**/testdata/** negation, whole-tree rather than per-extension, matching the scope .gitattributes already gives the same trees with src/core/**/testdata/** -text; a deeper .gitignore still wins, so src/core/.gitignore’s *.go rule keeps staged Go source copies out. Check git add’s staged COUNT against the on-disk count when banking a package with testdata — an ignore rule is silent, and this one had been waiting for the first fixture corpus named after a build artifact.

A second bank’s badge, corrected in passing

The reconvert’s three movers were debug/elf/README.md (this lane’s — the Tests badge flipping to 31/31 validated), go/internal/gcimporter/gcimporter.cs (row-harvest-2’s pre-documented mover, still pending for the next leveling regen, left untouched), and crypto/tls/README.md, whose badge still read not_yet_validated for a package master banked at 400/402 hours earlier. Badge regeneration is part of banking — debug/dwarf, debug/gosym and crypto/sha256 all carry theirs — so that one is a missed step, not drift. Adopted here with the fix’s own README, since it is the converter’s own output for a fact the roster already states.

Gates

Converter go test ./... ok, 240.2 s, zero failures · full check-no-regression.ps1 byte-identical across all 625 behavioral packages except this lane’s own guard artifacts (BlankIdentifierCollision/main.cs + package_info.cs, both re-baselined and re-verified on all four phases), 0 NOT MEASURED, 0 advisory warnings, 1,102 s; preflight solution integrity 627/627 and path casing 4,506/4,506 · seeded whole-stdlib reconvert 63 marked / 0 clobbered, 1,665 emitted, 1,662 identical / 3 movers / 0 new, all three classified above · sweep closure run-validated-sweep.ps1 -Filter debug/elf PASS 31 in 28 s, comfortably inside the 10-minute default, so no $longTimeouts floor is owed. No golib and no go2cs-gen change, so GolibTests/full-behavioral/both-slnx are not owed. Proof page converter-generated at 685aa2170 — the commit that carries the fix, not its parent.

The ranked queue, updated

  1. reflect.Value.Call variadic dispatch — unchanged at the top; the named owner of 13 of text/template’s 14 residual verdicts and predicted behind html/template’s 243.
  2. html/template’s Root A — the named-RHS type alias across the reference-model test boundary.
  3. panic(err) renders an address, not the message — golib PanicException.
  4. encoding/gob (106) — unchanged at 103 of 106; still the closest unbanked package.

⛔ MEASURED — internal/concurrent does not bank and the reason is STRUCTURAL: its test file is a whitebox consumer of an implementation the hand-own deliberately replaced. The crash behind it is rooted and fixed (2026-08-19, lane claude/row-harvest-3)

The one-diagnostic-remeasure entry left this row with two open items — “CS0426 ×1, unmoved. Plus a finding the board did not have: the converter CRASHES on this package’s hand-owned file.” Both are now resolved, in opposite directions: the crash is a bounded converter defect, fixed and gated; the CS0426 is not a converter defect at all and no converter change can close it.

The CS0426 reproduces exactly, and it is the hand-own’s own consequence

hashtriemap_test.cs(406,145): error CS0426: The type name 'node<,>' does not exist in the type 'concurrent_package' — still the only diagnostic. Line 406 is dumpNode’s signature:

func dumpMap[K, V comparable](ht *HashTrieMap[K, V])                        { dumpNode(ht, &ht.root.node, 0) }
func dumpNode[K, V comparable](ht *HashTrieMap[K, V], n *node[K, V], depth int) {  }

Two facts settle it. First, dumpMap is never called by any TestdumpNode is reached only from dumpMap and from itself, so this is dead interactive-debugging code that Go nonetheless compiles as part of the package. Second, src/core/internal/concurrent/hashtriemap.cs is a [module: GoManualConversion] native replacement: a ConcurrentDictionary-backed mapStore, with no node, no indirect, no entry, and no trie at all. dumpNode’s body reads n.isEntry, n.entry(), n.indirect(), i.parent, i.dead, i.children, e.key, e.value, e.overflow and ht.keyHash(…, ht.seed) — the complete private structure of the implementation the hand-own exists to not be.

So this is a new shape, and worth naming for the packages behind it: a hand-owned native replacement is invisible to production consumers, which only ever touch the public API — but a package’s own _test.go is a WHITEBOX consumer and may reference the replaced implementation’s internals. Satisfying it means either reimplementing the hash-trie (abandoning the replacement, and with it the reason it exists) or declaring dead scaffolding for dead code, which is the fake-but-plausible move hashtriemap.cs’s own header comment forbids. Neither is taken. 20 verdicts stay unmeasured, and this row should be read as closed-by-design rather than pending.

The class is likely small but is not measured: the other two fully-hand-owned packages (internal/godebug, internal/weak) and the partial hand-owns each carry the same exposure iff their suites reach past the public API.

The crash: one missing struct field in a duplicated literal

-debug (which suppresses the recover) turns the warning into a stack in four seconds:

panic: runtime error: invalid memory address or nil pointer dereference
strings.(*Builder).String(...)
main.(*Visitor).visitFile(…) visitFile.go:106
main.emitAutoConversionSiblings.func1(…) autoSiblingOperations.go:114

emitAutoConversionSiblings hand-rolled a copy of newFileVisitor’s literal — the constructor documented as “constructs the per-file Visitor with every eagerly-required field initialized” — and the copy had drifted by two fields: blankImportInits (nil, so visitFile’s v.blankImportInits.String() dereferences nil — the panic) and manualConversion (false, though the field’s whole meaning is “this file’s destination .cs is hand-owned and the emitted text lands in the .cs.auto review sibling”, which is precisely this pass). It calls the constructor now.

Why it hid for so long, twice over. The emitter runs ONLY in conversionDriver’s unmarkedFileCount == 0 branch — a FULLY hand-owned package — so exactly the class of three reach it, and all three are the files the warning names. A PARTIAL hand-own takes the normal per-file loop with its write target redirected, which is why the ManualConversionSiblingState behavioral guard never caught this: it is a partial package and has always worked. And the recover phrases the panic as visit file error … in "hashtriemap.go", which reads as a defect in that file — the hand-owned file’s own header comment had duly recorded the wrong cause (“panics visiting this generic file”, “runs only six of the whole-package pre-passes”). Genericity and the pre-pass set had nothing to do with it; both comments are corrected in place.

Measured by A/B of two seeded whole-stdlib reconverts

  pre-fix post-fix
visit file error warnings 3 (hashtriemap.go, godebug.go, pointer.go) 0
.cs.auto present 20 22 (+internal/concurrent, +internal/weak — neither had EVER been produced)
freshly emitted 21 of 22 (math/unsafe.cs.auto is seeded-only)
differing among the 20 common 1internal/godebug/godebug.cs.auto, and only because it went from not-emitted to emitted
marker gate 63 / 0 63 / 0
corpus movers 3 (both classified, adopted) 1 (gcimporter.cs, row-harvest-2’s carry)

The 19 byte-identical siblings are the evidence that the manualConversion switch is output-neutral for everything that was already being produced, and ManualConversionSiblingState regenerating state.cs.auto byte-identically is the positive control that the guard still exercises the path.

Two backlog corrections this measurement produced

Gates

Converter go test ./... ok, 240.5 s, zero failures · full check-no-regression.ps1 byte-identical across all 625 behavioral packages, 0 NOT MEASURED · both seeded reconverts above · no golib and no go2cs-gen change. No roster row, no proof page, no disclosures, no converted test sources committed: one structural non-bank, one converter fix.

📉 NARROWED — sync/atomic 99 → 104 of 108: two unsafe.Pointers now compare as BOXES, which is what golib was already built for. The residual is three roots and none of them is the pointer family (2026-08-19, lane claude/row-harvest-3)

The escape/box-copy entry left this row at 99 of 108 with three named residual roots, the second of which was “Go’s pointer-IDENTITY compare k != p is emitted as k.Value != p.Value, a deref-compare that nil-derefs on the nil probe … pre-fix it compared pointers-into-copies and failed ‘orderly’, so that fix EXPOSED it to full severity.” That root is now closed, and closing it did more than its own three tests: the test host survives the run.

The measurement, before and after, on one tree

  pre-fix post-fix
C# verdicts RECORDED 35 of 108 108 of 108
agreeing with go test not computable 104
how the run ended host died inside TestHammerStoreLoadOutOfMemoryException in RecordGoroutinePanic’s String.Join, then a nil-deref panic clean exit; TestHammerStoreLoad contained as a per-test infrastructure-error

The escape/box-copy entry’s 99 was obtained by running the host DIRECTLY with the hammer family excluded and diffing against the recorded go map, because the host death made a full-pipeline compare impossible. 104 is a full-pipeline number — every verdict recorded by the differential harness itself — so the two are not composed the same way, and the honest statement of this lane’s own yield is the table above: from 35 recorded verdicts to 108.

The defect: unsafe.Pointer is the one pointer that CARRIES an address rather than being one

golib already models that exactly. Pointer : ж<uintptr> holds the address as its Value, and it overrides ж<T>.Equals to compare PointerOrderToken (IsNull ? 0 : Value.Value) so that equality, hashing and ordering are one fact about the address — the override exists because the converter mints a fresh box on every uintptr → unsafe.Pointer conversion (875 call sites), so two boxes over one address are ONE Go pointer. The base ==/!= operators route through it and it is nil-safe by construction.

The emission never reached it. convIdent’s pointer-context arm renders an unsafe.Pointer ident as x.Value — correct where an address is genuinely wanted — and convBinaryExpr sets that context for both operands of a comparison, since isPointer() counts unsafe.Pointer (a go/types Basic of kind UnsafePointer, not a *types.Pointer). So k != p emitted k.Value != p.Value: right by accident for two non-nil pointers, and a NullReferenceException on a nil one, because a nil unsafe.Pointer local is default! — a C# null. TestLoadPointer, TestStorePointer and TestSwapPointer each walk testPointers(), whose first element is nil.

The fix suppresses the pointer context for an equality comparison with unsafe.Pointer on both sides. The scope is exact rather than conservative: Go admits no other pairing without a conversion (unsafe.Pointer == *T and == uintptr are type errors), and comparison against untyped nil has its own arm above and is untouched. It also repairs a shape nobody had filed — a MIXED comparison, where a selector operand rendered as the box and an ident operand rendered as its address (x.i != p.Value, (~e).tag != tag.Value); both sides are boxes now.

Corpus reach: seven shipped runtime sites, all verified compiling

runtime/{alg,map,map_fast32,map_fast64,mbarrier,traceback}.cs and runtime/pprof/map.cs — every one a deref-compare collapsing to a box compare (return x == y;, if (dst == src), cgoTraceback != traceback). Built clean (runtime.pprof closure, 0 errors, 123 s) and then RESTORED per corpus-regen policy. Worth flagging for the next leveling regen: these seven are a CORRECTNESS fix, not cosmetics — each is a latent nil-deref on the shipped path. sync/atomic/value.cs.auto is adopted here, since it is a review sibling this fix directly re-emits.

The residual: 4 divergences, 3 roots, and a correction to the recorded list

Root Verdicts State
unsafe.Pointer Reinterpret write-backᏑuaddr.Reinterpret<uint32, atomic.Int32>() and the view’s writes are lost (AddInt32Method: val=0 want 400000) 2 (TestHammer32, TestHammer64) unchanged, and now the largest residual
late-goroutine host death — the pointer hammer fires Fatalf from a goroutine after the test window 1 (TestHammerStoreLoad) contained: was a process kill that cost 73 verdicts, is now one infrastructure-error
reflect alignment 1 (TestAutoAligned64) unchanged

TestUnaligned64 skips identically on both sides. Correction to the escape/box-copy entry’s residual list: atomic.Value CAS type-identity is NOT among the divergencesTestValue_CompareAndSwap and its parent both agree. This lane does not claim the credit: sync/atomic/value.cs is a [module: GoManualConversion] NATIVE reimplementation on Volatile.Read/Interlocked.CompareExchange and GetType() that never touches unsafe.Pointer, so nothing in this fix can reach it. It is recorded as measured, cause unattributed.

No bank: 4 divergences stand, so no roster row, no proof page, and the converted test sources are removed.

The fix made an existing guard VACUOUS, which CNR caught and is worth recording

CNR reported a second intended mover this lane did not predict: UnsafePointerKeywordParam. That project’s whole premise was the arm this fix retires — “an identifier of type unsafe.Pointer used in a pointer context (e.g. a comparison operand) emits name.Value; built from the RAW Go name, a parameter named new came out as new.Value, which C# parses as the new operator (CS1526)”. With comparisons rendering boxes, its emission holds no .Value at all, so it no longer exercised the keyword sanitization it exists to guard — a live guard quietly reduced to a compile check.

Five candidate shapes were probed for one that still reaches the arm — uintptr(new), *(*int32)(new), a struct-field store, pointer arithmetic, and a call argument — and none emits <name>.Value; each takes a (uintptr) cast or the bare box. So rather than guess at a trigger, the project was widened to those seven arms, every one of which builds its text from the Go name and so carries the identical sanitization risk ((uintptr)@new, ~(ж<int32>)(uintptr)(@new), h.p = @new, asUintptr(@new)), with the comparison arm kept as the control that it renders @new and not new. All four phases green, and the guard is stronger than before.

Left flagged rather than claimed: convIdent’s unsafe.Pointer .Value arm may now be unreachable from converter output entirely — every <keyword>.Value site in the corpus is in a hand-written _impl.cs, not an emission. That is an indirect measurement, not a proof, and the arm is harmless where it stands; confirming it needs converter instrumentation over a corpus run, which is its own small task.

Gates

Converter go test ./... ok, 205.2 s, zero failures · full check-no-regression.ps1 byte-identical across all 625 behavioral packages except the guard’s own intended artifacts, 0 NOT MEASURED · seeded whole-stdlib reconvert 63 marked / 0 clobbered, 1,655 identical / 8 movers / 0 new — the seven above plus go/internal/gcimporter/gcimporter.cs, row-harvest-2’s pre-documented carry · guard ManagedAtomicPointer proven failing-first on both Target and Output (exit code 2 — the pre-fix binary crashes on the nil operand), green on all four phases with the fix in. No golib change: the golib side was already correct, which is the point.

🔎 ROOTED, NOT TAKEN — flag’s CS1929 ×10 is the Δ-RENAME and the adapter disagreeing about one method name; the owner is ImplementGenerator (2026-08-19, lane claude/row-harvest-3)

Re-measured on current master with this lane’s three converter fixes in: CS1929 ×10, unmoved — five test-package types (boolFlagVar, flagVar, interval, URLValue, zeroPanicker) × two sites each, every one inside a generated ImplementGenerator adapter. The one-diagnostic-remeasure entry correctly named the owner as the generator rather than the converter; this narrows it to the exact disagreement, which is one name.

The disagreement

flag_test.go’s types implement flag.Value with pointer-receiver String/Set. In the whitebox test compilation those names COLLIDE with the production flag package’s own members (the test variant carries using static of production), so the converter’s name-collision pass Δ-renames them, and everything downstream follows it correctly:

// flag_test.cs — the converter's emission
[GoRecv] internal static @string ΔString(this ref boolFlagVar b) {  }
[GoRecv] internal static error  ΔSet(this ref boolFlagVar b, @string value) {  }
// RecvGenerator's ж-overload — faithfully renamed too
internal static @string ΔString(this ж<boolFlagVar> b) {  }

ImplementGenerator’s adapter does not. It forwards using the Go method name:

global::go.@string global::go.fmt_package.Stringer.String() => m_box.String();   // CS1929
global::go.error  global::go.flag_package.Value.Set(@string _) => m_box.Set(_);  // CS1929

The interface members it implements are named correctly — Stringer.String, Value.Set are the INTERFACE’s names and must never be renamed. Only the forwarding target is wrong: it should be m_box.ΔString() / m_box.ΔSet(_). With no String extension on ж<boolFlagVar>, C# reports the nearest candidate it can see, which is why the board previously recorded it as “binds bytes_package.String” — that is the diagnostic’s suggestion, not a real binding.

This is also why the class is test-package-shaped rather than general: production packages rarely produce the collision that triggers the rename, so the adapter’s use of the Go name is normally indistinguishable from the emitted name.

Why it was not taken here, and what the next lane faces

A go2cs-gen change owes the FULL behavioral suite plus BOTH slnx builds, and this lane had no room for that ledger. The remedy is also a real design choice rather than a one-liner, because the rename is not currently recorded anywhere the generator can read it:

24 verdicts sit behind it, unmeasured — the wall is a build block, so nothing about flag’s runtime behavior is known yet.

📉 NARROWED ×3, banked 0 — text/template 49 → 50 of 52, html/template’s wall falls from 7 errors to 1, and sync/atomic’s alignment row is rooted as structurally unsatisfiable (2026-08-19, lane claude/near-miss-finish)

The near-miss batch: three packages the harvest queue put within a few verdicts of banking. None banks, and the honest summary is six bridge/converter defects rooted and fixed, one written-and-measured fix deliberately REVERTED because shipping it would have cost more verdicts than it bought, and two residuals priced as structural rather than pending.

A correction to the batch’s own premise, before anything else

The brief listed sync/atomic’s residual as “the atomic.Value CAS type-identity pair, the reflect-alignment row, and one more”. That is the escape/box-copy entry’s list, which row-harvest-3 had already corrected in place: TestValue_CompareAndSwap and its parent AGREEsync/atomic/value.cs is a [module: GoManualConversion] native reimplementation on Volatile.Read/Interlocked.CompareExchange/GetType() and nothing about the pointer work could reach it. Re-measured here on current master, unchanged. The real four are TestHammer32, TestHammer64 (one root), TestHammerStoreLoad and TestAutoAligned64.

text/template — 49 → 50 of 52, and the residual is now ONE arc, not four roots

Four of the variadic-call entry’s residual roots were bridge defects; all four are fixed, and TestComparison banks outright.

Root Verdicts State
Value.Index / Value.Slice over a Kind-STRING Value TestComparison + 4 rows of TestExecute fixed — Go’s own arms; a string indexes to its i’th BYTE, and slices to its OWN type so a named string stays named
three-index Value.Slice3 1 row of TestExecute fixed — it was never bridged at all, and the auto form nil-dereferenced rather than degrading
typed-nil rendering ((*W)(nil).Error(), html typed nil) 2 rows of TestExecute fixed — two separate defects, both on the one-nil-encoding rule (below)
chan direction / chan receive TestIssue43065 + 1 row of TestExecute open, and the whole residual

TestExecute still fails, so the package sits at 50. But its remaining failure is the SAME arc as TestIssue43065, which is a real narrowing of the picture: text/template’s entire residual is channels.

The four bridge fixes, and why each was invisible

Each degraded to a value that reads as a real answer — which is what kept them unnoticed under a green compile.

  1. Value.Index / Value.Slice on a string panicked reflect: call of reflect.Value.Index on string Value — Go’s message for a kind that does not support indexing AT ALL, so it read as “strings aren’t indexable” rather than “this arm is missing”. A string Value has no element type, so it could not share the container route; both arms are Go’s own (a non-addressable uint8 byte; a window of the receiver’s own type).
  2. Value.Slice3 was not in the hand-owned set, so it kept the auto conversion, which reinterprets the never-populated ptr slot as a raw unsafeheader.Slice and edits Data/Len/Cap in place. That dereferences nil outright — surfacing as invalid memory address or nil pointer dereference, which reads like a corpus bug, not a missing member. Bridged over the SAME golib window machinery Slice uses (a new 3-index GoReflect.SliceWindow overload), so the two- and three-index forms cannot disagree.
  3. Value.Call erased a typed nil crossing into INTERFACE space. Go’s assignment to an interface-typed parameter BUILDS an eface and an eface keeps the type half; marshalCallArg read the slot’s raw null and handed that across, so the callee’s reflect.ValueOf(arg) answered the INVALID zero Value and text/template’s printableValue reported <no value> where Go prints <nil>. It packs through packInterfaceValue — the same rule Value.Interface() already used one call away — when and only when the destination is interface space. A concrete parameter type builds no eface and is untouched (asserted as a control).
  4. A nil RECEIVER threw in the runtime duck-typing SHELL tier. golib’s error<T>.Error() read m_target_ptr.Value BEFORE choosing between its ж-receiver and by-value overloads, so a nil pointer threw — for a pointee the ж overload it then selected never needed. Go’s method set belongs to the TYPE, so (*W)(nil).Error() dispatches normally and the method decides what nil means.

    This one is worth reading twice, because both its invisibility mechanisms are reusable. First, the throw never surfaced: fmt’s handleMethods wraps every Error() call in Go’s own catchPanic, which prints <nil> for a nil-pointer argument — so the symptom was a wrong RENDERING, not a crash. Second, it reproduced ONLY where the (type, interface) pair resolved through the runtime shell rather than through a generated nominal adapter, i.e. only when nothing in the program converts that type to that interface explicitly. A probe that added var e error = ptr to make the failure clearer made it disappear. The guard therefore carries a type reached exclusively through the shell tier; Blob/Tag beside it cannot guard this, because their pairs are recorded.

⛔ The one that was written, measured, and REVERTED — and it inverts the queue’s #1 ranking

text/template’s last non-disclosure root is walkRange ranging a channel, and the bridge refused it: reflect’s auto recv/send open with the very downcast Type.ChanDir retired — reinterpreting the descriptor onto the linker’s chanType record and reading .Dir out of the memory that follows the value slot. Behind a synthesized descriptor that reads zero, zero is InvalidDir, and Dir & RecvDir == 0 holds for EVERY channel — so a plain bidirectional chan string was refused as send-only. (Past that test neither could have worked either: chanrecv/chansend0 are external stubs the PartialStubGenerator emits as NotImplementedException.)

Both were hand-owned over golib’s channel<E> with direction asked of abi.ChanDir — the one authority — and measured: TestExecute and TestComparison both PASS, taking the package to 51 of 52.

Then TestIssue43065 hangs. Its channel is make(chan<- int); the bridge cannot know the direction, so walkRange’s ChanDir() == SendDir guard does not fire, and the now-working Recv() blocks forever on a channel nobody will send to. The package deadline expires, 67 of 118 events are recorded, and 51 verdicts are lost — against the 1 the fix was worth.

So the ranking is wrong, and this is the lane’s most useful finding. The board has carried reflect.Value.Recv and chan-direction cargo as independent items, with direction ranked as a self-retiring disclosure class. They are not independent, and the dependency runs the other way:

The chan-direction disclosure class is currently self-limiting only because recv is broken. Bridging recv converts a fast, attributable failure into an unbounded hang. Direction cargo is therefore a PREREQUISITE for the recv bridge, not a later improvement on it.

Reverted for that reason, and because the hang risk is not confined to this package: any banked row that reflectively ranges a channel it believes bidirectional would acquire the same failure mode, and proving otherwise needs a full sweep this lane could not afford. The two ~25-line implementations and their reasoning are in this entry’s commit message; a lane taking the direction arc can reproduce them in an hour, and should land them together.

Recorded plainly so nobody re-derives it: abi.ChanDir answering BothDir is still the honest answer, and no descriptor answer helps here. BothDir lets the receive proceed (hang); InvalidDir fails the same guard; SendDir would reject every range-over-channel in the corpus. The direction has to be carried by the VALUE — make(chan<- int) is where it is born and where the converter can still see it — which is a channel<T> field plus every make/narrowing-conversion site plus an abi read. A feature arc, unchanged in size, but now with a second package waiting behind it.

html/template — Root A is CLOSED; the wall is 7 errors → 1

243 verdicts, still build-blocked, but on a single diagnostic.

Root A (CS0246 ×6, FuncMap could not be found) is fixed. The board had it as “widening the seed vs making the renderer qualify — a real choice, NOT taken”. Measured, the choice resolves itself, because the alias is not where the recorded reasoning assumed:

// src/core/html/template/template.cs, line 4 — the PRODUCTION conversion's own declaration
global using FuncMap = go.text.template_package.FuncMap;

seedProductionAliasLifts’s narrowness rests on “a named RHS already renders through its own qualified name”. That is false for exactly this shape: the alias is declared as a compilation-scoped global using in the FILE that declares it, the renderer spells the BARE name in production and test alike, and a reference-model test project compiles *_test.cs only — so the declaring file is absent and the name resolves nowhere. (The test metadata file does already declare the cross-package two-hop templateꓸFuncMap, which is what makes this a name-resolution gap rather than a missing import.)

So a named RHS is seeded too — and only the name half. The type half stays anonymous-RHS-only: a named RHS has its own qualified spelling and is already rendered through it, so recording it in productionAliasLiftedTypes would re-spell references that already compile. The recorded collision worry is answered by measurement rather than by argument: across the converted stdlib an EXPORTED type X = <named> exists in four packages onlyhtml/template (FuncMap), os (DirEntry/PathError/FileInfo/ FileMode), internal/reflectlite (Kind), debug/buildinfo (BuildInfo) — and a _test.go cannot redeclare such a name in the package’s own scope. The two banked packages in that set (debug/buildinfo 197, internal/reflectlite 27) are swept as a gate below.

Root C is now the SOLE wall, and it is one site in the entire Go 1.23 tree (examplefiles_test.go:90) — defer of a VARIADIC func literal:

defer((params ꓸꓸꓸstring dirsʗp) => {  }, dir1, dir2, ref );   // CS0411

A NON-variadic literal emits explicitly-typed parameters ((@string p, @string q) => …) which convert to Action<T1,T2> and infer fine; a variadic one has no such conversion. Not taken here — it needs a real visitDeferStmt transform and this lane had no gate budget left — but it is now scoped rather than sketched. The two candidate shapes, both preserving Go’s defer-TIME argument evaluation:

  1. Keep the eager-argument rung (defer<T1,T2> snapshots the arguments, which IS Go’s rule) and make the callee invocable: force renderLambdaParams for a variadic literal and wrap the callee in its golib family delegate type, so the emission is defer((ᴛ1, ᴛ2) => ((Actionꓸꓸꓸ<@string>)(<literal>))(ᴛ1, ᴛ2), dir1, dir2, ref ᒐ). T1/T2 infer from the eager arguments, not from the lambda. The obstacle is mechanical: the cast has to be placed around the callee INSIDE convCallExpr’s rendering, not by surgery on the returned text.
  2. Hoist to the arity-0 rung: a typed local for the literal plus one per argument, then defer(() => ᴛ1(ᴛ2, ᴛ3), ref ᒐ). More emitted lines, no inference question at all, and lambdaContext.deferredDecls is already the hoist sink.

iifeDelegateType(sig) already renders Actionꓸꓸꓸ<@string> for the signature, so neither shape needs new type-naming machinery.

sync/atomic — the alignment row’s OFFSET half is closed; its POINTER half cannot be

TestAutoAligned64 asserted two things and failed on the first:

if o := reflect.TypeOf(&signed).Elem().Field(1).Offset; o != 8 {  }              // now PASSES
if p := reflect.ValueOf(&signed).Elem().Field(1).Addr().Pointer(); p&7 != 0 {  } // structurally unsatisfiable

StructField.Offset had stayed unpopulated on the r39d rule, reasoning that a Go byte offset exists only to be added to a data pointer, and managed storage has no such pointer. True of Offset as an ADDRESS, false of Offset as layout METADATA — the only way anything has ever read it, including abi.StructType, which populates it from GoReflect.GoFieldOffsets and has all along. Reading it from that same memoized walk is what makes the two Go-specific rules come out right where a naive Marshal.OffsetOf would not: a Go zero-size field occupies nothing (its C# surrogate is one byte) and an align64-bearing field is padded to its 8-byte boundary. It answered 0 before, which is a real answer for a field at the front of a struct — so an unpopulated descriptor read as a LAYOUT failure. The r39d rule still bites where it should: one unknowable field size makes GoFieldOffsets answer null for the whole struct, and every field keeps the zero.

The SECOND assertion reads a pointer’s low bits as a memory-alignment fact. Value.Pointer() is reflectPointerToken — a deliberate, stable IDENTITY token, explicitly not an address (the whole FINDING-managed-box-uintptr-lifetime position). Making the token 8-aligned would fabricate a memory property the model does not have, so this is a structural residual, not a pending fix. It is also a disclosure SHAPE none of the four ratified classes covers — a test asserting a property of a real machine address — and opening a fifth class was outside this lane’s brief. Recorded for the coordinator to rule on.

The hammer roots are NOT bounded, and the reason is a layout mismatch worth naming. TestHammer32/ TestHammer64 fail on AddInt32Method: val=0 want 400000, from addr := (*Int32)(unsafe.Pointer(uaddr)) over a *uint32. In Go that reinterpret is exact: noCopy is a zero-size struct, so atomic.Int32{_ noCopy; v int32} is 4 bytes. In C# an empty struct is 1 byte and forces padding, so the surrogate is 8 — and ReinterpretAliasesStorage’s SizeOf<TDst>() <= SizeOf<T>() test correctly refuses the alias and drops to the raw-address route, where the writes are lost. The size test is not the defect and must not be relaxed: aliasing an 8-byte view over a 4-byte slot would read and write past the box’s value slot. Closing it means making the C# struct actually 4 bytes — explicit layout with Go-computed field offsets, corpus-wide — which is a converter emission arc, not a member fix. TestHammerStoreLoad is the same family one level out.

On this machine the host does not merely fail TestHammerStoreLoad, it DIES in it — the late-goroutine Fatalf storm reaches RecordGoroutinePanic, which tries to serialize a 693 MB JSON string (The JSON value of length 693174514 is too large) and then nil-derefs, losing 72 of 108 verdicts. row-harvest-3 measured containment on its own machine; the containment is timing-dependent and does not hold here. So sync/atomic reads as 104 of 108 or 35 recorded of 108 depending on the host, and the difference is not a regression either way.

A side finding, recorded and NOT rooted

%#x of a uintptr renders %!x(uintptr=252987927579865330) where Go prints 0x…. fmt’s printArg DOES carry a case uintptr f: arm and it is ordered after case nuint f:, so the arm exists and something upstream of it is not matching; this lane saw it only as the text of TestAutoAligned64’s failure message and did not chase it. Every %x/%d of a uintptr in the corpus is affected.

Gates

Converter go test ./... ok, 201.0 s, zero failures · GolibTests 172/172 · full check-no-regression.ps1 byte-identical across all 628 behavioral packages except this lane’s own guard artifacts (ReflectStructTagCopy, ReflectTypedNilInterface, the new ReflectStringWindow), 0 NOT MEASURED, 0 advisory warnings; preflight solution integrity 630/630 and path casing 4,520/4,520 · full behavioral suite PASS at 601 projects, 2,148.0 s — Transpile 601/601, Compile 601/601, Target 601/601 byte-identical, Output 575 compared / 0 failed (26 skipped, no package main) · go2cs.slnx Debug --no-incremental 0 errors (287 warnings, 983 s) · seeded whole-stdlib reconvert 63 marked / 0 clobbered (line-anchored via git grep, path-precise), 1,624 emitted identical / 65 differing / 0 new, and the 65 classify 56 CRLF phantoms + 9 real movers, none of them this lane’s: gcimporter.cs (row-harvest-2’s documented carry), the seven runtime unsafe.Pointer box-compare sites row-harvest-3 flagged for the next leveling regen, and runtime/mfinal.cs.auto (a review sibling on its own staleness schedule) · sweep spot-check of the two banked packages the alias widening can reach: debug/buildinfo PASS 197 in 33 s and internal/reflectlite 1 pass / 0 fail in 124 s, the latter carrying the widening’s one intended emission move (package_test_info.cs gains global using Kind = …abi_package.ΔKind;, adopted here because a banked package’s committed test sources exist to reproduce byte-identically from a clone).

A byte-for-byte reconvert comparison reports CRLF phantoms AS MOVERS — 56 of the 65 here. The converter emits LF inside a multi-line string literal while .gitattributes pins the working tree to CRLF, so any emitted file holding one differs in bytes and matches CR-stripped. Classify with a CR-insensitive compare before reading a mover count as drift; the raw number is not the finding.

Guards proven failing-first by seven separate neuterings, each reproducing its own defect and nothing else: the string Index arm (exit 2 — the pre-fix binary panics), the Slice3 capacity bound (stdout), the Call-into-interface packing (stdout, the call-into-any row alone), the error<T> overload order (exit 2 — here the assertion succeeds and the throw is unrecovered, unlike the fmt path that swallowed it), StructField.Offset (stdout), the named-RHS alias seed, and the validation-pack gating. One new behavioral project (ReflectStringWindow) and two extended (ReflectTypedNilInterface, ReflectStructTagCopy).

-tests tree churn classified and RESTORED per the standing rule, nothing unclassified: two CRLF phantoms (html/template/doc.cs, text/template/doc.cs — modified with an empty numstat) and the initᴛᴛtests hook (html/template/package_init.cs, +7 real lines). No converted test sources committed: no package validates.

A banking-hazard fix nobody asked for, found by paying it

Regenerating ONE corpus package after a converter change — go2cs <goroot-pkg-dir> <core-pkg-dir>, the normal lane move — strips that package’s validation-proof block from its .csproj, silently un-shipping its proof sheet at the next push-nuget. The block was gated on the invocation MODE (-stdlib, later widened to -stdlib or -tests), and the single-package form is neither. It is the same defect ce82093b0 fixed for -tests, reached through a different door, and it is the harder one to catch: only the .csproj moves, and a lone .csproj diff in a reconvert reads as ordinary emission drift.

Fixed by keying on the OUTPUT LOCATION instead — “under <go2csPath>/core/” — which closes both doors at once and cannot be re-opened by adding a mode. -recurse, behavioral fixtures and end-user modules all write elsewhere and keep their historical bytes. CLAUDE.md’s reconvert rule that “any change to a production .csproj is real drift” is exactly right, and it is what caught this — the diff appeared, was not restored, and was root-caused instead.

The ranked queue, updated

  1. Channel direction as descriptor cargo — now a PREREQUISITE, not an improvement. It gates reflect.Value.Recv (whose bridge is written and measured: text/template 50 → 51 of 52) and it retires its own disclosure class. Landing the recv bridge WITHOUT it converts a fast failure into a hang.
  2. html/template’s Root Cdefer of a variadic func literal, ONE site in the corpus, sole wall on 243 verdicts, with two scoped remedies above.
  3. encoding/gob (106) — unchanged at 103 of 106; still the closest unbanked package.
  4. sync/atomic’s zero-size-field layout — a Go empty struct is 0 bytes and its C# surrogate is 1, so every (*Named)(unsafe.Pointer(p)) over a struct with a noCopy/align64 field falls off the alias route. Two sync/atomic verdicts today; the SHAPE is corpus-wide and currently silent.
  5. %#x of a uintptr — renders %!x(uintptr=…); unrooted, corpus-wide reach.

📉 NARROWED — flag’s CS1929 ×10 build wall is CLOSED and the package runs at 23 of 24; the generator now reads the EMITTED name, and the remedy the board priced as “durable” turned out to record a fact it could already observe (2026-08-19, lane claude/heavy-pair)

The row-harvest-3 entry rooted this precisely — the converter Δ-renames the test types’ String/Set, RecvGenerator follows, and ImplementGenerator’s adapter forwards to the GO name — and priced two remedies: (a) have the converter RECORD the member rename for the generator to consult, or (b) have the generator apply the Δ-rule as a fallback. It called (a) the durable direction, on the reasoning that it makes the emitted name a fact the generator consults rather than a rule it re-derives.

That reasoning is right and it selects a THIRD option, which is what this lane took. The generator does not need the rename recorded, because it already enumerates the fact: localImplNames is built before the member list and holds every method the struct declares in either receiver form. (a) would have recorded, in a new attribute surface, a fact already sitting in a local variable — and the generator would STILL have had to consult the declared set for the receiver. Strictly larger footprint (a golib attribute change, a converter emission, a fourth spelling of the rename to keep in step), same result.

What was actually wrong — two positions, one name

An interface member is IMPLEMENTED under the interface’s name and FORWARDS under the emitted one. The adapter spelled the interface’s name at both:

global::go.@string global::go.fmt_package.Stringer.String() => m_box.String();   // CS1929
global::go.error  global::go.flag_package.Value.Set(@string _) => m_box.Set(_);  // CS1929

The left-hand side is correct and must never be renamed. Only the forwarding target was wrong. With no String extension on ж<boolFlagVar>, C# reported the nearest candidate it could see — bytes_package.String(ж<bytes_package.Buffer>) — which is why the one-diagnostic re-measure recorded it as “binds bytes_package.String”. That was the diagnostic’s suggestion, never a real binding.

Common.ResolveForwardMemberName matches the interface member against the declared set, exact name first and the ShadowVarMarker projection only as a second pass — the identical two-pass shape TypeExtensions.GoMethodNameMatches and AdapterBinder.ResolveReceiverMethods already run at run time. So the compile-time adapter, the shell binder and the structural probe now agree by construction instead of by coincidence; the generator disagreeing with a rule golib had already banked WAS the defect. Exact-first is load-bearing, not stylistic: Δ is a Unicode letter and so a legal Go identifier character, and a genuinely ΔX-named Go method must never be displaced by a projection of X.

The half the diagnosis did not name: the RECEIVER

ForwardReceivers is keyed by DECLARED names too, so fixing only the call target leaves a value receiver stranded. URLValue’s ΔString(this URLValue v) has no [GoRecv] and therefore no RecvGenerator ж-twin, so it needs m_box.Value.ΔString(); the Go-name lookup missed and fell through to the m_box default, which is CS1929 again. Both positions read the resolved name. Emission now:

// boolFlagVar — [GoRecv] ref primary, binds the ж-twin
=> m_box.ΔString();
// URLValue — value receiver, needs the deref'd value
=> m_box.Value.ΔString();

The same resolution is wired into ValueAdapterImplTemplate and InterfaceImplTemplate, which consume the SAME member list — in the latter it also decides methodOverriden, since a Go-name miss read a renamed declaration as absent and sent the member down the promotion path. Non-null only when the struct itself declares the Δ name, which is exactly when the embed hops are skipped, so the hop lookups keep reading the interface member’s own name — what the EMBEDDED type declares it under.

flag: 0 → 23 of 24, and the residual is not a defect

24 verdicts, 23 agree. The one divergence is TestDefineAfterSet, and it is not a bug in the conversion:

expected panic("flag myFlag set at .*/flag_test.go:.* before being defined"),
but got panic("flag myFlag set at C:\…\src\core\flag\flag_test.cs:1112 before being defined")

flag.go records the caller with runtime.Caller(2), and the managed traceback picks the correct logical frameflag_test.cs:1112 is flags.Set(myFlagˢ, valueˢ), exactly Go’s flag_test.go:852. What differs is the SOURCE-FILE IDENTITY: the executing source really is the .cs, so runtime.Caller reports it, and Go’s assertion is a regex over .*/flag_test.go:.* — which the emission misses twice over, on the extension and on the path separator. Reporting .go would be a lie about what ran.

No disclosure was taken, deliberately. It fits none of the four classes: it is not an allocation count, not a liveness assertion, not a channel direction, and — the one worth stating — not host-limit, whose bar is a structural property of the deployment shape that retires itself when the shape changes. This would not. The durable remedy is a converter one and is priced here rather than taken: emit #line directives mapping each statement back to its .go origin, which would make BOTH the file and the line Go’s, natively through the PDB, and would also land debugger stepping in Go source. It changes every golden in the corpus and is its own arc. So flag does not bank — one suite, one honest residual, no roster row, no proof page, no committed test sources.

Gates — the full generator ledger

A go2cs-gen change owes all of it, and all of it is green:

Gate Result
converter go test ./... ok, 146.5 s, zero failures
go2cs.slnx (--no-incremental) 0 errors, 725.8 s
go2cs-stdlib.slnx (--no-incremental) 0 errors, 411.2 s
full behavioral suite PASS — 600 projects, 2,285 s; Transpile 600, Compile 600/0/0, Target 600, Output 574 + 26 skip
full check-no-regression.ps1 byte-identical across all 627 packages, 728.9 s, 2 advisory warnings; preflight 629/629 registered, 4,520/4,520 path casing
GenTests 24/24

Inertness is the claim the corpus gates are testing, and they confirm it: ForwardName is null for every member the collision pass left alone, so nothing outside a Δ-renamed adapter moves.

Guards, both proven failing-first (src/tests/GenTests/CollisionRenamedForwardTests.cs, 6 tests): neutering ResolveForwardMemberName to return null fails exactly 3 (the resolution and both template forward assertions); reverting the RECEIVER lookup alone to the Go name fails exactly 1 — the value-receiver case — which is what proves that half is independently load-bearing rather than incidental.

For the next lane

The measurement hazard row-harvest-3 recorded held: every result here is from SEQUENTIAL runs, and the flag build was purged (bin/obj/Generated) before each measurement, since a generator change is invisible to an incremental build whose obj/ already holds the previous emission.

📉 NARROWED — encoding/xml 366 → 384 of 386: the canonType process-kill is CLOSED, its root was a reinterpret to a LARGER struct, and the last root is not what the board recorded (2026-08-19, lane claude/heavy-pair)

Re-measured on current master BEFORE changing anything, which is what the handoff asked for and what paid: the bridge work landed since the census (variadic Call, the reflectlite mini-bridge, interface-nilness + Go-name-survives-lifting) had already retired three of the five recorded roots. 366 → 369 of 386 for freeTestMarshal/47 (arity-mangled generic name), TestMarshal/64 (embedded-field order) and the TestMarshal parent all agree now. Two roots were left, not five.

Then one fix takes it to 384 of 386.

The canonType kill: the board named the symptom, and the root is one level up

Recorded remedy: “Route the feeding path through abi.synthType.” That treats the missing stamp as the defect. The stamp is missing because the FEEDER cannot exist in the managed model at all.

rtype.FieldByIndex does Go’s (*structType)(unsafe.Pointer(t)). ReinterpretAliasesStorage takes the aliasing arm only when the destination FITS in the source, and structType is strictly LARGER than rtype — it carries PkgPath and Fields past the embedded Type. So the pair falls to the raw-address route, the derived box names storage that is not a structType, and Ꮡt.of(structType.ᏑType) hands back an abi.Type whose sysType is null. The auto body’s FIRST statement, toType(&t.Type), is what trips the assert. Stack, verbatim:

canonType ← toType ← structType.FieldByIndex ← rtype.FieldByIndex
          ← xml.addFieldInfo ← getTypeInfo ← unmarshal ← DecodeElement ← Decode

And the assert is a Debug.Assert, so the process DIES (0x80131623) rather than failing a test. That is why 15 verdicts came back EMPTY. An empty verdict reads like a suite that did not run — the same silent under-reporting findFirstFile1 and readReparseLink produced — so the cost of this root was never 15 failures anyone could see; it was 15 absences.

A comment in value_impl.cs claimed the branch was dead: “synthType always stamps sysType after its own nil guard, and every canonType caller feeds a synthType/abi.TypeOf box or nil.” It is not, and it has not been for as long as anything called FieldByIndex. An assert that documents itself as unreachable is the one to distrust; this one has been killing xml’s suite since the package first ran.

The fix, and why it is smaller than the rest of its family

Nothing native is involved and no layout is transcribed. The reinterpret exists ONLY to reach a descriptor the receiver already holds, so the hand-own reaches it directly: manualConversionFuncs gains rtype.FieldByIndex, and value_impl.cs seeds f.Type = toType(Ꮡt.common())common() IS the abi.Type Go’s &t.Type names after the reinterpret, synthType-stamped, reached by a route the managed model can express. No structType is ever synthesized. Go’s index walk is verbatim, and each hop goes through the already-hand-owned rtype.Field, so the promoted-field projection stays GoReflect’s.

The transferable rule, added to the reinterpret family in the reference: when a reinterpret’s target is a struct-kind SPECIALIZATION that only EXTENDS the source, reach the shared prefix directly rather than re-viewing the whole record — the extra fields are exactly what makes the alias unrepresentable, and they are usually not what the code wanted. structType.FieldByIndex is left auto and is now unreachable from rtype.

The last root is NOT “the copy is not copying its backing bytes”

That description is wrong in both halves, and a lane taking it at face value would go looking in the wrong package. Measured:

The assertion that actually fires is the SECOND one, "CopyToken(CharData) uses same buffer.": after data[1] = 'o', reflect.DeepEqual(tok1, tok2) is still true. The two are wrongly EQUAL, not wrongly SHARING — the opposite of the recorded reading.

Narrowed as far as this lane’s budget allowed, and it is a reflect root rather than an xml one: deepValueEqualBoxed’s []byte fast path is live1 is slice<byte>, which a NAMED byte-slice type does not satisfy — CharData is a wrapper struct, not a slice<byte> — so the comparison falls to the element-wise loop over Value.Index. That loop is where the next lane should start. Worth 2 verdicts, and it is the whole of what stands between encoding/xml and a bank.

Measured

  Verdicts
Census (escape-box-copy lane) 366 of 386, 20 divergences in 5 roots
Current master, before this lane 369 of 386, 17 divergences in 2 roots
With the FieldByIndex hand-own 384 of 386, 2 divergences in 1 root

Zero process terminations, zero asserts, zero EMPTY verdicts. No bank — one root short of a row: no roster row, no proof page, no committed test sources, -tests artifacts removed and the tracked -tests-closure dirt restored (xml/package_init.cs’s initᴛᴛtests hook, the CRLF phantoms in fmt/json, and json/package_test_info.cs’s one added global using ΔToken = object; — that last one rooted rather than waved through: json’s artifact was banked 2026-08-17 and the xml alias-seeding chain landed 2026-08-18, so a regeneration picks it up; nothing to do with this change).

Gates

A reflect hand-own plus a converter registry entry owes the full ledger, and the canaries first because a bridge regression would surface there before anywhere else:

Gate Result
canary fmt 63/63 — unmoved
canary encoding/json 491/491 — unmoved
converter go test ./... ok, 143.7 s, zero failures
go2cs-stdlib.slnx (--no-incremental) 0 errors, 358.0 s
go2cs.slnx (--no-incremental) 0 errors, 633.6 s

Two instrument notes this lane paid for

A single-package go2cs <pkg> is the WRONG instrument for refreshing a core package’s .csproj. It strips the validation-pack block — the standing “0 8” restore family — and that is correct behavior, not drift: validationPackBlock is gated on -stdlib or a -tests rewrite of a core package, and a bare single-package run is neither. Restore the .csproj, or use the filtered -stdlib form. The rule in CLAUDE.md (“any change to a production .csproj is real drift, stop and root-cause it”) caught this immediately and is worth keeping exactly as written.

A watchdog that reports “EXITED after 0s” is reporting a LAUNCH FAILURE, not a fast gate. A CNR run in this lane died in 0.16 s on a bad flag (-SkipBuild belongs to run-validated-sweep.ps1, not check-no-regression.ps1) and the poll dutifully reported completion with exit 0. The tell is the log’s EXITCODE= being EMPTY rather than 0$LASTEXITCODE was never set because the script threw. Read the .err file before banking any gate that finished implausibly fast; a sawRunning flag in the poll makes the distinction mechanical.

encoding/xml BANKS at 386/386 — the last root was reflect.DeepEqual unable to see a NAMED slice’s backing array, and it is the named-MAP defect with one word substituted (2026-08-19, lane claude/xml-bank)

Row 155. The largest bank since encoding/json (491), and the whole of what stood between the heavy-pair lane’s 384 and a row was two verdictsTestCopyTokenCharData and TestCopyTokenComment.

A correction to the handoff, and it is the finding

The heavy-pair entry narrowed the residual precisely and named the wrong line for it:

deepValueEqualBoxed’s []byte fast path is live1 is slice<byte>, which a NAMED byte-slice wrapper never satisfies, so comparison falls to the element-wise Value.Index loop.

Both halves of that sentence are true, and neither is why the values compared equal. The fast path is never reached: sliceData — the probe that answers a Value’s backing array and window offset — reads m_array/m_low off the boxed object’s own type, and a generated named-slice wrapper has neither (it holds a slice<E> STRUCT one level down). Both sides therefore resolve to (null, 0), and the ReferenceEquals(data1, data2) && low1 == low2 identity short-circuit — Go’s &x[0] == &y[0] rule — fires two lines above the fast path and returns true.

Measured, not argued. A/B over a twenty-row guard, each half neutered alone:

  rows wrong
master (both halves absent) 8 of 20
fast path fixed, sliceData untouched 8 of 20 — unchanged
sliceData fixed, fast path untouched 0 of 20
both 0 of 20

So sliceData is the root and the fast path is an independent, real, silent gap. Both ship; only the first is load-bearing, and a lane told “fix the fast path” would have measured no movement at all and gone looking for a second defect that does not exist.

It is the named-MAP defect with SLICE substituted throughout

The fix is a few lines and it was already written, one function away, for maps — mapBacking’s own comment describes this failure verbatim:

without it BOTH sides of a named-map comparison resolved to null, the ReferenceEquals(m1, m2) short-circuit above matched them as “the same map object”, and two named maps of equal length were reported deeply equal REGARDLESS of their contents (identityRoot was blind the same way, so a named-map cycle was never detected either).

Every clause holds for slices with the nouns swapped, plus one the map arm does not have: a nil named slice compared equal to an empty one, because the nil/empty rule (data1 is null != data2 is null) tests the same two nulls. sliceData now takes the same second step, gated on the type being slice-KINDED so a struct that merely HAS a slice field can never be mistaken for one, and terminating because the nested value is a strictly smaller struct that carries the pair itself.

The transferable rule: a container probe that reads golib’s private backing fields must be written for the WRAPPER as well as the raw container, in the same change. Two of these have now been found separately, with identical symptoms. The probes of this shape are mapBacking (fixed 2026-07-26), sliceData (fixed here) and identityRoot’s default: arm; anything new that reaches for m_array/m_low/a backing Dictionary inherits the obligation.

The symptom named the wrong package, which is why it survived a green compile

TestCopyTokenCharData clones a token’s buffer, mutates the ORIGINAL, and asserts the two are no longer deeply equal. Its failure text is CopyToken(CharData) uses same buffer. — which reads as a copy that failed to copy, and sends a reader into encoding/xml’s CopyToken, bytes.Clone, and the slice<byte>CharData conversion. All three are correct: Clone allocates a fresh backing array and the generated conversion aliases exactly as it should. The values were wrongly EQUAL, never wrongly SHARING. (The heavy-pair lane had already made this correction once, against the board’s earlier “the copy is not copying its backing bytes” framing; it is recorded again because the test’s own message will keep asserting the wrong thing.)

The first assertion in each of those tests — the unmutated clone compares equal — passed throughout, which is what kept the defect invisible: half of every such test agrees with a probe that answers “same buffer” for everything.

The []byte fast path, since it ships too

Go selects the bytealg.Equal special case by the element KIND, never by the slice’s or the element’s name: a raw []byte, a defined slice type over byte (xml.CharData, net.IP, net.HardwareAddr, tar’s format types) and a slice over a defined byte element all qualify. The managed arm tested live is slice<byte>, which only the first satisfies, so the other two took the elementwise Value.Index walk — correct answers, silently off the fast path. It now asks GoReflect.TryByteSliceView, the element-kind alias Value.Bytes/SetBytes are already built on (AliasOfElement and its ByteAliasableElement<E> gate), so all three shapes take one route in both APIs. Both sides are the same Go type by the AreEqual check above it, so one view test settles both.

Guard

The existing DeepEqual behavioral project is extended by twenty rows rather than forked — its named-MAP block is the direct precedent and now sits beside its named-SLICE twin. Eight of the twenty printed the wrong answer against the pre-fix binary, every one of them wrongly true; all 63 pre-existing rows agreed before and after. The rows: the CopyToken shape verbatim (clone, mutate the original, re-compare); a named byte slice through an interface, as a slice element, as a map value; nil vs empty vs self; length mismatch; a named slice over a DEFINED byte element and one over string, so the fix cannot be byte-specific; the identity short-circuit where it is genuinely true; and a self-referential type recur []any cycle, which is accidentally true before the fix (the short-circuit fires) and terminates after it only because the unwrap reaches the real backing array.

encoding/xml — 386 of 386

386 matched · 0 disclosed · 0 skipped · 0 empty · 0 errors, 8 Example/Benchmark declarations excluded as everywhere. No disclosure manifest: the package needs none. The suite is one long reflection walk checked against Go’s own answer — decoder tokenization over the whole grammar, namespace resolution in both directions, the Marshal/Unmarshal struct-tag grammar, Marshaler/Unmarshaler at every depth, EncodeToken’s well-formedness rules, and the CVE set — which is why its last roots were all reflect and none was xml.

Pipeline: 114 s wall for convert + build + run + compare, so no $longTimeouts floor is warranted.

Two emission moves that are NOT this lane’s — ADOPTED rather than restored, and that is a change of practice

The canary sweeps flagged two content movers. Neither can be this lane’s: a hand-owned C# corpus file is skipped by the converter and cannot influence emission at all. Both belong to converter changes already on master whose corpus regen is pending:

File Owner Sweep that proves it
internal/fmtsort/sort_test.csptr.Value ==ptr == a8a0b1827, row-harvest-3’s “two unsafe.Pointers compare as BOXES” internal/fmtsort PASS 3
encoding/json/package_test_info.cs+global using ΔToken = object; the alias-seeding chain — dd11e1e35 (2026-08-18) seeds an anonymous-interface RHS, which type Token any is; b3a5f56e1 later widened the same seed to named RHSs encoding/json PASS 491

The heavy-pair entry above RESTORED the ΔToken line, having rooted it correctly and identically. This lane adopts it instead, and the disagreement is worth resolving rather than alternating. The case for adopting is the one the near-miss lane made when it took internal/reflectlite’s global using Kind = …abi_package.ΔKind;: a banked package’s committed test sources exist so the suite is reproducible from a clone, and json’s are not — regenerate them today and you get a line the repository does not have. Restoring keeps the commit tidy at the cost of a permanent re-flag on every future sweep and a bank that no longer reproduces. Both movers are proven by this lane’s own green sweeps of the exact packages they land in, which is the near-miss standard, so they are adopted here and the practice is stated rather than left to the next lane to re-derive.

internal/fmtsort additionally shows the box-compare change reaches committed TEST sources, not only the seven runtime production sites row-harvest-3 flagged for the next leveling regen.

Gates

Canaries first, because a bridge regression surfaces there before anywhere else: fmt 63/63 unmoved, internal/fmtsort 3/3, encoding/json 491/491 unmoved. Full behavioral suite PASS at 601 projects, 3,240.9 s — Transpile 601/601, Compile 601/601, Target 601/601 byte-identical, Output 575 compared / 0 failed (26 skipped, no package main), 0 timeouts / 0 NOT MEASURED. Guard counter-proven failing-first by the A/B table above (both halves neutered separately, then together).

No converter, generator or golib source changed — the fix is one hand-owned corpus file (reflect/deepequal_impl.cs, [module: GoManualConversion]) whose two changed functions are both private static, so no public surface moved: no seeded-reconvert marker gate and no converter go test/CNR are owed, and the compile risk is bounded to the one assembly the behavioral suite builds 601 projects against.

The -tests tree churn classified per the standing rule: encoding/xml/package_init.cs gained the initᴛᴛtests() hook (+7 real lines, the fourth -tests-closure shape) and was restoredzero committed package_init.cs in the corpus carry it, and the tests project compiles *_test.cs only (EnableDefaultCompileItems=false, explicit items), so the hook is inert to the bank either way.

The queue, unchanged except for xml leaving it

  1. Channel direction as descriptor cargo — still the prerequisite for the written-and-measured reflect.Value.Recv bridge (text/template 50 → 51 of 52).
  2. html/template’s Root Cdefer of a variadic func literal, ONE site in the corpus, sole wall on 243 verdicts.
  3. encoding/gob (106) — 103 of 106, now the closest unbanked package.
  4. sync/atomic’s zero-size-field layout, 5. %#x of a uintptr — unchanged.

📉 html/template’s LAST wall falls — the package RUNS for the first time at 240 of 243, and the variadic-literal cast closes a second door nobody had opened (2026-08-19, lane claude/xml-bank)

Root C, taken as scoped. html/template goes from build-blocked with 0 measured to 240 of 243 matching, 0 empty verdicts, 0 terminations. It does not bank; the three residuals are named below and none of them is this arc.

The wall was one diagnostic and the board had priced it correctly

Re-measured on this lane’s branch before changing anything: exactly one error, exactly where the near-miss entry left it.

examplefiles_test.cs(113,9): error CS0411: The type arguments for method
'builtin.defer<T1, T2>(Action<T1, T2>, T1, T2, ref GoFrame)' cannot be inferred from the usage.

The board offered two remedies. Remedy 1 was taken — keep the eager-argument rung, force the temp-parameter form, and make the callee invocable by casting it — and its “mechanical obstacle” is real but small: the cast goes around funcName immediately after convCallExpr builds it, which is four lines and needs no new type-naming machinery, because iifeDelegateType has rendered Actionꓸꓸꓸ<@string> from a variadic signature all along.

defer((1, 2) => ((Actionꓸꓸꓸ<@string>)((params ꓸꓸꓸstring dirsʗp) => {  }))(1, 2), dir1, dir2, ref );

Go’s defer-TIME argument evaluation is untouched: dir1/dir2 remain the rung’s eager arguments, ᴛ1/ᴛ2 are what the thunk receives at unwind. The guard asserts exactly that by reassigning both arguments after the defer and checking the thunk still prints the originals.

Three things the shape needed that the two priced remedies did not mention

  1. go has the identical hole. visitGoStmt’s func-literal arm leaves renderLambdaParams false for the same reason visitDeferStmt’s did, so go func(x ...T){…}(a, b) fails the same way. Both take the same arm, in the same change — the corpus has no such site today, and a fix that covers one and not its twin is the kind of asymmetry that gets rediscovered.
  2. The NULLARY rung fights the fix. defer f() normally trims the invocation to hand golib a method group, and a variadic literal has none: trimming ((Actionꓸꓸꓸ<nint>)(<lit>))() leaves the family delegate in an Action slot (CS1503). The trim is now suppressed for this callee shape and the invocation is wrapped instead. Found by the guard, not by reasoning.
  3. The IIFE interception’s own restriction was stale. convCallExpr phase 1a excludes a variadic literal with the comment “delegate type would need a params array” — untrue since iifeDelegateType learned the family form. Falling through to the normal path now lands on the same cast, so func(parts ...int) int { … }(1, 2, 3) (previously CS0149) compiles as a side effect rather than as separate work.

Two adjacent walls MEASURED here, deliberately not closed, neither caused by this change

The guard was written wider than the fix on purpose, and it paid twice.

Wall Repro that proves it independent Why not here
A SPREAD argument to any deferred variadic calldefer f(nums...) emits nums.ꓸꓸꓸ, a Span<T>, as the type argument of defer<T>; C# forbids a ref struct there (CS9244) a NAMED variadic callee, no func literal anywhere, emits the identical defer(ᴛ1 => f(ᴛ1), nums.ꓸꓸꓸ, ref ᒐ) and fails the same way — compiled and observed closing it means passing the SLICE and spreading inside the thunk, at every variadic deferred call in the corpus
An empty variadic call passes an empty slice where Go passes NILf() on func f(parts ...int) answers parts == nil true in Go, false here a plain DIRECT call, no defer and no literal: Go prints 0 true, the conversion prints 0 false — compiled and observed argument CONSTRUCTION, corpus-wide reach, unrelated to delegate conversion

Both are recorded rather than absorbed, and the guard carries a comment at each excluded row saying which wall it would otherwise be testing — so the next lane inherits the measurement, not the gap.

html/template — 240 of 243, and the residual is three DIFFERENT owners

Test Panic Owner
TestExecute reflect: recv on send-only channel, from walkRange queue #1, chan-direction as descriptor cargo — the identical root behind text/template’s last verdict. One arc retires both.
TestErrors nil dereference in ж<T>.op_OnesComplement, from text/template/parse.ErrorContext a ~-deref defect in the shared parse package, unrooted here
TestRedundantFuncs interface conversion: interface {} is <>f__AnonymousDelegate0, not go.Funcꓸꓸꓸ<object, @string> the same missing conversion as Root C, one boundary over: a variadic func in an any slot keeps C#’s SYNTHESIZED anonymous delegate type, so Go’s funcMap[n].(func(...any) string) assertion cannot match. Root C fixed the CALL-callee boundary; this is the assignment/composite-literal one.

That third row is the useful one: it says the variadic-literal conversion gap is a family with at least two members, and that the second member is now the cheapest thing standing between html/template and a two-verdict residual. Whoever takes it should reuse variadicFuncLitCallee and iifeDelegateType — the seam is already there.

Gates

Converter go test ./... ok, 375.3 s, zero failures (projitemsIntegrity_test included — no new .go file, so nothing to register). Full check-no-regression.ps1 byte-identical across all 628 behavioral packages except this lane’s own guard artifact (DeferLambdaParam.cs, the intended new golden), 0 NOT MEASURED, 0 advisory warnings, 1,255 s; preflight solution integrity 630/630, path casing 4,527/4,527. Full behavioral suite PASS at 601 projects, 1,887.0 s — Transpile 601/601, Compile 601/601, Target 601/601 byte-identical, Output 575 compared / 0 failed (26 skipped), 0 timeouts / 0 NOT MEASURED. go2cs-stdlib.slnx Debug --no-incremental 0 errors, 446.9 s; go2cs.slnx Debug --no-incremental 0 errors, 873.0 s. Canaries re-run AFTER the converter change: fmt 63/63, internal/fmtsort 3/3, encoding/json 491/491, encoding/xml 386/386 — the row this lane banked, still reproducing from the committed roster.

Guard counter-proven failing-first by neutering each half separately: the temp-parameter force alone leaves CS0411 ×4, the delegate cast alone leaves CS0149/CS1503 ×6.

html/template’s -tests artifacts removed and its -tests-closure production dirt restored (doc.cs, a CRLF phantom; package_init.cs’s initᴛᴛtests hook, +7 real lines) — the package does not validate, so no roster row, no proof page, no committed test sources.

The queue

  1. Channel direction as descriptor cargo — unchanged at the top, and now gating THREE things: the written-and-measured reflect.Value.Recv bridge, text/template’s last verdict, and html/template’s TestExecute.
  2. A variadic func in an any slot — new, and the cheapest of html/template’s three; reuses this arc’s seam.
  3. encoding/gob (106) — 103 of 106, the closest unbanked package.
  4. sync/atomic’s zero-size-field layout, 5. %#x of a uintptr — unchanged; joined by the two variadic-argument walls above, which are cheap to state and corpus-wide in reach.

📐 The Caller path is SPELLED Go’s way — this lane opened on two stale premises, and the one live defect it found is the half of flag’s #line remedy nobody priced (2026-08-19, lane claude/edwards25519-a, laptop G)

This lane was briefed to implement crypto/internal/edwards25519’s ratified Option A and, as a stretch, flag’s #line arc. It re-measured first, per the standing never-trust-a-census-older-than-three-merges rule, and both premises were stale — in opposite directions. What follows is the re-measurement, the one live defect it surfaced, and the arc it re-prices with evidence.

Premise 1 — Option A is DONE, and the board still says it is not

81a08a15a (2026-08-11, lane L4) landed the tuple-spec init-order relocation exactly as the ratified FINDING-init-order-tuple-specs.md §5A specified: both emission sub-shapes, the refusal warning retired, TestPackageTupleVarSpecInitOrderRelocation and the InitOrderTupleSpecs behavioral test as guards. 3907a064c banked the production emission (crypto/internal/edwards25519/package_init.cs, calling initᴛidentity(); initᴛgenerator();), so the r60 harvest note that it is “deliberately UNCOMMITTED” is spent too.

The ranked queue handed on by row-harvest-3 still reads “the ratified Option A tuple-spec init-order fix is still unimplemented” as its queue item 5. That sentence is what produced this lane. Two board statements about one package disagreed, the older one was right, and nothing in the file said which was which. Recorded here as the correction.

Re-measured on master today (98bff3efc; the converter was rebuilt from HEAD first — the binary in the worktree was a day stale and 20 KB different, which is route #1 waiting to happen):

   
verdicts 55
matching 54
divergent 1TestAllocations
excluded 4 benchmarks (Phase-4D)
skipped / disclosed 0 / 0

That reproduces r60’s 54/55 exactly. The residual’s number has moved: TestAllocations now reports 98 objects per run (9,800 over 100 runs, 1,324,800 bytes) where r60 measured 109 — the week’s arcs took eleven off it without anyone claiming them. It stays undisclosed on the standing ruling (a near-budget lower-bound count is an optimization target, not an impossibility) and the package stays unbanked, held by that one row. No converter or corpus change was needed or made for edwards25519.

Premise 2 — flag’s 23 of 24 is NOT on master

The stretch was briefed against flag at 23 of 24 with TestDefineAfterSet as the sole residual. That measurement is real, but it belongs to claude/heavy-pair-7be2d2, which is pushed and unmerged: 7eeeda893 (the ImplementGenerator forward-name fix) and db3bb4b9c (the reflect reinterpret fix) are not ancestors of master. Measured on master, flag still sits behind its CS1929 ×10 build wall — this lane re-ran the pipeline and got the same ten diagnostics against the same five test-file flag.Value implementors, with every verdict empty. So from master there is no 23-of-24 to finish, and flag’s #line consumer is not reachable until that branch merges.

⚠ Worth generalizing: git log --all lists commits on branches HEAD does not contain, so a brief quoting a merged-sounding result is not evidence that the result is on master. git merge-base --is-ancestor <sha> HEAD is the check. It cost this lane one pipeline run to learn.

What DID land: runtime.Caller spells its path Go’s way

Chasing the #line remedy surfaced a live, previously unnamed defect one layer beneath it.

Go records source paths with forward slashes on every platform. Measured directly on this Windows box with a Go control program: runtime.Caller and runtime.CallersFrames both answer C:/Program Files/Go/src/runtime/proc.go — forward slashes, drive letter and all. The CLR hands back whatever the PDB holds, which on Windows is backslash-separated, and internCallerFrame recorded it verbatim. Every converted program therefore answered C:\…\log_test.cs where Go answers C:/…/log_test.go: a divergence in the spelling of the path, sitting on top of the already-recorded, deliberately deferred divergence in what the path points at.

The two are independent, and only the second is the position-map arc. The first is an ordinary fidelity gap, and it is now closed: goSourcePath (runtime/managed_impl.cs) applies Go’s rule at the two places a frame’s file reaches a program — internCallerFrame, which every Caller / Callers / CallersFrames answer is interned through, and appendGoFrames, which renders runtime.Stack’s traceback. Converted path/filepath accepts either separator on Windows exactly as Go’s does, so no consumer pays for the normalization, and log’s Lshortfile trim — which looks for the last / — starts working as a side effect.

Guard, proven failing-first (RuntimeCallerFrames, the project that already guards Caller’s frame accounting): five assertions across the three surfaces, output-compared against go run. Before the fix exactly those five lines diverged, and all eleven pre-existing assertions still matched:

assertion Go C# before C# after
caller file uses forward slash true false true
caller file uses host separator false true false
frames files use forward slash true false true
frames files use host separator false true false
traceback uses host separator false true false

On a forward-slash host all five are trivially true on both sides, so the guard bites on Windows, which is where the two spellings differ — said in the test’s own comment, so a Linux run’s silence is not mistaken for coverage.

The #line arc, re-priced with four measurements

The board prices the remedy for the source-file-identity class as “emit #line directives … which would make BOTH the file and the line Go’s, natively through the PDB.” Most of that holds; one load-bearing part does not. Measured in a probe project rather than argued:

  1. #line does reach StackFrame. A #line 852 "…/flag_test.go" region makes GetFileName()/GetFileLineNumber() answer with that file and that line. The PDB route needs no golib change — that part of the pricing is confirmed.
  2. The #line DIRECTIVE cannot supply the separator. Roslyn resolves and normalizes the directive’s path. Absolute forward-slash (C:/Program Files/Go/…), unix-rooted (/go/src/…) and relative (go/src/…) forms all came back backslash-separatedC:\Program Files\Go\src\flag\flag_test.go. No spelling of the directive survives. So #line alone leaves TestDefineAfterSet failing, because `.*/flag_test.go:.*` needs a literal / before the filename. The remedy is two halves#line supplies the .go extension and the Go line, goSourcePath supplies the separator — and they compose, because the normalizer runs over whatever the PDB hands back. With the separator half landed, the arc’s remaining distance on flag is one miss, not two. ⚠ One qualifier, measured in the same probe rather than left as a stronger claim than the evidence supports: the PathMap compiler option does produce a forward-slash path (-p:PathMap=C:\Program Files\Go\src\=/goroot/ yielded /goroot/flag/flag_test.go). So the separator is reachable at the compiler as well — just worse there. PathMap is a whole-compilation option that rewrites every source path in the PDB including the emitted .cs positions, it needs the GOROOT prefix as a per-package build property, and it does nothing for runtime.Stack or for any frame reached without a directive. That is why this lane settled the separator at the runtime.
  3. A CS diagnostic inside a #line region reports the GO position — measured: C:\Program Files\Go\src\flag\flag_test.go(854,17): error CS0029. This cost is not on the board and it is not small: every census this board carries is of the shape <file>.cs(NNN): CS####, and the whole compile-wall workflow reads diagnostics against emitted C# line numbers. A corpus-wide #line emission relocates all of them onto .go files that are not in the project.
  4. Per-statement #line adds ~28–47% more lines to a converted file — measured on flag/flag.cs (+28%), strings/strings.cs (+42%) and edwards25519/edwards25519.cs (+47%) — interleaved between every statement, across ~3,200 corpus .cs files and ~600 behavioral goldens.

Costs 3 and 4 are exactly the two the side-car alternative does not pay; it pays a file and a csproj item per package instead. That trade now has numbers on both sides, which it did not before. This lane did not take either half of the emission arc: it changes every golden in the corpus and cuts against a stated project goal, which is coordinator territory, and from master it would not have closed flag anyway (premise 2).

Blast-radius census for the separator fix

A verdict can only move if something reads the file string. Measured over the 154-row roster:

Gates

A src/core/runtime hand-own change plus one behavioral guard; no converter change, so no CNR is owed (CNR gates converter emission, and the one golden that moved here moved because its .go source did).

Gate Result
go2cs-stdlib.slnx (--no-incremental) 0 errors, 157 warnings, 369 s
go2cs.slnx (--no-incremental) 0 errors, 287 warnings, 728 s
full behavioral suite PASS — 601 projects, 1,862.8 s; Transpile 601, Compile 601/0/0, Target 601/601 byte-identical, Output 575 compared + 26 skip
full run-validated-sweep.ps1 (154 packages, 17,730 expected verdicts) 152 pass / 2 fail, 14,729 s (4 h 05 m) — both failures proven pre-existing, below
RuntimeCallerFrames filtered PASS all four phases; failing-first proven by the table above

CNR is not separately owed: it gates converter emission, there is no converter change, and the behavioral suite’s Transpile + Target phases re-transpiled all 601 packages and byte-compared every golden — which is the stronger form of the same check. The one golden that moved moved because its .go source did, and it was re-baselined through UpdateTestTargets --createTargetFiles (which touched nothing else: no other .cs.target, no *Tests.cs test-class block).

Both sweep failures are PRE-EXISTING — and one of them is a banked row that has regressed on master

Neither package’s tests call runtime.Caller/Callers/Stack at all, and both were re-run individually to root them rather than left as sweep noise.

For the next lane

  1. The position-map arc now has a named first half that is already paid. Whoever takes it inherits goSourcePath and needs only the .go identity — and should choose between #line and the side-car on measurements 3 and 4 above, not on the older two-line pricing.
  2. flag cannot be finished from master. claude/heavy-pair-7be2d2 merges first; the residual after it is one miss, not two.
  3. edwards25519 is one row from banking and the row is moving on its own — 109 objects at r60, 98 today. The ж-box arc owns it; nothing else in the package is open.
  4. crypto/tls needs an owner now. Five verdicts against a banked 402, pre-existing, bisectable across two merges. Nothing banks or re-banks while the flagship row is red.
  5. Nothing banked here. No roster row moved, no proof page was written, no test sources were committed: edwards25519 stays at 54/55 and flag is not reachable from master. The proof page the sweep regenerated (docs/validation/current/archive.tar.md, which flipped TestFileInfoHeaderSymlink to skip/skip — both runtimes agreeing, on an unprivileged host) was restored, not banked: it encodes this machine’s symlink privilege, not a validation change.

    ✅ The crypto/tls regression is ROOTED and FIXED — the guilty merge predates the bank’s own merge, the mechanism is the whitebox test model’s two latent defects, and the fix reverts nothing (2026-08-19, lane claude/tls-regression, laptop G)

The edwards25519-a entry above found the flagship row red at master (397 of 402, five divergences) and named two candidate merges. Both candidates were innocent — and so was every merge in the briefed window. The bisection had to walk PAST the bank itself to converge, and where it landed is the finding that matters beyond this row.

The bisection — seven probes, and the window was wrong

The reproduce (filtered sweep at master, this host) confirmed three REAL divergent tests — TestMarshalUnmarshal (+ its *tls.SessionState child) and TestQUICHandshakeError — plus TestBogoSuite’s 3,242 Go-side children flooding as one-sided rows (below), with TestCertCache matching its pinned disclosure normally. A fast probe replaced the 11-minute sweep for bisection: convert + build the tls tests closure at each commit (scoped purge first) and run the two failing tests directly in the built host, ~4 minutes per probe. Verdicts, newest first:

probe MarshalUnmarshal / QUIC emitted test-source shape
98bff3efc (near-miss batch) RED whitebox-reference
c961112ad (row-harvest-3) RED whitebox-reference
0390fece4 (variadic-call) RED whitebox-reference
5a16458ad (the tls bank’s own merge) RED whitebox-reference
837fd3a01 (reflect-minibridge) RED whitebox-reference
d1ed1f7c1 (local-iface-cast — GUILTY) RED whitebox-reference
b5a82df19 (its first-parent) GREEN recompile

So the regression entered with d1ed1f7c1 — which merged BEFORE the bank. The tls-mint-site lane forked at b5a82df19, proved 400+2 on its own tip (whose converter still emitted the RECOMPILE test model for tls), and merged at 5a16458ad — into a master that already carried local-iface-cast. Each side green alone; the union red; nothing swept the union. A lane’s sweep proof binds its own tree, never the merge result — the process rule this adds to CLAUDE.md’s gate doctrine, alongside the derived-never-carried reflect-canary set.

The mechanism — a model FLIP exposing two latent whitebox defects

d1ed1f7c1’s converter change (“a local named value source in call syntax records its pair”) moved records off the production-anchored partition, so recordsRequireProductionAnchor stopped firing for crypto/tls and the test conversion stopped FALLING BACK to the recompile model: tls converts under whitebox-reference from that commit on (production types stay in the referenced crypto.tls.dll; test sources compile alone against a friend bridge). That flip is INTENDED — it is the model encoding/xml banked 386/386 under — but tls’s suite exercises two whitebox shapes xml’s does not, and both had latent defects:

  1. ImplementGenerator stubbed bridge-implemented interface members (TestMarshalUnmarshal). *SessionState satisfies the internal handshakeMessage interface ONLY through test-declared methods (marshal/unmarshal live in handshake_messages_test.go — Go lets test files add methods to production types). The sealing-marker classification (“a cross-assembly unexported interface method with nothing to forward to is a stub”) gathers its no-local-implementation evidence from the struct’s own declaration syntax — which a PRODUCTION type referenced from a test compilation does not have — so both members classified as markers and the adapter compiled with => default! bodies: marshal answered an EMPTY buffer with nil error, unmarshal answered false, and the test reported “#15 failed to unmarshal 0x…” with no diagnostic anywhere (the trailing %x printing nothing was the tell). Fix: the evidence scan now covers the friend bridge’s extensions by receiver simple name in BOTH forms — direct-ж, and [GoRecv] ref (which forwards through its RecvGenerator ж-twin, exactly as IsRefRecv already routes the local form). Guarded failing-first by GenTests.WhiteboxBridgeAdapterTests (the REAL generator run over a two-assembly model of this shape) and the ref-scan unit rows in FriendBridgeBoxReceiverTests.

  2. golib’s error<T> shell never joined the carrier-unwrap protocol (TestQUICHandshakeError). Go interface equality is decided on the dynamic (type, value), never on which CARRIER holds it — and under whitebox the same AlertError value legitimately rides two different carriers: production has no GoImplement<AlertError, error> record (it only ever boxes the value into any), so fmt’s %w assert minted golib’s runtime shell error<AlertError>, while the test’s errors.Is target arrived as the test assembly’s generated value adapter. Every go2cs-gen-generated shell implements IInterfaceAdapter so AreEqual can unwrap it; error<T> — the ONE hand-written shell — did not, so it fell to reference equality and the same alert value never matched itself (quicError then failed to find the chain’s alert the same way, wrapping alertInternalError). Fix: error<T> implements IInterfaceAdapter with the identical member every generated shell carries — the ж box when pointer-backed (Go pointer identity), the value otherwise. Guarded failing-first by GolibTests.ErrorShellCarrierEqualityTests (both flavors, plus the protocol membership itself). Under recompile neither defect could fire: production sources recompiled INTO the test assembly, so the struct declaration was local (evidence present) and AlertError implemented error nominally (no carrier on either side).

What this run could and could not verify on this host

Post-fix, the filtered sweep’s real divergence set is emptyTestMarshalUnmarshal, TestMarshalUnmarshal/*tls.SessionState and TestQUICHandshakeError all pass THROUGH the sweep machinery, and TestCertCache matches its pinned disclosure. What laptop G cannot reproduce is the banked run’s TestBogoSuite shape: here the GO ORACLE’s own BoGo run expands to 3,243 subtests and fails at its child runner’s 600 s deadline (642 s, package-level fail), so its passing Go-side children flood the comparison as one-sided rows and no disclosure arm can withdraw them — the same host asymmetry the edwards25519-a entry measured pre-fix, present in every probe including the GREEN one, and unreachable from any go2cs layer. The row’s 400+2 confirmation on a host whose Go BoGo behaves as banked (laptop R ran it in 32 s) is owed at merge, per the new post-merge-bank rule. The banked test sources were deliberately NOT refreshed: the committed tree still carries the recompile-shape emission from the bank, the reconvert now produces the whitebox shape, and that refresh belongs to the next milestone rebank, not to a regression fix.

Corrections to the record

runtime/debug’s COMPILE WALL IS CLOSED — 0 of 9 → 2 of 9, and the traceback’s RECEIVER half lands; the residual is three capability roots and the deferred position map (2026-08-19, lane claude/runtime-debug)

The board carried this package at 0 of 9 behind CS0264 + CS0715, “the same static-class-operator defect as internal/reflectlite. Two packages, one root” — and then recorded, when reflectlite’s CS0715 retired, that runtime/debug was “still unmeasured since the CS0715 retirement”. It is measured now, and the recorded wall is entirely gone: the host builds with zero CS errors (warnings only, all pre-existing corpus classes) and every one of the nine verdicts is real.

The re-measure

2 of 9 matchTestSetGCPercent skip-parity (Go skips it on Windows: testenv.SkipFlaky, issue 20076) and TestSetMaxThreadsOvf pass. Go’s own baseline for the package is 8 pass + 1 skip, so the denominator is right. The seven failures decompose into four roots, none of them a converter defect:

Test(s) Root Class
TestWriteHeapDumpNonempty, TestWriteHeapDumpFinalizers, TestWriteHeapDumpTypeName WriteHeapDump throws — Go’s heap-dump format is a serialization of the Go heap through Go’s own type descriptors, which do not exist under the CLR capability, outside the four disclosure classes
TestReadGCStats no per-GC pause HISTORY. ReadMemStats reports a real NumGC (6) and PauseTotalNs but leaves PauseNs/PauseEnd/LastGC zero; the hand-owned readGCStats reports the same aggregates and an empty history. The test is a self-consistency check between the two surfaces (len(stats.Pause) == min(NumGC, 256)), so it needs no real pause values — it needs the two to agree, which today they do not capability, outside the four classes
TestFreeOSMemory MemStats.HeapReleased is deliberately zero, so after.HeapReleased <= before.HeapReleased fires as no memory released: 0 -> 0 capability, outside the four classes
TestStack the traceback’s file paths point at the emitted .cs, where Go names the .go — the deferred Go-source position map the board already carries deferred arc

Three of the four would need a disclosure class that does not exist — “a runtime capability the managed runtime does not provide”, which none of the classes actually in use reaches (alloc-profile, alloc-count-semantics, codegen-liveness, host-limit, chan-direction; see the class-count note below) — and the fourth is coordinator-deferred. The lane therefore did not bank, did not disclose, and did not fake any of the three — a one-byte write would satisfy all three heapdump assertions (none of them parses the dump), and that is exactly the kind of technicality the four-class bar exists to refuse. Boarded for a ruling, not taken.

What DID land: a traceback frame is SPELLED Go’s way on the receiver, too

Chasing TestStack surfaced the second half of the fidelity gap 35c806679 opened. That commit separated which source a frame’s path points at (the deferred position map) from how the path is written (an ordinary fidelity gap, closed). The same separation applies one field over: which function a frame names, versus how the NAME is written.

Go names a method frame with its receiver type between the package and the method. Measured on this box with a Go control, on both surfaces a program can read:

shape Go’s traceback / Frame.Function
pointer receiver main.(*T).ptrmethod
value receiver main.T.method
generic receiver main.G[...].gmethod — the LITERAL [...], never the instantiated argument
package-level func main.plain — no qualifier

goFrameName (runtime/managed_impl.cs) emitted the flat <pkg>.<name> for all of them, so a method frame answered runtime/debug_test.ptrmethod where Go answers runtime/debug_test.(*T).ptrmethod. A converted Go method is a C# extension method on the package class whose first parameter is the receiver — this ref T for a pointer receiver (the [GoRecv] form), this T for a value one, RecvGenerator’s boxed this ж<T> overload for the pointer form reached through a pointer value — and that this is the whole discriminator: a package-level func is a plain static method and keeps its bare name, exactly as Go renders one. The boxed form is detected through IPointer<T> rather than ж<T> itself, so a generated named-pointer wrapper answers the same way.

Both call sites of goFrameName gain it, which is what Go does: appendGoFrames (the rendered traceback) and internCallerFrame (every Caller/Callers/CallersFrames answer’s Frame.Function) — the Go control confirms the two spellings are identical upstream.

Proven against Go’s own test, not only against a guard. TestStack’s assertion failures went 7 → 5 on the same pipeline run: the two function-name assertions (runtime/debug_test.(*T).ptrmethod, runtime/debug_test.T.method) now agree with Go, and the five that remain are all the file-path half — four expected prefix "\truntime/debug/stack_test.go" and one "\ttesting/testing.go". That is the position-map arc and nothing else, so runtime/debug is now a named consumer of it: closing the position map is worth one more verdict here.

Guard, proven failing-first (RuntimeCallerFrames, the project that already guards Caller’s frame accounting and the separator): five assertions across the three shapes plus two negative controls, output-compared against go run. Before the fix exactly those five diverged, all inverted, and all sixteen pre-existing assertions still matched:

assertion Go C# before C# after
traceback names pointer receiver true false true
traceback names value receiver true false true
traceback drops pointer receiver false true false
traceback drops value receiver false true false
traceback names generic receiver true false true
traceback names plain func (control) true true true
traceback parenthesizes plain func (control) false false false

The two controls are the point of the negative half: a package-level func must NOT grow a qualifier, and the guard fails in both directions.

The measurement hazard this lane paid for

BehavioralRunner builds Release; a worktree’s stale bin/Debug is a FALSE reading. The first failing-first diff was taken against bin\Debug\net9.0\<proj>.exe, which in a reused worktree was eleven days old — from before 35c806679 landed — so it printed 11 of 23 lines and the diff read as “the C# side crashes at the separator assertions”, a defect that does not exist. The runner’s artifact is bin\Release\net9.0; against it the failing-first state was the clean five-line inversion above. A direct dotnet build <behavioral>.csproj is also NOT a substitute for reading the runner’s artifact: outside solution context $(go2csPath) does not resolve and the build dies in CS0234/CS0246 on golib itself, which reads like a corpus break and is not one.

What this lane did NOT do

No roster row, no proof page, no disclosures, no committed test sources: runtime/debug does not validate at 2 of 9. The package’s -tests artifacts are removed and the corpus restored, so the tracked changes are the one runtime hand-own, the guard’s Go source, and its three regenerated emission files.

A doc drift found while ruling the disclosures — the roster says FOUR classes, the manifests use FIVE

docs/ValidatedTestPackages.md states “Four classes exist” and names alloc-profile, codegen-liveness, host-limit, chan-direction. The 21 committed manifests use five class strings: those four (41 / 5 / 26 / 3 uses) plus alloc-count-semantics (6 uses, in context, io, os, strings), which the board itself calls “the established alloc-count-semantics class (io, strings, bytes)” and ConversionStrategies-Reference.md records as strings’ first use. It is a real, ruled class — AllocsPerRun counts mallocs in Go and BYTES on the CLR, so a count assert can never agree — and it is simply missing from the roster’s prose. Not fixed here (it changes published roster text and the framing its arithmetic hangs off); flagged for a coordinator pass.

It changes nothing about this package: none of the five reaches “a runtime capability the managed runtime does not provide”, which is what all three of runtime/debug’s capability roots need.

What runtime/debug needs, ranked — three rulings and one arc

  1. A ruling on the capability class. WriteHeapDump (3 verdicts), the per-GC pause history (1), and HeapReleased (1) are five of the seven, and all three are the same question: may a package disclose a runtime capability the CLR does not provide? The host-limit bar is written narrowly and deliberately — “a structural property of the deployment shape, never an unimplemented-but-fixable defect” — and none of these is a deployment-shape property. Two of the three are also arguably fixable at real cost, which is what makes it a ruling rather than a reading:
    • HeapReleased has a managed analogue (the cumulative decrease in GCMemoryInfo.TotalCommittedBytes, i.e. memory this process handed back), but it is a NEW measurement surface every ReadMemStats consumer inherits, runtime/metrics included.
    • The pause history is reachable — a gen2 GC callback or an EventPipe listener can record real PauseNs/PauseEnd, and NumGC already counts gen2 collections, so the ring would line up by construction — but it is an always-on recorder every converted program pays for, and finalizer-timing skew would make the count assert flaky. A design, not a defect fix.
    • WriteHeapDump is the one with no honest managed form at all: Go’s format is defined over Go’s type descriptors. Note the trap for whoever takes it — none of the three tests parses the dump (they check size >= 1 and “does not crash”), so a one-byte write banks all three and proves nothing.
  2. The position mapTestStack’s remaining five assertions, and this package is now a named consumer alongside log, log/slog and flag. The frame-SPELLING half is fully paid on both axes now (separator 35c806679, receiver here); what is left is purely the .go identity. Two details for whoever takes it, both measured here: the test’s frame() helper checks a code string AND a file prefix per frame, and all five code checks now pass — the five failures are one file-prefix miss each. And the expected prefix is the bare -trimpath form (runtime/debug/stack.go, no GOROOT), because the test spawns a child to read the binary’s BAKED-IN GOROOT and the converted runtime.GOROOT() answers empty. Incidentally that child round trip — os.Executable() + exec.Command on the converted host — WORKS; it is only the path identity that is missing.
  3. Nothing else. There is no converter defect in this package. Whoever returns after a ruling inherits a package that builds clean, produces nine honest verdicts, and has each of them rooted.

Gates

A src/core/runtime hand-own change plus one behavioral guard; no converter change, so no CNR is owed — and the behavioral suite’s Transpile+Target phases are the stronger form of the same check.

Gate Result
GolibTests 172/172, 16 s
go2cs.slnx Debug --no-incremental 0 errors, 617 s
Full behavioral suite PASS — 601 projects: Transpile 601, Compile 601/0/0, Target 601/601 byte-identical, Output 575 + 26 skip, 0 fail, 2,838 s
Targeted validated sweep, the traceback blast radius 20 packages / 1,702 verdicts / 0 fail, 839 s
runtime/debug pipeline, before and after 2 of 9 both runs; TestStack 7 assertion misses → 5

The blast radius was re-derived against the CURRENT roster rather than reused: the board’s census for the separator fix named 11 packages, and it is 12 now — internal/reflectlite joined the roster on 2026-08-18, after that census was taken. Six roster packages’ own _test.go read a traceback (context, encoding/base64, encoding/json, io, log/slog/internal/benchmarks, sync) and six more read one from converted PRODUCTION code (database/sql, go/types, internal/fuzz, internal/reflectlite, os/exec, testing/slogtest); substring filters pulled in eight more for free. Two facts narrow the risk further, both measured: only RuntimeCallerFrames in the whole 601-project behavioral corpus reads a traceback at all, and every Go test that pins Frame.Function (log/slog’s two) pins a package-level name, which this change leaves untouched by construction.

Standing post-sweep dirt, classified — and one PRE-EXISTING staleness worth recording

Every tracked file the sweep moved was restored, not banked. All of it is the documented classes: CRLF phantoms (base64_test.cs, five encoding/json *_test.cs), the global::go.* root escape (bufio/{bufio,scan}.cs, internal/reflectlite/{swapper,type,value}.cs), and the initᴛᴛtests() package_init.cs hook at +7 real lines (go/types, internal/fuzz).

One is NOT on the board and should be: encoding/base64’s committed test-info files are stale against the current converter. A -tests re-run emits them SMALLER — package_test_info.cs loses six lines and package_info_internal_test.cs two, all of it the <ImportedTypeAliases> block’s bytes/strings aliases and four GoImplicitConv<…, ж<…>>(Indirect = true) records. Root: the converter mints that block from each imported package’s production package_info.cs, and bytes/strings no longer carry those records there — they live in bytes/package_info_internal_test.cs now — so the committed files predate that move. encoding/base32 and fmt hold the same records and would drift the same way. It is benign (base64 validates 17/17 with the block empty) and it is pre-existing, not this lane’s: A/B’d by stashing the runtime change and re-running the same filter, which reproduced 0 2 and 0 6 on the same two files with an identical 17/17. Recorded so the next sweep reader restores it instead of chasing it — or levels all three at a rebank.

net/netip’s CS0019 pair CLOSES, and the churn the deferral was waiting on measures ZERO (2026-08-20, lane claude/runtime-debug)

The board deferred this one explicitly: “Bounded: the shape is a complement of a BasicLit whose go/types type is a basic other than int … Deliberately NOT taken here — it is a production-reachable emission change and belongs in an arc with room to measure its corpus churn.” The measurement is the deliverable, so it was taken first and the fix shaped around it.

The defect, and why exactly ONE width has it

Go’s &^ renders & ~, and C# applies ~ to a promoted operand — so a bare literal complements as int. Whether that binds is a property of the enclosing result type, and it binds everywhere but one place:

result x &^ 1 emits binds?
narrower than int (byte, ushort) (byte)(x & ~1) yes — promotes to int, truncates under the result cast
uint32 (uint32)(u & ~1) yes — uint & int promotes both to long (debug/macho’s Magic32 &^ 1 is the corpus instance)
int64 (int64)(x & ~1) yes — the int widens
uint64 (uint64)(x & ~1) noulong and int share no type C# converts to → CS0019
nuint / uintptr already cast yes — this file’s native-int block has imposed the type since NativeIntConstMask

So the rule is not “cast constants under &^”; it is uint64 completes the unsigned-64 group uint64/nuint/uintptr that convBinaryExpr.go already treats together” (the constant-fold arm groups the same three, for the same reason). The other two carry the cast already; this adds the third.

The predicate is a BasicLit, and that is what makes the churn zero

tv.Value != nil would have been the obvious predicate and would have been wrong. A BasicLit is the only operand shape that reaches ~ untyped:

That distinction is why the corpus number below is 0 and not “a few dozen”.

The churn, measured twice under identical seeding

Two seeded whole-stdlib reconverts, each into a FRESH wiped root (6,004 files seeded; bin/obj/ Generated excluded — the converter reads none of them, and the analyzer’s generated filenames blow past MAX_PATH under any temp root), classified emitted-vs-seeded by sentinel mtime:

Seeded full-corpus reconvert emitted identical differing new marked clobbered
this branch (549 s) 1,664 1,606 58 0 63 0
CONTROL, master’s converter (548 s) 1,664 1,606 58 0 63 0

The differing sets are identical file for file — diff of the two lists is empty — so the set difference is ∅ and the fix moves ZERO corpus files. The 58 are master’s own standing pre-regen drift (the known core/README.md attribution phantom, encoding/xml/README.md, and a spread of doc.cs), unchanged by this lane and outside its scope. Full CNR agrees from the other side: 628 behavioral packages re-transpiled unconditionally, one mover — the guard itself — 0 NOT MEASURED.

So the deferral’s premise is now answered: the churn is nil, and the fix stands.

Guard, proven failing-first — including the negative half

BitwiseUntypedConst already guards this exact family for a NAMED untyped const (Float64bits(f) &^ signBit); the bare literal is its sibling and now lives beside it. Before the fix the project failed to COMPILE with exactly one diagnostic, and it is netip’s verbatim:

main.cs(13,21): error CS0019: Operator '&' cannot be applied to operands of type 'ulong' and 'int'

clearLow32 (uint32) is the negative control and it is the load-bearing half: it compiled before the fix and emits (uint32)(u & ~1) unchanged after it, which is what demonstrates the change is exactly as wide as the defect rather than a blanket cast.

Gates

Gate Result
converter go test ./... ok, 469 s
full CNR (628 behavioral packages, unconditional re-transpile) 1 mover — the guard’s own main.cs — 0 NOT MEASURED, 0 WARNING, 1,080 s
seeded whole-stdlib reconvert, branch and control 63 marked / 0 clobbered both; differing sets identical; 549 s / 548 s
BitwiseUntypedConst Transpile/Compile/Target/Output all pass; failing-first proven

The measurement hazard this half paid for

A seeded reconvert cannot run under the scratchpad path. At 161 characters it leaves too little headroom under Windows MAX_PATH, and the seed copy dies part-way through archive/tar on an ImplementGenerator output whose filename alone is ~140 characters — reported as Could not find a part of the path, which reads like a missing source and is not one. Use a SHORT lane-prefixed root and exclude the build-output directories; the ritual’s inputs (committed .cs/.csproj/README.md, version.props, docs/validation) are all that is actually seeded for.

net/netip re-censused: 7 build errors → 5, and the residual is ONE root

The pipeline was re-run against the fixed converter. The CS0019 pair is gone and nothing replaced it; net/netip now stops on five diagnostics in a single family, all fuzz_test.cs:

CS0315 ×5 — 'ΔAddr' / 'AddrPort' ×2 / 'ΔPrefix' ×2 cannot be used as type parameter 'P' in
           checkStringParseRoundTrip<P>: no boxing conversion to 'netip_test_package.netipTypeCmp'

That is the board’s recorded structural root, unchanged and unmoved: a generic function constrained by a TEST-declared interface cannot be instantiated with a PRODUCTION type under the white-box REFERENCE model. C# enforces where P : netipTypeCmp nominally, and go2cs-gen can only make a production type satisfy a test-declared interface with an ADAPTER class, never the partial declaration a nominal constraint needs — crypto/x509’s identity split seen from the other side. No bank was expected and none was taken: the package still produces zero verdicts. What changed is that its build wall is now a single named question with a coordinator-level answer (reference vs recompile model), rather than a structural root plus a bounded emission defect sitting in front of it.

The other three roots the previous lane closed (the exported-over-unexported accessibility clamp, the same-package CS0426, the embedded comparable constraint) stayed closed across this reconvert.

⛔ NEITHER TEMPLATE PACKAGE BANKS — the chan-direction class covers ONE of their three channel rows, not three; html/template closes its two non-cargo residuals and reaches 242 of 243 (2026-08-20, lane claude/template-banks, laptop G)

This lane was briefed to bank both template packages on the ratified chan-direction disclosure class — text/template at 50+2, html/template at 240+1 behind two bounded fixes. The two fixes landed and are the lane’s shipped work. The disclosures did not, and must not: re-measured against the class’s own retirement test, only ONE of the three channel rows is a chan-direction shape. The other two are a missing implementation, and disclosing them would launder it.

Three corrections to the brief’s premises, before anything else

  1. Master is 0c83c34c5 (the edwards25519 merge). The tls-regression and runtime-debug merges the brief expected are NOT in it — both exist only as origin/claude/tls-regression-2997cd and origin/claude/runtime-debug-5259b5. This lane branched from 0c83c34c5 and every number below is against it.
  2. The roster prose reads FOUR classes, not five, and the fifth name the brief and the stretch item both use — alloc-count-semanticshas never appeared in docs/ValidatedTestPackages.md (git log -S over the file returns nothing). It is nonetheless a live class: three banked packages pin rows with it (io ×2, strings ×3, context ×1) and their proof pages carry it. So the roster under-declares a class its own rows use — a documentation gap, not a ruling, and left for the coordinator rather than self-ruled here.
  3. sync/atomic’s TestAutoAligned64 fifth-class question is still open and is unrelated to this lane; noted only because the brief’s “five classes” may have been counting it.

The classification, and the one test that decides it

All four residual rows across the two packages were re-measured on this lane’s branch. Three of them stop in walkRange’s channel arm with the identical signature:

panic: reflect: recv on send-only channel
   at go.reflect_package.recv(ΔValue v, Boolean nb)   reflect/value.cs:1773
   at go.reflect_package.Recv(ΔValue v)               reflect/value.cs:1762
   at go.text.template_package.walkRange(…)           text/template/exec.cs:530

One signature, one call site, and yet the rows do NOT share a class. The discriminator is the class’s own self-retirement property — “landing it retires this class, and the pinned rows begin passing” — applied mechanically:

Row What Go does With direction carried as descriptor cargo Class
text/template TestIssue43065 make(chan<- int) ranged; walkRange’s ChanDir() == SendDir guard fires and Go reports range over send-only channel the guard fires, the error is produced, Recv is never called — the row passes chan-direction
text/template TestExecute (row range count) ranges count(5), a bidirectional chan string the guard correctly does not fire, Recv() is called, and recv reaches chanrecv — a bodyless partial the PartialStubGenerator fills with throw new NotImplementedException("chanrecv: external (assembly or cgo) function is not implemented")the row still fails ❌ missing implementation
html/template TestExecute (row range count) identical row, identical helper (func count(n int) chan string); html/template’s tests contain no directional channel at all same ❌ missing implementation

abi.ChanDir already answers BothDir for every channel (internal/abi/type_impl.cs, hand-owned), so the direction is not what refuses the two bidirectional rows. reflect.recv is the AUTO conversion and never asks it: it reinterprets the descriptor onto the linker’s chanType record and reads .Dir out of the memory after the value slot, which behind a synthesized descriptor is zero — 0 & RecvDir == 0 — so every reflect.Value.Recv() panics as send-only whatever the channel is. That is the defect the near-miss-finish lane wrote a bridge for and measured (TestExecute and TestComparison both PASSED), then reverted because bridging recv without direction turns TestIssue43065 into an unbounded hang. Its ranking finding stands and this lane re-proves it from the other side: direction is the prerequisite, and it is not the remedy for these two rows.

So a chan-direction disclosure for either TestExecute would pin a row that the class’s own remedy does not make pass — which is precisely the bar host-limit’s ruling set (“an entry must name a structural property … never an unimplemented-but-fixable defect”) and the charter’s “a disclosure is only for asserts the CLR provably cannot satisfy”. Measured, both are satisfiable: one of them has already been made to pass once.

Consequence: neither package banks today, and the arithmetic is exact.

Package Verdicts Matching now Residual Would bank at
text/template 52 50 TestExecute (recv), TestIssue43065 (chan-direction) 51 + 1 disclosed once recv is bridged; 52 + 0 once direction lands too
html/template 243 242 TestExecute (recv) 243 + 0 — no disclosure at all

The TestIssue43065 disclosure is written out below rather than committed, because a manifest belongs with the banking commit that verifies it end to end (the standing rule). Whoever lands the direction arc inherits it ready to paste:

{
 "name": "TestIssue43065",
 "class": "chan-direction",
 "signature": "reflect: recv on send-only channel",
 "reason": "the test ranges a `make(chan<- int)` and expects Go's `range over send-only channel` error, which text/template's walkRange produces from `val.Type().ChanDir() == reflect.SendDir`; channel DIRECTION is a representational limit -- a Go channel type emits as golib's channel<T> whatever its direction, so the bridge describes only the bidirectional type and abi.ChanDir answers BothDir, the honest answer for the type it can see. The guard therefore never fires and the range proceeds into Recv. Carrying direction as descriptor cargo the way array dims are carried makes the guard fire and retires this row with the class"
}

⚠ Note the signature it would pin is the SAME string the two non-disclosable rows fail with. That is not a flaw in the pin — the manifest matches on {test name, signature} together and the other two rows are different tests — but it is worth stating, because it means a future recv bridge that changes only the message would silently un-pin this row and the sweep would (correctly) go red.

html/template’s two non-cargo residuals — both rooted, both fixed, 240 → 242 of 243

Neither was the arc the board had them under, and the first is the more valuable finding.

TestErrors — the sealing marker’s stub is a design for the wrong half of the rule

The board carried this as “a ~-deref defect in the shared parse package, unrooted”. It is not a deref defect; the deref is the symptom two frames downstream of a withheld [assembly: GoImplement] record.

panic: runtime error: invalid memory address or nil pointer dereference
   at go.ж`1.op_OnesComplement(…)                    golib/ж.cs:967
   at …parse_package.ErrorContext(ж<Tree> Ꮡt, Node n) text/template/parse/parse.cs:156
   at …html.template_package.Error(ΔError& e)        html/template/error.cs:75

The chain, in one pass:

The rule the stub rests on is half a rule. “Go never lets a sealing marker be called from outside its package” is true, and irrelevant: the marker is called from INSIDE, on a value the consumer boxed, which is the entire point of sealing an interface. The stub must therefore be a last resort, and it is always avoidable in one place — Go scopes an implementation of such an interface to the declaring package, so the declaring assembly can always forward the marker natively and export the adapter.

Fix: the speculative POINTER record’s depth gate carves out for a sealed interface. When the interface carries an unexported method, the pointer record falls back to the VALUE form’s depth-2 bound instead of being withheld — still bounded (deeper than one embed hop is still refused), and the shape the strict gate was written for (StructPointerPromotionWithInterface’s MyCustomError, whose promoted member resolves through the wrong embedded POINTER hop) is an all-exported interface that never reaches the arm. The gate’s own justification — “withholding a speculative record is always safe, because the consumer keeps the local adapter it had before” — is exactly what is false for a sealed interface, and that sentence is now corrected in the reference doc.

Corpus footprint: one line, [assembly: GoImplement<BranchNode, Node>(Pointer = true)] in text/template/parse/package_info.cs. parse’s generated adapter forwards tree()/writeTo() natively and is public sealed; html/template’s cast site now emits new parse.BranchNodeжNode(…) through the existing foreign-adapter-exists arm.

What is still stubbed is what should be — and the corpus now holds none of it. A pair the declaring package genuinely cannot realize (promotion deeper than one hop, a generic, an unexported target) would still mint a consumer-local adapter with a silently stubbed marker. Censused on the FRESHLY GENERATED output of the whole-stdlib reconvert below: 1,307 ImplementGenerator adapters, ZERO with a => default! or empty-body member. Before the fix the same census found exactly one, and it was this one. ⚠ The census must be taken on freshly generated files only — the seeded root carries the previous build’s Generated/ folders (they are gitignored, so git clean -fd leaves them), and a stale crippled adapter sits there looking exactly like a live finding; discriminate by mtime against the reconvert’s sentinel.

TestRedundantFuncs — the boxing side of a fix whose reading side landed in June

panic: interface conversion: interface {} is <>f__AnonymousDelegate0,
       not go.Funcꓸꓸꓸ<object, @string>

html/template’s funcMap is a map[string]any of func(...any) string escapers assigned as method groups; TestRedundantFuncs reads them back with funcMap[n].(func(...any) string). The ASSERT side was fixed when convTypeAssertExpr learned to render an anonymous signature through iifeDelegateType. The BOXING side was not: C# gives a method group at an untyped destination its NATURAL function type, which for a non-variadic signature is Func<…>/Action<…> — go2cs’s own lowering, so those already agree — but a params signature has no BCL delegate, so C# synthesizes one and the box carries <>f__AnonymousDelegate0. The assert was right, the box was wrong, both emitted by the same converter.

Fix: a variadic func entering EMPTY-INTERFACE space is cast to its Go func type at the boundary — ((Funcꓸꓸꓸ<any, @string>)(attrEscaper)). This is the same carry-your-Go-type rule the pointer box and the untyped-constant box already apply at that same finite slot set, so it lives with them in typedNilInterfaceBoxing.go, and both sides now name the type through one renderer (getCSharpTypeNameiifeDelegateType) and cannot drift. A NON-empty interface target needs nothing: a bare func type has no methods and satisfies no other Go interface.

The board priced this as “the same missing conversion as Root C, one boundary over” and said to reuse variadicFuncLitCallee/iifeDelegateType. Half right — iifeDelegateType is reused; variadicFuncLitCallee is not, because this is not a callee shape and the boundary is the empty-interface slot set, not the call site.

The alloc-count-semantics gap, and the stretch measurement it was asked for

The brief’s stretch item asked whether the “now-restored” alloc-count-semantics class definition covers math/big’s and net/http/internal’s near-budget alloc rows, which the 2026-08-10 coordinator ratification left characterized and undisclosed.

First, the class is not in the roster and never has been. docs/ValidatedTestPackages.md says “Four classes exist” and lists alloc-profile, codegen-liveness, host-limit, chan-direction. git log -S"alloc-count-semantics" -- docs/ValidatedTestPackages.md returns nothing — the name has never been in that file. It is nonetheless live: io pins 2 rows with it, strings 3, context 1, and all three proof pages carry it. Proposed bullet, for the coordinator to take or reword:

Second, the measurement — and it says the class does NOT cover either row. Both tests go through testing.AllocsPerRun, but the shim has not been byte-derived since r58a: it is a hybrid, and which branch produced a figure decides the question.

Shim branch (r58a hybrid, core/testing/testing.cs) What the figure IS Comparable to Go’s Mallocs?
zero bytes exactly zero, in both units yes
nonzero bytes, counter charged ≥1 site a go2cs-runtime object COUNT, floored at 1, stated as a lower bound yes
nonzero bytes, counter charged nothing allocated BYTES per run no — this is the alloc-count-semantics case

Measured on this branch, through the pipeline:

Row Reported Shim note Reading
net/http/internal TestChunkReaderAllocs mallocs = 2; want 1 “counted 200 go2cs-runtime object allocations (64,000 bytes) over 100 run(s) — the figure reported above is an allocation COUNT per run … a LOWER BOUND on the true object count” a COUNT, in Go’s unit. The class does not apply
math/big TestNewIntAllocs wanted 0 allocations, got 1.000000, all seven inputs “counted 100 go2cs-runtime object allocations (81,600 bytes) over 100 run(s) — … an allocation COUNT per run … a LOWER BOUND” a COUNT, in Go’s unit. The class does not apply

So the class does not cover them, and the 2026-08-10 ratification was right for a reason it did not have to state: these rows are not a unit mismatch at all. The figure is in Go’s own unit, it is a LOWER bound (so the true count can only be ≥ the reported one — the assert can never be satisfied by better measurement, only by allocating less), and one extra golib object per run is an optimization target. Disclosing them under alloc-count-semantics would be a category error on top of the laundering the original ruling refused: the class names something the measurement cannot express, and here it expresses it exactly.

Recommended (coordinator’s call): keep both undisclosed; add the class bullet above to the roster so its own banked rows stop citing a class the document does not define.

What this lane deliberately did NOT do

Shipped

Change File Footprint Guard (proven failing-first)
The speculative POINTER record carves out for a SEALED interface: an unexported interface method drops the depth gate to the VALUE form’s depth-2 bound samePackageImplements.go (+ interfaceHasUnexportedMethod) see reconvert census below CrossPkgLib/CrossPkgUserEmitter gains a value-returning sealed member nodeTag() and DescribeEmitter(e Emitter), the ErrorContext shape. Neutered, *Branch prints branch/ where Go prints branch/brn; *Leaf is the control and reads leaf/lf either way
A VARIADIC func entering EMPTY-INTERFACE space is cast to its Go func type typedNilInterfaceBoxing.go (the boundary’s two entry points + a new variadicFuncBoxCastType), wired into convCompositeLit’s slice-element and struct-field any arms through the existing castArgToType plumbing see reconvert census below VariadicFuncTypeAssert, extended — the guard whose own comment recorded this as “a separate latent defect, out of this guard’s scope”. Four new shapes (literal direct to any, method group as a map[string]any element, through a plain assignment, as an []any{…} element) plus a non-variadic control. Neutered, all four print no match; the control keeps passing

Both rows also re-confirm their packages’ standing counts on this branch, unmoved by this lane’s two converter changes: math/big 224 of 226 (TestNewIntAllocs + TestMulUnbalanced, exactly the r58b pair) and net/http/internal 9 of 10.

A side finding, measured not chased: encoding/xml’s README still says “not yet validated”

The seeded reconvert’s mover list carried one file this lane does not own and nobody has recorded: src/core/encoding/xml/README.md. The committed badge line reads Tests-not_yet_validated-orange; a reconvert emits Tests-386%2F386_validated-brightgreen linking the package’s proof page. The package BANKED at 386/386 in f83702d6d and its proof page landed with it, but the README’s badge is composed only at CONVERSION time, so the bank never refreshed it — the published GitHub and NuGet page for roster row 155 advertises an unvalidated package.

It is exactly one package, not a class. Censused across all 155 packages that have a proof page in docs/validation/current/ and a README: encoding/xml is the only one carrying Tests-not_yet_validated. Left for its owner (a one-line README correction, or the next regen), and recorded here so it is not rediscovered as drift.

Gates

Converter go test ./... ok, 319 s, exit 0 — including projitemsIntegrity_test (no new .go file, nothing to register) and TestStdLibMetadataInSync. ⚠ Two readings from an earlier, CONCURRENT run were false and are worth carrying: TestStdLibMetadataInSync reported “stdlib-metadata.txt is STALE — run go generate . while src/core was dirty from this lane’s own -tests pipelines; against the restored tree it passes, and running go generate on that reading would have banked a metadata file generated from a -tests corpus. Re-run before believing it. The other failure was real and is fixed: TestUntypedInterfaceFuncLitResultType pins the emitted text of a func literal in an any slot, and its pred row is the VARIADIC one, so the new boxing cast wraps it. The guarded property is intact — the literal still states its Go result type INSIDE the cast — and the expectation now pins both in one string, which is strictly stronger than what it pinned before.

Full check-no-regression.ps1 over all 628 behavioral packages: 7 changed files, 0 NOT MEASURED, 0 advisory warnings; preflight solution integrity 630/630, path casing 4,527/4,527. Every one classified, and two of the seven are the point:

File Why
CrossPkgLib/{lib.go,lib.cs,package_info.cs}, CrossPkgUser/{main.go,main.cs,package_info.cs}, VariadicFuncTypeAssert/{main.go,main.cs} this lane’s guard sources and their intended new emissions
ReflectBridgeClosure/main.cs a variadic func in an []any{…} element read by reflect.TypeOf — now carries (Actionꓸꓸꓸ<@string, nint>)
ReflectVariadicCall/main.cs a map[string]any of three variadic literals — all three now cast

Seeded whole-stdlib reconvert (fresh root, seeded with core + version.props + docs/validation, single run): 63 marked / 0 clobbered (line-anchored git grep, path-precise), 1,664 emitted / 1,651 identical / 13 differing / 0 new, compared CR-INSENSITIVELY so no phantom is counted as a mover. The 13 are 4 this lane’s + 8 documented pre-existing carries + 1 unowned:

All src/core dirt from the four -tests pipeline runs (text/template ×2, html/template ×4, math/big, net/http/internal) classified and RESTORED per the standing rule, nothing unclassified: three CRLF phantoms ({html/template,text/template,math/big}/doc.cs, empty numstat), two initᴛᴛtests hooks ({html/template,math/big}/package_init.cs, +7 real lines each), math/big/prime.cs (phantom), and the four real movers above, which belong to a leveling regen rather than to this converter-fix commit. No package validates, so no roster row, no proof page, no committed test sources.

Full behavioral suite PASS at 601 projects, 2,374.6 s — Transpile 601/601, Compile 601/601, Target 601/601 byte-identical, Output 575 compared / 0 failed (26 skipped, no package main), 0 timeouts / 0 NOT MEASURED.

Pipeline verdicts on this branch, each re-measured after the fixes: html/template 242 of 243 (was 240), text/template 50 of 52 (unmoved — its residual is the recv defect plus the chan-direction row), math/big 224 of 226, net/http/internal 9 of 10.

Expected counts for the coordinator’s merge-result sweep (the banking-merge rule): this lane banks no row, so the roster is unchanged at 155 / 215 and no sweep row’s count moves — every banked package is expected at exactly its current number.

The canary question answers itself from the reconvert, which is stronger than a canary list: the sealed-interface carve-out moved exactly two package_info.cs files in the whole corpus (text/template/parse gains the record, html/template drops the local one it no longer needs). No other package in the standard library has a pair the carve-out newly admits, so no banked package’s records changed and none needs re-validating on that account. The variadic-boxing cast reaches two more files (html/template/escape.cs, text/template/funcs.cs), neither in a banked package. If a canary is wanted anyway, go/types (557) is the broadest consumer of a sealed foreign interface and its emission is byte-identical under the reconvert.

Gates (continued): the reconverted corpus BUILDS

The reconverted temp root (seeded with src/gen so the analyzer resolves) builds its own generated go2cs-stdlib.slnx: Build succeeded, 0 errors, 157 warnings, 416 s. That is the gate that matters for the record change — it is what proves the newly recorded pair generates an adapter that compiles, and that no other package’s adapters moved out from under it.

The queue, re-ranked by what this lane measured

  1. reflect.Value.Recv/chanrecv — promoted, and it is now the ONLY thing between html/template and a 243/243 bank with zero disclosures. It is written and measured (near-miss-finish) and must land WITH direction, not before it.
  2. Channel direction as descriptor cargo — unchanged as the prerequisite, now with its exact value: it retires ONE row (text/template’s TestIssue43065, disclosure text above) and unblocks #1. Landing both takes text/template to 52/52 and html/template to 243/243, no disclosures in either.
  3. encoding/gob (106) — 103 of 106, still the closest unbanked package that neither of the above touches.
  4. sync/atomic’s zero-size-field layout, 5. %#x of a uintptr — unchanged.

The sealed-interface stub’s residual leaves the queue: censused after the fix, the corpus has no adapter that silently stubs a member (1,307 generated, zero stubbed). The shape stays worth suspecting, not tracking.

✅ CHANNEL DIRECTION IS DESCRIPTOR CARGO and reflect.Value.Recv/Send land WITH it — the chan-direction class RETIRES, and both template packages bank as rows 156-157 (2026-08-20, lane claude/cargo-recv, laptop G)

The arc the last three entries kept pointing at. internal/reflectlite goes 27+3 → 30+0, text/template 50 → 52 of 52 with NO disclosure, html/template 242 → 243 of 243. The roster reaches 157 / 215 (73.0%), 18,414 matching verdicts, 79 disclosed.

Two corrections to the brief’s premises

  1. claude/template-banks was NOT in master — it existed only as a local branch, and html/template cannot reach 243 without its two fixes. It is MERGED into this lane rather than waited on, which also makes every measurement below bind the UNION of both changes, per the banking-merge doctrine the crypto/tls regression established. Both conflicts were docs and both resolved by keeping both sides.
  2. text/template’s TestIssue43065 needs no disclosure at all. The template-banks entry wrote its manifest out ready to paste; it is not pasted, because the row PASSES. That entry’s discrimination was exactly right — one of the three channel rows was chan-direction and two were a missing chanrecv — and the consequence of landing both halves is that all three pass.

The design question the brief gated on: does the cargo fit the GoArrayDims precedent?

Yes — but only because a NIL channel can carry a direction, and that is the whole design.

All three internal/reflectlite rows read the direction at a type position, where there is no live channel to measure: new(<-chan int) and new(chan<- string) describe the zero value behind a pointer, and struct{ x chan<- string } describes a field nothing ever assigned. A direction that rode only on a made channel would have retired none of them. Putting it on the channel<T> STRUCT rather than on its heap ChanCore is what reaches them: direction belongs to the Go TYPE, so two values of different directions may share one core, and the zero value of a directional type has no core at all yet still has a direction (channel<T>.SendOnly / .RecvOnly).

With that, the positions map one-for-one onto the array dims’, and the plumbing is the same plumbing:

Position Array length Channel direction
the constructed value new(32) new channel<nint>(0, GoChanDir.Send)
a struct FIELD’s zero = new(4) initializer = channel<@string>.SendOnly initializer
behind a POINTER PointeeArrayDims PointeeChanDir
the FABRICATED zero ZeroValueOf(st, dims) ZeroValueOf(st, dims, chanDir)
a func PARAMETER [GoArrayDims(32)] not carried — no measured consumer

abi.Type.chanDir joins BOTH interning keys (shared with reflect’s canonType), Type.String() renders the arrow from the same cargo, and a POINTER hands its pointee’s direction down UNSHIFTED through Elem(). Unstamped answers BothDir — what the accessor already reported, and the honest answer for a type nothing narrowed — so only directional sites move.

The fourth position was found by MEASUREMENT, not by reasoning, and it is the useful lesson

The first pipeline run came back 29 + 1 disclosed, not 30. TestAssignableTo and TestSetValue flipped; TestTypes did not. The guard had already proven the struct-field route working through reflect, so the shape looked covered — and the discriminator is that reflectlite’s helper does not ask the TYPE at all:

func TypeString(t Type) string { return fmt.Sprintf("%T", ToInterface(Zero(t))) }

It fabricates a zero FROM the descriptor, boxes it, and re-describes it. The direction survived Type.String() and died at the box, because GoReflect.ZeroValueOf handed back default(channel<T>). So the fabricated zero carries the cargo now, for exactly the reason reflect.Zero of an array type is already sized from the descriptor’s dims: a value the bridge invents must describe itself the way the descriptor does, or the cargo is lost the first time anything boxes it. The guard was extended with that shape rather than left as the thing that missed it.

Three latent defects the cargo exposed, all fixed here

None is this arc’s own work; each was invisible because every ChanDir() answered BothDir.

Defect Why it was silent Consequence once directions are real
internal/reflectlite’s hand-owned haveIdenticalUnderlyingType chan arm dropped Go’s FIRST rule (“x is a bidirectional channel value, T is a channel type, and V and T have identical element types”) with one direction, both of Go’s two rules agree for every pair var r <-chan int = make(chan int) reports unassignable
reflect.Value.Len() had no IChannel arm at all 0 is a real length — the same silence the named-string arm had every channel Value reported len 0 while Cap() answered correctly one method away
Value.send cannot use Go’s assignTo nothing reached sendrecv’s guard refused first assignTo’s managed form returns a Value carrying only the never-populated raw ptr slot and drops the boxed companion, so the channel received a bare null

The third is worth carrying: send now marshals through marshalIntoSlot, the rule Value.Call already used for a call argument, so a channel send and a call argument box a typed nil identically. That is the third boundary to join the one-nil-encoding rule.

The hang, reproduced and bounded

near-miss-finish measured that bridging recv ALONE turns TestIssue43065 into an unbounded hang costing 51 verdicts. That coupling is this lane’s acceptance test, and the guard carries it as a timeout-bounded reproduction: walkRangeShape is text/template’s channel arm in miniature, run in a goroutine against time.After, so a regression prints a named line instead of wedging the suite.

Proven failing-first by neutering each half SEPARATELY:

Boundaries, stated rather than discovered later

The corpus footprint, and the four movers that had to be PROVEN pre-existing

Three-target seeded emission (windows,linux,darwin; fresh root, single run, per the r41 rule): 63 marked / 0 clobbered on every target, path-precise and line-anchored via git grep. The L3 merge wrote 90 artifacts; 33 differ CR-insensitively and every one is classified —

Class Count What
this arc 16 a directional channel FIELD gaining its initializer, in crypto/tls, net/http ×3, net/{windows,linux,darwin} ×6, os/exec, os/signal, runtime/{windows,linux,darwin}/trace ×3, time — 32 field declarations, zero off-shape lines
template-banks 4 its two fixes’ own emission (parse/package_info.cs +1 record, html/template/package_info.cs −1, escape.cs, funcs.cs)
pre-existing carries 13 gcimporter; the runtime box-compare family, re-measured at 9 rather than the recorded 7 (alg, map, map_fast32, map_fast64, mbarrier, traceback, pprof/map, plus linux/mem_linux and darwin/mem_darwin — the two per-GOOS members no windows-only reconvert can see); two per-GOOS package_info.cs losing a readmemstats_m_bySize lift record; encoding/xml’s stale README badge

Zero unclassified. The four that were not obviously pre-existing were proven so rather than assumed: master’s converter was rebuilt from git archive and re-emitted runtime for linux, and it reproduces both shapes exactly — while linux/trace.cs is byte-identical under it, which is what proves that file’s change IS this arc’s. ⚠ Worth keeping as a technique: a single-target reconvert on the host GOOS cannot see a per-GOOS carry at all, so the runtime box-compare family has been under-counted at 7 since it was recorded.

Only this arc’s 16 are banked, plus template-banks’ 4 — the latter because they are no longer optional: html/template validates through them, and restoring parse/package_info.cs is what made TestErrors fail again on the first html/template run, a measured self-inflicted repro of exactly the defect that lane rooted. The 13 pre-existing carries are RESTORED and belong to a leveling regen.

src/core dirt from the five -tests runs classified and restored per the standing rule: {html,text}/template/doc.cs and reflectlite’s {swapper,type,value}.cs (the global::go.* root escape), plus html/template/package_init.cs’s initᴛᴛtests hook (+7 real lines).

The stale-validation-badge class is a PIPELINE gap, not four accidents

template-banks censused encoding/xml’s README as the only package advertising Tests-not_yet_validated while its row was banked, and left it as “a one-line correction, or the next regen”. It is neither an accident nor a one-off: every bank leaves a stale badge, and the mechanism is exactly locatable.

The badge is composed at CONVERSION time from docs/validation/current/<dot-id>.md and src/version.props (readmeBadgeLine), but writeReadmeFile is gated to options.convertStdLib (projectFileWriter.go) — deliberately, so a behavioral-test or single-package conversion does not litter its directory. A -tests run writes the proof page and does not write the README; a single-package conversion does not write the README either. So the badge only ever catches up on the next whole-stdlib reconvert, which is not part of banking.

This lane hit it three more times at once (internal/reflectlite frozen at 27/30, {html,text}/template at not_yet_validated) and closed all four by the authoritative route — a seeded single-target -stdlib reconvert taken AFTER the proof pages updated, overlaying only the README movers. That is a repeat of the same manual step every future bank will owe.

The durable fix is one line of gating, deliberately NOT taken here because it wants its own emission gate: let a -tests conversion that WROTE a proof page also refresh that package’s README, or equivalently drop the convertStdLib gate to “stdlib package” rather than “stdlib run”. Whoever takes it should keep the litter rule the gate exists for — the test is the PACKAGE’s provenance, not the run’s mode.

Two measurement hazards this lane paid for

Both are variants of traps CLAUDE.md already names, and both cost a wasted cycle.

A poll that waits for a process to DISAPPEAR fires instantly if it has not yet APPEARED. until ! tasklist | grep -qi go2cs.exe; do sleep 20; done looks like “wait for the converter to finish” and is actually “wait for the converter to not be running” — which is TRUE for the seconds between launching a command and its converter starting (here, a 1.6 GB seeding copy). The waiter exited immediately and its tail then failed on a log that did not exist yet, which reads exactly like a crashed run. This is the same class as the recorded exit $true inversion: the poll must be written on a POSITIVE condition the target actually produces — wait for the log to contain the completion marker, not for a process table to be empty.

Two conversions raced into ONE temp root, and it was self-inflicted. The r41 rule (“never convert twice into the same root”) was honored in intent and broken in fact: a nohup … & launch was replaced with a harness-tracked background launch without killing the first, and both wrote the same -go2cspath. Caught by tasklist showing two go2cs.exe, not by any output — the r41 corruption is silent until a build fails. The rule needs a companion habit: census the process table before launching a conversion, because “I meant to replace that run” is not the same as having replaced it.

Gates

Converter go test ./... ok, 174.1 s, exit 0 (projitemsIntegrity_test included — the one new .go file is registered; TestStdLibMetadataInSync passes, and no package_info.cs record moved for this arc, so go generate is not owed).

Full check-no-regression.ps1: byte-identical generated .cs and .csproj across all 629 behavioral packages, 0 NOT MEASURED, 2 advisory converter warnings, 673 s; preflight solution integrity 631/631, path casing 4,534/4,534. Re-run from scratch after the one late converter edit (a local-variable rename) so the gate binds the source at HEAD rather than the source it was launched against.

Full behavioral suite PASS at 602 projects, 1,671.6 s — Transpile 602/602, Compile 602/602, Target 602/602 byte-identical, Output 576 compared / 0 failed (26 skipped, no package main), 0 timeouts / 0 NOT MEASURED.

Solution builds, all --no-incremental: go2cs.slnx Debug 0 errors, 715 s (the gate no other harness covers, and this arc changes golib’s IChannel/channel<T> surface); go2cs-stdlib.slnx -p:GoTargetOS=windows 0 errors, 381 s; -p:GoTargetOS=linux 0 errors, 364 s. The linux build is not optional here — six of this arc’s sixteen corpus movers live in net/linux/ and runtime/linux/, which the default windows build never compiles. bin/obj purged between the target switch (916 of 935 directories; the 19 that resisted are long-path Generated folders in vendor/coverage packages, none platform-varying, and --no-incremental covers them).

Row sweeps, from the committed roster: html/template 243, text/template 52, text/template/parse 52 — 3 pass / 0 fail, 284 s. internal/reflectlite PASS at its NEW count, 30 — which is the proof the class retirement is real rather than locally observed.

Canaries — the five largest banked reflect consumers BY VERDICT COUNT, recomputed from the roster at gate time per the doctrine (go/internal/gcimporter 583, go/types 557, encoding/json 491, crypto/tls 400, encoding/xml 386; the two rows this lane banks are reflect consumers too but at 243 and 52 they do not enter the set):

Canary Expected Result
go/internal/gcimporter 583 PASS (492 s)
go/types 557 PASS (256 s)
encoding/json 491 PASS (98 s)
encoding/xml 386 PASS (103 s)
crypto/tls 400 FAIL — rooted to the GO baseline, see below

Guards proven failing-first by neutering each half separately (both transcripts above).

src/core dirt from the five -tests runs and the sweeps classified and RESTORED per the standing rule, with ONE exception banked deliberately: text/template/parse/parse_test.cs (2/2), which is template-banks’ variadic-boxing fix reaching a BANKED row’s committed test sources. It is banked rather than restored because the package re-validates at its full 52 with the new emission, so restoring it would leave every future sweep reporting the same drift forever.

⚠ The crypto/tls canary reports FAIL, and the cause is the GO baseline on this host

Recorded in full because a red flagship row is exactly what the banking-merge doctrine exists to catch, and this one is a FALSE red that will recur.

The sweep’s verdict maps say it precisely. The C# side ran its banked 402 rows and every verdict is identical to the committed proof page — zero differences. Exactly one row diverges between the two languages, TestCertCache (Go pass, C# fail), and that row is itself a banked codegen-liveness disclosure. The count still moved:

Row Banked model This run
TestBogoSuite Go pass, C# fail → a host-limit DIVERGENCE, counted as disclosed Go FAIL, C# fail → the row AGREES, so it is not a divergence at all
TestCertCache Go pass, C# fail → disclosed unchanged

Live disclosed = 1 where the roster banks 2, and the sweep’s own disclosed count moved check fires. The C# side did not move; the GO side did. On this host go test actually executed the BoGo matrix — 3,243 TestBogoSuite/... subtest rows appear on the Go side against the banked model’s absence — and failed it, which is unsurprising given what that row’s own disclosure documents about the runner’s 10-minute child deadline and 5,481-case matrix.

Nothing in a go2cs branch can influence this. The Go baseline is go test -json over GOROOT’s own crypto/tls sources with the Go toolchain, and on the Go side the BoGo shim is Go’s own test binary, not the converted host. The row is environmental — network reachability of the boringssl module, and how far the runner gets before its own deadline — and it would reproduce at master on this machine.

The finding worth carrying is about the PIN, not about tls. A disclosure pins a divergence, so it is only stable while the GO side is stable. TestBogoSuite’s Go side is itself load- and network-dependent, which makes this row brittle in BOTH directions: the sweep goes red when Go starts failing (here) and would go red again when Go goes back to passing on a quieter host. That is a property of disclosing a row whose baseline is not deterministic, and it is worth a coordinator ruling rather than a lane’s patch — the honest options are to annotate the row the way host-conditional annotates verdict COUNTS, or to accept agreement-on-failure as satisfying a disclosure.

This lane changed nothing here and banked nothing here: the regenerated crypto/tls test sources were RESTORED, including the pre-existing staleness they exposed (the committed sources still emit tls_package where the converter now emits tls_internal_test_package, the form 132 other packages already use — a stale bank, unrelated to this arc, and not something to level inside a red canary).

The queue, after this

  1. encoding/gob (106) — 103 of 106, now the closest unbanked package and the queue top. Its recorded need is the map key/elem dims cargo, which shares this arc’s plumbing: synthType already takes cargo, descriptorDimsKey already renders it, and Key()/Elem() are the two accessors that would consume it. Whoever takes it should read chanDirectionCargo.go first — the converter-side shape is a third of a page, and the four-position table above is the map for it.
  2. sync/atomic’s zero-size-field layout, 3. %#x of a uintptr, 4. the two variadic-argument walls (a SPREAD argument to a deferred variadic call; an empty variadic call passing an empty slice where Go passes nil) — all unchanged. The variadic-func-in-an-any-slot item drops off, landed with template-banks.

The chan-direction class leaves the disclosure roster, taking the count from five back to four.

RULING – TestBogoSuite’s pin gains a HOST-CONDITIONAL annotation; agreement-on-failure never satisfies an unannotated disclosure (coordinator, 2026-08-20)

The cargo-recv gate put the choice cleanly (above): a disclosure pins a divergence, and this row’s divergence has a nondeterministic GO side – network reachability of the boringssl module and the runner’s own 10-minute child deadline decide whether Go’s TestBogoSuite passes, expands into its 3,243-row matrix, or fails, per host and per run. The C# side was proven unmoved (zero verdict differences against the committed proof page), so the red is entirely the baseline’s.

Of the two honest options, the narrow one is ruled IN and the broad one is ruled OUT:

  1. REFUSED – accepting agreement-on-failure as satisfying a disclosure in general. That would make every pin self-satisfying the moment its Go baseline breaks for any environmental reason, which is exactly the staleness the sweep’s disclosed count moved check exists to catch. A disclosure asserts Go passes, C# provably cannot; if the sweep stops noticing when the first half fails, the assertion quietly decays into C# fails.

  2. ADOPTED – the per-row annotation, keyed the way host-conditional verdict-COUNT annotations already are. Mechanics, binding on the implementer:

    • The signature-pinned manifest entry for a NAMED row may carry a host-conditional marker plus one sentence naming the environmental dependency. Only rows with coordinator-accepted rooting evidence receive it; TestBogoSuite (the rooting above) is the first and only member today.
    • An annotated row satisfies the sweep in EXACTLY two shapes: Go pass / C# fail (the pinned divergence) or Go fail / C# fail (agreement, on a host where the Go premise fails). EITHER shape accounts the row as DISCLOSED – never as matching – so the roster arithmetic is host-stable: crypto/tls reads 400 + 2 on every machine.
    • Go-side subtest children of an annotated row (the BoGo expansion) are excluded from one-sided accounting; they are the annotated row’s own baseline flapping, not new rows.
    • Any movement on the C# side still fires, in both directions. The pin stays strict where it can be strict; the tolerance is confined to the half that was never deterministic.
    • The proof page carries the same note the roster’s internal/zstd row models: name the dependency, name both accepted shapes.

Queued as a bounded lane item (compare machinery + manifest schema + the crypto/tls entry + proof page + a run proving both shapes account as disclosed). Until it lands, a crypto/tls sweep on a host whose Go BoGo run fails is a KNOWN false red with this section as its rooting – re-read the verdict maps before believing any other explanation.

NOTE – the hand-leveled .tests.csproj files level themselves at their next sweep; BANK that drift, don’t chase it (coordinator, 2026-08-20)

The preflight-trio metadata wave hand-leveled every banked .tests.csproj because only a -tests run regenerates them – and the hand-leveling approximated the emission short by five properties (the conditioned TargetFramework fallback, Authors, the Company chain, Copyright, RepositoryType, ApplicationIcon). The union gate’s banking sweeps proved it: text/template and html/template regenerated theirs to the full ratified shape, the sweep reported CONTENT drift, and both banked at the gate after the sweep itself had built and passed with them.

Standing classification: a .tests.csproj drift of exactly this shape at any future sweep of a banked package is the SAME catch-up – bank the regenerated file (the converter’s emission is the authoritative shape), one per package as sweeps touch them. A single scripted leveling pass over the remaining hand-leveled files is queued as a bounded item for whoever wants the drift gone in one commit; until then this note is the classification, not a mystery.

✅ SHIPPED — TestBogoSuite’s pin is host-conditional; the second accepted shape is DISCLOSED, and the guard is proven on fixtures because the host cannot be made to produce it (2026-08-20, lane claude/bogo-host-conditional)

Implements the ruling immediately above, nothing broader. The annotation is one optional manifest field, the oracle gains one accepted status pair, and the proof page learns to say so.

The schema: the marker IS the sentence

testDisclosure gains hostConditional — a string whose non-empty value is simultaneously the marker and the one sentence naming the environmental dependency. Two fields would have allowed a row marked without saying what it depends on, which is the one thing the ruling requires of an annotated row; one field makes that unrepresentable. A blank-but-present value is a load-time error, on the same footing loadTestDisclosures already puts an empty signature: marking a row widens the oracle by a whole status pair, and a manifest that widens it while naming nothing is exactly the “a broken disclosure must not widen the oracle” case. schemaVersion stays at 1 — the field is optional and no reader validates the version.

crypto/tls’s TestBogoSuite is the first and only member.

The oracle: one more accepted pair, and the flood exclusion it drags with it

Two edits in matchTerminalStatuses, and the second is the one nobody would predict from the ruling’s text.

Site Before After
the classification loop Go fail / C# fail never reaches the divergence arm at all — the statuses are EQUAL, so the row falls through to the bottom and counts as an ordinary agreed failure intercepted BEFORE the equal-status arm: disclosed when the signature still matches, a strict mismatch under the first shape’s own wording when it does not
disclosureRoots admits a root only on Go pass / C# fail also admits an ANNOTATED root on Go fail / C# fail — the C# signature pin is hoisted out and applies to both

The second is load-bearing and easy to miss: shape (b) is precisely the shape in which the Go side DID reach its case fan-out, so an annotated root that does not root leaves TestBogoSuite’s 3,243 Go-only case rows to land as one-sided mismatches — a comparison drowned in rows that say nothing about the converted code, on a run where the C# side never moved. The existing withdrawal rule already does the right thing once the root is admitted; nothing else in it changed, and a C#-side row that EXISTS under an annotated root still compares strictly.

What still fires, both directions. The C# side is pinned by signature in BOTH shapes, so a failure that moved is a named mismatch, not a quiet reclassification. A converted side that starts PASSING leaves the disclosed set, the count moves, and the sweep’s disclosed count moved check fires — which is the self-retirement path this row’s own disclosure text already promises. An UNANNOTATED row gains no second shape at all.

The proof page: the totals line would have broken at the evidence layer

proofDisclosedNames derives its set from verdict disagreement — which is exactly what shape (b) does not produce. Left alone, a page regenerated on a shape-(b) host reads 401 matched · 1 disclosed where the roster banks 400 + 2, and renders the row as a plain matched fail | fail: the host-stable arithmetic the ruling asks for, broken one layer below the sweep. The renderer now reads the annotation back from the manifest, which is safe precisely because it was reached: a fail/fail annotated row whose C# output missed the pin is a mismatch, and a mismatch never validates, so no page is written.

The row’s note is rendered from the manifest, not hand-written into the page — a hand edit does not survive a regeneration, and the note must never drift from the annotation the oracle actually applies. It follows the internal/zstd model: name the dependency, then name both accepted shapes.

Proof — and why three of the four guards can only be fixtures

Shape (b) cannot be forced on a host whose Go BoGo run passes, and this host’s does. The fixtures ARE the proof for it, and each was proven failing-first by neutering the half it guards:

Guard Neutered What fired
TestHostConditionalDisclosureAccountsInBothShapes the classification arm disclosed: [TestPinned] — 1 where the row banks 2, the 401 + 1 arithmetic in fixture form
TestHostConditionalRootWithdrawsGoOnlyDescendantsWhenGoFails the root admission the flood, verbatim: three Go-only children as one-sided mismatches
TestHostConditionalRowRendersDisclosedWhenBothSidesFail the page’s manifest read-back the totals line lost the disclosed row
TestHostConditionalMarkerMustNameItsDependency the loader’s blank check a marker naming nothing loaded silently

Each also carries its own controls, so the tolerance cannot spread: an unannotated agree-fail row stays an ordinary matched agreement, an unannotated root floods (that IS the control), a moved C# signature is a mismatch, a C# side that starts passing leaves the set, and an unannotated disclosure renders no note.

Gates

Converter go test ./... ok, 285.086 s, exit 0 — 387 tests, zero failures, including projitemsIntegrity_test (the four new guards extend the existing disclosedParentAggregation_test.go, so there is no new .go file to register) and TestStdLibMetadataInSync.

Live crypto/tls sweep on this host (laptop R, Ryzen 7 PRO 6850U): PASS 400, 1 pass / 0 fail, 991 s. This host produced shape (a)TestBogoSuite Go pass / C# fail — which is the regression half of the proof: the pinned divergence still accounts exactly as banked with the new arm in the loop. The comparison record reads 402 Go rows / 402 C# rows, 2 disclosed, 3,242 withdrawn, 0 errors, so 402 − 2 = 400 matched, unmoved.

The regenerated proof page’s diff is the provenance line and the note, nothing else — no verdict row moved, the totals line still reads 400 matched · 2 disclosed, and the withdrawal line still reads 3,242. Corpus dirt from the sweep classified per the standing rule and restored: six *_test.cs CRLF phantoms (handshake_client, handshake_server, handshake, key_schedule, link, tls), each proven CR-stripped-equal to HEAD byte for byte rather than trusted to an empty --numstat. No production .cs mover, no .csproj change, no initᴛᴛtests hook. The sweep’s own drift report named exactly one content change, this lane’s manifest edit.

check-no-regression.ps1 over all 629 behavioral packages: 1 changed file, 0 NOT MEASURED, 0 advisory warnings; preflight solution integrity 631/631, path casing 4,534/4,534. The one changed file is not this lane’s and not a converter change — it is ReflectChanDirection/ReflectChanDirection.csproj, a UNION CARRY: claude/cargo-recv added that guard from a fork that predates claude/preflight-trio’s csproj-metadata leveling, so the committed file still spells <TargetFramework>net9.0</TargetFramework> unconditioned, a literal <Company> and <Copyright>, and no <RepositoryType>. Censused at HEAD, that is exactly one file of 629 — the other 628 already carry the leveled form — so it is a stale bank, not an emission change, and it reproduces at the union base untouched by anything here. RESTORED per the foreign-mover rule and left for its owner: one re-transpile of that package levels it and takes CNR green.

This lane’s own change cannot reach the emission path at all — loadTestDisclosures, matchTerminalStatuses, proofDisclosedNames and renderValidationProofPage are compare/proof-only and no caller of any of them writes a .cs or .csproj — but the gate is run rather than argued.

What this lane deliberately did NOT do

The ruling’s closing caveat — “until it lands, a crypto/tls sweep on a host whose Go BoGo run fails is a KNOWN false red” — is now historical. Such a host reads 400 + 2 like every other.

CORRECTION – the .tests.csproj leveling-pass queue item retires EMPTY: the census is zero (coordinator, 2026-08-20)

The note two sections up predicted the remaining hand-leveled .tests.csproj would drift one sweep at a time and queued a scripted leveling pass. Measured, the population does not exist: git grep -l "<Company>The go2cs Authors</Company>" -- 'src/core/**/*.tests.csproj' returns 0 of 157 at master – independently measured on laptop R at the same base, which is what caught the over-prediction. The preflight-trio hand-leveling was complete and exact across every file it touched; the only two short-form files in the corpus were the template pair, created along the cargo-recv fork line BEFORE the template change existed, and both banked at the union gate. The standing classification stays (a .tests.csproj emitted by a pre-template fork catches up at its sweep – the shape to expect from any lane that forked early), but the queued pass is withdrawn: there is nothing to level.

CLOSED (2026-08-20, lane claude/tests-csproj-stale-badge) – the stale-validation-badge PIPELINE gap, and the residual the gate does not close

The class named above (“The stale-validation-badge class is a PIPELINE gap, not four accidents”) is closed at the gate half. writeReadmeFile’s options.convertStdLib gate is now the named predicate emitsPackageReadme = options.convertStdLib || rewriteOfCorePackage(projectFileName, options) – the SAME widening validationPackBlock’s gate already took, against the same predicate, for the same reason. Because rewriteOfCorePackage tests the OUTPUT LOCATION (under the runtime root’s core\ tree) rather than the invocation mode, a corpus package levels its README on ANY regeneration – -tests, single-package, -stdlib alike – while the litter rule the gate exists for is preserved structurally: no fixture, example, -recurse output or end-user output path can satisfy it. Guarded by TestPackageReadmeEmissionFollowsPackageProvenanceNotRunMode, both arms proven failing-first by neutering each separately.

The measurement that matters most is a byte-identity, not a refresh. Three banked packages (hash/adler32, crypto/rc4, unicode/utf8) had their badges deliberately staled, then were run through -tests -test-action convert: with the pre-fix binary all three stayed stale (the defect reproduces); with the fix all three returned to their committed bytes exactly. The committed READMEs were emitted by -stdlib, so byte-identity is the proof that the -tests emission AGREES with the -stdlib emission – this opens no new standing-restore family, which was the live risk in widening the gate at all. A full -test-action all on hash/adler32 then validated 2 of 2 with git status clean across src/core AND docs – zero dirt, zero .tests.csproj drift – and a filtered -stdlib over the same three moved nothing but the six root attribution files at an EMPTY numstat (the documented CRLF phantoms).

The residual: the board’s two formulations are NOT equivalent

The item offered them as alternatives – “let a -tests conversion that WROTE a proof page also refresh that package’s README, or equivalently drop the convertStdLib gate to ‘stdlib package’ rather than ‘stdlib run’”. They are not equivalent, and the difference is an ordering the gate cannot reach. The README is composed during CONVERSION; the proof page it reads is written at the END of the COMPARE (emitValidationProofPage, testConversion.go). So within one -test-action all the README is always built from the proof page as it stood BEFORE that run.

Consequence, measured by moving docs/validation/current/hash.adler32.md aside to simulate a fresh bank and running one full all: the run validates 2 of 2 and writes a green page, and the README it emitted in the same run reads Tests-not_yet_validated-orange beside its own green proof page. The next conversion of that package levels it (verified: back to green, byte-identical to committed).

So: a package whose counts are UNCHANGED is now self-leveling, which is the common case and the one that was silently rotting. A package whose counts CHANGE – every fresh bank, and every rebank that moves a number – is still one run behind, and now visibly self-contradictory rather than merely stale. A bank still owes one extra conversion of its own package after the pipeline; that is cheaper than the whole-stdlib reconvert it replaces, but it is not nothing.

Closing it needs the OTHER formulation – re-emit the README after the compare has written the page. That is a second emission point, not a gate change, and it carries a real hazard that is why this lane did not take it uninstructed: -test-action compare does NOT convert, so the converter’s package globals (packageDoc, packageSourceDir) are empty on that path, and naively re-calling writeReadmeFile there would emit a README with no doc body – a destructive, corpus-wide rewrite that would read as ordinary reconvert drift. Whoever takes it should either restrict the re-emission to actions that actually converted (convert/all), or refresh only the badge LINE in the existing file rather than re-rendering the document.

crypto/internal/edwards25519 CANNOT bank from any SIGNED-OFF stage of the ж-box design — its 98 objects are receiver-position and class-3b traffic, and Phase A’s mechanism has no constituency on the row’s path (2026-08-20, lane claude/zh-box-reduction-impl)

Briefed as the ж-box arc’s implementation lane with crypto/internal/edwards25519 54/55 → 55 as the banking target. It banks nothing, and the reason is not a shortfall in the mechanism’s tuning: the target row is not reachable by anything DESIGN-zh-box-reduction.md has signed off, and that is a structural property of the row’s call path. The crypto/tls canary the brief asked for ran on BOTH sides of the bogo merge and is reported below as a pair.

The base — the hard check failed, then passed mid-lane

b4d999a9c was absent when the lane opened: origin/claude/union-157 was dde5d53b5, carrying 606e41abb (the ruling half) but not 2e7fc3225 (the machinery half), which sat unmerged on claude/bogo-host-conditional. It landed during the lane; the branch is re-based to bd79cb16e and the hard check passes. The interval was not wasted — it bought the pre-machinery control arm that makes the canary record below a genuine A/B rather than a single reading.

The finding — the row’s bill is B′ and class 3b, both outside this design’s sign-off

Measured, this host, bd79cb16e (-tests -test-action all -test-timeout 15m, explicit -go2cspath): crypto/internal/edwards25519 is 54 matched / 1 divergent of 55, the sole divergence TestAllocations: Go="pass" C#="fail", reading 98 objects/run (9,800 counted over 100 runs; 1,324,800 B → 13,248 B/run) against want 0. That reproduces the 2026-08-19 edwards25519-a figure to the object: the row has not moved, and it is stable, not drifting.

The reason is visible in the emitted sources, not inferred. The test body is

p := NewIdentityPoint(); p.Add(p, NewGeneratorPoint()); s := NewScalar()
testAllocationsSink ^= s.Bytes()[0]; testAllocationsSink ^= p.Bytes()[0]

and every allocation site on that path is one of three things, none of them Phase A’s:

Allocation-site counts on the executed path, from the committed .cs: projP1xP1.Add 16, Element.Invert 9, Point.bytes 6, projCached.FromP3 4, Point.fromP1xP1 4, Scalar.bytes 2, plus the five @new<T>() and checkInitialized’s variadic params-array — the right magnitude for 98, and not one of them a lowerable unexported package-level function’s pointer parameter.

This is the A3 measure’s own fact read from the other side, and nobody had read it onto this row: nistec fell −96.5 % because its bill is fiat leaf functions; edwards25519’s bill is point-arithmetic methods. §3.7 is explicit that B′’s constituency is receiver-chain traffic and that B′ “needs its own design increment and its own measurement, and it is NOT part of this sign-off”; §9’s plan ends at B1, which §4 states is “count-neutral except one row” (the &s[i] element-ref) and byte-significant. No signed-off stage moves this row, which is why A2/A3 moved it 109 → 98 and then nothing.

What the row would actually take — stated so it can be ruled, not chased

  1. B′ (method dual emission) — a ref-receiver/ref-parameter overload beside the ж method for direct calls on a statically-known receiver. The row’s dominant term, and a design increment rather than an implementation detail.
  2. Class 3b — the five @new<T>() per run. Phase C.
  3. Residue: checkInitialized’s variadic params-array; Bytesarray<byte>(32) backing.

A want-ZERO row needs all three. The near-budget ruling’s logic applies unchanged — 98-vs-0 is an optimization target, not an impossibility — so the row stays undisclosed; nothing here proposes otherwise.

The crypto/tls canary — run on BOTH sides of the merge, and the machinery is INERT on shape (a)

The brief predicted this host needs the machinery to read 400 + 2, and that this lane’s run would be the first LIVE shape-(b) proof. Neither held — reported as asked, either way.

  base wall sweep TestBogoSuite Go / C# children matched
pre-machinery dde5d53b5 (no 2e7fc3225) 795 s PASS 400 pass / fail 3,242 true
post-machinery bd79cb16e 902 s PASS 400 pass / fail 3,242 true

Both runs are shape (a) — the ordinary pinned divergence, which the unannotated disclosure already satisfied and whose Go-only children disclosureRoots already absorbed. Go’s BoGo baseline reached completion AND expanded (3,242 TestBogoSuite/… children) on both. The roster row is crypto/tls | 400 | 2, and the sweep’s matched count plus its disclosed-count check read 400 + 2 on both sides of the merge — host-stable, as the ruling requires.

What that pair does and does not prove:

Standing post-run dirt, classified and RESTORED (nothing banked from it)

What this lane did NOT do, stated plainly

For the coordinator — the decision this lane hands back

Rule the edwards25519 row’s owner; it is not the signed-off ж-box arc. Either commission B′ as its own design increment — the row’s dominant term, and §3.7 already names a constituency far beyond it (runtime/proc 387 sites × 3 GOOS, h2_bundle 212, database/sql 154, os’s of() chains), so the increment pays well past this one row — or move edwards25519 off the 75 %-terminal path and pick a fourth candidate whose residual is reachable from a signed-off stage. The 98-vs-0 count stays undisclosed either way.

RULING – B’ is COMMISSIONED as a 1.23.12-era design increment; edwards25519 moves OFF the 75% path; flag takes the fifth slot (coordinator, 2026-08-20)

The zh-box lane’s finding two sections up is ratified in full: no signed-off stage of DESIGN-zh-box-reduction.md reaches crypto/internal/edwards25519’s want-zero row, the 98-vs-0 residual stays UNDISCLOSED (it is an optimization target, not an impossibility – disclosing it would launder exactly what the host-limit bar forbids), and the row is stable at 54/55.

B’ (method dual emission) is commissioned as its own design increment with its own measurement – scheduled for the 1.23.12 era, NOT before the terminal. Its constituency is where SS3.7 priced it: runtime/proc 387 sites x 3 GOOS, h2_bundle 212, database/sql 154 – the netpoll, http and database arcs the campaign continues into after the terminal, on the corpus where every row re-derives anyway (the H10 economics). Spending it now to reach one .1 row would buy the row twice. edwards25519 banks when B’ and Phase C land, and it will be the natural first measure of both.

The terminal path therefore re-ranks. After encoding/gob (#158, in flight): net/netip (#159, model-selection arc, no ruling needed) -> sync/atomic (#160, at 104/108, pending the alignment/pointer-identity ruling) -> runtime/debug (#161, pending the introspection ruling) -> flag (#162) – ROOTED-NOT-TAKEN above at CS1929 x10, the Delta-rename and the adapter disagreeing about one method name, owner ImplementGenerator. If any of the three pending rows falls through its ruling, the replacement comes from the ranked near-miss queue, not from re-litigating this one.

Also ratified from the same lane: shape (b) of the host-conditional pin is an OPPORTUNISTIC capture. Standing instruction, no lane required: whoever meets a red crypto/tls whose C# side is unmoved keeps that run’s go2cs_test_comparison.json before restoring past it.

⛔ MAP KEY/ELEM DIMS ARE DESCRIPTOR CARGO and both array<T>-length rows retire — but encoding/gob reaches 105 of 106 and does NOT bank, because reflect.ArrayOf/StructOf is not disclosable (2026-08-20, lane claude/map-key-elem-cargo, laptop G)

The queue top, taken on the recipe the cargo-recv entry wrote for it. The cargo lands, the array<T> LENGTH class retires whole, and the retirement test applied mechanically says the row still does not bank: 103 → 105 of 106, one residual the remedy does not clear and no class it belongs to.

The recorded need was half the shape, and MEASUREMENT supplied the other half

The board recorded gob’s need as “map key/elem dims cargo” and located it exactly — T1.Marr map[[2]string][2]*float64, decodeMap allocating from mtyp.Key()/mtyp.Elem(), a map descriptor carrying no dims. That was right, and it was two of the four positions the arc needed. The other two came from running the pipeline rather than from reading it:

Position Found by What it is
a map field’s KEY dims the recorded need Key() had no cargo slot at all
a map field’s ELEM dims the recorded need Elem() had a slot; a map never filled it
a POINTER-chain field’s dims the FIRST run T1.N *[3]float64, Indirect.A ***[3]int — same root, unrecorded
the pointer hop on the VALUE side the SECOND run Value.Elem() read dims from the live value only

The unifying statement is one sentence, and it is what the arc actually is: a struct FIELD’s dims come from its zero instance, which reaches an array the field IS and nothing an array is BEHIND. FieldArrayDims reads = new(N) back off Activator.CreateInstance(declaringType); on a pointer field that instance holds a nil pointer with no pointee to measure, and on a map field a nil map whose key and element types no entry could reveal. Both hops are ordinary at a decode target, which is exactly a struct nothing has populated yet — so the datum has to be in the emitted C#, which is the conclusion the func-PARAMETER position reached and takes the same carrier.

The design fits the GoArrayDims precedent because the slots are named for ACCESSORS, not for kinds

Cargo slot Attribute Carries Handed down by
abi.Type.arrayDims [GoArrayDims] an ARRAY’s own dims, a POINTER’s pointee’s, a MAP’s element’s Elem() — tail for an array, unshifted for a pointer and now a map
abi.Type.keyDims [GoMapKeyDims] a MAP’s key’s dims Key()

Nothing about arrayDims changed meaning. Its cross-kind meaning was already “what Elem() hands down” — that is why a POINTER carries its pointee’s unshifted — so a MAP simply joined the arm it already had, and the one accessor with no slot got one. keyDims joins both interning keys (abi.descriptorDimsKey, shared with reflect’s canonType) for the third time and the third instance of one reason: map[[2]string]V and map[[3]string]V are distinct Go types over one managed map<array<@string>, V>.

type T1 struct {                              // TestEndToEnd
    Marr map[[2]string][2]*float64
    N    *[3]float64
}
type Indirect struct{ A ***[3]int }           // TestIndirectSliceMapArray
[GoArrayDims(2), GoMapKeyDims(2)] public map<array<@string>, array<ж<float64>>> Marr;
[GoArrayDims(3)]                  public ж<array<float64>> N;
[GoArrayDims(3)]                  public ж<ж<ж<array<nint>>>> A;   // ONE stamp, any depth

The fourth position was found by MEASUREMENT again, and it is the same lesson one layer over

With the stamps in, TestEndToEnd passed and TestIndirectSliceMapArray’s root moved one frame — out of gob: wrong type (***[]int) for received field Direct.A and into panic: reflect: reflect.Set using unaddressable value, inside growSlice. reflect.Value.Elem() recovered a pointee’s dims from the LIVE value alone, while rtype.Elem hands the descriptor’s cargo down unshifted. gob’s decIndirect walks a ***[3]int target by allocating each level from value.Type().Elem(), so a hop reading the live value reads the nil pointer it is standing on, allocates a zero-length array from the dimension-less descriptor, and the next hop measures that zero as the truth. Value.Elem() now prefers the carried dims, falls back to the live measurement (ValueOf(&[100]T{}).Elem().Type() still carries 100), and descends them through EVERY pointer hop — not only the one whose pointee is the array, because ***[3]int’s intermediate pointees are pointers.

That is cargo-recv’s fourth-position lesson restated at the Value layer: a value the bridge hands out must describe itself the way the descriptor does. Both arcs found their last position by running the pipeline, not by reasoning about it, and in both cases the shape “looked covered”.

The retirement test, applied mechanically — and the row does NOT bank

Row Before After Root
TestEndToEnd gob: length mismatch in decodeArray PASS map key/elem dims
TestIndirectSliceMapArray wrong type ([3]int) for received field Indirect.A PASS pointer-chain field dims + the Value-side hop
TestIgnoreDepthLimit typelinks: external … not implemented unchanged reflect.ArrayOftypesByString → the typelinks() stub

The array<T> LENGTH class retires: both of its rows pass, and the census the reflect-minibridge entry left has one line in it instead of two. The third row is not disclosable, and the bar rules it out by name. TestIgnoreDepthLimit builds a 101-deep nested array with reflect.ArrayOf and wraps it in reflect.StructOf — runtime TYPE CONSTRUCTION, which the managed runtime could in principle perform. host-limit’s text is explicit that an entry “must name a structural property of the deployment shape, never an unimplemented-but-fixable defect”, and the three allocation/liveness classes do not reach it at all; the fifth, chan-direction, retired this morning and was the only representation class there has ever been. So there is no class to disclose under, the roster’s own rule stands (“a package that almost passes never appears”), and encoding/gob stays off it at 105 of 106.

What banks is the converter arc, not the row. That is the honest split the brief asked for, and it is worth more than the row would have been: the cargo is corpus-wide machinery, the two rows it retired were the board’s oldest standing reflect-bridge chip, and what remains is a single named feature rather than a class.

Boundaries, stated rather than discovered later

The corpus footprint is SIX LINES, and the boundary rules are why

Seeded whole-stdlib reconvert — fresh root, single run, -stdlib -comments, 304 packages, 380 s, converter exit 0 — with version.props and docs/validation seeded beside core so the README badges compose. Marker gate 63 marked / 0 clobbered, path-precise and line-anchored (git grep -lE '^\s*\[module:\s*(go\.)?GoManualConversion\]', whole files, re-measured not carried). Of 1,663 emitted artifacts: 0 new, 0 absent, 13 differ CR-insensitively, and the split is clean.

Class Count What
this arc 5 files / 6 lines, every one a stamp, ZERO off-shape compress/flate/inflate.cs (bits *[maxNumLit+maxNumDist]int → 316; codebits *[numCodes]int → 19) · internal/fuzz/fuzz.cs (hashes map[[sha256.Size]byte]bool → key 32) · net/http/h2_bundle.cs (pings map[[8]byte]chan struct{} → key 8) · runtime/symtabinl.cs (inlTree *[1 << 20]inlinedCall → 1048576) · hpack/huffman.cs (children *[256]*node → 256)
pre-existing carries 8 gcimporter’s multi-result any-slot arm, plus the runtime box-compare family (alg, map, map_fast32, map_fast64, mbarrier, traceback, pprof/map) — exactly the set cargo-recv named and RESTORED, minus the two per-GOOS members a windows-only reconvert cannot see

Zero unclassified. Only this arc’s five are banked; the eight carries are restored and belong to a leveling regen. That the whole footprint is six lines is the boundary rules working rather than luck: an ARRAY field keeps its = new(N) initializer, so the thousands of ordinary array fields in the corpus do not move, and every Go constant behind a stamp (maxNumLit + maxNumDist, sha256.Size, 1 << 20) is resolved by go/types at conversion time.

No package_info.cs record moved, so no go generate is owed — and that is asserted by TestStdLibMetadataInSync passing, not inferred from the absence of a diff.

One UNION artifact, rooted rather than assumed — and it is the merge-time rule again

CNR flagged ReflectChanDirection.csproj as a mover, which is not this arc’s shape at all (the diff is csproj metadata: TargetFramework hoisted to a condition, Company/Copyright derived, RepositoryType added). It is the union’s own artifact: 0efa5f79f (preflight-trio, which levelled the csproj template) and 22940de2f (cargo-recv, which ADDED that project) are SIBLING branches — neither is an ancestor of the other — so each lane’s CNR was green on its own tree and the merged tree is the first place a project emitted by the OLD template meets the NEW one.

Proven rather than argued: the union-base converter was rebuilt from git archive origin/claude/union-157 and re-emitted that one package, reproducing a byte-identical mover with none of this change present. Levelled here, because it is one file and every future CNR sees it otherwise. Worth carrying as the golden-side twin of the banked-row merge rule: a lane’s CNR proof binds its own tree, and a merge can make a NEW project’s committed emission stale without either side changing.

The residual, decomposed — it is two items and only one of them is a wall

TestIgnoreDepthLimit needs reflect.ArrayOf and reflect.StructOf, and this arc changed the price of the first:

Implementing ArrayOf alone does not flip the row — the test builds its 101-deep array and then wraps it in a StructOf — so the honest statement is that gob’s last verdict is priced at one small hand-own plus one feature, not at “the typelinks stub”.

Gates

⚠ Every wall-clock figure below was measured with a sibling lane active on the same machine (a zh-box run out of the go-netpoll-managed-poller worktree, confirmed by parentage rather than assumed) — so they are loaded numbers, at the top of the CLAUDE.md ranges, and should not be used to re-baseline the budget table.

Converter go test ./... ok, 146.0 s, exit 0projitemsIntegrity_test included, so both new .go files are registered; TestStdLibMetadataInSync passes, which is what ASSERTS that no package_info.cs record moved rather than inferring it from a clean diff, so no go generate is owed.

GolibTests 176 / 176 passed, 0 failed — including the new GoStructLayoutTests.FieldDims_ComeFromTheConverterStamp_WhereNoZeroInstanceCanMeasureThem.

Full check-no-regression.ps1: generated .cs and .csproj byte-identical across all 630 behavioral packages, 0 NOT MEASURED, 2 advisory converter warnings, exit 0, 769 s; preflight solution integrity 632 / 632 (631 + this arc’s guard), path casing 4,541 / 4,541 (4,534 + the guard’s 7 tracked files). Run at the COMMITTED tree, after the golden re-baseline — the first pass had caught a stale main.cs.target, because UpdateTestTargets had run before the guard gained its decIndirect walk.

Solution builds, both --no-incremental: go2cs-stdlib.slnx -p:GoTargetOS=windows 0 errors, 763 s (307 projects — this is what compiles all five corpus movers plus the abi/reflect changes); go2cs.slnx Debug 0 errors, 1,130 s — the gate no other harness covers, and this arc changes golib’s attribute surface and GoReflect.GoFieldInfo.

No linux build is owed here, and that is checked rather than assumed: every file this arc touches is FLAT — compress/flate/inflate.cs, internal/fuzz/fuzz.cs, net/http/h2_bundle.cs, runtime/symtabinl.cs, hpack/huffman.cs, internal/abi/type_impl.cs, reflect/value_impl.cs, golib — none sits in a per-GOOS folder, so the default windows build compiles every one of them. (The L3 lesson that a net/linux/-resident change needs its own target is real; it just does not apply to this footprint.)

Full behavioral suite PASS at 603 projects, 2,056.8 s — Transpile 603/603, Compile 603/603, Target 603/603 byte-identical, Output 577 compared / 0 failed (26 skipped, no package main), 0 timeouts / 0 NOT MEASURED. Not optional for this arc, and CNR does not substitute for it: CNR is transpile-only, while what changed here is RUNTIME — GoReflect.GoFields, which every %v walk and every json/xml member order reads, plus three reflect accessors and Value.Elem.

encoding/gob pipeline, -tests -test-action all -test-timeout 20m, three runs: 103 → 104 → 105 of 106, each root re-measured rather than predicted.

Canaries — the five largest banked reflect consumers BY VERDICT COUNT, recomputed from the roster at gate time per the doctrine. The derivation is also CHECKED rather than pattern-matched: the three larger rows are skipped because they are not reflect consumers, and that is measured — go/doc/comment (10,059), crypto/rsa (559) and internal/zstd (536) use reflect in zero of their converted test files, against 1–5 for each row below.

Canary Expected Result
go/internal/gcimporter 583 PASS (564 s)
go/types 557 PASS (193 s)
encoding/json 491 PASS (84 s)
encoding/xml 386 PASS (104 s)
crypto/tls 400 FAIL — the KNOWN false red; verified against the ruling, not assumed

There is no own-row sweep, because there is no own row: the encoding/gob pipeline at 105 of 106 is this lane’s measurement and it stands in that slot.

crypto/tls — the host-conditional ruling’s first live exercise, and it holds exactly

The coordinator’s ruling (same board, this morning) requires the C# verdict map to be verified unmoved against the committed proof page before a red here is dismissed. Done mechanically, parsing the proof page’s Verdicts table and the run’s own go2cs_test_comparison.json:

Check Result
C#-side differences vs the committed proof page 0 — all 402 banked rows present, every verdict identical
banked rows absent from the live C# map 0
Go-side differences 1TestBogoSuite, banked pass → live fail
TestBogoSuite/… subtest rows go 3,242, C# 0 — the annotated row’s own baseline expansion, excluded from one-sided accounting by the ruling
one-sided rows OUTSIDE that expansion 0
language divergences outside that expansion 1TestCertCache (go pass, C# fail), the banked codegen-liveness disclosure, unchanged

So the row’s live shape is Go fail / C# fail — agreement on a host where the Go premise fails, the second of the two shapes the adopted annotation accepts — and the C# side is provably untouched by this arc. Nothing in a go2cs branch can move it: the Go side is go test -json over GOROOT’s own sources with the Go toolchain. (cargo-recv measured 3,243 expansion rows this morning against this run’s 3,242 — the count varying with how far the runner gets before its own deadline is exactly the nondeterminism the ruling names.) The annotation implementation is R’s lane and is not taken here.

Post-sweep drift: 32 files, every one classified, all RESTORED

Twenty-eight are pure CRLF phantoms — modified with an EMPTY --numstat, the multi-line-literal class. The four with real hunks are all named classes and none is this arc’s:

File Hunks Class
encoding/xml/package_init.cs +7 the initᴛᴛtests hook — the -tests-closure shape named 2026-08-17
go/types/package_init.cs +7 same
go/internal/gcimporter/gcimporter.cs 1/1 the pre-existing multi-result any-slot carry, the same one the corpus census found
go/types/package_test_info.cs −3 three GoImplicitConv records the converter no longer emits — a stale banked test artifact, PROVEN pre-existing by re-converting go/types’ tests with the union-base converter, which drops the identical three

That last one is the same shape cargo-recv recorded for crypto/tls’s committed test sources, and it takes the same treatment: restored, recorded, and NOT levelled inside another lane’s arc.

The queue, after this

  1. encoding/gob (106) is still the closest unbanked package, but its price is now NAMED rather than a class. 105 of 106; the last verdict costs one small hand-own (reflect.ArrayOf, roughly free over the dims cargo) plus reflect.StructOf, which is runtime struct synthesis over System.Reflection.Emit and a feature arc in its own right. Whoever wants a bank sooner should measure the next candidate rather than take this one — this lane did not, and no claim about “the next closest” is made here on evidence it does not have.
  2. reflect.ArrayOf as a standalone hand-own. Worth doing on its own merits even though it does not flip gob’s row: ArrayOf is reachable from user code, the auto form dies in the typelinks stub for a reason the managed bridge never needs (it is reconstructing the linker’s arrayType record), and the dims cargo now makes the honest implementation a one-liner.
  3. The stale-validation-badge PIPELINE gap, unchanged and unclaimed since cargo-recv recorded it: writeReadmeFile is gated to options.convertStdLib, so a -tests run writes a proof page and never refreshes the README beside it, and every bank owes the same manual reconvert. One line of gating, wants its own emission gate.
  4. sync/atomic’s zero-size-field layout, 5. %#x of a uintptr, 6. the two variadic-argument walls (a SPREAD argument to a deferred variadic call; an empty variadic call passing an empty slice where Go passes nil) — all unchanged.

The array<T> LENGTH class leaves the standing-chip census; nothing joins it.

RULING x2 – sync/atomic’s alignment row closes as a FIX (the token becomes layout-truthful), and runtime-capability is minted as the fifth class with WriteHeapDump as its only member (coordinator, 2026-08-20)

Both held rulings, delivered together because they are the same charter question worked from opposite ends: when a managed runtime cannot hand a test what it asks for, is that a disclosure or a defect? The bar stays where host-limit put it – a disclosure names what is provably unsatisfiable, never what is unimplemented – and each ruling below is that bar applied once.

A. TestAutoAligned64’s pointer half: the identity token gains ALIGNMENT-TRUTHFUL construction. No fifth class; the row closes as a fix.

The row-harvest-3 rooting is accepted: Value.Pointer() is reflectPointerToken, an identity token, not an address. But the assertion’s semantic content is Go’s align64 GUARANTEE – a layout invariant the model genuinely honors (the atomics ARE atomic, and StructField.Offset already answers from the memoized GoFieldOffsets walk). A token whose LOW BITS mirror the Go-computed layout – mint bases 8-aligned, derive a field’s token as base plus its Go field offset – answers p & 7 from the SAME metadata that answers Offset: truth read from layout metadata, not a memory property fabricated. This is compatible with FINDING-managed-box-uintptr-lifetime as written: the token stays non-dereferenceable and lifetime-free; it merely stops carrying accidental low-bit noise that contradicts an invariant Go states and the model keeps. The lane that takes it owns the construction details and must measure token-distinctness is preserved.

Ratified alongside, from the same rooting: the hammer family (TestHammer32/64, TestHammerStoreLoad) is the zero-size-field LAYOUT EMISSION arc – explicit layout with Go-computed offsets for structs containing zero-size fields; Reinterpret’s size guard is correct and must not be relaxed. And RecordGoroutinePanic’s 693 MB serialization death is a bounded harness-robustness fix (cap the record), queued – it costs 72 verdicts on hosts where containment loses the timing race, which is a false host-dependence the harness owes nobody.

sync/atomic’s full price to bank: the token construction (1 row) + the layout arc (3 rows).

B. runtime/debug: the fifth class exists, and it admits ONE of the three capability roots.

runtime-capability – a test exercises a runtime facility whose output or behavior is DEFINED over the replaced runtime’s own internals (type descriptors, heap layout, GC bookkeeping), such that any managed rendering would be fabrication rather than implementation. The admission test is one question: does a truthful managed implementation of the asserted behavior exist at any cost? If yes, it is an arc with a price, never a disclosure.

Applied to the three roots the runtime-debug lane ranked:

The class joins the roster preamble WITH the first banking commit that uses it (exactly as chan-direction did), not before. runtime/debug’s full price to bank: the position-map arc (multi-consumer – log, log/slog, flag and this package all pin it) + the ReadMemStats design + the 3-row disclosure. It HOLDS its slot on the terminal path at that price; if the recorder design overruns, the substitute comes from the ranked queue via the measurement pass, not from re-litigating the class.

CLOSED (2026-08-20, lane claude/readme-rc4-test-regen) – the badge residual takes the OTHER formulation, and the -tests record drop it surfaced is rooted

Two items, both from the stale-badge lane’s own findings.

1. The one-run-behind residual closes at a SECOND emission point

The gate half (emitsPackageReadme) could not reach this and said so: the README is composed during CONVERSION while the proof page its Tests badge reads is written at the END of the COMPARE, so a package whose counts CHANGE – every fresh bank – emitted one run behind. refreshPackageReadmeAfterProof now re-emits the README immediately after emitValidationProofPage writes the page.

The hazard the write-up named is closed by CONSTRUCTION, not by a mode check. -test-action build|run|compare do not convert, so packageDoc/packageSourceDir are empty on those paths and a naive re-emission would write a doc-less README corpus-wide. The refresh therefore does not read those globals at all: the conversion-time write records what it composed the README from (packageReadmeEmission), and the refresh runs from that record or not at all – no conversion, no record, no write. The record is taken INSIDE the emitsPackageReadme gate, so one decision governs both emission points, and the refresh additionally requires the record to name the package being compared (a record cannot level someone else’s README).

Measured, the gate half’s own demonstration INVERTED. docs/validation/current/hash.adler32.md moved aside to simulate a fresh bank; ONE -test-action all:

Guards extend the gate half’s family: TestPackageReadmeRefreshFollowsInProcessConversionNotRunMode beside TestPackageReadmeEmissionFollowsPackageProvenanceNotRunMode. Both arms proven failing-first – with the refresh removed the residual reproduces; with the NAIVE formulation (re-emit from the converter globals, no record) every hazard assertion fires at once, including the doc-less rewrite and the compare-only write.

Standing note for sweep readers. writeReadmeFile is idempotent, so an unchanged package rewrites nothing and this opens no standing-restore family. The corollary: a README.md moving during a sweep now means that package’s validation counts moved. Read it as a finding, not as dirt.

2. The -tests package_test_info.cs regen drop is e61758549, one notch too wide

Reproduced on both cases. A -tests regen of crypto/rc4 dropped TWO records – [assembly: GoImplicitConv<...rc4_package.Cipher, ж<...rc4_package.Cipher>>(Indirect = true)] and using testing = go.testing_package; – and of go/types dropped THREE (Basic, Interface, Tuple). One root explains both, and it explains why the two rc4 lines travel together: they are emitted by the SAME record site, which calls recordConversionPackageUsing on both operands as it records the pair.

The site is applyImplicitConversion’s struct arm, reached because the argument loop applies the call’s FIRST parameter type to every argument: testEncrypt(t, desc, c, ...) pairs *testing.T (param 0) with *Cipher (arg 2) and records the harmless pointer-boxing pair Cipher -> ж<Cipher>, registering testing as the qualifier alias on the way past.

e61758549 (2026-08-18) widened typeDeclaredInConvertedPackage to subtract a WHITEBOX-PRODUCTION declaration, to stop internal/reflectlite’s flag(typ.Kind()) minting a phantom partial struct flag in the test class (CS1061). That reasoning is about HOSTING an operator, and it is right for a numeric record. It is one notch too wide for the pointer-boxing route, which hosts nothing at all: ImplicitConvGenerator looks the target up by struct declaration, finds none for golib’s ж<T>, and continues before choosing a host. recordsRequireProductionMutation already stated exactly that for the same shape; the predicate is now written once (pointerBoxConversionRecord) and both read it.

Why this is a defect of scope and not settled semantics, stated so it is cheap to overrule:

The boundary the fix must not cross, and does not. BOTH-FOREIGN pairs stay declined, exactly as the os syscall.Handle precedent had them before the whitebox exclusion existed. go/types alone reaches nine of those (types.Basic, ast.FuncType, token.FileSet, …); admitting the pointer-box shape unconditionally would have ADDED records rather than restored them. Only a whitebox-production operand is readmitted, which is why the regen lands on the committed bytes exactly.

Gate: -tests -test-action convert over crypto/rc4package_test_info.cs byte-identical to committed, whole-package git status clean. go/types likewise, its only remaining movers the documented families (package_init.cs initᴛᴛtests() at +7 real lines, twelve CRLF-phantom *_test.cs), restored. Guards TestWhiteboxProductionPointerBoxConvStillRecorded (three arms: the record, the qualifier alias it carries, and the both-foreign boundary) and TestPointerBoxConversionRecordShape, each proven failing-first by neutering the exemption away and then over-broad in turn.

Blast radius, MEASURED – three of seven, and they are banked here

Seven packages had a package_test_info.cs written between e61758549 and this fix, so each could hold the shrunken form. Regenerated one by one: FOUR are unaffected (debug/elf, encoding/json, internal/reflectlite, text/template – the site is not reached, or the package is not white-box reference), and THREE were first banked inside that window, so they were born short and now regen LARGER:

package restored by the fix
crypto/tls six pointer-box records (Config, QUICConfig, clientHelloMsg, serverHelloDoneMsg, serverHelloMsg, ΔConnectionState)
encoding/xml Decoder, plus the testing qualifier alias in BOTH anchor files
html/template parse.TextNode – a DIRECT record, not Indirect – plus the parse alias in both

Those five files are banked with the converter fix rather than left behind. Not banking them would make every future sweep of those three packages report drift that is neither a CRLF phantom, nor a closure re-emission, nor an init hook – precisely the stop and root-cause it class, on three packages, indefinitely. Each re-validated at its banked count with the records in place: encoding/xml 386, html/template 243, crypto/tls 400 + 2 disclosed. Every other mover was the documented family (CRLF phantoms, the initᴛᴛtests() hook at +7 real lines, the proof page’s volatile date/commit line) and was restored, not banked.

The positive control is the same census read the other way: go/types was banked BEFORE the window, and its package_test_info.cs reproduces committed bytes exactly under the fix.

✅ CAPTURED (2026-08-20, lane claude/readme-rc4-test-regen) – the LIVE shape-(b) proof for TestBogoSuite’s host-conditional pin, off the opportunistic standing instruction

The bogo lane closed with this open: “NOT obtained: the live shape-(b) proof … Whoever next meets a red crypto/tls whose C# side is unmoved should keep that run’s go2cs_test_comparison.json rather than restoring past it.” The crypto/tls re-validation this lane owed for the rebank above is that run, on a laptop (Ryzen 7 PRO 6850U) where Go’s own BoGo baseline went red.

Measured, -tests -test-action all -test-timeout 20m, exit 0:

  reading
TestBogoSuite Go fail / C# fail – agreement on FAILURE, which an UNANNOTATED disclosure never satisfies
Go side fan-out reached it: 3,242 TestBogoSuite/… children
annotated root ROOTED them – all 3,242 withdrawn, zero left as one-sided mismatches
rows 402 Go / 402 C#
verdict status: validated, matched: true, errors: 0, disclosed = TestBogoSuite + TestCertCache
roster row 400 matched · 2 disclosed – unmoved

The fan-out line is the load-bearing one the ruling flagged: shape (b) is precisely the shape in which the Go side reaches its case expansion, so an annotated root that failed to root would have left 3,242 one-sided rows and failed the package. It rooted. Together with the bogo lane’s own shape-(a) canary (the machinery proved INERT there) the proof matrix is now complete on both arms, live.

The run’s comparison artifact is banked verbatim as evidence-crypto.tls-shape-b.json – it is normally git-ignored under src/core, and it is not reproducible on demand (the board records the Go baseline as nondeterministic across consecutive runs on one machine), so keeping it was the instruction. Delete it if the record is wanted in prose only.

The proof PAGE was restored, not banked: the committed docs/validation/current/crypto.tls.md states shape (a), and the annotation exists so the roster’s number never has to move with the host that last ran it.

net/netip’s STRUCTURAL WALL FALLS — 0 of 267 → 210 of 267 on a two-move model-selection arc — but the package does NOT bank: all 57 residuals are ONE root, allocation behavior, and zero of them are disclosable (2026-08-20, lane claude/banking-net-netip)

The board handed this row on as “#159, model-selection arc, no ruling needed”, with the wall named exactly right: “x509 needs the REFERENCE model to avoid a split identity, and netip’s fuzz_test.go needs the RECOMPILE model to satisfy a nominal constraint. One package, one model, and no model serves both files.” The first half of that is now closed — netip takes recompile, the suite builds and runs, and 210 of its 267 verdicts agree with go test. The second half is where the row stops, and it stops on a bar this campaign has already ruled twice.

The baseline is one root, not two — the recorded pair is already half closed

Re-measured on this base before any change: 5 errors, all CS0315, in fuzz_test.cs only. The xml-netip-alias entry recorded “7 in 2 roots”; its CS0019 half (^uint64(0) &^ 1 keeping C#’s int) closed at 144c60d2buint128_test.cs now emits ~(uint64)1 and the pair is gone. So the wall this lane met was the structural root alone.

The rule: an adapter serves interface BOXING, never a nominal CONSTRAINT

checkStringParseRoundTrip[P netipTypeCmp] is called with Addr, AddrPort and Prefix — all declared in the referenced production assembly — while netipTypeCmp is declared in fuzz_test.go. The white-box reference model’s whole premise is that interface-implementation records are RELOCATABLE: a production struct is foreign to the test compilation, so go2cs-gen emits a value or pointer adapter class in the test anchor instead of a partial production struct. That premise holds wherever the interface is reached by boxing. It has exactly one exception, and this is it: C# checks where P : netipTypeCmp NOMINALLY, against the type argument itself. The only thing that can satisfy it is the argument’s own base list; the only thing that can add to that is a partial declaration; and a type closed inside a referenced assembly admits no partial. No adapter stands in that position. Hence CS0315 five times, and hence the answer is to stop needing the reference model for this suite.

Two moves, both general:

Move What it does
The model-selection gate constraintProxyFor is the (type parameter, type argument) core every instantiation form already routes through — a generic NAMED type via constraintProxyArg, a generic FUNCTION via constraintProxySigArg. It now also records the pairs where the argument is a production type and the parameter’s constraint is a TEST-declared method-set interface. A non-empty set joins recordsRequireProductionMutation at the fallback site, so the suite reconverts under recompile. The constraint side reuses isMethodSetBeyondComparable — the same predicate getGenericDefinition uses to choose the nominal arm — so the gate and the emission cannot disagree about which constraints are nominal
The bare-alias rule goes MODEL-INDEPENDENT Behind the CS0315 stood one more error, and it was the xml-netip-alias lane’s own rule seen from the arm it had gated off: netip_test.cs spelling netip.AddrDetail for an alias export_test.go declares, CS0426. That fix required the white-box model, reasoning that only there does production live in another assembly. True, and beside the point — what makes the qualified spelling invalid is that a global using is a member of no class, which holds just as firmly when production is RECOMPILED in. Under recompile the alias is, if anything, more plainly in scope: production, internal and external are one compilation

The second move needed a third, smaller one. testExternalVariant — “current variant is the external <name>_test package” — was set only under the white-box model, so the recompile external half could not tell it was the external half and the rule could not fire. Which variant is converting is a fact about the SOURCES, not about the model; it is now set under every model. Nothing else reads it outside the white-box path (whiteboxBridgeDeclaredType is reachable only through testOwnedAdapterRef, which returns early unless the model is white-box reference), so widening it moves exactly the one rule that needed it. The derivation was extracted to testVariantOptions so the wiring itself is guardable — it is where this lane’s own first attempt failed, silently, with the predicate correct and unreachable.

And a fourth, which is the same lesson again: the two models park the package-under-test’s path in DIFFERENT fields. The reference models clear the self-import binding and retain it in testProductionPath; a recompile conversion keeps the binding, so it stays in testPackagePath and testProductionPath is EMPTY. The first version of the alias fix read testProductionPath alone, answered empty under recompile, and did nothing — a fix that builds, ships, and is inert. The shared accessor Options.packageUnderTestPath() is the remedy.

Blast radius, censused before the change — and it is net/netip alone

The model-selection gate can only fire where a _test.go file DECLARES an interface that is then used as a type-parameter constraint. A parse of every package in the Go 1.23.1 source tree finds that shape at four sites, total:

Site Fires?
net/netipnetipTypeCmp (fuzz_test.go) yes — value arguments (Addr, AddrPort, Prefix), production-declared
crypto/internal/nistecnistPoint[P] no — POINTER arguments (*P224Point); the boxed form is the existing constraint-proxy machinery’s business, and the gate takes value named types only
net/httpTBRun[T] no — pointer arguments, and *testing.T/*testing.B are FOREIGN types, not the package under test
cmd/compile/internal/testfooer no — cmd/ is not a converted package

The bare-alias widening has its own radius, and it is empty on the roster: only two banked packages are on the recompile model (crypto/ecdh, text/tabwriter) and NEITHER has an internal test file at all — both are external-only suites that fell back from plain reference — so no _test.go of the package under test declares anything for the rule to spell. The plain black-box reference model cannot reach the arm for the same structural reason.

Both packages were re-converted as the control. The result is the interesting part: every test-project artifact reproduces byte-identically.tests.csproj, package_test_info.cs and package_info_external_test.cs, which are exactly the files a moved model or a moved external-variant flag would rewrite. What DID move is three files, all two already-documented phantom classes and neither of them this lane’s: ecdh_test.cs and tabwriter_test.cs are CR-stripped IDENTICAL to HEAD (the multi-line-literal CRLF phantom), and crypto/ecdh/package_init.cs gains the initᴛᴛtests() hook as +7 REAL lines — the fourth -tests-closure shape CLAUDE.md names precisely because it survives a numstat filter. All three restored.

0 of 267 → 210 of 267, and the residual is ONE root

267 Go verdicts, 21 excluded (20 benchmarks, 1 fuzz), 210 agree, 57 diverge. Every one of the 57 is an allocation assertion, and they collapse to three parents:

Test Rows Assert Reported
TestNoAllocs/* 47 testing.AllocsPerRun(1000, f), want 0 1–10
TestAddrStringAllocs/* 5 want 1 2, 3, 106
TestParsePrefixAllocs/* 2 want 0 2
(their three parent rows) 3 aggregate of the above

Why NONE of it is disclosable — and the suite proves it against itself

The tempting move is alloc-count-semantics, the class io/strings/bytes established: Go’s AllocsPerRun counts mallocs, the managed shim reported BYTES, so no allocation behavior could satisfy a count assert. That class does not cover a single row here, for two independent reasons, both measured rather than argued.

  1. Every one of the 54 leaf failures reports a COUNT, not bytes. The shim is no longer byte-only: it charges golib’s own allocation sites — the structural mirror of Go’s runtime.MemStats.Mallocs — and falls back to the byte figure only when bytes are nonzero and the count is zero. Zero rows here took that fallback. The unit mismatch the class names is simply not present.
  2. The measurement reaches exact zero on this very suite, twice. TestAddrStringAllocs/zero and TestNoAllocs/IPv6Unspecified are want-ZERO alloc asserts that the C# side satisfies. The shim’s own contract is that zero bytes implies zero allocations exactly; two passing rows on the same code paths demonstrate the instrument can and does report the answer Go reports. A want-zero row that then reads 3 is reading three real allocations.

So the 49 want-zero rows are converted code allocating where Go does not, and the 5 want-one rows are converted code allocating at least twice where Go allocates once (the counter is a documented LOWER bound, so exceeding the want is one-directional evidence). That is an optimization target, not an impossibility — precisely what the edwards25519 ruling this week refused to launder, and precisely the distinction TestWriteStringAlloc was already held to (“the byte-derived shim CAN report 0, so the unit-mismatch ruling does not cover it … a real divergence, not a disclosure”).

net/netip therefore does not bank. No roster row, no proof page, no badges, no disclosure manifest, no committed test sources; the corpus is restored and this commit carries the converter change alone.

What the row is worth now, and what it costs

It is one root away, and the root is named: netip’s allocation profile under conversion. The 106 readings for Addr.String() on IPv6 are the loud end and probably one shape (the expanded-form builder), while the TestNoAllocs band at 1–10 is the broad end — 47 rows across constructors, accessors and parsers. Nothing about it is structural: the suite builds, runs, and agrees on everything that is not counting allocations. Ranked against the terminal path this is a .1-era optimization arc, not a wall, and the row should be re-offered when one is commissioned.

Gates

Converter go test ./... ok, with four guards, each proven failing-first by neutering its own fix — and the neuter of packageUnderTestPath fails the ALIAS guard too, which is the exact inertness this lane paid for once:

Guard Pins
TestNominalProductionConstraintForcesRecompile the gate, fed through constraintProxySigArg — the real instantiation path — with three negative controls: a test-declared argument, a production-declared constraint, and the recompile model itself
TestTestDeclaredAliasSpelledBare now BOTH arms of the bare-alias rule; its recompile case models the real option shape (testPackagePath, not testProductionPath), because the reference models’ field would have passed for the wrong reason and pinned nothing
TestVariantOptionsMarkExternalUnderEveryModel the external-variant flag under all three models, and the bridge overrides staying white-box-only
TestPackageUnderTestPathFollowsTheModel the accessor, both fields and the production-conversion zero

Full CNR byte-identical across all 629 behavioral packages, 0 NOT MEASURED, 2 advisory converter warnings, solution-integrity and path-casing preflights OK · the two banked recompile-model packages re-converted with every test-project artifact byte-identical (above) · six reflect-consumer canaries re-swept green, recomputed from the roster at gate time (above). No golib change, no corpus regen, and TestStdLibMetadataInSync passes untouched, so no go generate, go2cs.slnx or behavioral-suite gate is owed.

⚠ One measurement was thrown away and re-run rather than reported, because it would have been a false green of exactly the kind this repo catalogues: the first CNR built go2cs.exe at 21:53:42, which fell INSIDE the window in which this lane was neutering its own fixes to prove the guards failing-first. The binary under test was therefore not the binary being banked. The fixes are all -tests-only paths and could not have changed a production transpile either way — which is precisely why it would have passed, and precisely why it was not kept. Re-run from a rebuild at 21:58:02 with the source final.

The canary sweeps, and three foreign movers proven foreign by CONTROL

All six pass at their exact banked counts: go/internal/gcimporter 583, go/types 557, encoding/json 491, crypto/tls 400 + 2 disclosed, encoding/xml 386, html/template 243.

The tls row is worth naming: TestBogoSuite’s Go side flipped pass → fail on this host — shape (b), the host asymmetry the host-conditional pin was built for — and the pin absorbed it, leaving the row at 400 + 2 rather than red. That run’s go2cs_test_comparison.json was kept per the standing opportunistic-capture instruction before the page was restored.

The sweeps left 37 modified corpus files: 32 CRLF phantoms and 5 with real content. Three are the initᴛᴛtests() hook (encoding/xml, go/types, html/template package_init.cs). The other two are not phantoms and were NOT assumed foreign — they were controlled:

Mover numstat Verdict
go/internal/gcimporter/gcimporter.cs 1/1 a PRODUCTION lambda gaining an explicit (@string, error) return type
go/types/package_test_info.cs 0/3 three GoImplicitConv<T, ж<T>>(Indirect = true) records dropped — the go/types test-info regen-drop, R’s root

Both reproduce byte-for-byte under the BASE converter with this lane’s changes stashed out, so both are pre-existing corpus staleness rather than this arc’s. go/types also keeps its package_info_internal_test.cs and an untouched .tests.csproj through the sweep — it is still on the white-box model, which is the independent confirmation that the new gate never fired there, as the four-site census says it cannot. All 37 restored; this commit carries no corpus change.

Process note on the canary derivation. CLAUDE.md says the five reflect-consumer canaries are “recomputed from docs/ValidatedTestPackages.md at gate time, never carried forward”, and the 2026-08-19 derivation is recorded as go/internal/gcimporter 583, go/types 557, encoding/json 491, crypto/tls 402, encoding/xml 386. Recomputing it here, go/internal/gcimporter does not import reflect anywhere — production or test. What it contains is the STRING "reflect.Value", inside an expected-signature literal in gcimporter_test.go, which a text-grep derivation matches and an import-based one does not. This lane did not resolve which reading was intended and swept the UNION of both, since over-covering a canary set is free and under-covering it is what the rule exists to prevent. Whoever owns the rule should pin the derivation itself; a rule that says “derive, never remember” still has to say derive HOW.

RULING – canary derivation is by DIRECT IMPORT DECLARATION, never text-grep; and the record-drop scope judgment is RATIFIED (coordinator, 2026-08-20)

The canary gap the netip lane surfaced, closed. “The five largest banked reflect consumers BY VERDICT, recomputed at gate time” now derives membership mechanically: a package is a reflect consumer when reflect appears in the IMPORT DECLARATIONS of its production or test sources (go list-shaped – Imports, TestImports, XTestImports), never by text search over file contents. The two rejected readings each fail measurably: whole-file grep over-matches string DATA (go/internal/gcimporter matches only "reflect.Value" inside expected-signature test fixtures and imports reflect nowhere – data is not consumption); import-CLOSURE under-discriminates (fmt sits in essentially every closure and imports reflect, so the qualifier degenerates to “five largest rows, period” and loses the sensitivity it exists for). Direct-import is the middle that tracks actual bridge exposure and is computable without judgment. Consequences: gcimporter LEAVES the canary set unless a test of its own imports reflect; the set recomputes at gate time as always; and when two defensible readings disagree mid-lane, sweeping the UNION – as the netip lane did – is always acceptable. A superset canary sweep is never wrong; only a silently chosen subset is.

The e61758549 scope judgment is ratified as delivered. The readme-rc4 lane judged the whitebox-production exclusion one notch too wide (it reasons about hosting an operator, which the pointer-boxing route never does) and restored rather than widened, with the both-foreign boundary intact. The reasoning is adopted; nothing to overrule. Its three-package test-info bank (crypto/tls, encoding/xml, html/template – born short inside the two-day window, each re-validated at its banked count) is the correct application of the born-stale exception to the restore rule, and go/types reproducing exactly is the control that makes it so.

The shape-(b) proof matrix is COMPLETE, twice over. Both laptops caught live Go-side BoGo flaps within hours of each other: R’s during the tls test-info re-validation (fan-out reached, 3,242 children absorbed by the annotated root, zero one-sided, 400 + 2), G’s during the netip canary sweep (same shape, same arithmetic). R banked the irreproducible comparison artifact at docs/phase4/evidence-crypto.tls-shape-b.json. The opportunistic-capture standing instruction has paid out in full; nothing about the pin remains unproven.

MEASUREMENT PASS (2026-08-20, lane claude/measure-queue-panic-cap) – six near-miss candidates repriced: net/mail VALIDATES at zero price, math/big is 224 verdicts behind one arc already commissioned

Ruling x2 left a fallback clause without a source: “if the recorder design overruns, the substitute comes from the ranked queue via the measurement pass, not from re-litigating the class.” This is that pass. No fixes were made and nothing was banked – a fix inside a measurement lane un-controls the measurement, and taking a row is the coordinator’s call, not a measurer’s.

Why these six, and why the queue needed re-measuring at all

The ranked near-miss census is dated 2026-08-16, and it is stale by construction: three of its own “0 of N” rows have BANKED since (text/template 0 of 52, html/template 0 of 243, encoding/xml 0 of 386 are now rows 155-157). Selection was therefore “closest in that census, and not re-measured since”, which is the board’s own standing lesson from the 23-package breadth bank – re-scout the tail after any capability lands, not only the packages that capability names. Each was run -tests -test-action all, artifact kept, tree restored.

The priced ranking

package rows matched divergent residual root class price to bank
net/mail 11 11 0 ZERO – the banking commit itself
math/big 226 224 2 TestNewIntAllocs; TestMulUnbalanced alloc-count-semantics; open 1 disclosure + the ReadMemStats arc (already commissioned)
net/http/internal 10 9 1 TestChunkReaderAllocs 2/run vs 1 alloc-count-semantics 1 disclosure, no converter work
crypto/cipher 14 13 1 TestGCMAsm SKIPS where Go passes none of the five fits a RULING first; then 0 or an arc
debug/pe 10 9 1 reinterpret cannot re-length a managed array not disclosable rides the LAYOUT arc (commissioned)
log 9 7 2 TestAll source position; TestDiscard 3/run vs 0 – ; alloc-count-semantics the position-map arc (commissioned) + 1 disclosure

Excluded declarations are the ordinary deferred Example/Benchmark set in every row (math/big 67 benchmarks + 14 examples + 1 fuzz, crypto/cipher 9 + 10, log 5 + 2). One exclusion is NOT of that kind and is named for the record: math/big’s TestCalibrate is excluded-disclosed as requiring B.ResetTimer, a capability gap the harness already handles per-declaration.

The two that move the terminal path

net/mail validates 11 of 11, exit 0. It stood at 7 of 11 in the 2026-08-16 census, the four TestAddress* rows infrastructure-erroring on System.ArgumentException: Indices low, high and max represent a range outside bounds of the array reference – a three-index slice-bounds root. That root is GONE. It was dissolved by an intervening arc and this lane does not attribute which one (attributing it would need a bisect, and the measurement is the deliverable). Nothing is disclosed, nothing is owed: the price is the banking commit.

math/big reproduces 224 of 226 exactly – the r58b pair, unmoved, on a third independent measurement. It is by a wide margin the largest row available anywhere on the near-miss queue, and its two residuals are NOT equal in price:

crypto/cipher is a CLASS question, not a defect – recorded for a ruling, not self-ruled

TestGCMAsm builds two AEADs, one via the assembly path and one generic, and skips itself when reflect.TypeOf(asm) == reflect.TypeOf(generic) – Go’s own designed behavior on a platform with no distinct assembly GCM. The converted corpus has exactly one GCM implementation, so the C# side takes the skip branch the Go source defines. Go on windows/amd64 has the asm path, runs the body and passes. The row is Go pass / C# skip: agreement with Go’s INTENT, disagreement with Go’s VERDICT.

Against the five classes: not host-limit (the host can do it), not alloc-count-semantics, not chan-direction (retired), not codegen-liveness. runtime-capability REFUSES it by its own admission test – a truthful managed second implementation exists at a cost, so it is an arc with a price. But naming that arc is naming “build a second GCM whose only consumer is a differential test”, which is a strange thing to buy. The honest options a coordinator has are: rule the source-defined platform skip as skip-parity (price 0, row banks at 13 + 1 skip), or accept it as an arc. This lane declines to choose.

debug/pe is the layout arc wearing different clothes

TestReadCOFFSymbolAuxInfo reads back COFFSymbolAuxFormat5._ – declared [3]uint8 in Go and emitted correctly as new(3) – with 8 elements. Eight is exactly the length of the SOURCE struct’s COFFSymbol.Name [8]uint8, and the site is Ꮡsym.Reinterpret<COFFSymbol, COFFSymbolAuxFormat5>(). A managed array<T> is a REFERENCE carrying its own length, so a reinterpret cannot re-length it and the destination field ends up holding the source’s array. Go’s reinterpret is exact there (both structs are 18 bytes).

Same family as sync/atomic’s hammer rows – the C# struct is not the Go struct’s bytes – which Ruling A commissioned as the zero-size-field LAYOUT EMISSION arc (explicit layout with Go-computed offsets). Not disclosable: a truthful managed form exists, and it is that arc. debug/pe rides it, or takes a one-file hand-own of its symbol reader.

The ranking, as a selection

If runtime/debug (#161) holds its slot at the price Ruling B set, nothing here displaces it. If it overruns, the substitutes in order are:

  1. net/mail – 11 verdicts, price zero, bankable the day someone takes it.
  2. net/http/internal – 9 + 1 disclosed, price one manifest entry in an existing class.
  3. math/big – 224 verdicts, the largest row on the queue, priced at one disclosure IF the ReadMemStats arc lands (which #161 is already buying). Taking #161 and math/big together is the highest-verdict pairing available.

log is the sleeper: at 7 of 9 its price is the position-map arc plus one disclosure, and that arc is ALREADY a prerequisite of both #161 (runtime/debug) and #162 (flag). Whoever lands it should expect log to fall out nearly free, and should re-measure log/slog (153 of 213 at last count) in the same pass for the same reason.

What this lane did NOT do, stated plainly

Standing dirt, classified and RESTORED, nothing unclassified: math/big’s doc.cs/prime.cs (CRLF phantoms, empty numstat) and its package_init.cs (+7 real lines, the initᴛᴛtests() hook – the fourth -tests-closure shape CLAUDE.md names).

RULING – the terminal path is SELECTED from the measured ranking: net/mail banks now, log is #161, position-map is the linchpin arc (coordinator, 2026-08-21)

The measurement pass above is adopted as the selection instrument, and three of its judgments are ratified on the way: the cap’s HEAD-KEEPING truncation (a disclosure signature pins the FIRST failure by Contains – dropping the head would silently unpin disclosed rows roster-wide), the panic record’s exemption from the aggregate cap, and the CNR skip (the branch carries zero converter changes; CNR binds to converter changes, and GolibTests + the full go2cs.slnx build are the gates a golib change owes).

The path to 162, selected:

Row Package Price Status
#158 net/mail (11) ZERO – measured 11/11; a stale census said 7/11 and the root dissolved under an intervening arc bank immediately
#159 sync/atomic (108) token construction + zero-size layout arc (Ruling A) lane in flight
#160 runtime/debug (9) position-map + ReadMemStats design + 3-row runtime-capability disclosure (Ruling B) priced
#161 log (9) position-map + one disclosure – the SLEEPER: its arc is already #160’s prerequisite priced
#162 flag ImplementGenerator Delta-rename fix (+ position-map exposure per the runtime-debug census) rooted

The position-map arc is the linchpin: its named consumer set (runtime/debug, log, log/slog, flag) covers three of the five remaining rows. It is the highest-leverage single arc left on the 1.23.1 campaign and should be staffed accordingly.

Reserves, ranked as measured: net/http/internal, then math/big – whose 2026-08-10 off-the-table status is SUPERSEDED by the fresh 224/226: once #160’s ReadMemStats arc lands, its TestMulUnbalanced discriminator resolves and the row may cost one manifest entry for 224 verdicts – then gob-via-StructOf and netip-via-allocation.

Held for a future ruling, off the terminal path: crypto/cipher’s TestGCMAsm skip-parity shape (agreement with Go’s intent, disagreement with its verdict, no class fits). Ruled when a lane reaches it, not before.

🔁 flag RE-MEASURED at the MERGE RESULT — the Δ-rename root is LANDED, the row is 23 of 24, and its whole remaining price is the position-map arc at ONE miss, measured (2026-08-21, lane worktree-agent-a54643a27ddfc865c)

Briefed against the ROOTED, NOT TAKEN entry above as a live root to fix. It is not one any more, and saying so precisely is most of this lane’s product: 7eeeda893 is an ancestor of master, ImplementGenerator already carries ResolveForwardMemberName, and CollisionRenamedForwardTests already guards both halves of it. What was genuinely missing is the reading nobody had taken — flag at the union. claude/heavy-pair-7be2d2 measured 23 of 24 on its OWN tip; claude/edwards25519-a measured master WITHOUT that tip and got the CS1929 ×10 wall back. Neither is a measurement of the merge result, which is exactly the gap CLAUDE.md’s banked-row protection rule was written about.

The measurement

go2cs -tests -test-action all -test-timeout 10m "<GOROOT>/src/flag" src/core/flag, converter rebuilt from HEAD first, in a worktree whose bin/obj came from no other tree.

   
build 0 errors — the CS1929 ×10 wall is gone at the UNION, not only on its lane tip
verdicts 24
matching 23
divergent 1TestDefineAfterSet
skipped / disclosed 0 / 0
excluded 6 Examples (Phase-4D)
status failingdoes NOT bank

All five Δ-renamed flag.Value implementors the wall named (boolFlagVar, flagVar, interval, URLValue, zeroPanicker) now compile and RUN: TestUserDefined, TestUserDefinedBool, TestUserDefinedFunc, TestUserDefinedBoolFunc, TestUserDefinedBoolUsage and TestUserDefinedForCommandLine all pass. That reproduces heavy-pair’s 23 of 24 exactly, on a tree carrying every merge since — so the fix survived the union, which is the thing a lane-tip proof cannot say.

The new fact — the residual is ONE miss now, and it is MEASURED rather than predicted

claude/edwards25519-a landed goSourcePath (Go spells source paths with forward slashes on every platform; the CLR hands back the PDB’s backslashes) and predicted that with the separator half in, “the arc’s remaining distance on flag is one miss, not two.” Nobody re-ran flag after it, because from master flag did not build. The three strings side by side are the proof:

  string
Go’s assertion flag myFlag set at .*/flag_test.go:.* before being defined
C# at 7eeeda893 (pre-goSourcePath, as recorded above) … set at C:\…\src\core\flag\flag_test.cs:1112 …
C# today … set at D:/…/src/core/flag/flag_test.cs:1112 …

The backslash form misses .*/ outright — it contains no / at all, so the regex fails before it ever reaches the filename. Today’s form satisfies .*/ and then misses on exactly one token: .cs where the assert wants .go. So the position-map arc’s entire remaining job on flag is the source IDENTITY; the separator half of the two-half remedy is already paid, and the prediction is now a measurement.

The doctrine applied mechanically — and it refuses the row, again

TestDefineAfterSet checked against all five ratified classes:

class admits? why not
alloc-profile no no allocation is asserted
alloc-count-semantics no no count is asserted
codegen-liveness no nothing about collectibility
host-limit no its bar is a structural property of the DEPLOYMENT SHAPE that retires itself when the shape changes; a .cs file name does not retire that way
runtime-capability no its admission test is does a truthful managed implementation of the asserted behavior exist at any cost?yes, and it is priced: #line or a per-package side-car. A priced arc is never a disclosure

Same call heavy-pair made, re-derived rather than inherited. flag has no disclosable row and one unfixed-here row, so it does not bank: no roster row, no proof page, no committed test sources.

Repricing — flag is the position-map arc’s CHEAPEST consumer and its cleanest acceptance test

The RULING above prices row #162 as ImplementGenerator Delta-rename fix (+ position-map exposure)”. The first half is spent. flag’s remaining price is the position-map arc and nothing else, which makes it the only one of the arc’s four named consumers that banks on the arc ALONE:

consumer verdicts the arc buys what else that consumer still needs to bank
flag 1 nothing — 24/24, zero disclosures, zero other arcs
log 1 of its 2 one disclosure
runtime/debug 5 of its 7 the ReadMemStats measurement-surface design + the 3-row runtime-capability disclosure
log/slog 9 18 alloc-profile disclosures + the import-ordered-initialization arc

That is worth more than one row to whoever staffs the arc: flag is its acceptance test. It is a 24-verdict suite whose single failing assertion is a bare regex over a file name, with every other verdict already green and no disclosure manifest in the way — so the arc lands green on flag or it does not land. The other three consumers cannot give that signal, because each of them would still be red for reasons the arc does not own.

Independent verification of the guard, since the lane was here anyway

Charter §7, and cheap. Both failing-first claims in the 7eeeda893 entry above were re-derived on this tree rather than taken on trust (GenTests now totals 26, grown by other lanes since):

state result
unmodified 26/26 pass
ResolveForwardMemberName neutered to return null exactly 3 failRenamedDeclarationIsResolvedThroughTheMarker, PointerAdapterImplementsTheInterfaceNameAndForwardsTheEmittedOne, RenamedValueReceiverStillResolvesItsReceiverExpression
receiver lookup alone reverted to the Go name (ForwardReceivers[simpleMethodName]) exactly 1 failRenamedValueReceiverStillResolvesItsReceiverExpression

Both neuters were reverted; the tree is byte-clean. The claim that the RECEIVER half is independently load-bearing holds — it is the half the original CS1929 diagnosis did not name.

For the next lane

  1. Do not re-take flag’s root. It is landed, guarded, and verified at the union. The ROOTED, NOT TAKEN entry above and the RULING’s #162 price line are both SPENT as of this measurement.
  2. flag is a 23-of-24 row parked on ONE arc, and it is the arc’s cheapest and cleanest consumer. Whoever takes the position map should measure flag first and last.
  3. The generalizable trap this lane nearly re-paid: a brief naming a root is not evidence the root is live. git merge-base --is-ancestor <sha> HEAD costs nothing; edwards25519-a paid a pipeline run to learn it in one direction, and this lane would have paid a generator arc to learn it in the other. Read the OWNER of the named diagnosis (here ImplementGenerator) and grep it before designing.

Gates

None owed, and that is the honest accounting rather than a skip. No converter, golib, go2cs-gen, corpus or test-source change was made — the diff is this board entry alone, and the two generator neuters were reverted with a clean git status proven after each. Gates bind to change classes (charter §5); a docs-only append changes none of them. The measurement’s own instrument — the pipeline, run to completion, comparing against go test -json -count=1 — is reported above.

Standing dirt from the pipeline run, classified per CLAUDE.md and RESTORED, nothing unclassified: src/core/flag/flag.cs (7/7 numstat — the -tests-closure alias shape: the wider test closure collides os, so the alias emits Δos and the four references follow it; class 2) and src/core/flag/package_init.cs (+7 real lines — the initᴛᴛtests() hook, the FOURTH -tests-closure shape CLAUDE.md names). The nine untracked converted test artifacts were removed, not committed: flag did not bank. No package_info.cs record moved, so no stdlib-metadata.txt regenerate is owed.

✅ BANKED – net/mail is roster row #158, 11 of 11, and it is the first bank to carry its own badge (2026-08-21, lane claude/net-mail-position-map)

The selection ruling put this row first at price ZERO. It banks at exactly that: no converter change, no golib change, no disclosure, no manifest. The pipeline run IS the whole change.

  roster  157 / 215 (73.0%), 18,414 matching, 79 disclosed
       -> 158 / 215 (73.5%), 18,425 matching, 79 disclosed   (recomputed from the table itself)

The eleven verdicts are the RFC 5322 address-list grammar (quoted strings, comments, folding white space), RFC 2047 encoded-words in both B and Q form including a custom WordDecoder, group syntax, the obsolete and malformed matrix, Address.String() round-tripped back through the parser, and Date header parsing with its CFWS forms. Three declarations are excluded-disclosed as the ordinary deferred Example/Benchmark set.

What the stale census said, and what dissolved it

The 2026-08-16 near-miss census recorded net/mail at 7 of 11, the four TestAddress* rows infrastructure-erroring on System.ArgumentException: Indices low, high and max represent a range outside bounds of the array reference – a three-index slice-bounds root. The measurement pass found all four passing, and this bank confirms it twice more (the run above, and the own-row sweep).

The root is not attributed, deliberately. It dissolved under an intervening arc, and naming which one would need a bisect across five weeks of merges to add nothing to the row. What the episode is evidence FOR is already board doctrine and is now paid out twice in eight days: re-scout the tail after any capability lands, not only the packages that capability names. A row sat one measurement away from free for an unknown number of weeks.

The badge levelled in its OWN run – the first bank that did

Measured, and worth recording because it retires a standing step every previous bank owed. Before the pipeline: Tests-not_yet_validated-orange. After the SAME -test-action all invocation: Tests-11%2F11_validated-brightgreen, linking validation/1.23.1.6/net.mail.html. That is refreshPackageReadmeAfterProof (the badge lane’s second emission point) doing the thing it was built for, on the first fresh bank to reach it. No hand-levelling, no follow-up reconvert, no board entry owed to a later reader.

Gates

The bank carries zero code, so its gate is the pipeline plus the sweeps – and the canary set is derived at gate time by DIRECT IMPORT DECLARATION per the 2026-08-20 ruling, not remembered:

Gate Result
-tests -test-action all (the bank’s own run) 11 validated, 0 divergent, 3 excluded, 118 s
own-row sweep -Filter net/mail PASS 11
canary go/types (557) PASS 557
canary encoding/json (491) PASS 491
canary crypto/tls (400 + 2) PASS 400 – and Go’s own BoGo baseline went red again on this run, the THIRD live shape-(b) capture; the annotated pin absorbed it and the row did not move
canary encoding/xml (386) PASS 386
canary html/template (243) PASS 243

The derivation is worth showing once, because it reproduces the ruling’s own worked example: over the 158 banked rows, 62 declare a direct reflect import across Imports / TestImports / XTestImports, and the five largest by verdict are go/types 557, encoding/json 491, crypto/tls 400, encoding/xml 386, html/template 243. go/internal/gcimporter (583) is absent – the largest banked row on the board, and correctly NOT a canary, exactly as the ruling predicted: it matches "reflect.Value" only inside expected-signature test DATA and imports reflect nowhere.

Standing dirt classified and RESTORED, nothing unclassified: net/mail’s message.cs (CRLF phantom, empty numstat) from the bank’s own run, and the canary packages’ *_test.cs phantoms from the sweeps.

⚠ THE POSITION-MAP ARC OPENS: the FILE half is written and measured – TestStack goes 5 misses to 1 – and it is NOT banked, because the same measurements produce THREE questions this lane will not self-rule (2026-08-21, lane claude/net-mail-position-map)

The linchpin arc, taken to the point where the decisions are decidable on evidence rather than argued. Everything below was measured on this box against Go controls; the implementation is ~45 lines in one hand-own and is described precisely enough to re-write in minutes.

The identity source: there is nothing to record. It is already derived.

The handoff priced this as needing a recorded Go-file identity. It does not. goFrameName ALREADY derives the import path at run time and has been shipping it since the receiver half:

// "go.runtime.debug_package" -> "runtime/debug"
string importPath = typeName[3..packageSuffix].Replace('.', '/');

So the file half is that same derivation applied one field over: <importPath>/<stem>.go, where the .cs stem IS the Go stem by construction (the converter emits <name>.go as <name>.cs, whole-file hand-owns included – crypto/subtle/xor_generic.cs). No converter change, no corpus change, no new artifact, no recorded attribute. The derivation was EXTRACTED (goImportPath) so a frame’s function and its file read the same one: Go’s traceback pairs them, and two independent derivations could disagree about which package a frame belongs to while each looked right alone.

Two shapes deliberately keep their .NET path, on the principle goFrameName already states – a frame that is not converted Go code must not claim to be: a non-converted package class (golib, the BCL, the host), and a *_impl.cs hand-own COMPANION, which supplements a package rather than replacing a Go file (runtime/managed_impl.go does not exist, and answering it would fabricate a source rather than translate one).

Two derivation rules, both found BY measurement, neither in the handoff

A TEST variant’s files live in the package-under-test’s DIRECTORY, so a test frame’s file and its function name different things – and Go’s own TestStack asserts exactly that pairing: frame("runtime/debug/stack_test.go", "runtime/debug_test.T.method"). The function keeps _test; the file does not. Both variants carry a class suffix the directory does not, and the longer is stripped first because it ends with the shorter:

class first measured correct
go.runtime.debug_test_package runtime/debug_test/stack_test.go runtime/debug/stack_test.go
go.log_internal_test_package log_internal/log_test.go log/log_test.go

The first cost three of TestStack’s four converted-frame assertions; the second, log’s long-file form. Both are now right.

The acceptance test: 5 misses to 1, and the 1 is a question

runtime/debug’s TestStack, same pipeline, measured before and after:

  before  5 file-prefix misses (all five code checks already passed)
  after   1 -- and it is the HOST frame:
          in line "\tC:/…/src/core/testing/TestExecution.cs:593", expected prefix "\ttesting/testing.go"

Every converted frame now names Go’s file exactly. The verdict does NOT move (2 of 9), because the test fails if any assertion fails – so the acceptance test cannot close without ruling question 2 below.

The yield signal: the file lands exactly, and log still does not move

log’s TestAll, measured after both derivation rules:

  want   ^.*/[A-Za-z0-9_\-]+\.go:(63|65): hello 23 world$
  got    log/log_test.go:69: hello 23 world      (long form -- file EXACTLY Go's)
  got    log_test.go:69: hello 23 world          (Lshortfile -- file EXACTLY Go's)

log stays 7 of 9. The file half is complete and it is not the yield; the LINE is.

Question 1 – the file half is NOT independently shippable, because it makes a MIXED position

The settled doctrine is written in the hand-own’s own header: “Frame.File/Line name the CONVERTED .cs source, the source that honestly exists” and “Its file/line are therefore the converted .cs position, NOT the .go one”. Today log_test.cs:69 is a real position in the tree the program was compiled from. With the file half alone, log/log_test.go:69 is a position in neither tree: Go’s file, C#’s line.

The line was already not Go’s, so the change does not introduce line dishonesty – it trades internal consistency for Go shape. That trade is the doctrine call, and the honest options are: accept the mixed position as an interim; answer :0 (Go’s own unknown-line sentinel – honest, but it discards a diagnostic that works today); or hold the file half until the line half lands, i.e. treat the position map as INDIVISIBLE. This lane recommends the third and did not act on it.

Question 2 – may the hand-owned host claim testing/testing.go?

The last TestStack assertion expects the testing frame to name testing/testing.go. Our host is hand-written C# in go.testing_runtime, and it deliberately declines Go identity – goFrameName’s own text: “A frame that is not converted Go code (golib, the BCL, the test host) keeps its .NET name – inventing a Go name for it would be a lie.”

The counter-argument is real: the host IS the testing package, hand-implemented, and a Go program reading a traceback expects testing/testing.go there. That is the same shape as Ruling A’s alignment token – truth read from what the model genuinely is, versus a property fabricated. It is one line in the host to claim it, and it is the ONLY thing between the current state and the acceptance test.

Question 3 – the trimpath form is a property of the CORPUS, not of the runtime (measured regression)

The sharpest finding, and the reason nothing is banked. TestStack expects the bare -trimpath form because the converted runtime.GOROOT() answers empty. But applying that form uniformly makes a converted USER program diverge from Go where it currently agrees. Measured on the RuntimeCallerFrames behavioral program, both binaries, same box:

  Go C# with the file half
runtime.Caller file C:/…/RuntimeCallerFrames/main.go main/main.go
file is rooted true false

Go answers an ABSOLUTE path for an ordinarily-built program; the trimpath form is a property of how GOROOT packages are built, which go2cs has no notion of. So the transform cannot be applied uniformly, and the runtime has no honest discriminator for “is this frame a GOROOT package” – the candidates are a build-path convention (…/core/…, true for the corpus, silent for everything else) or a recorded marker (a real emission change, and the thing this design otherwise avoids entirely).

The guard gap this exposed, and the probe that closes it

RuntimeCallerFrames passed all four phases with the change in – Output included. Its five file-related assertions are separator BOOLEANS and an equality, every one of which is invariant under a wholesale change of what the file names. A guard over a string property must assert the property, not a predicate that survives it. The two-line probe that exposes the divergence is in the measurement above (print the file, and whether it is rooted); it belongs in the guard whichever way question 3 is ruled.

Blast radius, recomputed – and the stdlib half of it is clean

A verdict can only move if something READS a frame file. Recomputed over the 158-row roster (the board’s census said 11; it predates internal/reflectlite joining):

reads a frame file rows verdicts
own _test.go 6 – encoding/json 491, io 60, context 57, sync 44, encoding/base64 17, log/slog/internal/benchmarks 3 672
converted PRODUCTION code 6 – go/types 557, database/sql 137, os/exec 74, internal/fuzz 52, internal/reflectlite 30, testing/slogtest 17 867

Swept with the file half IN: 20 distinct rows, 1,705 verdicts, zero failures – the 12 above plus 8 the substring filters pulled in free (bufio 80, io/ioutil 28, testing/iotest 18, io/fs 18, internal/saferio 17, go/version 3, database/sql/driver 1, os/exec/internal/fdtest 1). The stdlib blast radius is EMPTY. Every row that reads a frame file reads it for “where am I”, not for a Go-tree property – which is what makes question 3 the whole decision: the change costs the corpus nothing and costs converted USER programs their agreement with Go.

What this lane did NOT do

Standing dirt from the arc’s measurement runs, classified and RESTORED

Six pipeline runs and thirteen sweeps moved nothing that was not already a named family: the -tests-closure production re-emissions (bufio/{bufio,scan}.cs, internal/reflectlite/{swapper,type, value}.cs – both in the sweep’s own $closureFiles), the initᴛᴛtests() hook (+7, go/types and internal/fuzz), CRLF phantoms across every swept package, and os/exec’s proof page (date/commit plus skip 8 -> 7, which encodes this host’s environment exactly as the archive/tar symlink precedent does; the row PASSED 74 either way).

Two are one-line REAL movers and both are the born-stale class, restored rather than levelled per the standing rule: encoding/base64/base64_test.cs gains a directional channel field’s .RecvOnly initializer (the cargo-recv emission, which postdates that bank), and database/sql/driver/package_test_info.cs gains global using Value = object; (the recorded base64/base32/fmt alias-block staleness). Both level at their own rebank, not here.

RULING – the position map is INDIVISIBLE and build-shape-faithful; the host never claims testing/testing.go, and that premise becomes a host-limit entry (coordinator, 2026-08-21)

The net-mail-position-map lane’s three measured questions, ruled. The governing principle for all of them: no fabricated positions – a reported file:line pair must exist in the tree the file names, and every identity a frame reports must be a conversion-time FACT, never a plausible composite.

  1. Indivisible – ratified as recommended. The file half alone mints log/log_test.go:69, a position in neither tree. File and line ship together or not at all. The line half’s content is fixed by the principle – the reported line is the GO source line, derived from conversion-time facts (the converter knows every emitted statement’s Go position) – and its MECHANISM (per-file line table, frame-record side channel, or otherwise) is the arc’s design to make, reviewed per charter SS7 before implementation.

  2. The host’s doctrine stands: it never claims testing/testing.go. A hand-own with no conversion relationship to Go’s source cannot honestly report a position in it – the line would be fabricated even where the file name is suggestive. TestStack’s fifth-frame assert rests on the premise that the testing framework’s frames come from testing/testing.go, which is a property of the test BINARY that the converted deployment shape structurally is not (the hand-owned host IS the design, per the ONE-testing-package ruling). That is host-limit’s own text, and the assert becomes a signature-pinned host-limit entry when runtime/debug banks – with its retirement path named honestly: structural and permanent, unlike the os/exec relocatability entries, it does NOT retire at the .NET 10 single-file host, and the entry must say so.

  3. The identity form is BUILD-SHAPE-FAITHFUL – the lane’s measured -recurse regression is ruled out of any landing shape. Go bakes the path at compile time: GOROOT-relative under the toolchain’s trimming for published stdlib, absolute for an ordinary untrimmed user build. go2cs reproduces exactly that, at CONVERSION time: the stdlib corpus reports the trimpath/import-path form (runtime/debug/stack.go – the truthful description of a package published without local paths), and a -recurse user module reports what Go would have baked for the same build – the absolute source path as of conversion, recorded per file. main/main.go where Go answers C:/.../main.go is a divergence and does not land.

  4. The RuntimeCallerFrames guard is blind to file identity (separator booleans pass under a wholesale identity change – the lane proved it by running the change through all four phases). Queued: strengthen it to assert real file identity alongside the arc, so the arc’s own guard is the one that would have caught this.

Consequences for the terminal path: flag (#162) banks under the indivisible pairing (its one miss is the file token; a Go-mapped line satisfies its regex); log (#161) needs the full pairing (its residual after the file half IS the line); runtime/debug (#160) takes the fifth-frame host-limit entry alongside its runtime-capability disclosures. The arc is now fully specified: one design increment, three consuming rows, acceptance measured on flag first and last.

MEASURED + RULED – the SS5.4 discriminator fires for ROOT (1): math/big’s TotalAlloc row is real over-allocation at 50.9x Go, and the row REROUTES to the zh-box arc (coordinator harvest of the S0/S1 lane, 2026-08-21)

The ReadMemStats S0/S1 measurement stage is merged (design doc SS7.1, ten subsections; GolibTests 191/191 on the lane’s tree; probes committed). The decisive number: T = P to within 40 bytes across six windows, with Go’s same-machine baseline at 1.01x its own bound – the converted path allocates at 50.9x Go, and the process-wide-counter hypothesis is dead. Consequences:

⚠ RULING A LANDS BOTH HALVES — TestAutoAligned64 + TestHammer32/64 close; sync/atomic reads 107 of 108 and does NOT bank, and the layout arc’s whole stdlib constituency turns out to be ONE struct (2026-08-21, lane claude/atomic-align-layout)

Ruling A commissioned two halves against sync/atomic: the identity token gains alignment-truthful construction (1 row), and the zero-size-field layout emission arc closes the hammer family (3 rows). Both are implemented and gated. Three of the four rows close. The fourth, TestHammerStoreLoad, is the row the ruling itself queued to R.

Half 1 — the token becomes layout-truthful

ж<T>.PointerOrderToken now mints every allocation base 8-ALIGNED (the identity hash lifted clear of the low 32 bits) and derives a field’s token as base + the field’s Go offset, read from the SAME memoized GoFieldOffsets walk that answers StructField.Offset. TestAutoAligned64 asserts Offset == 8 and ptr&7 == 0 about one struct; those are now one answer from one source rather than two that happen to agree.

Both construction paths reach it with the same (struct type, field name) pair, so they agree by derivation rather than coincidence: converted code arrives under go2cs-gen’s generated Ꮡ<field> accessor, reflect’s FieldAliasBox under a DynamicMethod named goref_<field>. The reflect prefix became a named constant so the pairing is a stated contract between siblings, not a coincidence of two string literals.

Two decisions on the record rather than left for the next reader.

Half 2 — and the mechanism the ruling names has a sharp edge

Reinterpret’s size guard admits an alias only when SizeOf(TDst) <= SizeOf(T). Go’s atomic.Int32 is struct{ _ noCopy; v int32 } — four bytes, v at offset 0 — while the naive C# surrogate is eight, because a C# field always occupies at least one byte. So the hammer family’s (*Int32)(unsafe.Pointer(uaddr)) over a *uint32 was refused at 8 > 4, the atomics acted on a DETACHED COPY, and loads answered zero. Explicit layout at Go’s own offsets restores the size and with it the alias.

C# has no zero-size struct, and that is not a detail. A zero-size field laid out at Go’s offset SHARES bytes with the field Go puts there, and assigning it writes its one C# byte over the neighbour — measured, 42 -> 0. Go’s write writes nothing. The remedy is readonly: the field stays DECLARED, so reflect’s walk, NumField() and StructField.Offset still match Go, while the one unfaithful operation becomes unexpressible rather than merely unlikely.

That remedy exposed a latent go2cs-gen defect. TypeGenerator skipped the Ꮡ_ accessor for a blank field but not for the converter’s UNIQUIFIED repeats (__, ___), so it emitted a writable-ref accessor for a field Go says has no address. Inert until the field went readonly, then CS8160. The skip now covers the whole all-underscores blank family — which is what the rule always meant, and what GoReflect.FieldAccess’s own header already documents as one Go concept.

The eligible population: 90 structs in, ONE struct out

Every narrowing below is a measurement, and two of them are corrections to this lane’s own first answer:

Census step Count Why
stdlib structs with >=1 zero-size field 90 go/types over std, gc/amd64 sizes
minus MANAGED 63 excluded .NET forbids overlapping a managed reference with anything — the overlap is a TypeLoadException, not a layout
minus array<T>-bearing golib’s array<T> is a struct over a SHARED T[] backing, so it is managed whatever its element is (this lane’s first census got this wrong and counted 31 eligible; the corrected number is 27)
minus EMBEDDED fields an embed’s C# storage may be a ж<T> box, which a GO-type walk cannot see; the honest classification is “unknown”, and unknown leaves the arc. Costs runtime.mutex, sync.WaitGroup, internal/fuzz.pcgRand
minus NAMED zero-size fields Go gives &s.pad an address, so go2cs-gen emits a writable accessor and readonly is CS8160 (found by CNR on ReflectStructTagCopy, whose layout carries pad empty). Blank fields have no address and no accessor, so only they take the readonly form
minus Go-size 0 9 excluded C# has no zero-byte struct; Size = 0 means “natural size”. Emitting nothing is truthful, faking 1 is not

What survives, measured by a seeded whole-corpus reconvert: one struct, runtime.gcBits (runtime/windows/mheap.cs, Size = 1). sync/atomic’s six are hand-owned, so the converter never emits them and the rows are closed by that hand edit. The converter emission is correct and general; its constituency today is one struct, and saying “corpus-wide emission arc” without that number would imply a sweep the corpus does not contain.

The arithmetic: 107 of 108, and the last row is R’s

Full-pipeline on this host, sync/atomic reads 35 recorded of 108 both before and after — the host dies inside TestHammerStoreLoad and the cap on what gets RECORDED is not something this arc can move. So the yield is stated by the row-harvest-3 containment method (host run directly, the killing row excluded, diffed against the recorded go map), which is how the historical 99 and 104 were composed:

  pre-arc post-arc
TestAutoAligned64 fail pass
TestHammer32 / TestHammer64 C#="fail" (the board’s “largest residual”: unsafe.Pointer Reinterpret write-back) pass both sides
containment, TestHammerStoreLoad excluded 107 C# verdicts, 107 agree, 0 divergent
TestHammerStoreLoad host death, 367 MB record still fails; record 153 MB

sync/atomic does NOT bank. The roster bar is that every Test matches, and one does not. The residual is also NOT disclosable: runtime-capability’s admission test asks whether a truthful managed implementation exists at any cost, and for a serialization cap the answer is plainly yes — which is why the ruling queued it to R rather than admitting it.

This entry’s containment number predates the merge of R’s RecordGoroutinePanic cap (05133488c), which this lane’s base did not carry. With the cap bounding the serialization death AND the layout fix closing the lost writes the storm fed on, TestHammerStoreLoad may now record an honest verdict; that re-measure is this lane’s immediate next step on the real union, and the row either reaches 108 and banks, or fails honestly at 107 and waits on its own root.

Gates

Converter go test ./... ok, 145.7 s · GolibTests 185/185, including nine new guards (four pinning the token construction and its distinctness, five the layout mechanism — the size, the alias admitting, the measured clobber, and the readonly remedy) · full CNR clean but for this lane’s own two artifacts, each green on all four phases: the new ZeroSizeFieldLayout behavioral test and UnexportedEmbeddedMarker, whose golden was re-baselined for an intended change · go2cs.slnx Debug --no-incremental 0 errors (owed: golib changed) · seeded whole-corpus reconvert exit 0, marker gate 63 marked / 0 violations, 1,662 emitted, 1,653 identical, 9 changed, 0 new — and 8 of the 9 are documented FOREIGN carries (gcimporter.cs, row-harvest-2’s; the seven runtime unsafe.Pointer box-compare sites row-harvest-3 flagged for the next leveling regen), leaving runtime/windows/mheap.cs as the only mover of this lane’s making · runtime built with that one stamp applied, 0 errors, then restored. No corpus regen is banked here.

Three measurement failures this lane paid for, all its own, all recorded because each read exactly like a real finding. (1) A corpus reconvert piped the converter through Select-Object -First 10, which terminates the pipeline and KILLS the child: exit=-1 at ~71 of 304, reading precisely like a converter crash. CLAUDE.md records this trap, and this lane had cited it hours earlier before using it anyway. (2) The replacement run seeded with PowerShell’s Copy-Item -Recurse, which did NOT reproduce the corpus’s L3 per-GOOS folders — so every platform-varying file emitted FLAT, producing 19 marker violations and 186 “NEW” files, which reads like a catastrophic converter regression and is in fact a failed seed. The marker gate is what caught it, which is the entire reason the ritual runs it before any conclusion. Seeding via tar and verifying the count exactly (3,238 = 3,238) fixed it. (3) The first draft of the token distinctness guard asserted something Go does not promise and failed correctly; the assertion was wrong, not the code.

📉 RE-MEASURED on the union with R’s cap — sync/atomic is 107 of 108 FULL-PIPELINE (35 recorded → 108), and TestHammerStoreLoad’s residual is proven FOREIGN to the alignment arc by control (2026-08-21, lane claude/atomic-align-layout)

The entry above closed three of Ruling A’s four rows and stated its own gap: its containment number predated R’s RecordGoroutinePanic cap (05133488c), so the fourth row could not be measured honestly. The cap is now merged, this lane merged onto aaacb1e40, and the re-measure resolves the fork the ruling left open.

The cap changes the measurement, not the verdict

  before the cap with the cap
C# verdicts RECORDED 35 of 108 108 of 108
how the run ended host died inside TestHammerStoreLoad; 367 MB record failed to serialize clean; row contained as one per-test infrastructure-error
comparison artifact 69–367 MB 1.1 MB
agreeing with go test not computable full-pipeline (107 by containment) 107 of 108, FULL-PIPELINE

So the 107 is no longer a containment composition — it is every verdict the differential harness itself recorded, directly comparable to row-harvest-3’s 104 rather than composed differently. The three rows this arc closed (TestAutoAligned64, TestHammer32, TestHammer64) hold on the union.

⚠ A correction to this lane’s own first reading of that file: an initial divergence count of ZERO was a bad regex — it matched C#="[a-z]*" and the one divergence is C#="infrastructure-error", which carries a hyphen. The count is 1, not 0.

The fork resolves the second way: the storm was NOT a consequence of the lost writes

The ruling’s re-measure question was whether TestHammerStoreLoad’s Fatalf storm was downstream of the pre-fix detached-copy writes — in which case the layout fix would close it and the row would read

  1. It does not. With TestHammer32/64 fixed and passing, the pointer hammer still fails, now bounded and legible:
Pointer: %!x(uintptr=0) != %!x(uintptr=7864)
testing: FailNow called from a goroutine other than the test goroutine for TestHammerStoreLoad

hammerStoreLoadPointer treats the pointer VALUE as a packed pair and asserts v & 0xFFFFFFFF == v >> 32. The observed v is N << 32 — low half zero, high half a small counter — so the uintptr → unsafe.Pointer → uintptr round-trip is not preserving the number the test stored.

That shape is NOT this arc’s, and the cap is what finally allowed the control

N << 32 resembles this arc’s new allocation-base format (identityHash << 32) closely enough that attribution could not be assumed. The control had been impossible: at master the record was 367 MB and never serialized, so the failure TEXT did not exist to compare. Under the cap it does.

Control run: golib reverted to the union’s version (no token change; the layout fix and the cap both retained), same single test, same host. Result — the identical shape:

Pointer: %!x(uintptr=0) != %!x(uintptr=12768)

Both sides also drop the same order of records (15.4M control vs 15.8M with the arc), so neither the token construction nor the layout fix feeds the storm. The residual predates this arc entirely and belongs to the unsafe.Pointer round-trip, not to the ж token or to zero-size layout.

Verdict: no roster row, and the residual is ROOTED not disclosed

sync/atomic stays off the roster at 107 of 108 — the bar is that every Test matches. The residual is not disclosable: runtime-capability’s admission test asks whether a truthful managed implementation exists at any cost, and a faithful uintptr ↔ unsafe.Pointer round-trip plainly has one. It is an arc with a price, and the price is now much better specified than “host death”: one named test, one named invariant (v & 0xFFFFFFFF == v >> 32 after a round-trip through unsafe.Pointer), a bounded 1.1 MB artifact, and 107 of 108 sitting behind it.

Roster arithmetic is therefore UNCHANGED at 158 / 215 · 18,425 · 79. The row is one root away and the root is named; it waits on that root, not on ceremony.

Gates for the merge result

The (b) commit’s gates stand as recorded. On the merge result: golib and the capped src/core/testing host both build clean, and the full sync/atomic pipeline runs to completion — which is itself the merge-result evidence, since a banking merge owes its own row’s re-proof and this lane’s row declines to bank. The corpus is restored: the -tests artifacts are removed and type.cs.auto — the review sibling a -tests run refreshes and an overlay does not — is restored rather than banked, per CleanupBacklog item 18.

✅ BANKED — sync/atomic validates 108/108 + 0 as row #159; the residual was the uintptr → unsafe.Pointer → uintptr round-trip, and closing it closes a GC-invisible dangling-reference hazard with it (2026-08-21, lane claude/unsafe-pointer-roundtrip)

The atomic-align-layout entries left this row at 107 of 108 full-pipeline with the residual rooted: TestHammerStoreLoad failing Pointer: 0 != N (v = N << 32), proven FOREIGN to the alignment arc by control, invariant named — v & 0xFFFFFFFF == v >> 32 must survive the round-trip. This lane closed that root. The re-run validates: 108 go / 108 C# / 108 matched / 0 disclosed / 0 errors, status: validated, with TestAutoAligned64, TestHammer32, TestHammer64 and TestHammerStoreLoad all passing both sides in one run.

The mechanism, exactly

TestHammerStoreLoad reinterprets one shared uint64 as *unsafe.Pointer (and as *atomic.Pointer[byte]) and hammers fabricated pointer VALUES through the atomic entry points — Go’s own comment: “write barriers on values that aren’t real pointers.” Emitted, the reinterpret is (ж<@unsafe.Pointer>)(uintptr)(paddr): a native-backed box whose pointee is a managed CLASS. For such a box ref addr.Value is Unsafe.AsRef<Pointer>((void*)A) — the slot reinterpreted as a CLR reference slot. Two consequences, each sufficient alone:

The fix: the slot holds the pointer’s VALUE

Go’s semantics for that memory are unambiguous — the 8 bytes hold the pointer’s value. The managed model cannot express that through ref Value (no ref Pointer can alias a number slot), so every ACCESS converts number ↔ box at the entry point, where the pointee type is known:

Layer Change
golib ж<T> IsNative/NativeAddress go PUBLIC as the discriminator (the seam documented the same way ManagedPointerTokens is — a runtime seam, not a Go surface), plus three atomic pointer-word accessors (ReadPointerWord, ExchangePointerWord, CompareExchangePointerWord). The one unsafe seam stays in golib: converter-emitted csproj compiles with AllowUnsafeBlocks=false, so the consumers could not host it
sync/atomic non-generic (doc_impl.cs) LoadPointer/StorePointer/SwapPointer/CompareAndSwapPointer branch on IsNative: word ops on the slot, (uintptr)p reading a Pointer’s number nil-safely, new Pointer(n) marking the zero address nil — so nil round-trips exactly
sync/atomic Pointer<T> methods (type.cs, hand-owned) Load/Store/Swap/CompareAndSwap branch identically; the mint (ж<T>)(uintptr)n is the same conversion the emitted reinterpret itself uses. Managed arms all unchanged

CAS on the native arm compares NUMBERS — which is Go’s comparison for unsafe.Pointer and the answer Pointer.Equals already gives, so no comparison surface moved.

The Fable-class check the assignment demanded: none of the three settled things bends

No doctrine amendment was needed; the fix lands BELOW the doctrine, at the boundary where raw memory meets the managed pointer model. The write-up ratified in advance: the entry stands on measurements only.

Guards, proven failing-first

GolibTests.NativePointerSlotAtomicsTests, six tests: the slot-content assert (store, then read the aliased storage back through its own managed box — DETERMINISTIC, no collector timing), the across-a-collection round-trip, Swap/CAS by number, nil as the zero word, the Pointer<T> method arm, and a single-threaded mini-hammer with forced collections. Neutered (the eight entry-point arms stashed; golib accessors left in place), 4 of 6 fail — the four deterministic ones. The two GC-shaped ones pass neutered because the test’s own locals root the stored box — recorded here so nobody reads them as the failing-first proof; the slot-content assert is.

ROOTED, NOT TAKEN: the managed arm of CompareAndSwapPointer CASes by REFERENCE and reports by VALUE

Interlocked.CompareExchange(ref addr.Value, @new, old) == old over a MANAGED *unsafe.Pointer slot: the CAS matches by reference, but the == verdict runs Pointer.Equals — by value. A re-minted box holding the current number therefore FAILS the exchange and REPORTS success: a silent lost CAS. Nothing banked reaches it (the managed arms are exercised and green through sync, sync/atomic and every atomic.Value consumer), it is plainly fixable (a value-compare CAS loop over the reference slot), so it is not disclosable — it is priced here for a follow-on and deliberately not smuggled into a banking commit at zero coverage.

Gates and arithmetic

Full pipeline -test-action all validated, exit 0 — proof page docs/validation/current/sync.atomic.md, the validation index, and the package README’s Tests badge all emitted by the validating run itself · GolibTests full · go2cs.slnx Debug --no-incremental · full behavioral suite · own-row sweep at the merge-result discipline’s lane half · six reflect-consumer canaries derived at gate time by DIRECT IMPORT against this tree’s 159-row roster (go/types 557, encoding/json 491, crypto/tls 400 + 2, encoding/xml 386, html/template 243, time 159; gcimporter absent by derivation) · committed test sources per the validated-package policy · type.cs.auto RESTORED not banked (the review sibling, CleanupBacklog 18) · no converter change, no corpus regen, no package_info.cs record moved — no CNR and no go generate owed.

Roster: 158 → 159 of 215 (74.0%) · 18,425 → 18,533 matching · 79 disclosed. Header recomputed from the table, not incremented.

🐧 MEASURED — the Linux operational-parity census: the fleet has a Linux lane again, the FIRST TWO rows ever VALIDATE on Linux, the native stdlib build is 0 errors, and every failure roots to a KNOWN class (2026-08-21, lane claude/linux-parity-census, laptop G + WSL2)

The assignment: stand up a Linux lane, run the instrument ladder, census what runs, what fails, and what each failure roots to — platform gap vs harness gap vs corpus gap — priced per PLAN-linux-operation.md’s F-series. All of that is below, and the headline outruns the assignment: the Phase-4 differential pipeline, which F1 recorded as “structurally unavailable” on Linux, validated two banked rows end to end — the L3 corpus quietly retired the premise.

The lane, and why it had to be rebuilt at all

The plan’s provisioned distro (F15 ✅, the r47a recipe; r48b’s measured ladder) ran on a 24-CPU WSL2 host — the i9-13900K’s signature, the machine that died 2026-08-09. Every Linux number in the plan was an orphaned baseline; nothing in the fleet could run the ladder. This lane rebuilt it on laptop G, collaboratively (the human ran the elevated wsl --install -d Ubuntu-22.04 and one process unstick; everything after was scripted):

The ladder — five rungs, ~24 minutes total, every verdict classified

Rung Result Classification
1 · converter go test ./... ok, 79.7 s, full suite — natively no gap; ~1.8× FASTER than the same suite on the Windows side of this laptop
2 · check-no-regression.ps1 byte-identical for 625 of 631; 6 NOT MEASURED 1 by design (FindFirstFileData, the plan’s own note) + 5 new members of the same class — see the F8 growth below. Platform gap, priced
3 · go2cs-stdlib.slnx -p:GoTargetOS=linux, NATIVE host 0 errors, 149 warnings, ~7.9 min no gap — the first native-Linux compile of the full 307-project converted stdlib in the project’s history. The Windows-host control run the same day: 0 errors, 149 warnings — the warning count matches exactly
4 · behavioral shard (34 Array* projects, all four phases) first attempt: apphost libhostfxr not found → one-line harness gap (DOTNET_ROOT); retry: 24 of 34 fully green including Output, 10 fail exit code C# 2 vs Go 0 the 10 root to F1, self-diagnosed: TypeInitializationExceptionos_package → internal/poll → syscall → DllNotFoundException("kernel32.dll") — the windows-flavor corpus on a linux host, with the corpus’s own RID-fallback WARNING banner printing the situation AND the remedy. The 24 passers print the same banner and survive: their init path never touches os_package. F1’s blast radius is program-init-reaches-os, not all-programs — materially narrower than the plan’s “all 515 stdout comparisons fault”
5 · validated-sweep shard unicode/utf8 PASS 14/14 (92 s) · container/heap PASS 7/7 the first Phase-4 rows ever validated on Linux. The full differential pipeline — convert on a Linux host, build against the corpus, run under the Go-semantics test host, compare verdict-for-verdict with go test — end to end, twice. (container/heap’s first attempt failed CS0246 without DOTNET_ROOT; with it, clean — same one-line harness gap as rung 4, two instruments confirmed unblocked by one export)

The one F-series repricing this census forces: F8’s set is SIX, not two

The five new conversion-time NOT MEASURED packages are all guards the Windows syscall arcs added after r48b’s baseline, each dated to its creating commit: PointerOutParameter (07d41538b, the **T out-parameter cell), SockaddrRoundTrip (1f641dade, the L10 blittable seam), SystemCertVerify (cac5455a7, the CryptoAPI chain), WsaProtocolInfo (0e9c34bf1, the WSAPROTOCOL_INFOW mirror), plus UnsafeStringEmpty (same symptom; provenance traces only to the casing commit d3223d252 — confirm its root when gating). This is the expected shape of the campaign’s own progress: every new Windows-semantic guard is a new F8 member. The gating work F8 priced should enumerate from CNR’s NOT MEASURED list at gate time, not from a frozen list of two.

What the census says the parity rung actually costs

Materially LESS than the plan priced, because two of its walls fell to other arcs in the interim:

  1. F1 is half-retired by measurement. The corpus compiles natively (rung 3), pure-compute rows VALIDATE (rung 5), and the failing class self-diagnoses with its remedy. What remains of F1 is plumbing, not architecture: the behavioral runner and the sweep need to bind the GoTargetOS=linux corpus on a linux host (today a plain Debug build binds the windows default — the banner’s own instruction), and the syscall-backed rows need the netpoll-arc Linux halves measured row by row. Measure, don’t assume held: nobody predicted two rows would validate today.
  2. One harness line: DOTNET_ROOT must be exported wherever the SDK lives outside the default location — apphosts (BehavioralRunner, the built test programs, sweep children) do not resolve the runtime from PATH. Two instruments proven unblocked by the one export. Belongs beside $IsWindowsHost in _paths.ps1’s documentation or the lane-setup recipe.
  3. The F15 recipe is current and fast (~4 min user-space on a fresh distro), with the OOBE transaction-lock trap above as its one new footnote.

No fixes landed beyond the sanctioned one-line unblock (an env var in the lane’s own scripts); the corpus, converter, golib and harness are all untouched by this lane — the deliverable is this census. The Windows-host GoTargetOS=linux control build (0 errors, 380 s, run before the distro existed) stands as the cross-host pair to rung 3.

The next rung, sized by this census

A Linux lane that VALIDATES banked rows exists today at the cost of: one DOTNET_ROOT export + -u root + the F15 recipe. The ordered work to widen it: (a) teach the two runners the GoTargetOS host default (or a flag) so behavioral Output and syscall-adjacent sweeps bind the linux corpus; (b) re-enumerate F8’s gating set from CNR at gate time (6 today); (c) sweep the banked roster’s pure-compute rows on Linux wholesale — rung 5 suggests a large fraction validates as-is, which would make the eventual “Linux-validated” column a measurement campaign, not a porting one.

LANDED + ROUTED – ReadMemStats S2/S3 merges: the recorder is always-on, ReadMemStats reads 0.0 B/call, TestReadGCStats passes all nine – and TestFreeOSMemory routes to OBJECT LIFETIME as a codegen-liveness candidate (coordinator harvest, 2026-08-21)

The implementation lane delivered the ratified design end to end (GcPauseRecorder.cs, the high-water HeapReleased with both honesty notes as code comments, NumForcedGC, the zero-rule fields asserted by guard, runtime/metrics untouched per OQ-4). The binding refinements closed hard: the 288 B/call GCMemoryInfoData box left the read path entirely – 0 B across all 200 bracketed windows, ceiling tightened 320 -> 0 – and every recorder read enforces Generation == MaxGeneration. Union gates at the merge: GolibTests 211/211 (the GC surface and the #159 native-slot change coexisting), spot sweeps of the protected consumers.

runtime/debug is now 3 of 9, and its remaining bill is fully composed:

Queue, small: (1) core/math/big and core/runtime/debug are in go2cs.slnx’s build closure via GolibTests but unregistered in the .slnx – one line each, deferred twice now for lane conflict-avoidance; take at the next quiet point. (2) time’s born-stale banked test sources have now surfaced in THREE lanes – the leveling commit is due. (3) Two CLAUDE.md budget rows re-measured this harvest (updated in the same commit): full go2cs.slnx ~3,546 s at 722 projects; full behavioral suite ~6,552 s at 603 packages (i7-5820K, 2026-08-21).

FUTURE ERA – step-through-GO debugging: the position-map tables are a source map, and .NET already has the player (user idea, recorded 2026-08-21)

An idea banked for a later era, so it is not lost to churn. The GoPositionMap line tables carry exactly the data a debugger needs – and .NET’s native source-mapping machinery is #line pragmas compiled into PDB sequence points (the mechanism behind stepping through .cshtml in Razor). The committed corpus rightly refuses #line (+28-47% of lines against the reads-like-Go objective), but a future OPT-IN build mode could inject the pragmas TRANSIENTLY at build time, generated mechanically from the position-map tables – never committed, full fidelity: breakpoints in .go files, stepping through Go source while the converted .NET assembly executes, TypeScript-over-JavaScript style.

Composes further with SourceLink: PDB documents mapped to github.com/golang/go at the pinned release tag would let a consumer F11 from their own C# into Go’s actual stdlib source, version-exact, from a published go.<pkg> NuGet package – the same mechanism that steps into the .NET BCL today, and a natural sibling of the nugetgo self-describing-package convention.

Era: post-terminal, .NET-10-or-later polish. Prerequisites already banked: the position-map arc (the data), the pinned-release publish convention (the SourceLink target). Nothing here is scheduled; this note exists so the right era finds it. —

2026-08-21 · Linux Phase-4 pipeline: the $(go2csPath) case-insensitive environment race — ROOT-CAUSED, harness-pinned, converter fix priced (lane G, claude/linux-measure-1)

Symptom (killed Linux measurement campaigns v1–v3): intermittent, package-shuffling hard build failures inside -tests -test-action allwarning MSB9008: The referenced project /root/go2csgen/go2cs-gen/go2cs-gen.csproj does not exist followed by a CS0246 storm on every golib type (GoType, slice<>, ж<>, string…), reported by the sweep as Go="pass" C#="" for the whole suite. 2–27 s fast-fails, ~coin-flip rate per pipeline child, retries sometimes green (v3: 8 retry-passes vs 17 retry-fails), isolated purged re-runs always green (14/14), two-package alternation reproducing 3-for-3 within ≤2 cycles. Every plausible suspect A/B-eliminated along the way: shared compilation (UseSharedCompilation=false changed nothing), straggler children (settle-to-zero + pkill), emission drift (csproj md5 stable), dotnet run alone (14/14 clean), pwsh wrapper (bare-converter repro), GoTargetOS plumbing.

Root (binlog-proven): four links.

  1. main.go:91-93 — when env GO2CSPATH is unset, the converter defaults it to filepath.Join(homeDir, "go2cs") and os.Setenvs it into its own environment. On the Linux lane the value is /root/go2cs — no trailing separator, and the clone happens to LIVE at ~/go2cs, so the default names a real tree (on Windows %USERPROFILE%\go2cs is the deploy-core root: valid or inert, never this repo’s src).
  2. testConversion.go:5663 — every pipeline child gets cmd.Env = append(os.Environ(), "go2csPath="+ensureTrailingSeparator(options.go2csPath)). os.Environ() now carries GO2CSPATH=/root/go2cs; the append adds go2csPath=/root/go2cs/src/. Two POSIX-distinct variables, both in the child env block.
  3. MSBuild resolves environment-derived properties case-insensitively, so both entries map to the ONE property $(go2csPath) — and which value wins is enumeration-order-dependent inside the .NET/MSBuild env-table plumbing: a per-process coin flip. Diagnostic replay of the failing build’s binlog (MSBUILDDEBUGENGINE=1) states the losing draw verbatim: Property 'go2csPath' with value '/root/go2cs' expanded from the environment.
  4. With $(go2csPath)=/root/go2cs (sans slash), $(go2csPath)gen/... concatenates to /root/go2csgen/... and $(go2csPath)core/... to /root/go2cscore/...: the analyzer and every stdlib ProjectReference dangle (restore graph confirms: _RestoreGraphAbsoluteProjectPaths=/root/go2cscore/...), golib types vanish, storm follows.

Why Windows never saw it in five weeks of sweeps: Windows environment blocks are case-insensitive at the OS level — GO2CSPATH and go2csPath are ONE slot, the converter’s append lands last and wins deterministically. The race is structurally Linux/macOS-only (POSIX case-sensitive environ + MSBuild case-insensitive property lookup). F-series classification: harness/converter platform gap, not corpus, not CLR.

Neutralization (landed, sanctioned harness class): pin GO2CSPATH to the correct slash-terminated src root so both names carry one value and either race winner is right — _paths.ps1 now exports it on Linux hosts when unset (beside the existing GoTargetOS default), and the campaign driver exports it in its env. Validated under the exact reproducing conditions: the 3-for-3 alternation runs 8/8 clean; campaign v4 relaunched under the pin.

Converter fix (PRICED, not patched — converter code is out of the measurement lane’s scope): the complete fix is at the child-env construction (testConversion.go:5663): scrub every case-insensitive variant of go2csPath from os.Environ() before appending the canonical entry — that closes the WHOLE class, including the nastier variant where a user’s ambient GO2CSPATH names a different real tree and the child build nondeterministically binds the wrong stdlib (a verdict that moves with the shell — exactly what the explicit -go2cspath doctrine exists to prevent). Optional hygiene on top: main.go:93 need not os.Setenv at all — the defaulted value is consumed immediately as the flag default and nothing downstream reads the env var again. Guard shape: a converter unit test over the child-env builder asserting exactly one case-insensitive go2csPath entry survives. Small, mechanical, Windows-behavior-neutral.

Full forensic trail (binlogs, replay, alternation logs): /root/f4* on the Linux lane distro; mechanism summary in the fleet mailbox entry of this date.

THE POSITION-MAP ARC LANDS: one record per converted file carries a WHOLE position, log and flag bank on it, and runtime/debug’s TestStack comes down to its one structural frame (2026-08-21, lane claude/position-map-arc)

The ruling’s three answers, implemented and measured. The design note is committed FIRST and on its own (docs/phase4/DESIGN-position-map.md) so a veto costs rework rather than archaeology; this entry records what the mechanism measured, what it forced that the ruling did not fix, and the two rows.

The mechanism, in one sentence

One [assembly: GoPositionMap("<go file>", "<cs file>", "<table>")] per converted file, emitted INTO that file, carrying the Go file’s identity AND its C#-line → Go-line table together.

Indivisibility is structural, not a rule. Both halves come from the one record, so no code path exists that could report a Go file with a C# line. A frame either finds a record and reports a Go position that exists, or finds none and reports the converted .cs position it always did — which is what golib, the BCL, the hand-owned test host and every whole-file hand-own do, each for the reason goFrameName already declines to invent a Go name for them. crypto/subtle/xor_generic.cs will not name crypto/subtle/xor_generic.go: its C# was WRITTEN, and no line of it corresponds to a line of Go.

Priced against the alternatives, and measured

#line (the board’s PDB-transport route) is disqualified twice over: +28–47% of corpus LINES, and CS diagnostics relocate onto .go files that are not in the project — the repo’s whole method is root-causing against the emitted .cs. A side-car resource and a per-package generated .cs price the same as the winner; what separates the winner is that two rules the others must STATE fall out of it for free — a hand-own carries no record because it is never re-emitted, and an L3 per-GOOS variant carries its own because the record is IN the variant.

Measured on a seeded whole-corpus reconvert:

   
files carrying a record 1,339 (all GOROOT-relative — the corpus is std, and std is what cmd/go trimpaths)
attribute text, total 370,644 bytes (0.35 MB)
corpus .cs, total 38.73 MB
share of the corpus 0.91%
largest single table 7,168 chars, crypto/internal/nistec/fiat/p521_fiat64.cs

The identity: three forms, and the middle one is FORCED

Discriminated by the SOURCE’s location, never by the conversion mode — which matters, because the -tests pipeline converts GOROOT packages through a single-package run, and a mode-keyed rule would hand log, flag and runtime/debug absolute GOROOT paths and fail TestStack outright.

A source beside its emitted .cs records its BARE NAME, rooted at run time against the .cs file’s own compile-time directory. Baking the absolute path instead is not an available option: it names a directory that does not exist on the next clone — a fabricated position on every other machine, and check-no-regression reporting the whole behavioral corpus as drifted in every sibling worktree on the first run. This is what lets the arc’s own guard assert Go agreement at all.

The ruling’s main/main.go regression is now structurally impossible

Not avoided — impossible. No code path anywhere composes a file name from a package, namespace or class name; the runtime reads a recorded string and, in one case, prefixes it with a directory the PDB gave it. positionMap_test.go pins it directly.

The guard the ruling queued, strengthened — and what it now measures

RuntimeCallerFrames passed all four phases under the file-half change because its five file assertions were separator booleans and an equality, every one invariant under a wholesale change of what the file names. It now asserts the property, printing VALUES so the stdout comparison against go run . IS the assertion and no constant in the guard has to be kept in step with the source it names:

                                   go run .                     converted C#
  caller file tail:                RuntimeCallerFrames/main.go  RuntimeCallerFrames/main.go
  caller file rooted:              true                         true
  caller line:                     27                           27
  caller line two frames up:       107                          107
  traceback names a go file:       true                         true

The same five lines under the file half alone would have read main/main.go and false.

The rows

flag — 24 matched, 0 disclosed. Exactly the ruling’s prediction: its one miss was the file token, and a Go-mapped line satisfies TestDefineAfterSet’s .*/flag_test.go:.*.

log — 8 matched, 1 disclosed. The yield signal the file half could not deliver: TestAll passes, because log_test.go:63/:65 is now what the frame reports. Its residual is TestDiscard, an at-most-one AllocsPerRun assert — alloc-profile, on bufio’s TestReadStringAllocs precedent: Go’s one allocation is the variadic []any, and its func(b []byte) []byte closure over format/v does not escape Output, so Go keeps it off the heap; the converted call must heap-allocate the params array, the display class the C# compiler emits for that same closure, and its delegate. Measured 3 golib-site allocations per run against Go’s 1, and that is a LOWER BOUND. The count is comparable — it is WHERE the allocations live that differs, which is what separates this class from alloc-count-semantics.

runtime/debug — 2 of 9, unchanged, and its TestStack is down to ONE assertion. Measured independently of the board’s file-half run and agreeing with it exactly:

  in line "\tC:/…/src/core/testing/TestExecution.cs:593", expected prefix "\ttesting/testing.go"

All four converted frames now name Go’s file. The fifth is frame("testing/testing.go", "") — the test framework’s own frame — and it is the ruling’s point 2 in mechanism form: it is not that the host DECLINES to claim testing/testing.go, it is that no conversion ever recorded a position for it. The row does not bank here (its ReadMemStats half is a separate arc mid-measurement); the entry it will take is ready to paste:

{
  "name": "TestStack",
  "class": "host-limit",
  "signature": "expected prefix \"\\ttesting/testing.go\"",
  "reason": "the fifth frame() assert requires the testing framework's own frame to name GOROOT/src/testing/testing.go, which is a property of Go's test BINARY: its testing package is compiled from that source. The converted deployment's testing package is the hand-owned host (src/core/testing) — the ONE-testing-package ruling's design, not an unimplemented conversion — so no conversion recorded a position for it and it honestly reports its own .cs position, exactly as golib and the BCL do. STRUCTURAL AND PERMANENT: unlike the relocatable-single-file entries, this does NOT retire when the host publishes self-contained and single-file, because the host would still be hand-written C# with no line-for-line relationship to Go's testing.go. It retires only if the test host itself becomes a conversion of Go's testing package, which the ONE-testing-package ruling deliberately forecloses"
}

What the mechanism forced that the ruling did not fix — written up, not self-ruled

The ruling says “stdlib → trimpath form via your goFrameName derivation + the two suffix rules”. This design RECORDS it instead. Three reasons, and the departure is flagged rather than assumed: the record has to exist for the line half regardless, so the derivation buys nothing; recording is strictly closer to the ruling’s own principle (“a conversion-time FACT, never a plausible composite”); and it retires the lane’s two measured suffix rules from the FILE half — a test variant’s file is runtime/debug/stack_test.go because that is what the converter SAW, not because a class-name suffix was stripped. The rules stay, unchanged, for the FUNCTION half, where Go genuinely keeps _test (runtime/debug_test.T.method), and where goFrameName still derives from the package because a function name IS a property of the package. Reverting to the derivation for GOROOT sources is localized and priced in the design note’s §11.1.

The measurement that no amount of reading the code would have found

An in-text sentinel is invisible to consumers of the finished file but NOT to the converter’s own reads of emitted text. On the first whole-corpus reconvert, 110 files emitted a block body where they had emitted an expression-bodied lambda: convFuncLit decides that collapse by testing that nothing but the block’s opening brace precedes the return, and it reads the block back as a string. Nothing was wrong with the map; the text simply was not neutral. The rule is now explicit and recorded in positionMapOperations.go — a site that INSPECTS or REWRITES captured block text reads it through stripPositionSentinels, a site that merely APPENDS it must not — and the standing guard is the corpus-wide byte-identity check below, which is why that check is a gate and not a formality.

Byte identity, and the 8 files that are LEVELLED rather than restored

Seeded reconvert against the committed tree, with each file’s own record removed: 3,234 byte-identical, 8 differing. All 8 were proven PRE-EXISTING by a control converter built from HEAD — its emission differs from the committed tree in exactly the same 8 files, and my emission is byte-identical to the control’s modulo the record. They are the born-stale class (the value-alias emission in runtime/{alg,map,map_fast32,map_fast64,mbarrier,traceback}.cs and runtime/pprof/map.cs, the multi-result any-slot return-type prefix in go/internal/gcimporter/gcimporter.cs), 28 lines in total.

They are levelled, not restored, and the mechanism forces it: a position map is computed from the text it describes, so restoring the text while keeping the record would produce wrong line numbers for those 8 files, and dropping the record would leave 8 converted files — runtime/traceback.cs among them — unmapped. Stated here because it is a change this bank carries that is not this arc’s.

Gates

Gate Result
design note committed FIRST, on its own a3e1d2229
converter go test ./... (incl. projitemsIntegrity, TestStdLibMetadataInSync) green, 288 s – so no stdlib-metadata drift owed
seeded whole-corpus reconvert vs committed, record removed 3,234 byte-identical / 8 pre-existing (control-proven)
go2cs-stdlib.slnx 307 projects, 0 errors, 409 s
go2cs.slnx (owed after a golib/runtime API change) 0 errors, 357 s
behavioral suite, all four phases PASS 603/603 transpile+compile+golden, 577 stdout comparisons vs go run ., 0 failures, 1,775 s
check-no-regression.ps1 NO REGRESSION – byte-identical .cs and .csproj across all 630 behavioral packages, 0 NOT MEASURED
GolibTests 181/181
-tests -test-action allflag 24 validated, 0 divergent
-tests -test-action alllog 8 validated, 1 disclosed (alloc-profile)
-tests -test-action allruntime/debug 2 of 9, TestStack at ONE assertion (the host frame)
full validated sweep, 160 packages / 18,457 verdicts 160 pass / 0 fail, 18,457 verdicts, 17,491 s (4 h 51 m)

The sweep is the whole roster rather than a derived canary set, deliberately: this is a corpus-wide EMISSION change – every converted file in the tree gained a record – so the direct-import derivation (runtime: 53 banked rows, largest go/internal/gcimporter 583, crypto/rsa 559, go/types 557, encoding/json 491, crypto/tls 400) and the board’s own frame-file blast radius (12 rows / 1,539 verdicts) are both proper subsets of what ran.

Post-merge gates, at the MERGE RESULT

The lane-tip proof above is necessary and not sufficient — the tls-regression rule. claude/union-157 (42282b2aa, row #159 sync/atomic) merged in; both conflicts resolved by UNION, and the roster arithmetic recomposed from the union rather than from either side: 161 / 215 = 74.9%, 18,565 matching verdicts, 80 disclosed, checked by summing the 161 rows rather than by trusting the header.

Gate (post-merge) Result
go2cs-stdlib.slnx, merged corpus, --no-incremental 0 errors, 466 s
own row flag PASS 24
own row log (filter also swept log/slog/internal/benchmarks 3, testing/slogtest 17) PASS 8 / 3 / 3 pass, 0 fail
the merge’s own new row sync/atomic PASS 108 — and re-validated through the pipeline at 108 / 1 skipped-identically / 0 divergent
reflect canary go/types PASS 557
reflect canary encoding/json PASS 491
reflect canary crypto/tls PASS 400
reflect canary encoding/xml PASS 386
reflect canary html/template PASS 243

The canary set is DERIVED at gate time, not remembered: the merge touches src/core/golib/GoReflect.FieldAccess.cs, which is reflect-bridge-touching, so the set is the five largest banked reflect consumers by verdict count over THIS tree’s 161-row roster (64 rows qualify). It differs from the list CLAUDE.md records — go/internal/gcimporter (583, the largest row overall) is absent because it imports reflect nowhere — which is exactly why the rule says derive rather than carry.

sync/atomic needs a budget above the pipeline’s 2-minute default: at the default its suite self-terminates and reports Go="pass" C#="" for eighteen tests, which reads precisely like total conversion failure and is not one. Validated at -test-timeout 15m, swept at -TestTimeout 20m.

A doctrine point the merge produced: a MERGE of a mapped file invalidates its map

Worth its own line because it generalizes past this arc. The map is a DERIVED artifact of the emitted text, so anything that changes that text without re-deriving the map leaves a plausible-but-wrong one — the exact class the ruling forbids, and silent, because no gate reads a line number against its source.

This merge did NOT trigger it, and why not is the instructive part. The one file the claude/union-157 merge changes substantially, src/core/sync/atomic/type.cs (+69/−20, Ruling A’s explicit layout), carries no record to invalidate — it is a whole-file [module: GoManualConversion] hand-own, so by construction it was never mapped and its frames report the converted .cs position. That is the indivisibility rule doing its job at exactly the moment it would have mattered. What the merge DOES leave is the complementary state: sync/atomic’s converted test artifacts arrive from a side that predates this change, so they are UNMAPPED rather than mis-mapped — re-emission makes them uniform with the other 160 rows, and is owed for that reason.

The rule: a merge that changes any converted .cs owes a re-emission of that file’s package before the gates. It is cheap — a filtered -stdlib, or the package’s own -tests -test-action all, which re-emits and re-validates in one step — and it is mechanical, because the affected set is exactly git diff --name-only <base> <theirs> -- 'src/core/**/*.cs' minus the files that carry no record anyway (golib, *_impl.cs hand-owns, package_info.cs/package_init.cs). Same shape as the standing “never convert twice into one root” rule: a step in the ritual, not a diagnosis to re-derive.

Standing dirt, classified and RESTORED

The -tests closure re-emissions from flag’s own pipeline run: flag/flag.cs’s using os -> using Δos collision rename and flag/package_init.cs’s initᴛᴛtests() hook (+7). Both are the named standing class and both are line-count NEUTRAL, so neither disturbs the position map that now rides in those files – restored to the -stdlib emission, where the tree rests. log/log.cs did not move at all. src/core/README.md came back as a CRLF phantom from the overlay (empty numstat) and was restored. runtime/debug’s converted test artifacts were removed rather than committed: the row does not bank here.

THE RELOCATION ROUND: the records move to the info files by user directive, the migration is the diff, and the design’s own SS6.4 doctrine caught the one defect before it banked (2026-08-21, lane claude/position-map-arc)

The merge was HELD one round: two coordinator entries posted before the lane’s final gates – the placement directive and its format addendum – went unread until the hold named them. Both misses would have been caught by a pre-gate mailbox poll; the lane adopts that rhythm. The round itself, in the order it ran:

The relocation (4c7fa5398)

Every [assembly: GoPositionMap] record leaves its converted file and lands in a delimited expository block – // <GoSourcePositionMaps>// </GoSourcePositionMaps> – in the package-info file of the COMPILATION that compiles the mapped source: package_info.cs for production, the test-info anchors for -tests variants, routed exactly as the GoImplement records are. Semantics-free by construction: the record shape is unchanged, indivisibility is a property of the record, and the assembly-scoped lookup cannot tell where an attribute was declared – the runtime did not change by a byte. Verified per the directive: the stdlib-metadata extract() does not scoop the block (pinned by TestStdLibMetadataExtractIgnoresPositionMaps against a synthetic info file carrying all three record families).

Two things the relocation surfaced that the in-file placement had made structurally free:

The migration (ea659f141) – and the defect the A/B caught

The diff IS the move: 1,811 corpus sources and 1,308 behavioral sources/goldens lose exactly their two record lines; 490 corpus info files and 631 behavioral package_info.cs gain sections. One named mover: runtime/windows/mheap.cs levels the union’s zero-size explicit-layout emission with a freshly derived table. The 148-record delta between old and new corpus counts closed exactly: declaration-only files (const tables, doc.cs) had EMPTY tables – GoLineFor answers 0 for every line, behaviorally identical to no record – and the relocated converter emits nothing for them.

The 469 committed test artifacts -stdlib cannot reach were migrated by script – and the script’s first output was WRONG in a way nothing would have flagged red: it moved each table verbatim while deleting the record’s two lines from the file top, leaving every C# line in every moved table skewed by exactly +2. That is SS6.4’s doctrine – the map is derived from the text it describes – firing on the migration itself, and it was caught because the round’s A/B ran one package through the real pipeline and compared bytes: sort’s script tables differed from the pipeline’s in their first delta. The fix re-derives (decode, shift every line by -2, re-encode with the canonical encoder), validated closed-loop TWICE: sort’s 7 records and log’s 1, byte-exact against pipeline ground truth in both rounds.

Gates, proportionate to a placement move per the hold entry

Gate Result
migration diff shape exactly the move – every non-info file -2/0, one named mover
check-no-regression.ps1 NO REGRESSION, byte-identical across all 631 behavioral packages
migrated go2cs-stdlib.slnx, --no-incremental 0 errors, 646 s
GolibTests on the union 211/211 – the hold entry’s predicted number
converter go test ./... (incl. the new extract guard, TestStdLibMetadataInSync) green, 227 s
RuntimeCallerFrames filtered, all four phases PASS 4/4
pipeline re-validations flag 24, log 8 + 1, sort A/B + own-row sweep PASS 63
own-row + canary sweeps at the pushed tip dispatched to the i9 worker as JOB-R1 (flag, log, sync/atomic, go/internal/gcimporter 583); results fold into the merge signal

The 160/0 full sweep from the first round stands as the mechanism’s evidence per the hold entry and was not re-run. SS11.1 (recording over derivation) and the stricter no-composition reading are RATIFIED per the same entry; log’s alloc-profile disclosure and the 67-file unmapped-until-their-own-rebank discipline are accepted.

⛔ MEASURED, DOES NOT BANK — runtime/debug’s NINTH verdict is TestSetCrashOutput, it was never in any inventory, and it is refused by every class including the one minted for this row (2026-08-21, lane claude/runtime-debug-bank)

The terminal row of the 1.23.1 campaign was fully composed on this board — Ruling B’s runtime-capability class for WriteHeapDump, the ruled fifth-frame host-limit entry with its drafted text, TestFreeOSMemory routed by measurement to codegen-liveness, and TestReadGCStats closed by S2/S3. Eight of nine verdicts had an answer. The ninth did not, because no inventory ever named it. Measured at d4c3e579d + a freshly built converter, -test-action all, -test-timeout 15m, explicit -go2cspath:

Test go test go2cs State Composed bill
TestReadGCStats pass pass MATCH closed by the S2/S3 recorder ✓
TestSetGCPercent skip skip MATCH (skip parity, issue 20076)
TestSetMaxThreadsOvf pass pass MATCH
TestStack pass fail ONE assertion, the fifth/host frame host-limit, drafted ✓
TestWriteHeapDumpNonempty pass fail WriteHeapDump throws runtime-capability
TestWriteHeapDumpFinalizers pass fail WriteHeapDump throws runtime-capability
TestWriteHeapDumpTypeName pass fail WriteHeapDump throws runtime-capability
TestFreeOSMemory pass fail second assert, the frame’s dead 32 MB codegen-liveness
TestSetCrashOutput pass fail six assertions NOTHING — never rooted

3 matched · 6 divergent. All four expectations the bill stated were met exactly, including TestStack at precisely one assertion (in line "\tD:/…/src/core/testing/TestExecution.cs:593", expected prefix "\ttesting/testing.go" — the line above it is a testenv t.Log, not a failure). The row does not bank on the ninth.

The census gap, stated plainly

The 2026-08-19 compile-wall entry recorded “2 of 9” and “the seven failures decompose into four roots” — and its table names six tests. 9 − 2 = 7, so exactly one failure was carried in the arithmetic and never in the prose. S2/S3 then closed TestReadGCStats, moving the row to 3 of 9 with six failures, five of them ruled. TestSetCrashOutput is the one that was counted but never named, and it appears nowhere else on this board. It is not a regression: the hand-own it lands on is unchanged since the 2026-08-01 consolidation (2e8066da6).

What TestSetCrashOutput actually measures, and why it is a defect

The test re-executes the test binary through exec.Command(os.Executable()) into TestMain’s setcrashoutput arm, which creates a file, calls SetCrashOutput(f, CrashOptions{}), println("hello"), then panic("oops"). The parent then requires both the crash file and the child’s stderr to contain panic: oops, goroutine 1 and debug_test.TestMain, and requires hello in stderr but not in the crash file.

A great deal of that works. The child spawns, the env propagates, hello reaches stderr, the panic happens, the child exits 2, and the crash file is created and read back cleanly. Two things diverge:

Against the five classes and the newly minted sixth:

class admits it? why not
alloc-profile / alloc-count-semantics / codegen-liveness no nothing is being measured or collected
host-limit no its bar is a structural property of the DEPLOYMENT SHAPE, and the shape is demonstrably not the obstacle — unlike os/exec’s relocated installExe, this child started, ran, printed and panicked. What is missing is output FORMAT. The bar’s own text excludes “an unimplemented-but-fixable defect”
runtime-capability no it refuses this by its own admission test. Does a truthful managed implementation of the asserted behavior exist at any cost? Yes — and cheaply, because every ingredient is already banked and proven in this same package: debug.Stack() produces the traceback, goFrameName spells runtime/debug_test.TestMain Go’s way (the receiver half, 2026-08-19), and the position map now names the Go file and line — TestStack’s four converted frames all agree with Go on this very run. A priced arc is never a disclosure

So the honest outcome is the one the charter asks for: the row declines. Disclosing it would launder exactly what Ruling B’s anti-laundering clause and host-limit’s bar were both written to refuse, and implementing the crash-report arc is not a banking lane’s change — it rewrites what every converted program prints on an unhandled panic.

The roster does not move

161 / 215 = 74.9%, 18,565 matching verdicts, 80 disclosed — recomputed by summing the table’s 161 rows, unchanged. 75% waits on the arc below.

The remedy, priced for whoever takes it

One arc, two halves, both ordinary implementation work:

  1. A Go-format crash report on an unhandled panic. The test host’s top-level handler prints panic: <value>, a blank line, goroutine N [running]:, then the Go-spelled traceback it can already produce. Blast radius is corpus-wide and behavioral — every converted program’s unhandled-panic stderr changes — so it wants a design note and charter §7 review, not a patch. Note the trap: the .NET wrapper text (System.AggregateException … ---> go.PanicException) is the host’s framing, so the fix belongs where the host decides what an escaped panic prints, not in runtime/debug.
  2. SetCrashOutput’s fd must receive that report. The slot already exists (s_crashFD); the crash printer tees to it. Mind the asymmetry the test pins: stderr gets println output and the report; the crash file gets only the report.

Consumers beyond this row: ExampleSetCrashOutput_monitor (currently excluded as an Example, Phase 4D) is the same surface, and any package whose suite asserts on panic output inherits half 1.

Two findings the next lane should not re-pay

The four entries that ARE composed, ready to paste

Banked here so the arc’s closing lane pays nothing to re-derive them. Signatures verified against this run’s captured output; TestStack’s is R’s drafted text, confirmed verbatim against the live failure.

{
  "schemaVersion": 1,
  "disclosures": [
    {
      "name": "TestStack",
      "class": "host-limit",
      "signature": "expected prefix \"\\ttesting/testing.go\"",
      "reason": "the fifth frame() assert requires the testing framework's own frame to name GOROOT/src/testing/testing.go, which is a property of Go's test BINARY: its testing package is compiled from that source. The converted deployment's testing package is the hand-owned host (src/core/testing) — the ONE-testing-package ruling's design, not an unimplemented conversion — so no conversion recorded a position for it and it honestly reports its own .cs position, exactly as golib and the BCL do. STRUCTURAL AND PERMANENT: unlike the relocatable-single-file entries, this does NOT retire when the host publishes self-contained and single-file, because the host would still be hand-written C# with no line-for-line relationship to Go's testing.go. It retires only if the test host itself becomes a conversion of Go's testing package, which the ONE-testing-package ruling deliberately forecloses"
    },
    {
      "name": "TestWriteHeapDumpNonempty",
      "class": "runtime-capability",
      "signature": "WriteHeapDump is not supported by the managed runtime",
      "reason": "Go's heap-dump format is a serialization of the Go heap through Go's own type descriptors; the heap a managed build would describe is not a Go heap and has no such descriptors, so any managed rendering would be fabrication rather than implementation and no truthful form exists at any cost. Pinned AS FAILING under the class's binding anti-laundering clause: this test checks only that the dump is non-empty (size >= 1) and never parses it, so a one-byte write would pass it while proving nothing — writing that byte is forbidden by this class's own text"
    },
    {
      "name": "TestWriteHeapDumpFinalizers",
      "class": "runtime-capability",
      "signature": "WriteHeapDump is not supported by the managed runtime",
      "reason": "Go's heap-dump format is a serialization of the Go heap through Go's own type descriptors; the heap a managed build would describe is not a Go heap and has no such descriptors, so any managed rendering would be fabrication rather than implementation and no truthful form exists at any cost. Pinned AS FAILING under the class's binding anti-laundering clause: this test checks only that the dump is non-empty and never parses it, so a one-byte write would pass it while proving nothing — writing that byte is forbidden by this class's own text"
    },
    {
      "name": "TestWriteHeapDumpTypeName",
      "class": "runtime-capability",
      "signature": "WriteHeapDump is not supported by the managed runtime",
      "reason": "Go's heap-dump format is a serialization of the Go heap through Go's own type descriptors; the heap a managed build would describe is not a Go heap and has no such descriptors, so any managed rendering would be fabrication rather than implementation and no truthful form exists at any cost. Pinned AS FAILING under the class's binding anti-laundering clause: this test checks only that the dump is non-empty and never parses it, so a one-byte write would pass it while proving nothing — writing that byte is forbidden by this class's own text"
    },
    {
      "name": "TestFreeOSMemory",
      "class": "codegen-liveness",
      "signature": "less than 16777216 released:",
      "reason": "the test allocates 32 MB inline in its own frame, clears the only named reference, and then asserts — from inside that same still-running frame — that FreeOSMemory has returned the memory to the OS. Go's per-safepoint liveness maps drop the allocation at its last use; the CLR reports a frame's slots live for the frame's whole lifetime, so the 32 MB is still rooted while the test is looking. Measured by the routing probe as a three-way control: the identical allocation behind a RETURNED call releases 33,689,600 B to the byte, and the inline form does not, invariant under Release and under untiered JIT. The first assert (HeapReleased must increase at all) PASSES on the S2/S3 high-water surface — 3,031,040 B of unrelated memory really is released — which is why the pin is the second assert's prefix and stops before its run-varying byte counts"
    }
  ]
}

The runtime-capability class does NOT join the roster preamble here. Ruling B says it joins with the first banking commit that uses it, exactly as chan-direction did, and there is no banking commit — minting a class into published roster prose for a row that did not bank would leave the preamble describing a class no manifest uses.

Gates

Stated as an accounting rather than a table of runs, because this commit changes one docs file and nothing else.

Gate Owed? Result
own-row pipeline measurement yes — it IS the finding run above, 3 matched / 6 divergent, exit 1
corpus restored yes git status clean; 9 untracked artifact paths + ignored pipeline output removed
converter go test ./... no — no converter file moved, and no manifest entry is being committed for the loader to accept not run; accounting stated
check-no-regression.ps1 no — no converter file moved, so no emission can have changed not run; accounting stated
go2cs-stdlib.slnx / behavioral suite no — zero tracked corpus drift not run; accounting stated
five-largest-reflect-consumer sweep (JOB-C1) no — that gate protects a BANK; there is no bank, no corpus change and no converter change not dispatched, deliberately — it would have spent an i9 hour proving a docs commit

NIT, banked (user, 2026-08-21) – [assembly: go.GoPositionMap(...)] drops its redundant go. prefix

The emitted position-map records carry a go. namespace prefix the info files do not need – the file’s existing using covers it, and every sibling assembly-level record (GoTypeAlias, GoImplement, GoImplicitConv) is emitted unprefixed. One emission-string fix in the converter plus the corpus-wide info-file diff it implies. RIDES THE QUEUED LEVELING REBANK (the time-class born-stale leveling + map-coverage completion, due after the crossing) so one deliberate regen levels all three families at once. Not taken inline by any current lane; the train does not stop.


2026-08-21 · Linux measurement campaign Part 3 — the full-roster census: 92/159 validate on Linux as-is; ONE nine-member seam prices most of the rest (lane G, claude/linux-measure-1)

The first wholesale Linux run of the banked roster is complete. Campaign v4 (WSL2 Ubuntu-22.04, laptop-class host, corpus at master d3e7a61e9, GoTargetOS=linux, per-package logs + ledger resume, GO2CSPATH pinned per the 2026-08-21 race entry): 159 rows swept · 92 PASS · 67 FAIL · zero infra recurrences after the pin (one benign purge-retry at row 10). Control leg: the i9’s JOB-001 swept the same roster on Windows at 0f2a66912 to 159/159 — so every Linux FAIL below is Linux-specific by measurement, not inference (SHA caveat: JOB-001 ran at union-157 = master + S2/S3; the delta touches golib GC surface only and no failing row’s shape implicates it).

PASS side (92): every pure-compute flagship — fmt 63, sort 63, strings 68, math 76, bufio 80, crypto/elliptic 82, internal/fuzz 52, unicode/utf8 14, internal/reflectlite 30, go/ast, go/constant, go/scanner, go/token, encoding/{asn1,base32,base64,ascii85,csv,hex,pem}, crypto/{aes,des,md5,sha256,sha512,dsa,elliptic,subtle,rc4,internal/*}, hash/* incl. maphash 22 inside its calibrated floor, index/suffixarray 12, math/{bits,cmplx,rand/v2}, container/*, text/scanner, text/template/parse, net/{url,mail,textproto,rpc/jsonrpc,http/fcgi,http/internal/ascii}, testing/{quick,iotest,slogtest}, time-free context/errors/expvar/maps/path/cmp… Full ledger in the campaign artifacts.

FAIL side (67), classified by MEASURED shape — five walls and a handful of singletons, not 67 defects:

Corrections against my own mid-campaign signals: the shard-2/3 “CRLF-fixture” hypothesis is REFUTED — the cluster shape was real but the root is W1’s poll seam, proven by the live catch and the 50-row signature census. Recorded per the measure-don’t-guess discipline.

Campaign infrastructure (what later Linux campaigns inherit): the GO2CSPATH case-insensitive env race (rooted + pinned, see the 2026-08-21 board entry; converter dedupe queued coordinator-side), _paths.ps1’s Linux pins (GoTargetOS + GO2CSPATH), the sweep’s -Exact filter, the ICU-safe duration table, per-package log retention with an idempotent resume ledger, and the two-monitor watch pattern. Roster snapshot: 159 rows at d3e7a61e9; flag and log banked mid-campaign in R’s arc and are NOT in this census — a two-row follow-up shard is owed once R’s merge lands, after pulling the distro clone forward.

What “Linux parity” requires, by the numbers: 92 rows already hold. One nine-member seam (W1) prices most of the remaining distance (~58 rows). Two converter emission defects (W2), one converter crash (W3), one roster-design item (W4), one capability arc (W6), and per-host budget calibration (W7) close the rest. The harness half (W5 + the race + the pins) is already landed on this branch.


2026-08-21 · FINDING for ratification — the W7 “hang” is Gosched ring starvation under the dedicated-thread goroutine model, Linux-amplified ≥15×; correctness NOT implicated, row #159 exonerated (lane G, claude/gosched-ring-finding)

Answers the census’s W7 row and the assigned reproduce-and-root. TestValueCompareAndSwapConcurrent (value_test.go:249) is not hung and not a latent #159 defect — it is a measured scheduling pathology, four-cell matrix on one machine plus the i9 control:

host short (100 goroutines · 10k handoffs) full (1000 goroutines · 100k handoffs)
Windows, laptop (16T) PASS, 183 s wall
Linux WSL2, same silicon PASS, 13 s ≥45 min, package timeout
Windows, i9 (24T) PASS inside normal suite time

Mechanism (each link measured or read from source, none theorized):

  1. The test is a strict token-passing ring: after warmup, value k can be advanced ONLY by goroutine k mod m — all 100,000 CAS advances are sequential cross-goroutine handoffs, and every miss spins through runtime.Gosched().
  2. Converted goroutines are DEDICATED OS THREADS by design (golib/builtin.cs records the doctrine: “goroutines get dedicated threads (Goroutine.Start)”), so the full test is 1000 CPU-bound kernel threads.
  3. Gosched() converts to Thread.Yield() (runtime/managed_impl.cs:105). On Windows the scheduler rotates equal-priority ready threads on yield, so the ring advances (183 s at 16T, faster at 24T — still ~200× Go’s sub-second, the scale factor). On Linux, Thread.Yield() is sched_yield(2), which CFS makes near-inert for CPU-bound threads: the yielder is commonly re-selected and the ONE runnable thread that can advance the ring waits out a fair-share epoch — per-handoff latency ∝ runnable threads, total ≥45 min, presenting as a hang (the OS factor, ≥15× at fixed hardware).
  4. Correctness is untouched: Linux short-mode passes in 13 s; Value.CompareAndSwap’s converted loop (value-equality gate + identity CAS + retry) is faithful; the suite’s other 107 verdicts pass on Linux, and nothing in the mechanism reaches the ж-box native-slot/token machinery of row #159.

Classification: a semantic-fidelity cost gap of the dedicated-thread goroutine model under mass-Gosched spin workloads, Linux-amplified by sched_yield semantics — the same class as the maphash “performance gap, not correctness” precedent, but with an OS multiplier that turns slow into effectively-hung on exactly the platform the Linux campaign measures.

Remedy options, priced (ratification required — Gosched’s contract and the dedicated-thread doctrine are settled surface):

Verification set when (a) lands: the four-cell matrix re-run (Linux full must complete), sync/atomic 108/108 on both OSes, and the standard reflect-consumer canaries derived at gate time — plus a Windows A/B on the i9 confirming no healthy-case regression (the suite’s other hammer tests are the sensitive canaries for added Sleep latency).


2026-08-22 · The Gosched ring: the ratified inert-only backoff is landed and safe but does NOT close W7 — the local design space is measured out (four variants, each refuted or unresolved by a NAMED mechanism), and the class points at M:N (lane G, claude/gosched-ring-finding)

Status of the ratified remedy (a): implemented per ratification and LANDED on the branch — golib/runtime/GoschedBackoff.cs (internal, IVT), runtime.Gosched delegates, failing-first guards green both OSes (GolibTests 213/213), Windows full ring unregressed (183 s → 84 s, the delta is scheduler variance — the backoff provably never fires under contention), Windows own-row sweep at tip: PASS 108/108 in 240 s (.cs.auto refresh drift restored per the documented class). But the Linux acceptance cell FAILS — and the failure is mechanistic, not tuning. The finding’s own “sched_yield is near-inert” premise holds only for the IDLE case; the contended ring’s yields context-switch (~6.5 µs measured) to other WRONG threads, so no local inertness predicate can ever fire. W7’s row stays an honest FAIL per affirmed doctrine (c).

The prototype cartography (all measured on the reproducing distro, full ring = 1000 dedicated threads · 100,000 strictly-serial handoffs):

variant escalation signal Linux full ring refutation mechanism
A (ratified) consecutive inert yields (!switched \|\| <2 µs) ≥749 s, killed never fires: contended yields are “effective” (6.5 µs switches to wrong threads)
B wall-gap burst count, sleep every 64th ≥658 s, killed sleep-every-64th keeps ~30% of 1000 threads runnable → woken owner waits a 25–100 ms CFS epoch per handoff
C B + full-drain past 4,096 ≥662 s, killed self-defeating reset: involuntary preemption under target contention inserts >500 µs wall gaps between consecutive calls — stack census caught the steady state (659 sleeping / 340 yielding, tier-2 capture, full-drain never reached)
D C with thread-CPU-TIME gaps (preemption-proof) ≥660 s, killed unresolved at prototype quality: clock_gettime(CLOCK_THREAD_CPUTIME_ID) path unverified, and the completion floor math no longer explains the miss

Two measured constants that reshape any future design: Thread.Sleep(1) actually costs 1.07 ms on Linux and 15.9 ms on Windows (granularity INVERTED from the naive assumption — any sleep-tier design taxes Windows 15× harder per escalation), and Go’s own ring completes because its Gosched is a ~100 ns userspace runqueue rotation over GOMAXPROCS threads — a floor no 1:1-dedicated-thread design reaches, since every handoff there pays kernel-primitive costs (yield storms, sleep quanta, or wake syscalls) times CFS’s ordering.

Recommendation: keep landed-A (it demonstrably closes the idle-spin shape, harms nothing measured, and its guards pin the behavior); leave W7 an honest FAIL with this cartography as the named cause; price the ring’s closure under the M:N goroutine scheduling horizon rather than further Gosched-local tuning — three refuted candidates with distinct mechanisms is the evidence the local space is exhausted, and this campaign now motivates M:N with hard numbers rather than architecture taste. Variant D’s residual uncertainty (pinvoke verification, serial-floor accounting) is recorded for whoever takes that lane; the prototypes live in the session scratchpad and the raw logs in the distro’s /root/ (ringB/C/D.log, stack1/2.txt).

RULING – W7’s disposition: honest FAIL, ring closure priced to the M:N horizon; Gosched tuning is CLOSED as an avenue (coordinator, 2026-08-22)

The backoff cartography’s recommendation is ratified as delivered. The landed inert-only backoff STAYS (it closes the idle-spin shape class-wide, improved the Windows ring 183 -> 84 s, and its guards pin it); the Linux ring row stays an HONEST FAIL, never disclosed – and further Gosched-local tuning is closed by measurement, not by fatigue: four designs refuted with named mechanisms, and the floor argument is structural (Go’s Gosched is a ~100 ns userspace runqueue rotation; a 1:1 dedicated-thread design pays a context switch, ~6.5 us measured, to a thread that is usually the wrong one). Ring closure is priced to the M:N scheduling horizon, where it joins the recorded constituency. Variant D is DEFERRED by this pricing – driving it to resolution would spend against an avenue this ruling closes; it reopens only if the M:N horizon slips far enough to re-price nearer-term relief. The two portable constants (Thread.Sleep(1) = 1.07 ms Linux / 15.9 ms Windows, inverted) are noted as the kind of measured fact every future scheduler design should start from.

2026-08-22 · LANDED + MEASURED — the internal/poll seam hand-own: the Linux flavor’s un-armable fds degrade to the blocking path; 28 of the census’s 61 W1 rows flip PASS on Linux, and the 33 that do not are attributed, row by row, to FIVE further seams that sit BEHIND the poller (lane R, claude/poll-seam-hand-own-5aadf1)

The assignment was the census’s W1 pricing, implemented at the layer the census named and then MEASURED against the same roster. One file landed — src/core/internal/poll/linux/runtime_netpoll_impl.cs — and the full 161-row Linux roster was re-run at the lane tip with per-row logs; the Windows control of the 61 W1 rows ran on the i9 (JOB-R2). No roster change (Linux rows do not formally bank until the per-OS arithmetic ruling lands); the deliverable is the flip count below — and the census’s “up to ~58 of 61” pricing is corrected by measurement to 28, because five more walls stand behind the poll wall for the rest. Every one of them is now named, rooted and priced.

What landed

Gates (lane tip measured at 19b63567d — the hand-own commit; the branch tip adds only the merge guard, the docs, and comment-only header corrections, proven comment-only by git diff 19b63567d..tip – src/core/internal/poll/linux/runtime_netpoll_impl.cs``)

Gate Result
GolibTests (Windows) 211/211
go2cs-stdlib.slnx -p:GoTargetOS=windows (Windows host, laptop R) 0 errors (6:00)
go2cs-stdlib.slnx -p:GoTargetOS=linux (NATIVE, WSL2 Ubuntu-22.04, --no-incremental) 0 errors, 149 warnings — the census’s exact count (566 s)
converter go test ./... (Windows) ok, 288 s (incl. the new merge guard + the L3 corpus walk)
L3 corpus guard TestCorpusHandOwnsFollowTheirPrincipals green
CNR / behavioral not owed and not run: no converter change; the Windows flavor is byte-untouched (the diff is one new file under linux/, docs, and one converter TEST file)

The measurement — Linux full-roster re-run at the lane tip

Harness: the repo’s own run-validated-sweep.ps1 -Filter <pkg> -Exact -SkipBuild per row under pwsh 7.5.4, _paths.ps1’s Linux pins (GoTargetOS=linux, GO2CSPATH), -TestTimeout 20m base with the script’s floors, per-row logs + an idempotent resume ledger, run detached (setsid under a held wsl.exe, the tree restored between rows). Host: laptop R’s WSL2 Ubuntu-22.04 — the distro was bare and was re-provisioned per F15 in ~4 min incl. clone (Go 1.23.1, SDK 9.0.317, pwsh 7.5.4; 16 threads, 15 GB); the linux slnx build was run first so the 161 per-package builds went incremental. Roster at 161 rows (the census’s 159 + flag, log).

Headline: 122 PASS / 37 FAIL / 2 COUNT of 161 — against the census’s 92 PASS / 67 FAIL of 159.

Flip arithmetic over the census’s 61 W1 rows (G’s verbatim ledger, mailbox 2026-08-22 00:20):

Class Mechanism (rooted) Rows Remedy (priced)
R1 — syscall.Stat_t by address Stat_t (linux/amd64) carries array<int64> X__unused, a managed reference, so the CLR lays the struct out itself; fstatat/Fstat hand the kernel (uintptr)Ꮡstat — the pinned managed image — and the 144-byte stat record lands on a field order that is not the kernel’s. Stat/Lstat funnel through fstatat. Probe (converted program, isolated clone): os.Stat(dir) → err nil, isDir = false, mode = p---------; Readdirnames/ReadDir/Read correct (dirent-typed); Glob → 0 and WalkDir → 1 follow from IsDir(). The Linux instance of the “wrappers passing a non-blittable struct by ADDRESS” class. 8: archive/zip (OpenReader sizes from Stat().Size() → “not a valid zip file” ×45), debug/dwarf (Glob over testdata → 11 subtests absent), html/template (3), path/filepath (wall-to-wall: Walk marks 0, Glob empty, EvalSymlinks “not a directory”, MkdirAll’s stat), io/ioutil (Stat size 0 want 3302), io/fs (WalkDir over DirFS), internal/diff (“no testdata”), go/internal/srcimporter (go/build over GOROOT src, by code path); partial in go/doc/comment, text/template the Windows Timezoneinformation precedent: TWO bodies (fstatat, Fstat) hand-owned in syscall/linux/ against a blittable mirror, transcribed into the managed Stat_t. The highest-leverage Linux item now — it gates every directory walk, every Stat-sized read.
R2 — the exec wall os/exec process launch on Linux (fork/exec, ensurePidfd, the BeforeFork family) — reached through testenv.MustHaveGoBuild/GoToolPath (go build -o /dev/null runtime), the gc importer’s go list -export export-data lookup, and exec.Command(os.Args[0]) self-re-execs. The converted sync.OnceValue re-panics the foreign exception as panic: nil (recover() sees no Go panic, valid false → panic(p) with p nil), which masks the real cause — a small honesty defect in its own right. 16: debug/buildinfo, debug/gosym (+R3), go/doc/comment (TestStd; +R1), text/template (TestLinkerGC; +R1), sync (TestMutexMisuse), math/rand (TestDefaultRace ×7), crypto/ecdh (TestLinker), crypto/ed25519 (TestEd25519Vectors), crypto (TestPureGoTag), internal/abi, internal/testenv, internal/types/errors (TestErrorCodeExamples gated), internal/godebugs, go/types (555 of 557 verdicts now produced — the census had it dying at first open; TestCheck/* + TestAtomicAlign via go list), go/internal/gcimporter (281/581; TestImportStdLib enumerates via go list std), go/importer the Linux process-launch seam (FINDING-linux-run-layer.md §5: rawVforkSyscall, runtime_BeforeFork/AfterFork/AfterForkInChild/BeforeExec/AfterExec) — a design-size item, not a wrapper repair.
R4 — syscall.rawSyscallNoError is still an announcing stub syscall_linux_impl.cs deliberately left it (“until something genuinely needs them”); the NoError family — Getuid/Getgid/Geteuid/Getegid/Getpid/Getppid/Gettid/Umask — calls it; os/user.currentUIDos.GetuidNotImplementedException, and the first throw poisons user.Current()’s sync.Once so every later call NREs. 3: archive/tar (TestFileInfoHeader infrastructure-error + 8 NRE aftermath via tar.statUnix), time (TestSleep: time.interruptKill(Getpid())), os/exec (PACKAGE level: the host dies in init before any verdict, 0/72; R2 behind it); also a test each in io/ioutil, path/filepath (root-uid guards) ONE body beside runtime_entersyscall in syscall_linux_impl.cs: RawSyscall6 ignoring errno. Trivial, and it now “genuinely” is needed.
R5 — the Linux sockaddr seam SockaddrInet4.sockaddr() (syscall_linux.cs:541-552): Go’s p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) converts to a uintptr round-trip minting an EMPTY array<byte> → index out of range at syscall.Bind/Connect; and RawSockaddrInet4.Addr [4]byte makes raw non-blittable for the by-address bind. The L10 Windows precedent (syscall/windows/syscall_windows_impl.cs, SockaddrRoundTrip), not yet mirrored into syscall/linux/. Reached BEFORE fd.init(). 2: encoding/json (490/491; TestHTTPDecoding via httptest), crypto/tls (PACKAGE level: TestMain’s listener; 0/3646 — the census’s tls row was the pollServerInit package error; this is the next seam behind it) mirror the L10 hand-own into syscall/linux/.
W1b — mmap (census-named) syscall.Mmap on the Linux flavor answers EINVAL for the page-boundary tests 2: crypto/sha1 (TestOutOfBoundsRead panic: invalid argument), bytes (the four *NearPageBoundary tests) the Linux mmap wrapper; separate raw seam
R3 — self-binary identity os.Args[0] is the managed PE DLL (MZ\x90\0), so elf.Open(os.Args[0]) fails “bad magic number ‘[77 90 144 0]’”; Go SKIPS these on windows (TestNoSectionOverlaps: “cmd/link doesn’t produce ELF binaries”), so they were never reachable on the Windows roster 1: debug/elf (+3 tests in debug/gosym, Go=skip there on this host) a host-limit disclosure class, or an os.Args[0] ruling
W4 — per-OS count mime validates at 18 vs Windows-banked 17: comparison status VALIDATED, matched = true, TestLookupMallocs disclosed exactly as on Windows; the extra verdict is TestTypeByExtensionUNIX (//go:build unix) 1: mime — functionally a Linux PASS; the COUNT is the roster-schema question the census already routed (W4) the OS-keyed expected count
R6 — unrooted time’s TestEnvVarUsage: ZoneinfoForTesting() reads “” after t.Setenv("ZONEINFO",…) on the Linux flavor (passes on Windows) 1 test its own look (zoneinfoOnce / platformZoneSources ordering on linux)

Control legs:

What this does and does not change on the board


✅ BANKED — the Go-format crash report lands and runtime/debug is roster row #162 at 75.3%: the ninth verdict closed as a FIX, which is what every class had already ruled it was (2026-08-22, lane worktree-agent-aef2216c6bd01a654)

The previous entry measured TestSetCrashOutput, found it refused by all five classes and by the sixth minted for this very row, and priced the remedy in two halves. Both halves are built, the test passes all six of its assertions, and the row banks — 4 matched · 5 disclosed, the terminal crossing of the 1.23.1 campaign.

The arc, in the order the mission required

The design note went first, in its own commit (docs/phase4/DESIGN-crash-report.md, 48712b2a4). Its whole content is that the target format is Go’s own and nothing is invented: panic: <value>, a blank line, goroutine N [running]:, then the traceback — the shape recorded verbatim in Go’s own runtime/debug/stack_test.go above TestSetCrashOutput’s read-back. Element 1 is PanicException.PanicText, banked. Elements 3 and 4 are appendGoFrames, which TestStack banks against Go frame for frame. The arc composes banked ingredients and renders nothing new.

WHERE, per the trap this board named. The System.AggregateException … ---> go.PanicException text is the host’s framing, so the printer lands at the two places a host decides what an escaped panic prints — golib’s AppDomain.UnhandledException backstop (every converted program) and TestHost.Run’s outer catch (the test host) — and never in runtime/debug, which owns only the destination. A recovered panic is untouched: nothing on the recover path, the GoFrame capture/claim protocol, or TestExecution’s per-test panic report moves.

The printer is golib’s, because golib is the only assembly the test host and every converted program share (core/testing references golib and time, and nothing else). golib cannot spell a Go frame name, so the dependency inverts exactly as RuntimeErrorPanic.IntegerDivideByZeroValue already inverts it for the divide-by-zero panic VALUE: golib declares the hook, core/runtime fills it from a module initializer, and with nothing registered the report falls back byte-identically to golib’s old single line. That is the repository’s own ruled idiom for this layering, and it makes an uninstalled renderer a strict non-regression rather than a wrong report. The alternative — relocating ~400 lines of traceback machinery into golib so no hook is needed — was considered and refused in the design: it rewrites machinery banked hours earlier (rows #160–#161) for no behavior a consumer can observe.

The tee. SetCrashOutput’s descriptor slot moved from runtime/debug’s hand-own to golib, which is where Go keeps it (runtime.crashFD, the symbol runtime_setCrashFD’s own //go:linkname names); runtime_setCrashFD forwards. The asymmetry the test pins needs no rule — program output reaches stderr through println/os.Stderr and never through the printer, so the descriptor only ever receives the report. Exit code stays 2.

What the child now prints

TestSetCrashOutput re-executes the test binary, panics inside TestMain, and reads back:

panic: oops

goroutine 1 [running]:
runtime/debug_test.TestMain()
	runtime/debug/stack_test.go:37
go.testing_runtime.TestHost.RunTests()
	.../src/core/testing/TestHost.cs:271
…

identically from both the crash file and stderr, with hello present in stderr and absent from the crash file, and the child exiting 2. All six assertions pass; before the arc all six failed.

The mixed frame spelling is the position map’s doctrine working as ruled, not a defect: TestMain is converted Go and reports a Go position — runtime/debug/stack_test.go:37, which exists in Go’s tree — while the frames beneath it are the hand-owned host and the BCL, which have no conversion relationship to any Go source and honestly report their own. Go’s own report has main.main() and _testmain.go there.

The arithmetic

4 matched · 5 divergent · 9 verdicts, exactly the previous entry’s table with its ninth row flipped: TestReadGCStats, TestSetGCPercent (skip parity, issue 20076), TestSetMaxThreadsOvf and now TestSetCrashOutput match; TestStack, three WriteHeapDump tests and TestFreeOSMemory are the composed disclosures. The converter’s own verdict line: “Validated 4 tests against go test (1 skipped identically on both sides, 5 disclosed-divergent (codegen-liveness, host-limit, runtime-capability), 2 disclosed-unsupported declarations excluded).”

Roster, recomputed by summing its own table: 162 rows, 18,569 matching verdicts, 85 disclosed — 162 / 215 = 75.3% (161 / 18,565 / 80 before). The terminal marker of the 1.23.1 campaign; the milestone ritual itself (MILESTONE-75pct-prep.md) is coordinator-owned and untouched here.

The five manifest entries, pinned as composed

Pasted verbatim from the previous entry’s block, and each signature re-verified against this lane’s own failure output rather than trusted:

Test Class Signature, as pinned Verified at this tip
TestStack host-limit expected prefix "\ttesting/testing.go" ✓ — the fifth-frame assert, exactly one line, TestExecution.cs:593
TestWriteHeapDumpNonempty runtime-capability WriteHeapDump is not supported by the managed runtime
TestWriteHeapDumpFinalizers runtime-capability (same)
TestWriteHeapDumpTypeName runtime-capability (same)
TestFreeOSMemory codegen-liveness less than 16777216 released: ✓ — less than 16777216 released: 0 -> 3031040; the OLD no memory released: 0 -> 0 text would NOT have matched

runtime-capability joins the roster preamble with this commit, per Ruling B — as chan-direction did, not before — and the preamble’s count prose moves to five with its own history sentence. host-limit’s bullet gains the permanent-retirement paragraph the position-map ruling required: TestStack does NOT retire at a single-file host, and both the preamble and the pinned reason say so.

2026-08-22 · LANDED + MEASURED — “the three bodies”: rawSyscallNoError and the struct stat mirror behind Fstat/fstatat close R4 and R1 on the Linux flavor; 7 (+1 validated at its Linux count) of the poll-seam lane’s 33 residual rows flip PASS, Linux roster 129/161 (+7 PASS / −8 FAIL / +1 COUNT over the 122 baseline) (lane R, claude/three-bodies-r4r1)

The assignment was the poll-seam entry’s R4 + R1 pricing — one body and two — implemented as per-GOOS hand-owns in the Linux syscall flavor and MEASURED against that lane’s own 122/161 baseline, W1-style: the rows the two classes attributed first, then the whole roster as the control; the i9 ran the Windows control of the same rows. R2 (the exec wall) and R5 (the sockaddr mirror) stayed off this lane, as routed.

What landed (branch claude/three-bodies-r4r1, tip = this entry's commit; the measured SHA for src/core is 9374cd61b (= c44b64500 + the value-type guard fix), src/core-identical to every later commit on the branch; the measured SHA is c44b64500, the hand-own commit)

Gates

Gate Result
runtime/debug pipeline, -test-action all VALIDATES 4 + 5, exit 0
GolibTests (full) 226/226 pass; 15 new crash-report guards, 5 proven failing-first
behavioral suite (full) PASS 604/604 Transpile+Compile+Target, 578 Output compared, 0 failed, 26 skipped (1,673.5 s), zero tracked drift after 604 re-transpiles
go2cs.slnx --no-incremental Build succeeded, 0 errors (31 m 17 s)
converter go test ./... ok, exit 0 (235.7 s) — owed because a manifest is being committed
banking sweep (see below) 8 pass / 0 fail, 2,262 verdicts, no count drift
check-no-regression.ps1 not owed — no converter .go file moved, so no emission can have changed. The behavioral suite’s own Target phase byte-compared all 604 goldens (ok) and left zero tracked drift, which is the same evidence CNR produces

The canary set, derived rather than remembered

The five-largest-reflect-consumer sweep derives its set at gate time by DIRECT import declaration (Imports/TestImports/XTestImports from one go list -json std, never a text grep), per the 2026-08-20 ruling. At this tip that yields go/types (557), encoding/json (491), crypto/tls (400), encoding/xml (386), html/template (243) out of 65 banked rows importing reflect directly. That reproduces the set the atomic-align lane and the position-map lane each derived independently, and the qualifying count tracks the roster as it should: 62 of 158 rows, then 64 of 161, now 65 of 162. go/internal/gcimporter (583, the largest banked row) is absent again, for the already-recorded reason — it matches "reflect.Value" only inside expected-signature test DATA and imports reflect nowhere — so the stale list is still the one CLAUDE.md carries, and re-deriving is still what keeps it from being used.

Swept, each -Exact -TestTimeout 40m, plus database/sql (the panic-frame-sensitive row) and sync, and the new row itself:

Package Verdicts Result Time
sync 44 PASS 40 s
database/sql 137 PASS 123 s
html/template 243 PASS 116 s
encoding/xml 386 PASS 109 s
crypto/tls 400 PASS 724 s
encoding/json 491 PASS 87 s
go/types 557 PASS 364 s
runtime/debug 4 PASS 25 s

The set was not dispatched to the i9: a TO i9 · JOB-C2 entry must name a tip the i9 can fetch, and this lane’s brief says to push nothing, so no tip exists off this box to bind. It was swept here.

Dirt

This row’s own dirt is empty, exactly as the previous entry recorded: a full -test-action all produced no CRLF phantoms, no -tests-closure production re-flip, no package_init.cs hook and no production .csproj change. The committed test sources, the manifest, the proof page, the index row and the README badge are this bank’s deliberate additions. runtime/debug is the first banked row to carry a fuzz seed corpus (testdata/fuzz/FuzzParseBuildInfoRoundTrip, three files, 118 bytes) — only two GOROOT packages have one and the other is not banked — and it is committed because the generated .tests.csproj references all three as <None Include=…>, so a committed csproj without them would be broken.

The SWEEP found dirt in four OTHER packages, and it is banked-artifact staleness from two earlier arcs, not this one. Restored here rather than levelled, because levelling it would bury a bank under an unrelated regen — but recorded so it is not re-diagnosed:

No converter file moved in this lane, so neither can be attributable to it; all four packages passed at full expected count. This is precisely the banked test-source staleness charter §5 says the sweep is the only gate that can see.

Two harness findings, both budget rather than corpus, and both worth not re-paying:


2026-08-22 · The .NET 10 performance scout — same-silicon three-way: broad 10–20% JIT wins, String HALVES, three named regressions, and the bflat Fib anomaly attributed (lane G, claude/dotnet10-perf-scout)

Method. SDK 10.0.400 (GA-line — “current .NET 10”, not an RC) installed side-by-side to a user-local dir; the machine’s 9.0 default untouched. Because the corpus targets net9.0, the 10 leg selects the runtime via env (DOTNET_ROOT + DOTNET_ROLL_FORWARD=LatestMajor), verified by a FrameworkDescription probe (ambient .NET 9.0.18 → leg-B .NET 10.0.11 → restored) — both legs execute IDENTICAL IL, so the delta is pure runtime/JIT codegen. All legs same-day, same silicon (Ryzen 5 PRO 6650U, 6C/12T, G-LAPTOP — CORRECTED 2026-08-22 from an original “Ryzen 7 PRO 6850U … NOT the perf-canon 6650U host”: the pre-anchor fleet records had both laptops as 6850U, and this box probes as a 6650U, the perf-canon CLASS. Nothing in this entry moves — every figure here is a same-machine A/B and the ratios were always internal to this box — but the silicon is now named correctly per LANES.md), quiet box, run-performance.ps1 --no-aot, median-of-5 discipline unchanged. Go columns reproduce across legs within noise (e.g. Fib 118.3 vs 119.0) — the same-day control the method demands.

**Execution time, milliseconds (Go 9-JIT 10-JIT 10Δ vs 9):**
Benchmark Go .NET 9.0.18 JIT .NET 10.0.11 JIT Δ
Startup 25.2/26.5 243.1 (9.66×) 285.0 (10.75×) +17% REGRESSION
Fib 118.3/119.0 180.6 (1.53×) 161.5 (1.36×) −11%
Sieve 67.7/77.0 110.5 (1.63×) 98.4 (1.28×) −11%
MatMul 109.2/109.3 181.9 (1.67×) 144.7 (1.32×) −20%
String 109.7/108.6 1,278.2 (11.66×) 615.4 (5.67×) −52% — HALVED
StringView 18.7/19.3 21.2 (1.13×) 19.1 (0.99×) −10%, now AT Go parity
StringMatch 197.4/196.9 1,005.6 (5.09×) 873.6 (4.44×) −13%
Map 635.7/630.4 559.6 (0.88×) 429.5 (0.68×) −23% (already sub-Go, now 0.68×)
Sort 144.9/145.0 429.9 (2.97×) 366.9 (2.53×) −15%
Channel 41.9/41.3 83.7 (2.00×) 105.8 (2.56×) +26% REGRESSION
IfaceCall 186.1/185.9 375.1 (2.02×) 374.2 (2.01×) ~0
Iface 95.8/96.7 523.4 (5.46×) 567.2 (5.87×) +8% regression
IfaceShell 24.4/21.7 875.4 (35.86×) 772.8 (35.59×) −12%
RefLower 234.5/238.8 605.2 (2.58×) 654.2 (2.74×) +8% regression

Reading for the hop: the wins land exactly where the transpiled corpus hurts most — the String family (the 11.66× embarrassment halves to 5.67×; StringView reaches Go parity), dense compute (MatMul −20%), and Map (already beating Go, now 0.68×). The regressions are narrow and named: Startup +17% (JIT-path process start — AOT is the startup story anyway), Channel +26% (synchronization-heavy; worth a targeted look at the hop), Iface/RefLower +8% (interface dispatch shapes). Net: the hop’s CPU expectation is a solid single-digit-to-20% improvement across most of the corpus with a >2× win on string-heavy code, financed by two narrow regressions to re-measure at hop time.

The bflat breadcrumb (PLAN-bflat-perf-exploration.md Finding 4) — attributed: bflat’s Fib 70.9 ms is NOT generic .NET-10 codegen: the 10-JIT Fib is 161.5 ms (−11%, nothing like halving). AOT controls (same-day, both SDKs’ ILC, this box): 9-AOT Fib 178.2 ms (ILC 9.0.19, 964 s publish), “10-AOT” Fib 177.1 ms — identical (1,138 s publish) — and the identity is STRUCTURAL: SDK 10.0.400 publishing the net9.0 TFM resolves Microsoft.DotNet.ILCompiler/9.0.19 (runtime-pack-versioned), so no AOT leg reachable from this corpus runs ILC 10 at all. Attribution: the bflat halving requires the net10 ILC+framework PAIR bflat ships — nothing reachable from the net9.0 corpus reproduces it (10-JIT gives −11%, SDK-10 AOT is ILC-9-identical). It becomes measurable exactly AT the hop, and stands priced as a to-verify upside there, not as evidence for pre-hop action.

RC/toolchain friction for the hop plan: none encountered on the JIT path — SDK 10.0.400 installed side-by-side cleanly (official dotnet-install, -NoPath), env-based runtime selection worked first try, net9.0 IL runs on 10.0.11 under LatestMajor with zero NETSDK/analyzer noise, and the go2cs-gen analyzer (netstandard2.0) loaded unmodified. AOT-path friction, all measured: (1) the ILC runtime-pack binding above — the hop plan must not expect pre-hop AOT measurements; (2) the runner’s up-to-date check REUSES a stale publish across SDK-env changes (a 51 s “10-AOT leg” re-measured the 9-ILC binary; purge the benchmark’s bin/obj before any cross-SDK A/B); (3) Roslyn 10 newly warns CS7022 on PerformanceRunner’s top-level-statements + Runner.Main shape (benign); (4) a net9.0 app under the 10 SDK still RUNS on the 9 runtime — JIT legs need DOTNET_ROOT + DOTNET_ROLL_FORWARD=LatestMajor, verified by a FrameworkDescription probe, or the “10 leg” silently measures 9.

Discipline notes: no corpus changes, no version.props changes, no global.json committed — the worktree is byte-clean apart from the runner’s own regenerated artifacts (restored). Numbers are THIS box’s; the perf-canon README stays authoritative for its own host and was not touched (the runner’s README mirror was left unstaged/restored). | converter go test ./... (Windows) | ok, 294 s (incl. TestLinuxOnlyEntriesAreScopedToLinux and the L3 corpus walk admitting the new linux/ companion) | | syscall.csproj linux flavor, NATIVE in the distro (+ os, its first consumer) | 0 errors (syscall), 0 errors (os) at 9374cd61b | | syscall.csproj windows flavor (untouched) | 0 errors (the Windows compile set does not contain the two new files; the registry entries are linux-scoped) | | go2cs-stdlib.slnx -p:GoTargetOS=linux (NATIVE, --no-incremental) | 0 errors, 149 warnings — unchanged from the poll-seam lane’s gate (716 s) | | go2cs-stdlib.slnx -p:GoTargetOS=windows | 0 errors (6:53) | | GolibTests | 213/213 | | check-no-regression.ps1 (converter registry changed) | NO REGRESSION — byte-identical across all 632 behavioral packages (2 advisory converter warnings): the linux-scoped registry entries have zero behavioral emission footprint | | behavioral --filter StatLayoutTruth (Windows) | Transpile/Compile/Target/Output all PASS | | solution integrity | 634/634 registered, path casing OK |

The measurement — Linux roster re-run at c44b64500 against the 122 baseline

Harness as the poll-seam lane’s: the repo’s own run-validated-sweep.ps1 -Filter <pkg> -Exact -SkipBuild per row under pwsh 7.5.4 on laptop R’s WSL2 Ubuntu-22.04, _paths.ps1’s Linux pins, -TestTimeout 20m base with the script’s floors, per-row logs + resume ledger, detached; the native linux slnx build first so the 161 per-package builds go incremental. Order: the 13 R1/R4-attributed rows, then the other 26 residuals of the 122/161 baseline, then the 122 PASS rows as the control.

The 13 attributed rows — every attribution cleared exactly as predicted:

The other 26 baseline residuals: all 26 re-measured at exactly their baseline sub-counts (bulk check of each row’s comparison JSON: encoding/json 490/491, sync 43 + 7 disclosed / 51, math/rand 36/43, go/types 504 matched of 555 produced, go/internal/gcimporter 15/281, crypto/ecdh 46/47, crypto/ed25519 7 + 1 / 9, bytes 82 + 6 / 92, crypto/sha1 12 + 1 / 14, flag 23/24, internal/cpu 6/8, sync/atomic 89/108 before its W7 timeout, crypto/tls/os/signal/syscall conversion-blocked, mime and crypto/rand validated at their Linux counts, …) — none was attributed to R1/R4 and none moved, up or down. Their classes stand as the poll-seam entry recorded them: R2 ×16, R5 ×2, W1b ×2, R3 ×1, W2 ×2, W4 ×2, W6 ×1, W7 ×1

Control — the 122 baseline PASS rows: 122 of 122 still PASS — zero regressions (4 h 40 m of row time in all; crypto/dsa 2,444 s and hash/maphash 2,026 s under the concurrent Windows gates, both inside their floors). With the three validated COUNT rows (mime 18, crypto/rand 302, path/filepath 54 — each comparison VALIDATED at its Linux count), 132 of 161 rows now validate functionally on Linux.

Headline: 129 PASS / 29 FAIL / 3 COUNT of 161 (+7 PASS / −8 FAIL / +1 COUNT over the poll-seam lane’s 122 / 37 / 2); 7 (+1 validated at its Linux count) residual rows flip to sweep-PASS, +1 validates at its Linux count. Windows control (JOB-R3, i9, the 39 baseline non-PASS rows at 5e5bf29c6): 39/39 PASS, 0 FAIL, 14,148 verdicts — the change is linux-flavor-only, measured.

What it does and does not change on the board



2026-08-22 · LANDED + MEASURED — the exec wall falls: posix_spawn at the forkExec seam, and the R2 ladder flips 10 of 19 with every residual named (lane G, claude/exec-wall-impl)

The ratified design (DESIGN-linux-exec.md, all seven OQs) is implemented and measured. Three commits carry the arc: the OQ-6 GoFrame foreign-unwind fix (sequenced FIRST per ratification — failing-first guards red→green, so every residual below names its true exception instead of panic: nil), the whole-file hand-own src/core/syscall/linux/exec_unix.cs (forkExec rewritten on posix_spawn — fd shuffle as parent-computed file_actions data, pgid/sid/sigmask via spawnattr, addchdir_np probed-by-call for Dir, unmapped SysProcAttr fields failing by NAME, PidFD filled post-spawn via pidfd_open [OQ-4’s door, opened early: race-free pre-first-wait], every native buffer unmanaged-for-the-call-window per the exec_windows soundness rule; everything else in the file converted-verbatim, exec_linux.cs dead code untouched), and the LinuxSpawnBasics behavioral test (one golden, both spawn seams: Windows four-phase PASS at creation).

The 19-row Linux ladder at the tip (45m budgets, per-row logs in the distro’s /root/r2-logs/):

Gates: CNR byte-identical across all 633 behavioral packages (LinuxSpawnBasics included) · GolibTests Windows 232/232 expected (230 + the two OQ-6 guards; spawn-seam gates linux-compile-gated) · distro spawn gates (§5.1 synchronous-ENOENT + OQ-2 reaper-under-GC): both PASS — the synchronous contract measured (ENOENT from the call itself), the CLR reaper measured pid-targeted (unobserved child survives 8 forced gen-2 collections to a delayed Wait4) · Windows behavioral full suite: PASS, 606 projects (all four phases, 1,959 s — the GoFrame change proven across every defer/panic path at runtime) · Windows full-roster control (JOB-G1, i9): **all 162 rows / 18,569 verdicts accounted (first pass 160/2; both reds — a crypto/ecdsa CLR access-violation and a phantom CS8983 blaming a time/sleep.cs that compiled clean 11 rows later in the SAME run — retried green in isolation; three data points incl. an environment-invariant srcimporter flip converge on box-transient full-roster contention, and the retry arithmetic closes on the exact banked total)** — breadth deliberate, the GoFrame change touches every defer path · marker census 66 (line-anchored, re-measured; exec_unix.cs new) · syscall linux-flavor build 0 errors.

What “Linux parity” looks like after this arc: the R2 wall is DOWN as a class — process spawning works, the residuals are five SMALLER named seams (R5-sockaddr [R’s, in flight], hasWaitingReaders, setUsesCgo, the summary-verdict seam, the gosym toolchain mystery) plus two capability disclosures (race detector, root-posture credentials pending OQ-3’s harness line). Rows do not formally bank on Linux until the per-OS arithmetic ruling; the flip arithmetic above is the measured input to it.


2026-08-22 · LANDED + MEASURED — the “summary-verdict seam” dissolves into five measured roots; four rows flip on Linux, one validates at its per-OS count, and a GC-corruption wall class gains its Linux instance (lane G, claude/summary-seam)

The assignment’s premise corrected first, per the measure-don’t-guess discipline: the census’s four-row “package-verdict-only” class was a LOG ARTIFACT — my ladder’s sweep-log extraction saw only divergent records, and the sweep prints package rows when named tests match; the comparison JSONs held ordinary per-test residuals throughout. What the rooting actually found was better than one seam: a causal CHAIN of five, each measured into place, several load-bearing far beyond these rows.

  1. os.Args[0] under an apphost was the managed assembly, not the process image (runtime/goargs_impl.cs). Probed both launch modes: GetCommandLineArgs()[0] reports the .dll in BOTH; Environment.ProcessPath is the apphost (apphost mode) or the dotnet muxer. Every exec.Command(os.Args[0]) self-re-exec — sync TestMutexMisuse, flag TestExitCode, the idiom across Go’s suites — died on fork/exec …dll: permission denied. argv[0] is now ProcessPath when the process is not the muxer; muxer mode keeps the documented mirror. (os/exec was immune all along: Go 1.23 moved its helpers to testenv.Executable → procfs.)
  2. The pidfd door, closed wrongly and reopened by strace. A -1 sentinel in SysProcAttr.PidFD is NOT “unsupported” at os’s layer — ensurePidfd plants the field on its own kernel probe and getPidfd uses it UNCHECKED (waitid(P_PIDFD, -1, …) = EINVAL, every exit code -1). The spawn seam now fills a real pidfd_open(pid) — race-free pre-first-wait — and OQ-4’s deferral is re-scoped to “the fill is required; the WAIT path needed the fix below”.
  3. SiginfoChild was a memory-corruption instance of the non-blittable-out-param wall (internal/syscall/unix/linux/siginfo_linux.cs, now a blittable Size=128 hand-own). Go’s padding fields (_ [is64bit]int32, __ [100]byte) emit as golib array<T> — managed CLASS REFERENCES — so every kernel offset past 12 shifted by 8: Status read from a dead offset (every child exit = 0, flag/os-exec’s got 0/got -1 era), and the kernel’s Pid word wrote RAW BITS OVER A LIVE GC REFERENCE. That is the Windows Timezoneinformation class exactly, remedied its established way — and it is a standing hazard pattern wherever the kernel writes into converted structs with array padding. Named-not-claimed lead: the fleet’s crypto/ecdsa CLR access-violation watch-item is precisely this corruption’s shape.
  4. Two linkname rows the exec seam made reachable (converter + linux regen): go/types.srcimporter_setUsesCgo (pull, handle-authorized, textproto’s shape) and syscall.hasWaitingReaders (push from sync, bare shape, os.runtime_args’ precedent). One lesson each: the sweep collapses BUILD errors into bare FAIL <pkg> rows with zero diagnostics (the by-hand doctrine is what exposed CS0117 behind three batches of silence — harness honesty item, priced), and a regen cannot reach a hand-own — sync’s push source went public only in rwmutex.cs.auto; the live hand-owned file carries the access change BY HAND with a comment naming the registry row.
  5. The verdicts at the tip (Linux, banked counts): sync PASS 44 · flag PASS 24 · go/internal/srcimporter PASS 7 · go/types PASS 557 — the roster’s second-largest suite, full count on Linux · debug/buildinfo comparison-VALIDATED at 204 vs banked 197 (gcc un-gates the c-shared/pie subtests; the crypto/rand W4 per-OS-count class, one more row for the arithmetic ruling). os/exec remains FAIL with a characterized residue — a mid-suite host death leaving a cs=None block, the TestLookPath dot-semantics family, and the root-posture credential case (OQ-3’s harness line still pending) — priced follow-on, logs retained.

Gates: converter go test 192 s green · CNR: byte-identical across all 633 behavioral packages (the linkname rows change nothing the behavioral corpus reaches) · GolibTests Windows: 230/230 (the two linux-gated spawn gates excluded by flavor here; green on the distro) · Windows stdlib slnx 0 errors, sync/syscall Windows rebuilds 0 errors (the linux-flavor files never compile there; the public push source is benign) · Linux flavor proven by the sweeps themselves · marker census 67 (line-anchored, re-measured; siginfo_linux.cs and exec_unix.cs this lane’s additions).

2026-08-22 · LANDED + MEASURED — the sockaddr mirror on Linux (R5, a PREREQUISITE that moves the socket wall from sockaddr() to FD.Init), the GoPositionMap leveling of syscall/linux, and W1b ROOTED to golib’s native-snapshot slice (lane R, claude/sockaddr-mmap-posmap)

The assignment was the poll-seam entry’s two two-row seams plus my own position-map finding. Two of the three re-sized under measurement before a line was written, and the entry says so first: R5’s two rows cannot flip from the sockaddr mirror alone, because a Linux socket is un-armable until a readiness poller exists (the mirror is the poller’s prerequisite, landed here); and W1b is not a syscall hand-own at all but golib’s unsafe.Slice-over-native SNAPSHOT (rooted by probe, priced, routed out by the coordinator as its own golib DESIGN increment — the two rows wait honestly). The third — the GoPositionMap drift — is leveled.

What landed (claude/sockaddr-mmap-posmap: lane work at a38f6c7da, merged with master 1d6542c73 as b2b271209; this entry’s commit is the tip; measured SHA e7800600d)

Gates (measured SHA e7800600d, tip = this entry’s commit)

Gate Result
converter go test ./... (Windows) ok, 309 s (incl. TestSockaddrFamilyIsScopedToEachHandOwningFlavor and the L3 corpus walk admitting the new linux/ companion)
syscall.csproj + net.csproj linux flavor, NATIVE in the distro 0 errors / 0 errors
syscall.csproj windows flavor (untouched) 0 errors
syscall.csproj + net.csproj linux flavor, NATIVE, at the MERGED tip b2b271209 (this lane + G’s exec-wall in the same assembly; probe clone, --no-incremental) 0 errors / 0 errors — the union gate
go2cs-stdlib.slnx -p:GoTargetOS=linux (NATIVE, --no-incremental) 0 errors, 149 warnings — unchanged count (692 s)
go2cs-stdlib.slnx -p:GoTargetOS=windows 0 errors
GolibTests (golib untouched) 228/228
check-no-regression.ps1 (converter registry changed) NO REGRESSION — byte-identical across all 632 behavioral packages (2 advisory converter warnings): the scope change has zero behavioral emission footprint
solution integrity CNR preflight: all 634 behavioral projects are registered; all 4555 tracked behavioral paths, path casing OK
Windows control (JOB-R4, i9, 8 rows: encoding/json, crypto/tls, the six banked net/*) 8/8 PASS, 0 FAIL, 1,010 verdicts (crypto/tls clean; i9, JOB-R4, at a38f6c7da)

The measurement — Linux roster re-run at the measured SHA against the 129 baseline

Harness as the previous two lanes (the repo’s own sweep per row under pwsh, _paths.ps1’s Linux pins, -TestTimeout 20m + floors, per-row logs + resume ledger, detached, the native linux slnx build first). Order: encoding/json, crypto/tls, crypto/sha1, bytes, then the other 28 residuals of the 129 baseline, then the 129 PASS rows as the control.

R5’s two rows, the measured yield: both still FAIL, and both MOVED exactly as priced — encoding/json stays 490/491 with TestHTTPDecoding now failing as httptest: failed to listen on a port: listen tcp6 [::1]:0: operation not permitted (the sockaddr() index-out-of-range panic is gone; Bind succeeded; FD.InitpollDesc.initruntime_pollOpen’s EPERM is what net.Listen now returns), and crypto/tls’s package-level infrastructure-error is operation not permitted from its TestMain listener instead of the sockaddr panic. The socket wall is now FD.Init, i.e. the poller — the next lane’s design.

W1b’s two rows: crypto/sha1 and bytes unchanged (FAIL; the W1b shape — routed out, waiting honestly).

The other 28 baseline residuals: all 28 at exactly their baseline C# sub-counts (bulk check of each row’s comparison JSON — path/filepath validated 54/54, time 156/157, go/doc/comment 10,058/10,059, text/template 51/52, go/types 504, sync 43 + 7 disclosed, mime/crypto/rand validated at their Linux counts, os/signal/syscall conversion-blocked, sync/atomic 89/108 before its W7 timeout, …). The only movement is on the GO side of cgo-dependent suites now that build-essential is installed on this distro (JOB-005’s fix, applied after the three-bodies control): debug/buildinfo 197 → 204 Go verdicts, go/internal/gcimporter 581 → 582, go/internal/srcimporter 7/7 Go-pass — the C# side is unchanged in every one, so these are a Go-baseline effect, not a corpus one; the roster’s expected counts remain the Windows-shaped ones until the per-OS ruling

plugin — W3 re-exposed by gcc, the one control row that moved, attributed and REPRODUCED: it FAILs in 5 s at conversion time with the census’s exact W3 signature (go2cs panic at conversionDriver.go:228). Root, from the isolated probe clone at the measured SHA: with CGO_ENABLED=1build-essential has been on this distro since the three-bodies control (JOB-005’s fix) — plugin’s cgo file enters the package load, pkg.Syntax carries the cgo-processed file that pkg.GoFiles does not, and line 228 indexes pkg.GoFiles[i] by pkg.Syntax’s ipanic: runtime error: index out of range [2] with length 2; the same conversion under CGO_ENABLED=0 succeeds (exit 0). So the poll-seam and three-bodies lanes’ PASS plugin 1 was the CGO-off artifact and the census’s W3 verdict was right; the class is a converter defect with a one-line remedy (name the file from pkg.Fset.Position(file.Pos()).Filename, or walk CompiledGoFiles, rather than GoFiles[i]) — NOT taken in this lane (the converter is out of its scope beyond the registry), priced for whoever takes W3. It is the same Go-environment effect as the cgo-dependent count moves above, in its conversion-time form.

Control — the 129 baseline PASS rows: 128 of 129 stay PASS. The one mover is plugin — W3 re-exposed by the distro’s gcc, reproduced and attributed above; not a lane effect.

Headline: 128 PASS / 30 FAIL / 3 COUNT of 161 (−1 PASS / +1 FAIL, and that one is plugin’s environment flip, not the change, over the three-bodies lane’s 129 / 29 / 3) — by design, **0 flips: the lane’s yield is the moved wall, the leveled info file, and the rooted W1b, not rows.** Windows control (JOB-R4, i9, 8 rows at a38f6c7da): 8/8 PASS, 1,010 verdicts.

The Linux socket wall, priced (for routing)

With the sockaddr mirror landed, the Linux socket path is socket()Bind/Connect OK → FD.InitpollDesc.initruntime_pollOpen → EPERM → net.Listen/Dial return operation not permitted. Closing it is the Linux readiness poller the netpoll design’s §8 names as a separate design; the simplest honest shape, priced for routing (NOT started in this lane):


2026-08-22 · DESIGN PROPOSED — the Linux readiness poller: epoll + one drain thread + the Windows descriptor state machine, priced against the poll(2) loop; the socket wall’s remedy, not yet its landing (lane R, claude/linux-poller-design)

Commissioned on the mailbox at the close of the sockaddr-mirror lane (“the design note, not the implementation”), written per the ReadMemStats template: docs/phase4/DESIGN-linux-readiness-poller.md, STATUS PROPOSED, nine open questions each with a recommendation, none self-ruled. It is the half the Windows netpoll design’s §8 deferred, and it inherits that design’s §2 contract inventory and §5 deadline story verbatim rather than re-deriving them.

Nothing landed in src/. The implementation lane starts on ratification, from the S0 probes.



2026-08-22 · The ecdsa-AV lead — PROBED AND RETIRED: three limbs, each closed by census or construction; the watch-item stays box-transient (lane G, claude/av-lead)

The assignment: does crypto/ecdsa’s Windows execution path reach the SiginfoChild corruption family — any of the struct-passing census’s latent wrappers, or any array<T>-padding-in-kernel-struct site — such that the 0xc0000005 watch-item gains a mechanism? Bounded and measured; three limbs.

Limb 1 — the latent-wrapper census, mechanically intersected: ∅. go list -deps crypto/ecdsa + Test/XTest imports against the census’s open rows (GetIfEntry/net.Interfaces, getStartupInfo/unreached, the Cert* trio/crypto/x509 system verifier, the 13-member **T OUT-param class): the production closure reaches none; the TEST closure imports crypto/x509 — the sharpest moment of the probe — but every x509 call in the suite is MarshalPKCS8PrivateKey/ParsePKCS8PrivateKey, pure-Go ASN.1 with no path to the system store. No net, no process-startup surface.

Limb 2 — array<T>-padding-in-kernel-struct sites: the same census, the same ∅. On Windows that class IS the struct-passing census (an array<T> field is exactly what makes a struct non-blittable); no new members exist outside it (the census’s own discipline), and ecdsa reaches none.

Limb 3 — the ж→uintptr transient-pin window: closed BY CONSTRUCTION, not by absence of evidence. The dll_windows-era warning (“a compacting GC could invalidate a transient pinned address mid-call”) predates the current operator: implicit operator uintptr(ж<T>) calls EnsureStableAddress(), which installs a PERSISTENT PinnedBuffer.PinOnly held by the box and released only at box death — and a syscall’s argument-rooted box is reachable for the call’s whole duration. crypto/rand’s entropy reads under ecdsa’s big-int GC churn therefore have no movement window. (The exec_windows hand-own’s unmanaged-copy rule remains belt-and-braces for structs, not a correction of the pin.)

Verdict: no mechanism candidate — the stop-and-root clause does not fire. The watch-item’s own evidence (the i9’s three-point analysis: load-correlated, non-reproducible, a same-run phantom CS8983) stands as the best account: box-transient contention. What stays LIVE from the family: the SiginfoChild class is real and now has one Linux member fixed; the standing rule it adds — any struct the KERNEL writes whose converted form carries array<T> padding is corrupt-by-layout — applies to every FUTURE wrapper and to the Linux flavor’s remaining kernel-writing structs, and belongs in the wrapper-census’s preflight rather than in this watch-item.

RULING – per-OS verdict arithmetic: a count is a fact about (package, OS), and the roster gains an OS-keyed annotation, never a blend (coordinator, 2026-08-22)

The constituency that forced this: crypto/rand discovers 302 verdicts on Linux against 298 banked, path/filepath 54 against 61, debug/buildinfo 204 gcc-ungated against 197, mime 18 – every one a case where Go ITSELF runs a different test set per GOOS (build-tagged tests, GOOS-keyed skips, capability gates). The principle follows directly:

  1. A verdict count is a fact about (package, OS). Differing counts across OSes are Go’s own design, not a divergence – PROVIDED the difference attributes to Go’s per-OS test sets, which the differential already enforces row by row (the C# count must match GO’S OWN count on the same OS, same as always). Counts are never averaged, blended, or footnoted away.
  2. The roster’s banked columns stay WINDOWS-authoritative for the 1.23.1 era – the header arithmetic, the badges, and the proof pages as they stand are the Windows campaign’s record.
  3. Linux validation gains a formal home: an OS-keyed row annotation. A row that validates on a Linux leg records linux: N + D beside its Windows columns (the host-conditional COUNT machinery generalized by one dimension – path/filepath’s floor-plus-named-set precedent is the shape). The header gains ONE Linux progress line (“Linux: X of validated at their Linux counts"), summed from the annotations exactly as the Windows header sums from the table.
  4. The sweep honors the dimension: under GoTargetOS=linux it validates a row against its linux annotation where one exists, and reports comparison-validated-at-count (today’s honest interim) where none does. Proof pages gain the OS column AT THE ANCHOR RELEASE, per the release-trigger design – not before, so the pages move once.
  5. Implementation routes to a bounded harness lane (roster parser + sweep check + the annotation format), landing WITH or BEFORE trigger condition (d)’s dual-OS consolidation sweep. The four constituency rows are its acceptance cases. Until it lands, Linux results keep their census/board homes exactly as the last five lanes have recorded them.

This is release-trigger condition (c) delivered as doctrine; its implementation is condition (d)’s prerequisite step, not a new gate.

FUTURE HARVEST (user, 2026-08-22) – the Span-unification sweep beyond the native-slice v1 minimal set

Banked so the little wins are not lost. The ratified native-backed slice design (OQ-1) lands with a MINIMAL span-unified set (copy, bytealg, the string conversions) – deliberately, so the correctness model ships unblended with harvest. The remainder is this item: once v1’s branch-cost gate measures clean, sweep golib’s remaining bulk helpers onto the Span-over-either- backing shape, one adoption at a time, each its own small measured win (zero-copy + JIT-vectorization on the common managed path). The target surface is strings/bytes – the same one the .NET 10 scout measured halving under the new JIT – so the adoptions compound with the hop. Era: post-v1-measure, opportunistic; each adoption carries its own micro-gate (the Perf* row it touches, no regression on the branch gate). These wins are individually small and collectively the point.

LANDED – the per-OS annotation harness: the ruling’s implementation half, four rows encoded, Windows byte-identical (coordinator harvest, 2026-08-22)

Release-trigger condition (d)’s prerequisite is complete at 249b47b74. The grammar (goos: N [+ D] as the row’s final middle-dot segment, both anchors load-bearing so prose cannot false-parse; windows refused BY NAME since the columns are the Windows answer), the four constituency rows encoded from their board sources and round-tripped (crypto/rand linux: 302 · mime 18 + 1 · path/filepath 54 · debug/buildinfo 204), the header’s one Linux progress line summed from annotations exactly as the Windows line sums from columns, a 51-check standalone format guard with TWO failing-first proofs, and the honest third bucket: off-Windows rows without an annotation report comparison-validated-at-count (the NOT-MEASURED shape, still exit 1). Windows behavior proven byte-identical three ways at three tips.

Two coordinator notes made durable: (1) the format guard stays STANDALONE until a quiet point decides its wiring – hooking it into the sweep preflight would add a new Windows failure mode, which the lane’s own gate correctly refused; (2) the one documented limit – host-conditional ABSORPTION on an OS-annotated row still rejects, because its evidence (the committed proof page) is Windows-shaped. Proof pages gain the OS column AT THE ANCHOR RELEASE per the ruling, and the rejection is honest until then.

The $(go2csPath) case-insensitive environment race: FIXED AT THE CONVERTER — the export is gone, every child env carries one spelling (2026-08-22, lane worktree-agent-a39a6070f9f4e34d9)

Closes the remedy PRICED in the 2026-08-21 rooting entry above. Both halves landed, not just the hygiene one — and the census is what forced that.

Census first (measure, don’t assume). GO2CSPATH is READ in exactly ONE place in the whole converter — the defaulting block in main.go — and the value is consumed immediately as the -go2cspath flag default; nothing reads it back afterward, and no MSBuild file in the tree references an uppercase $(GO2CSPATH) (every reference is $(go2csPath)). So the os.Setenv had no consumer and could simply go, which is what the coordinator’s routing note preferred. But the same census settled the question the note left open: stop-exporting ALONE does not satisfy the invariant. A user may set GO2CSPATH — it is the documented way to choose a runtime root, and it is exactly what the Linux harness pin does — so honoring it as the flag default leaves it in os.Environ(), and a plain append still hands the child TWO case-distinct spellings. The pin makes both spellings carry the same value, which is why it neutralizes the failure; it does not close the class.

Spawn census, for the record: the converter has THREE child-process sites — go env (projectFileWriter.go), git rev-parse (testConversion.go), and runCommandWithTimeout — and only the third reaches MSBuild, as the parent of every dotnet build/dotnet run/go test the pipeline spawns. The packages.Config.Env sites spawn the Go toolchain, which does not read go2csPath. One site to fix, not a family.

The fix (two functions, both in src/go2cs).

  1. resolveGo2CSPathDefault (main.go) replaces the inline defaulting block and does NOT os.Setenv. The contrast is preserved in its comment so a later tidy-up does not restore symmetry by accident: GOROOT/GOPATH above are exported deliberately, because the go children read them; GO2CSPATH has no such consumer.
  2. childEnvWithGo2CSPath (testConversion.go) replaces append(os.Environ(), "go2csPath="+…) at runCommandWithTimeout — it drops every case-insensitive variant inherited from the parent, then appends the canonical entry with the resolved, separator-terminated root.

The invariant is stated in the source, once: a user-set GO2CSPATH is honored (as the flag default); the converter never exports its own derived value; a child environment carries exactly one spelling. The scrub is the clause that holds regardless of the invoking shell — including the nastier variant the rooting entry named, where an ambient GO2CSPATH points at a DIFFERENT real tree and the child build binds the wrong stdlib nondeterministically.

Guard, failing-firstsrc/go2cs/childEnvGo2CSPath_test.go, five tests under the plain go test ./..., no new harness. The parent environment is constructed LITERALLY rather than read from the OS, because only a POSIX environ can hold two case-variants at once — so the guard measures the same shape on Windows, where the defect is unreproducible in vivo. Neuter proof, both halves:

Neuter Result
childEnvWithGo2CSPath → plain append (the pre-fix shape) 3 FAILchild environment carries 4 go2csPath spellings, want exactly 1: [GO2CSPATH=/root/go2cs go2csPath=/stale/tree/ Go2CsPath=/another/tree go2csPath=\repo\src\]
resolveGo2CSPathDefault → re-add the os.Setenv 1 FAILconverter exported its derived root as GO2CSPATH="C:\Users\ritchie\go2cs"

Gates (converter change class, this machine, solo):

Gate Result
converter go test ./... ok — 222s (carries the five new guards and projitemsIntegrity, which the new file is registered in)
full CNR, 633 behavioral packages NO REGRESSION — byte-identical .cs + .csproj, 1,141s; 2 advisory warnings, 0 NOT MEASURED. Emission-neutral as predicted: the change moves only child-process environments, never emitted text
-tests -test-action all on unicode/utf8 14/14 validated vs go test, 107s, git status -- src/core clean — the pipeline runs with the export gone

The Linux proof is NOT this lane’s, and the harness pin STAYS. There is no distro on this box, so what is proven here is that the fix is emission-neutral, that the pipeline still runs end to end, and that the child-env shape is right by construction. The race itself is structurally POSIX-only and can only be re-measured on Linux. Pin-retirement condition: a Linux lane runs the reproducing configuration — the two-package alternation that failed 3-for-3 within ≤2 cycles — with GO2CSPATH deliberately UNSET and _paths.ps1’s pin block removed, and gets a clean run; at that point the pin in src/_paths.ps1 (and the campaign driver’s export) retires as dead weight rather than as protection. Until then it stays and remains correct: it sets the variable the converter still honors as a default, and the scrub makes the value it sets unreachable by the child regardless. One note for whoever retires it — the pin’s comment cites main.go:93 and testConversion.go:5663, both stale after this change; retire the comment with the block rather than repairing the line numbers.

The class trap (case-insensitive environment-variable races: Windows-immune, POSIX-live, MSBuild property resolution as the collision site) is recorded in CLAUDE.md beside the harness/false-green notes, stated generally enough to outlive this one variable. —

2026-08-22 · LANDED + MEASURED — the native-backed slice<T>: W1b closes, both rows validate on Linux, and golib’s hottest type grows one word (lane G, claude/native-slice-impl)

The ratified design implemented as written (DESIGN-native-backed-slice.md, all five OQs). Three commits: the failing-first guard family, the model, and the census sweep the design demanded as its first commit — which is the entry’s most transferable lesson, below.

The change. slice<T> carries m_nativeBase beside its managed backing — the ж<T> dual-mode precedent (#159’s native-slot doctrine) applied to the slice header, 0 meaning “managed” so every pre-existing path is untouched but for a predicted branch. ONE creation door (slice<T>.OverNativeMemory, reached only from unsafe.Slice’s IsNative arm) enforces unmanaged-T with a named panic: the SiginfoChild corruption class made unrepresentable rather than discoverable. Both indexers return refs into the mapping; Reslice carries the base with identical window arithmetic; ToSpan is the discriminant-once unification point that copy, CopyTo, IndexOf and the string conversion now ride; append within capacity writes the mapping and past capacity DETACHES to managed — Go’s own spec, not an invention; builtin.Ꮡ’s two overloads mint address boxes, so (uintptr)Ꮡ(s,i) is the real address and Mprotect(b[:pagesize]) finally hands the kernel the mapping.

The acceptance case, measured on Linux — W1b is CLOSED:

row before (three lanes’ standing residual) at the tip
crypto/sha1 FAIL — TestOutOfBoundsRead panic: invalid argument comparison-VALIDATED, COUNT 13 vs banked 12 (per-OS); sole divergence TestAllocations — the CLR-impossible disclosure class
bytes FAIL — four page-boundary tests as infrastructure-errors comparison-VALIDATED, COUNT 86 vs banked 82 (per-OS); every page-boundary and mmap test PASSES; six residuals, all allocation-count

Both rows join the per-OS-count constituency (crypto/rand 302, debug/buildinfo 204, mime 18, path/filepath 54) now before the coordinator’s arithmetic ruling.

The lesson worth carrying: the census is the work, not the epilogue. The design spec’d an m_array touch census as the implementation’s FIRST commit; the first pass shipped the obvious members and let bytes’ page-boundary tests find the struct Enumerator. The sweep that followed — done properly, every touch dispositioned — then found FOUR more the failure had not: a SECOND enumerator class (SliceEnumerator, unreached by the first fix), the IList<T> setter, buffer (now a named panic: pinning is a managed concept, and a native window’s answer is an element address), the byte-reinterpret alias, and header identity in GetHashCode/operator==. A defect that reveals one member is not a census; it is one member.

Gates: GolibTests 235/235 Windows · 237/237 Linux (the five-guard family: aliasing, real addresses, append-detach, cross-backing copy, and the crypto/subtle managed-aliasing regression arm) · behavioral full suite PASS 606 projects — the slice is under every converted line in the corpus · golib + unsafe builds 0 errors both flavors · perf trio A/B (§5.3’s measured branch-cost gate): String 1,223.0 and StringView 20.8 within noise; Sieve produced the arc’s methodological lesson — it read 145.9/144.2 ms against a 110.5 ms morning baseline (+30%), the inline unsafe block was found to cost inlinability and fixed (144.2 → 126.1 ms behind a NoInlining helper), and then a PAIRED same-session A/B exonerated the arc entirely: pre-change golib 142.3 ms vs with-fix 145.0 ms, both far above the morning figure. The regression was MACHINE DRIFT; the inlining fix is kept because it is right on its own merits. Doctrine: on a laptop, a perf comparison against an earlier-session baseline is not evidence — only a paired same-session A/B is · CNR: byte-identical across all 633 behavioral packages · Windows full-roster control (JOB-G2, i9): **162/162 PASS, 0 FAIL, exactly 18,569 verdicts — the full banked roster, clean on the first pass (no transient reds this time)** — breadth maximal by construction, and the native path is unreachable on Windows (no row mints a native pointer), so managed behavior must be identical.

What this retires: unsafe.cs’s documented snapshot limitation, and with it the class the board named “sufficient for reading a block a syscall returned” — every unsafe.Slice over native memory now ALIASES, so syscall.Environ rides the same arm (OQ-3’s ride-along, no behavior change: it reads once and never writes) and the snapshot arm is deleted rather than maintained beside its replacement.


2026-08-22 · MEASURED — R3’s Linux leg closes: debug/elf validates at 31, and debug/gosym’s last row is Go’s OWN skip reaching us honestly (lane G, claude/linux-smallitems)

The board’s “gosym MZ-magic mystery” was R3, and the argv[0] fix already dissolved it. R3 was recorded as: os.Args[0] is the managed PE image, so elf.Open(os.Args[0]) fails bad magic number '[77 90 144 0]' — the ASCII of MZ\x90\0. The summary-seam lane’s argv[0] correction (apphost mode reports the PROCESS image, not the managed .dll) changed what that call sees on Linux; nobody had re-measured the rows since. Measured now at master 71a95c8ff:

row recorded (R3) measured at master
debug/elf FAIL — elf.Open(os.Args[0]) on PE magic, 1 row + 3 gosym tests PASS 31 · comparison-VALIDATED, ZERO divergences
debug/gosym FAIL, R3-attributed one divergence: TestSymVersion, Go=pass C#=skip

And that one divergence is not a defect — it is Go’s own skip, reached correctly. TestSymVersiongetTablecrack(os.Args[0])parse, whose FIRST act is if f.Section(".gosymtab") == nil { t.Skip("no .gosymtab section") }. The C# host reports exactly no .gosymtab section: elf.Open now SUCCEEDS on the apphost (the ELF parse works — that is the argv[0] fix delivering), the binary genuinely has no Go symbol table because it is a .NET apphost, and the converted run takes Go’s own documented skip path. Go’s Linux run doesn’t skip only because ITS test binary is a Go binary with a .gosymtab.

Classification: a host-limit disclosure of the relocatable/self-binary family — the same class as os/exec’s 27 banked Windows disclosures (a .NET apphost is not the thing Go’s self-inspecting tests assume), and NOT the R3 wall, which is closed. The row needs no code: it needs a signature-pinned disclosure at its formal Linux bank, alongside the per-OS-count annotation work the coordinator deferred for crypto/sha1/bytes.

What this leaves of R3: nothing on Linux. The board’s R3 row (debug/elf + 3 gosym tests) is retired by measurement; the residual is one honest disclosure line awaiting its bank.

2026-08-22 · LANDED + MEASURED — the Linux readiness poller: epoll + one drain thread + the Windows descriptor state machine; the socket family opens on Linux (encoding/json flips, crypto/tls goes 0 -> 400 matching), and two further walls are rooted behind it — one fixed, one routed (lane R, claude/linux-poller-impl)

The design was ratified this morning with all nine OQs as recommended; this is its S0 + S1, landed and measured the same day. One file — src/core/internal/poll/linux/runtime_netpoll_impl.cs, 674 lines — replaces the fallback poller in place: epoll_create1(EPOLL_CLOEXEC), ONE background drain thread in epoll_wait(-1), and the Windows flavor’s managed descriptor state machine (Ready/Expired/generations/Timer, pollBlock/pollReset/pollSetDeadline/pollUnblock) copied verbatim per ⟨OQ-7⟩ with Go’s eventErr arm added. Edge-triggered EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET exactly as runtime/linux/netpoll_epoll.cs arms it; epoll_event.data carries an opaque token (table insert BEFORE EPOLL_CTL_ADD, EPOLL_CTL_DEL BEFORE close(2)FD.destroy’s own ordering); every kernel byte a native Marshal image through the keystone syscall(2) binding, so no ж address ever reaches the kernel and internal.poll.csproj’s shared <AllowUnsafeBlocks> stays false (⟨OQ-9⟩ — no regen owed, and the Windows build of internal/poll is byte-untouched). No break eventfd (⟨OQ-2⟩); EINTR retried (⟨OQ-3⟩ fail-loud otherwise); regular files refused by the kernel’s own EPERM (⟨OQ-4⟩). Nothing else in src/ changed — not os, net, syscall, golib, the converter, the keystone, or the Windows flavor.

S0 — the four kernel probes, and the one place the design was wrong about itself

Run in the distro as plain C# over libc (independent of go2cs), recorded in the design’s new §7.1 with its source:

Probe Measured Consequence
(a) epoll_ctl(ADD) by descriptor kind regular file EPERM, directory EPERM, pipe 0, TCP socket 0 ⟨OQ-4⟩ holds: the kernel IS the regular-file refusal; no fstat, and the 28 fallback-flipped rows keep their exact errno
(b) the packed 12-byte struct epoll_event round-trips through Marshal.WriteInt32/WriteInt64 at {0,4}, unaligned 8-byte read at offset 16 included ⟨OQ-9⟩’s safe form suffices
(c) EINTR rate under load 0 in 20 s of 1 s slices while spawning 22,819 children (a SIGCHLD each) and running 7,758 gen0 GCs The design’s own prose was wrong — it called EINTR “the normal case, not a corner”. Under the CLR it is rare: the runtime routes signals away from arbitrary threads. The retry stays (a never-restarted syscall is owed it), but as a correctness guard, not a hot path. Corrected in the doc and in the file.
(d) EPOLL_CTL_ADD during an in-progress epoll_wait(-1) the blocked waiter got the new descriptor’s edge 1 ms later, no break write ⟨OQ-2⟩ holds: the drain thread never needs interrupting

S1 — the guards, by hand on the distro (⟨OQ-6⟩): all four byte-IDENTICAL to go run

Guard Result
PipeCloseUnblocksRead IDENTICAL — prints Go’s read unblocked: read \|0: file already closed. Under the fallback this printed read did NOT unblock: the visible flip. os.Pipe arms; Closeevict wakes the parked reader.
NetListenSmoke (8 lines) IDENTICAL — bind, distinct ports, accept deadline set/cleared, close, rebind, close-is-sticky
TcpLoopbackRoundTrip (14) IDENTICAL — IPv4 and IPv6 round trips end to end, plus CloseRead/CloseWrite breaking blocked operations
NetDeadlineMatrix (12) IDENTICAL — every assertion: blocked read times out and PARKS, expiry is STICKY, cleared/past/replaced deadlines, per-mode independence and 'r'+'w', closing beats timeout, and both race arms (expiry beats buffered data; data inside the deadline is delivered; no stale expiry afterwards — the generation check)

That last row is what the Windows design called “the hard part” and priced its iteration budget for. It cost none: the copied state machine satisfies Go’s deadline semantics on the READINESS model unchanged — the strongest evidence available both for ⟨OQ-7⟩’s copy and for §4.7’s claim that dropping the cancel-and-harvest dimension leaves the race surface tractable.

Gates

Gate Result
internal.poll.csproj linux flavor, NATIVE 0 errors, 0 warnings
go2cs-stdlib.slnx -p:GoTargetOS=linux (NATIVE, --no-incremental) 0 errors, 149 warnings, 418 s — warning count unchanged from the sockaddr lane’s gate, so the poller adds none
go2cs-stdlib.slnx -p:GoTargetOS=windows 0 errors (--no-incremental, 6 m 52 s) — the control: the poller is under linux/ and the Windows build never compiles it
GolibTests (golib untouched) 230/230 (golib untouched; run as the standing gate)
check-no-regression.ps1 not owed — no converter change, no registry entry, nothing emitted moves
Windows control (JOB-R5, i9, the 8 JOB-R4 rows) 8/8 PASS, 0 FAIL, 1,010 verdicts, 815 s at 00cc122c9 — identical to JOB-R4 row for row (encoding/json 491, crypto/tls 400, the six banked net/*), and its corpus drift is JOB-R4’s too. The poller change has zero Windows-visible footprint, as the per-GOOS file placement promised.

The measurement — Linux roster re-run at 00cc122c9 against the sockaddr lane’s 128/30/3

crypto/tls — the flagship row, from 0 verdicts to 400 matching, which is the Windows banked count exactly. It was a package-level operation not permitted (0 of 3,646 Go-enumerated). With the poller alone the suite RAN but ate its 30 m deadline in TestVerifyHostname; with net.runtime_rand implemented (below) it completes, and the comparison at the lane tip a5429a611 reads:

  verdicts pass fail
Go (linux, same machine) 402 389 13
C# host 402 387 15
comparable / agreeing / differing 402 400 2

Thirteen tests fail on BOTH sides (environment-driven — the suite’s network- and BoGo-dependent arms), so the honest statement is: of the 402 verdicts both sides produce, 400 agree, and exactly two diverge. Both are attributed, and neither is the poller:

Per this morning’s per-OS ruling that is a fact about (crypto/tls, linux); it is reported, not blended with the Windows-authoritative columns, and nothing is banked.

Two walls behind the poller, both rooted with stacks rather than guessed:

  1. net.runtime_rand — an unimplemented //go:linkname stub, FIXED here (src/core/net/dnsclient_impl.cs, one body, the shape of its three precedents os/tempfile_impl.cs and math/rand’s two). net reaches it only through the pure-Go resolver — randInt picks the DNS query ID (linux/dnsclient_unix.cs:54) and weights SRV selection and address shuffling — so Windows, which resolves via GetAddrInfoW, never touched it and the stub survived the whole Windows campaign. On Linux the pure-Go resolver IS the resolver, so the platform’s FIRST name lookup died there, on a lookup goroutine, leaving its caller waiting forever: that is why crypto/tls ate a 30-minute deadline instead of failing. Platform-neutral file; net builds 0 errors / 0 warnings; Windows behavior unchanged.
  2. The UDP wall — measured, priced, NOT taken here. With (1) in, the TCP path matches Go exactly (8.8.8.8:53 -> connection refused in 99 ms vs Go’s 72 ms) but DNS still times out, and a loopback UDP probe names it: bind works, then System.NotImplementedException: RecvfromInet4internal/syscall/unix.RecvfromInet4 (internal/syscall/unix/linux/net.cs:14) reached via internal/poll.ReadFromInet4 -> net.readFrom -> UDPConn.ReadFrom. It is one of eight //go:linkname stubs in that one file (Recvfrom/Sendto/SendmsgN/Recvmsg × Inet4/Inet6). This is precisely the seam the sockaddr lane recorded as uncovered, so it is that family’s next increment and its tools already exist (syscall/linux/sockaddr_linux_impl.cs’s readNativeSockaddr/writeNativeSockaddr plus the keystone). Routed, not taken: this lane is the poller.

The 161-row roster re-run (at 00cc122c9, without the DNS fix — no other roster row resolves a name): 145 PASS / 11 FAIL / 5 COUNT of 161 (baseline, the sockaddr lane: 128 / 30 / 3) — 17 flips, ZERO regressions, plus two rows improving FAIL to a validated per-OS COUNT (debug/buildinfo 204, go/internal/gcimporter 582).

The attribution matters more than the total, and this entry will not claim what it did not do. My branch point is master 662b1595f, which already carried G’s exec-wall arc AND G’s summary-seam arc; the 128/30/3 baseline predates both. So of the 17 flips, ONE is the poller’sencoding/json PASS 491, TestHTTPDecoding’s httptest loopback round trip, exactly the row the design’s bill named first — and the other sixteen (sync, math/rand, flag, crypto, crypto/ecdh, crypto/ed25519, go/types 557, go/importer, go/doc/comment, go/internal/srcimporter, text/template, debug/elf, internal/abi, internal/testenv, internal/types/errors, internal/godebugs) are G’s, already reported on their own board entries. crypto/tls reads FAIL in this ledger because the run predates the runtime_rand commit; its real Linux number is the 400-of-402 measured at the lane tip above.

Zero regressions is the number this lane is actually accountable for. The poller changes what os.Pipe, FIFOs, ttys and sockets DO — every one of them goes from blocking to armed — and nothing that passed before stopped passing, including the pipe-adjacent rows (bufio 80, io 60, io/fs 18, io/ioutil 28, os/exec/internal/fdtest 1, mime/multipart 52).

The 16 residuals are all known, attributed classes — nothing unexplained: crypto/tls (the UDP wall; 400/402 at tip), crypto/sha1 + bytes (W1b — closed upstream by G’s native-backed slice while this run was in flight, so those two are stale here rather than residuals of this lane), the per-OS COUNT constituency (path/filepath 54, mime 18, debug/buildinfo 204, go/internal/gcimporter 582, crypto/rand 302 — the ruling’s own acceptance cases), time (R6 ZONEINFO), os/exec (G’s exec residue), debug/gosym (G’s, now classified as a host-limit disclosure), internal/cpu (W6), os/signal + syscall (W2 test-variant emission), sync/atomic (W7, ruled), plugin (W3 — the converter panic gcc re-exposed, rooted in the sockaddr lane; FAIL in the baseline too, so unchanged).

What this closes, and what it does not

Closed. The Linux socket family is OPEN: listen, accept, connect, TCP read/write, deadlines and close-unblocks all behave as Go’s, down to a connection refused that matches to the error string. internal/poll’s ten contracts have real bodies on both shipping platforms now, and the fallback poller’s documented degradations are retired — os.Pipe’s Close unblocks a parked reader, SetDeadline on a pipe is honored, and a blocked read no longer holds its thread in read(2) where nothing can reach it. W1’s last remnant (the socket half the fallback could not serve) is gone; crypto/tls’s Linux leg exists at all for the first time.

Not closed, and named rather than implied:

One process note worth carrying. Three detached sweep launches died before this lane’s run stuck, all with the same signature (log stops, distro alive, no diagnostic): setsid bash … still sits in the launching turn’s reapable process tree, and a git fetch from the /mnt/c local remote inside a no-tty session compounds it. setsid --fork nohup with stdio detached, plus pre-positioning the tree so the chain does no git work of its own, is what made it survive — the same class as CLAUDE.md’s existing background-reaping caveat, one layer deeper.

S3 — the off-roster socket ledger (in flight at the time of this entry)

net/smtp, net/http/{httptest,cgi,httputil,cookiejar} and net/rpc are reachable now in principle, and are being measured with the RAW -tests pipeline rather than the sweep — run-validated-sweep.ps1 is roster-driven and answers “No banked packages matched filter” for an off-roster package, which is worth recording because it is a five-second trap for the next lane that tries. First result: net/smtp is conversion-blocked (Go enumerates 19 verdicts, the C# host 0) — a test-CONVERSION wall, not a poller one, and therefore a converter item rather than something this lane’s file can reach. The remainder follow in the merge signal.


2026-08-22 · MEASURED — S3 of the Linux poller: FOUR of the six socket-ledger packages validate on Linux with ZERO divergences (142 verdicts), and the two that do not are off the poller’s axis (lane R, claude/linux-poller-impl)

The poller entry above closed with S3 in flight; this is its result, and it is the strongest consumer evidence the arc has. These are the packages the Windows netpoll design’s §7 froze as its unlock ledger — the ones that have been walled at FD.Init on Linux since the platform existed.

Measured at the lane tip a5429a611 in the isolated probe clone, with the RAW -tests pipeline:

package status comparable agree differ note
net/smtp validated 19 19 0 Windows has it at 9/14, walled
net/http/httptest validated 55 55 0 the loopback HTTP server the whole family rests on
net/http/httputil validated 53 53 0 reverse proxy over real sockets
net/rpc validated 15 15 0  
net/http/cgi failing 39 15 24 not the poller — all 24 are TestCGI*/TestChild*, every one of which spawns a CHILD CGI process; the exec axis, and Windows has this row at 36/39
net/http/cookiejar conversion-blocked not the poller — its emitted TEST HOST does not resolve golib (CS0234 on go.GoPositionMap, go.time_package in package_info.cs); a test-host emission gap on a package that has never been through -tests, i.e. a converter item

142 verdicts across four packages, every one matching Go on the same machine. Nothing is banked — these are off-roster, and per the per-OS ruling they are facts about (package, linux) reported here.

A five-second harness trap, recorded so the next lane does not pay it twice. run-validated-sweep.ps1 is ROSTER-driven: -Filter <off-roster-pkg> -Exact throws “No banked packages matched filter” and returns in ~4 s, which is why off-roster candidates go through the raw pipeline. But the raw pipeline needs what the sweep supplies for free: src/_paths.ps1 pins $env:GoTargetOS = 'linux' on a Linux host so every child dotnet inherits it. A bare go2cs -tests from a shell does NOT get it — the test host then builds the Windows flavor and dies at run time with kernel32.dll.so: cannot open shared object file, which the pipeline reports as conversion-blocked and reads exactly like a converter wall. My first S3 pass produced three such phantom walls (net/smtp, net/http/httptest, net/http/cgi); all three evaporated on the re-run with GoTargetOS=linux exported, and two of them are in the validated column above. Export it in any raw -tests harness, and treat a conversion-blocked verdict whose log mentions kernel32 as a harness fault, never a finding.

2026-08-23 · Condition (d) verdict arithmetic — how the dual-OS consolidation legs are judged (coordinator ruling, relayed on the mailbox in flight)

The Linux leg reports the QUADRUPLE — PASS / FAIL / COUNT / CVAC — and three readings are fixed before its verdict composes. (1) An attributed CVAC row is GREEN for (d): the comparison validated at a count on this OS; the row is merely unbanked, which is a roster gap, not a regression. (2) A FAIL row is within green iff attributable to an already-classified seam (UDP wall, exec-adjacent residue, object-lifetime, and kin) — crypto/tls on Linux is the worked example: FAIL with exactly the classified pair (TestVerifyHostname = UDP wall, TestCertCache = object-lifetime), completing in 711 s where it previously ate a deadline (the runtime_rand fix paying for itself). (3) The sweep’s exit code is NOT the leg’s verdict: the script exits 1 whenever CVAC > 0 — its honest not-banked-for-banking posture — so an exit-1 leg whose non-PASS rows are all attributed is a green leg. Judge by the quadruple plus the attribution table, never the exit code alone.

Post-leg obligations minted by the same run: crypto/sha1 (linux: 13) and bytes (linux: 86) annotations bank on master against the leg’s record, retiring their CVAC rows; crypto/tls owes its own small per-OS item (a linux: annotation plus the per-OS disclosure shape for its two classified divergences). Fleet trap recorded in flight: any harness that greps the sweep’s verdict lines must include CVAC in its verdict class set — a pre-ruling regex records CVAC rows as no-verdict, which reads exactly like a dead leg (R hit this at row 4 and lost ~14 min). Per-row wall times are native as of 4e91a03e2 ([NNNs] on every verdict line, the H5 number); the in-flight legs predate it and carry their own ledgers/mtime derivations instead.

2026-08-23 · FIRST darwin census (CI run 32611912106 at cfb8b295b) — the wall is ONE package, ONE root, and the CI matrix itself is proven

The user dispatched os-matrix.yml -f goos=darwin -f stage=census — the first time the darwin corpus flavor has been BUILT anywhere. Both mac legs (osx-x64, osx-arm64) failed IDENTICALLY: 19 errors, all in os, all from one rootdir.cs’s three call sites of readdir (lines 45/77/101, CS1061 + the CS8130/CS8183 deconstruction cascade). Root: darwin/dir_darwin.cs carries the converter’s bodyless-placeholder comment for readdir (“hand-converted with managed semantics in the package’s *_impl.cs”) — the suppression fired for darwin exactly as for windows — but the companion exists ONLY as windows/dir_windows_impl.cs (266 lines; Go’s windows readdir reinterprets a raw GetFileInformationByHandleEx buffer). Go’s dir_darwin.go is the same raw-metal class (libc DIR* walking via opendir/readdir_r), so suppression is CORRECT there too — what is missing is the darwin companion that was never authored, because darwin never built. Linux needs neither: dir_unix.go’s readdir auto-converts (it reads through f.pfd.ReadDirent), which is why the linux flavor never surfaced this.

Two findings beyond the fix itself. (1) The dependency closure BELOW os compiles clean on darwin — errors are own-errors of leaf-most failures, so syscall/internal/poll/runtime darwin flavors all built; packages above os were skipped, so unknown walls MAY remain behind it, but the measured state echoes the Linux wall’s shape (one package, one class). (2) A converter/L3 invariant worth guarding: hand-own SUPPRESSION is platform-blind while the IMPLEMENTATION is platform-scoped — the L3 routing should assert that every GOOS whose emission suppresses a member also receives an impl companion providing it (a marker-gate-shaped check, natural home platformHandOwn_test.go); today the gap is only discoverable by building the flavor.

Work item minted: author darwin/dir_darwin_impl.cs (same signature as the windows companion’s readdir(this ж<File>, nint n, readdirMode mode); the faithful path is libc opendir/readdir_r through the darwin syscall flavor that now provably compiles, with the windows companion as the structural model). Opus-class lane work, small and precedented; then re-dispatch the census to find what stands behind os. The CI matrix itself is PROVEN by this run: checkout, pinned toolchains, env report, ~10 min census build, artifact upload and the skip-of-later-stages-on-failure all behaved exactly as designed on first dispatch.

2026-08-23 · CVAC amendment (G) — the summary-line classifier is the sharper trap

Amendment to the condition-(d) arithmetic entry above, from G’s harness audit: the verdict-line grep is the EASY half of the CVAC hazard. The dangerous half is any harness that decides PASS/FAIL from the sweep’s SUMMARY line (if '1 pass' → PASS; else FAIL): a CVAC row’s summary is not “1 pass”, so it falls through to FAIL — a FALSE RED with the row’s own green CVAC line sitting in the log contradicting it, which is harder to notice than R’s NOVERDICT blank. Rule: classify from the VERDICT line, test CVAC first, and carry a cvac= column in any totals so a green class is never silently absent. All five of G’s drivers and R’s leg driver are patched; this entry exists for the next harness author.

2026-08-23 · FIRST native-Linux control (CI run 32613375229 at 43280f0bb) — GREEN, 208 s

The user dispatched os-matrix.yml -f goos=linux -f stage=behavioral-smoke on a hosted ubuntu runner: converter build + filtered behavioral run, all green in 208 s. Why it matters: every prior Linux datum in the campaign came through WSL on Windows hosts — this is the first validation on NATIVE Linux, no WSL layer, no Windows host, and it corroborates the WSL evidence independently at the smoke level. It also ran at the current master tip, four commits past the consolidation SHA, so post-merge master is freshly proven on a second OS. Calibration figure banked: 208 s smoke on a standard 4-core hosted runner (the CI-overflow shard sizing in PLAN-hop-campaign.md can hang off it). The matrix has now proven BOTH of its stated purposes on its first two dispatches: darwin census (found the readdir wall) and native-Linux control (green). Stage-gating verified too - census and sweep-shard steps correctly skipped.

2026-08-23 · SECOND darwin census (run 32615001128 at fdaeae154) — wall #1 CLOSED on real hardware; wall #2 is two cgo-flavor leaves, and it is CONVERTER work

The re-dispatch against G’s branch, both mac legs byte-identical fingerprints: the 19 os errors are GONE — the readdir companion compiles on real darwin, arm64 and x64 alike — and the build marched deeper to the next leaves. The branch merged on that round-trip (master 73f84546b, branch retired): companion + the red-then-green L3 suppression-companion guard + the projitems registration.

Wall #2: 10 errors, two leaf packages, all in darwin’s CGO-FLAVOR files — paths no other GOOS compiles: os/user/darwin/cgo_lookup_unix.cs and net/darwin/cgo_unix.cs. The telling fact: 7 of the 10 errors are in go2cs-gen GENERATED output, so this wall is converter/generator emission, not a missing hand-own — a different species from wall #1. Classes: (1) CS1729 x4 — ImplicitConvGenerator mints inverse conversions for the C-type mirror wrappers (Byte/Int32/Int64/UInt32) assuming a 1-arg constructor those declarations do not carry; (2) CS0246 x2 — TypeGenerator emits doBlockingWithCtx_result.g.cs for a GENERIC function’s result struct without the type parameter in scope (a generics gap in the struct-lift); (3) CS0266 x2 — long→uint numeric emission in cgo_lookup_unix.cs; (4) CS0246 x2 — addrinfoErrno→int inverse + a type-name slip in cgo_unix.cs. All four are reproducible on Windows via -p:GoTargetOS=darwin local builds (G proved the flavor builds locally), so the mac runners are the proof, not the dev loop. Work item minted; everything behind os/user and net remains unmeasured until these leaves fall.

Board-mechanics note, recorded because it bit twice: the append that landed the native-Linux entry used offset arithmetic against the guard line and ATE the < of the closing comment, leaving ---!-- + the endraw tag + ... --> (the tag deliberately not spelled with its brace syntax here: quoted inside a raw guard it TERMINATES the guard – the exact defect that took Pages down at f37ba28ef, and then AGAIN via this very line before it was reworded) — Liquid still parsed (endraw terminates raw even there) so Pages stayed up, but the junk rendered. This append repairs the guard and retires the arithmetic: appends reconstruct the tail explicitly.

2026-08-23 · Span-unification census DONE — the harvest is nine discrete adoptions, not a sweep; one LATENT golib bug banked in passing

The future-harvest item is now scoped from evidence: full census at CENSUS-span-unification.md (DRAFT, read-only, master d03f086c1). Headline: golib is more span-clean than the hypothesis assumed — ToSpan() is already the discriminant-once unification point, @string.Bytes already feeds the comparison/hash/concat paths — so the win is nine discrete adoptions with ~10 further sites dispositioned honest-no. First tranche by win×low-risk: C1 builtin.copy(slice<byte>, @string) routes through the COPYING implicit conversion (a full string-length allocation + second copy per call; every strings.Reader.Read pays it — fix is a two-line span copy); C2 same-type copy unification over ToSpan (merges the managed/native fork); C3 the IByteSeq copy-constructor element loops; C4 a ReadOnlySpan Append overload; C5 []rune(s) bypassing LINQ. All golib-only, converter untouched, CNR as the verifier.

BANKED FINDING (latent correctness, found in passing): builtin.cs:863–864 — the heterogeneous copy fallback indexes dst[dst.Low + i] / src[src.Low + i], but the slice indexers are already window-relative, so any nonzero-Low operand DOUBLE-OFFSETS. Unreachable from converted Go (Go’s copy is same-type — which is why no gate ever caught it); live for hand-written/interop code only. The sibling overload’s own comment (builtin.cs:807–810) states the rule the line breaks. Fix rides tranche C2.

Scheduling: ALL of it is golib, therefore inside the release-eve freeze — first post-release golib work, sized lane-scale. Process note from the census: several candidates delete CHARGED allocations, so the bytes/strings signature-pinned disclosure manifests may re-baseline in the favorable direction after the tranche — expect it, do not read it as drift. —

2026-08-23 · DESIGN PROPOSED — the Linux UDP seam: the eight //go:linkname datagram helpers, the decode half of the sockaddr mirror, and the last wall between the poller and name resolution (lane R, claude/linux-udp-design)

Commissioned in the overnight standing orders; written per house style: docs/phase4/DESIGN-linux-udp.md, STATUS PROPOSED, six OQs each with a recommendation, none self-ruled. Ratification requested on the mailbox.


2026-08-23 · MEASURED — JOB-007’s Linux leg: the formal dual-OS consolidation sweep at 18770d083 is 152 green of 162 with ZERO regressions, and every one of the ten FAILs is an already-classified seam (lane R)

Release-trigger condition (d), Linux half. Run at merged master 18770d083 on R-LAPTOP (Ryzen 7 PRO 6850U, WSL2 Ubuntu 22.04), gate first: go2cs-stdlib.slnx -p:GoTargetOS=linux native --no-incremental 0 errors / 149 warnings in 465 s — the same warning count as before the poller merged, so the union added none. Sweep aggregate 19,113 s (5.3 h); per-row wall times banked in DATA-sweep-row-walltimes.md beside the i9’s Windows table (H5).

The arithmetic — a QUADRUPLE, because the per-OS ruling added a verdict class

149 PASS · 10 FAIL · 3 CVAC of 162 → 152 green. CVAC (“comparison-validated-at-count”) is the class the ruling’s item 4 introduced and it went live in this sweep: a row whose C# side matched Go’s own count on this OS but which has no linux: annotation to bank against. A CVAC row is validated, so it counts green. The three: crypto/sha1 13, bytes 86, go/internal/gcimporter 582.

Against my pre-merge 161-row measurement: 7 flips, ZERO regressions, 1 newly-measured row. The seven flips are the per-OS machinery landing rather than new code — path/filepath, debug/buildinfo, mime, crypto/rand gained linux: annotations and so validate instead of reporting COUNT; crypto/sha1 and bytes are G’s W1b closing; go/internal/gcimporter reports CVAC. The COUNT class is now empty.

The ten FAILs, each attributed — this is the green bar, so it is itemised rather than summarised

row wall attribution
crypto/tls 711 s 2 divergences of 402 comparableTestVerifyHostname (behind the UDP wall, DESIGN-linux-udp.md) and TestCertCache (object-lifetime: SetFinalizer + forced GC + a 4 s refcount wait). 400 agree — the Windows banked count exactly
time 857 s R6 — the ZONEINFO / TestEnvVarUsage row, unchanged since the census
os/exec 740 s G’s named exec residue (mid-suite host death, TestLookPath dot-semantics)
debug/gosym 27 s G’s host-limit disclosure — TestSymVersion is Go’s own skip (no .gosymtab section: the apphost is a .NET binary)
internal/cpu 26 s W6 — capability-detection divergence
os/signal 36 s W2 — test-variant emission defect (converter)
syscall 35 s W2 — same class
sync/atomic 1,258 s W7 — the Gosched ring, RULED as an honest FAIL pending the M:N horizon
plugin 6 s W3 — the converter panic gcc re-exposed (conversionDriver.go:228 indexes GoFiles[i] by Syntax’s index under CGO_ENABLED=1)
runtime/debug 32 s newly measured — row #162 did not exist in my earlier 161-row runs. TWO already-classified classes stacked: TestFreeOSMemory fails (the object-lifetime class DESIGN-readmemstats-surface.md §7.2.3 documents) and the host then dies mid-suite, leaving the other nine as cs=None — the shape G named in the exec residue

Nothing unexplained, and nothing new. The one row that had never been measured on Linux resolves to two existing classes rather than a finding.

A harness note worth carrying

My per-row driver’s verdict regex was ^\s+(PASS|FAIL|COUNT)\s, written before the per-OS ruling, so the first two CVAC rows recorded as NOVERDICT — which reads exactly like “the sweep produced nothing”, i.e. like a broken leg. Caught at row 4 by reading the row logs, patched to include CVAC, the two bad ledger rows dropped, and the run resumed (the gate had already passed, so the resume skipped it; ~14 min lost). Any harness that greps the sweep’s verdict lines needs the same one-word patch.

2026-08-23 · UDP S1 DONE AND MEASURED (lane R, parked under the freeze) — the datagram seam closes; three findings outlive the arc

S1 works, measured on the distro: internal.syscall.unix + net build linux-native 0/0 with the REAL csproj; loopback UDP round trip AND a connected-UDP DNS exchange against the resolv.conf nameserver are byte-identical to go run (32 out, 160 back, id and answer count matching). Branch parked merged-ready (GPG park + emission-visible csproj change), first in line after the release. The UdpLoopbackRoundTrip guard follows, parked the same way.

Finding 1 — the DNS residual is NOT a syscall wall. net.LookupHost still times out in converted code while the transport underneath provably answers — the residual is in net’s own resolver machinery, ABOVE the socket layer. Routed as a diagnosis-first item (root it, no blind fix); S2’s msghdr pair is NOT implicated (connected UDP reads via plain read(2)).

Finding 2 — the L3 csproj-regen trap (⟨OQ-3⟩ amended in the design doc): a seeded single-package reconvert emits NO csproj; a single-TARGET stdlib filtered run regenerates one that silently DELETES the other GOOS ItemGroups. The instrument for an L3 package’s csproj regen is the THREE-target emission, full stop. Would have shipped a broken corpus if landed unmeasured.

Finding 3 — platform-specific hand-own companions need PRINCIPAL-LESS names. The L3 merge routes X_impl.cs into every folder its principal X.cs occupies — net.cs is //go:build unix, so a companion named net_impl.cs was copied into darwin/ carrying LINUX syscall numbers (measured). Named net_linux_impl.cs it is principal-less and stays put; re-measured: linux/ present, darwin/ absent. Rule: a companion whose BODY is platform-specific takes a name with no principal, and says why in its header.

Fleet ledger — the WSL crash-dump disk shape: a three-target regen is a ~20 GB operation; driving the WSL VHD to ENOSPC remounts the distro read-only AND writes enormous crash dumps to %TEMP%\wsl-crashes on the HOST (136 GB observed, C: to 0 bytes). Check that directory first when a box mysteriously reads 0 free; the ext4 inside was undamaged. R recovered to 136 GB free; lane healthy.

2026-08-23 · The .slnx registrations item CLOSES — and the investigation re-scopes it correctly first

The queue item read “.slnx registrations (math/big, runtime/debug)”. Investigated before editing, three facts established: (1) *.tests.csproj files are registered in NO solution BY DESIGNsolutionGenerator.go:175-195 documents why (pipeline-staged, git-ignored inputs mean a converted test project cannot build from a clean tree); the item was never about them. (2) The REAL gap: core/math/big and core/runtime/debug sit in go2cs.slnx’s build closure via GolibTests ProjectReferences but were unregistered — and the minimal CLOSED addition is THREE entries, because core/bytes is math/big’s own direct dependency and was unregistered too (BFS-verified; adding two would have broken the registered set’s transitive closure). Landed as a hand edit to the hand-maintained solution, check-solution-integrity.ps1 green. (3) The generated go2cs-stdlib.slnx needed nothing — both production csproj were already in it.

Follow-up item minted (not taken now): the closure convention has drifted wide — the full member closure of go2cs.slnx reaches 124 core projects and 55 are unregistered, dominated by SystemCertVerify’s crypto/x509 closure plus net/context/json/flag/os.exec/os.signal/ path.filepath/time.tzdata. Same VS-only failure mode as ever (harnesses build by path), so it rots invisibly; a leveling pass plus a gate that polices core registrations the way check-solution-integrity.ps1 polices behavioral ones would retire the class. Itemized in the investigation report. Also fixed in passing: CLAUDE.md’s stale mention of a src/go2cs-examples.sln that no longer exists.

2026-08-23 · R’s resolver rooting: three findings, one severe — and the struct-passing census RE-PRICES to a leveling arc

Finding 1 — the LookupHost residual is RESPONSE HANDLING, not transport. An in-process fake nameserver (echoes every query as a well-formed zero-answer response, same ID, same question) splits it cleanly: Go accepts in 1 ms and reports “no such host”; the conversion retries four identical, correctly-formed 42-byte queries and rejects every response, timing out at ~10 s. Exonerated by individual probes: the S1 datagram transport, query construction/randInt/runtime_rand, golib reslicing (the earlier 45-byte reading was the probe’s own fault — finding 2), and the address decode. Remaining fork: the connected read never delivers, or dnsmessage.Parser.Start/the ID compare rejects — fork-split by instrumented scratch-clone build sanctioned, diagnosis-only past it.

Finding 2 — GENERATOR-CLASS: the converted type assertion misses interface satisfaction via an EMBEDDED interface plus directly-added methods (c.(PacketConn) on a user type embedding net.Conn with ReadFrom/WriteTo added — Go takes the UDP arm, the conversion takes TCP framing). ImplementGenerator witness territory; ~40-line repro in R’s scratch; the live resolver path is NOT affected (*UDPConn’s assertion works — proven by the fake-test’s own 42-byte queries). Queued for the generator lane (G) when its queue reaches it. Method note: Go’s-own-baseline caught the first wrapper hiding the interface from BOTH runtimes — the only reason a phantom defect was not reported.

Finding 3 — SEVERE, at master: net.Interfaces() kills the process on Linux (AccessViolationException in anyToSockaddrRecvfromNetlinkRIB): the generated wrapper hands the kernel the address of a MANAGED RawSockaddrAny, and the kernel’s write corrupts the embedded array<int8> reference — the AV-not-panic is the tell (a bounds-checked indexer would panic on empty; only corrupted state AVs). Fix commissioned to R ahead of everything: the mirror pattern (native image + typed decode), Recvfrom minimally, Recvmsg/Sendmsg staying with S2’s evidence gate unless literally the same lines.

The re-pricing (coordinator ruling). G corroborated lane-to-lane within minutes: this is the FIFTH confirmed instance of the kernel-writes-over-managed-array class (Timezoneinformation, win32finddata1, ProcessEntry32, SiginfoChild, RawSockaddrAny) — three platforms, one root, one remedy proven four times, and the class has escalated from wrong values to memory corruption on a public API. The standing census’s “deliberately not fixed speculatively” doctrine has EXPIRED on its own evidence bar: each new instance now costs a diagnosis night that the proven mechanical remedy would have pre-empted. Minted: the struct-passing leveling arc — sweep the censused wrappers (the board’s 9-row census plus the instances named since) with the mirror pattern, G’s two darwin notes carried in (**T out-params are a stacked second defect; [StructLayout(Sequential, Size=N)] with explicit tail padding). Post-release, R’s queue after F3/the Windows wrappers/F1; per-wrapper evidence (a probe per fix, no blind batch) still applies — it is the SCHEDULING that changed, not the proof standard.

2026-08-23 · Census #3 (wall #3 pre-rooted from the coordinator seat) + two fleet-ledger traps + the Windows-UDP seam ruling

Wall #3 (run 32639211594 at d550cdc08): 9 errors, two leaves, both rooted before dispatch. (1) vendor/golang.org/x/sys/cpu compiles BOTH a flat and a darwin/ package_info.cs — the L3 partial-routing question for a package_info that varies by GOOS. (2) crypto/x509’s CS0234 on macos_package with the ProjectReference PRESENT and correct: the darwin-exclusive internal/macos package holds sources only in darwin/ but its csproj has NO GoTargetOS compile-selection block, so it builds a “successful” EMPTY assembly and the dependent fails — the bookkeeping-artifact class through a new door; the fix belongs in csproj emission for platform-exclusive packages. Dispatched to G; the next mac verdict proves walls #2 and #3 together.

Windows UDP wrappers — ruled a SUBMIT-SEAM extension, not a wrapper fix (R, halted at the design line): a Windows UDP send needs the operation record, a native OVERLAPPED and native WSABUFs — all private to syscall’s WSA hand-own. A public Go-shaped seam on syscall is rejected on that file’s own header doctrine; the ratified path is an amendment to DESIGN-netpoll-managed-poller.md §4.3–4.5 extending the GoAsyncIO rendezvous, then implementation under it. Not urgent (no roster row sends UDP on Windows); queued behind F1’s resolver fork-split.

Fleet-ledger trap 1 — bash glob eats a PowerShell gate invocation: powershell.exe -File <script> *>&1 from a BASH shell glob-expands the * into a positional argument; the script rejects it, the wrapper exits 0, and an eight-second “pass” never ran the gate. The *>&1 idiom is PowerShell-side only — from bash, quote it or omit it. (R, caught on its own CNR run and re-run properly.)

Fleet-ledger trap 2 — partial-class FIELD-INITIALIZER ordering: a hand-own file of a partial class must never initialize a field (e.g. = modws2_32.NewProc("WSASendTo")) from a GENERATED sibling file’s static field — C# orders static field initializers within a type but NOT across the files of a partial class, so the sibling can still be null and the first use dies in a nil dereference far from the cause. Defer the lookup to first use (??=); any hand-own reaching a generated mod*/proc* needs this. (R, found by crash while filling WSASendtoInet4.)

2026-08-23 · The nested-field-pointer finding, CENSUSED and COMMISSIONED — 4 hazard sites, 1 function, 1 package: the family’s sixth sighting, smallest yet

The finding (lane R, F1’s fork-split): taking a pointer to a field of a NESTED struct field and writing through it is silently lost — p := &b.header.questions; *p++ emits count = Ꮡ(b.header).of(Δheader.Ꮡquestions), boxing a COPY of the intermediate field, so the write lands in the copy. Rooted with a 40-line no-DNS repro (via-field-pointer and slice-elem-field both lost; direct mutation fine). Surfaced as dnsmessage.Builder emitting QDCOUNT=0 — which is BOTH real-world symptoms at once (nameservers ignore the malformed query; the resolver waits out its deadline).

The census (CENSUS-nested-field-pointer.md, exhaustive balanced-paren parse over all 5,565 Ꮡ(…) sites): the hazard shape appears at exactly 4 sites, all write-context, all in incrementSectionCount (vendor/golang.org/x/net/dns/dnsmessage/message.cs:1349–1361) — not on the roster, but 13 files under src/core/net reference dnsmessage, so net’s future validation walks into it. All lookalikes classified per-site: the two-arg element form is the PROVEN-correct aliasing (guarded by SliceElementFieldAddress), the 6 one-arg .of lookalikes are read-context, and the no-.of family is benign — with one watch item: 9 unix-only Ꮡ(syscall.ForkLock).RLock/RUnlock write-context package-var sites activate with the Linux campaign. Behavioral coverage of the hazard shape: ZERO — nothing contradicts the finding, nothing guards the fix yet. Reproducibility note: the corpus address-of glyph is U+13D1 (a U+13E1 grep returns a false all-clear).

Family placement: the SIXTH sighting of the address-of-copy-boxing family (element aliasing, xml’s binding box, sync/atomic’s local box, gob = fifth), and the smallest. The ж-box arc is the PERFORMANCE axis over the same neighborhood and does not touch this correctness gap.

COMMISSIONED (to R, after the §4.7 implementation lands): direct fix with guard per the family’s one-base-shape-per-fix precedent — the emission chains the address from the receiver root (.of(...).of(...), a form already routine at 309 sites) instead of boxing the intermediate field; one new behavioral guard for the write-through shape; dnsmessage regen; CNR pass. Corpus churn today: one file, four lines. Parks under the freeze like the rest.

2026-08-23 · THE DARWIN CORPUS COMPILES — census run 32649840220 at c003d32af: ZERO errors on osx-x64 AND osx-arm64; there is no wall #4

The third GOOS reaches its Phase-3 moment. Census history, all four runs on real Apple hardware within ~24 hours of the FIRST darwin build ever attempted: 19 errors (os.readdir hand-own gap) → 10 (cgo-flavor emission classes) → 9 (the case defect + the selector-less csproj) → 0. Both architectures byte-agree at every step. Wall #1 was a missing platform companion; walls #2–3 were converter/emission classes rooted one layer deeper than each census read — and every root now carries a guard.

Credits where the record should hold them: lane G fixed all three walls (with two corrections of the coordinator’’s pre-roots and one of its own correction — the measured form won every time); lane R’’s readdir companion opened the door; the CI matrix was the only darwin compile surface the project has, and the coordinator-driven dispatch loop turned each fix-to-verdict round trip into minutes.

The branch (claude/darwin-cgo-flavor-emission @ c003d32af) PARKS merged-ready despite the green — its corpus stage includes linux-flavor content (cpu’’s new linux/package_info.cs), which is inside the release-eve freeze class; it merges in the fixed post-release order (after the netlink fix). What darwin-COMPILES unlocks, priced but not scheduled: the darwin census stage on CI flips from wall-finding to REGRESSION-GUARDING (cheap, dispatchable at any branch tip); darwin behavioral-smoke becomes possible on the mac runners; and operational validation (a darwin -tests lane) remains UNCOMMITTED — evidence-ruled, per the ladder, not a rung by default.

2026-08-23 · F1 BANKED (e44bed59f, parked) — the sixth family sighting closes; two instrument-discipline traps join the ledger

The nested-field-pointer fix landed as commissioned: the emission chains one .of(…) per hop from the receiver root, marking walks the same chain, single-hop stays byte-identical, pointer hops excluded by type (already their own box). Guard ReceiverNestedFieldAddress is A/B-proven in the direction that matters: against the UN-fixed converter it compiles clean and prints 0 for every value-chain write — the defect’’s exact scope, and why the guard had to be behavioral rather than a golden. Seeded full-stdlib reconvert: zero corpus differences beyond the six-file footprint; CNR clean across all 634. Two first-cut defects were caught by GATES, not by reading: an IMPLICIT address (poly1305’’s promoted Sum(&mac) — no ast.UnaryExpr exists for the marker scan to see) produced CS0103 that only the reconvert-and-BUILD caught — CNR could not, no behavioral test has that shape; and name-matching the chain root over-marked a shadowing local, caught by a golden churning with no behavior change. Object-identity matching and the direct-ж requirement close both.

Ledger trap 3 — a per-package go2cs <pkg> reconvert into src/core is NOT a regen instrument. Its closure differs from the -stdlib driver’’s; applying one as a regen emitted csproj/.cs.auto/extra-file drift across three packages (recovered by checkout). The family rule now has three members: single-package emits no csproj (OQ-3 amendment), single-TARGET destroys L3 groups (same), and per-package-into-corpus drifts the closure. The only regen instruments are the seeded -stdlib run (single flavor) and the three-target emission (L3).

Ledger trap 4 — the 10-minute foreground cap vs corpus-scale operations: a 2.5 GB seed copy and a full -stdlib run each exceed a harness foreground budget on laptop-class machines — seed in one call, launch the converter DETACHED in the next, per the established Start-Process pattern.

2026-08-23 · Span tranche 1 (C1–C5) PARKED at c6e098926 — and a RULING: allocation-class golib changes gate by COUNTING, not timing

Tranche complete: GolibTests 276/276 (42 new guards), CNR byte-identical ×633 (the proof it was golib-only), behavioral 606 green. Two scope corrections worth the record: C2b was TWO inseparable defects — the banked double-offset plus an InvalidCastException in the same heterogeneous-copy arm (intlong crashed rather than converting); no plain-primitive pair reaches the arm AND survives the cast, so a guard for the offset alone would have been a test written to pass — fixed together through one ConvertElement<T>. C4 was solved by NARROWING: widening the existing span overload instead of adding a second, which would have put a CS0121 ambiguity in front of every collection-expression call site — the exact hazard the census flagged, avoided by subtraction.

The instrument ruling (coordinator, ratifying G’’s recommendation): the paired same-session timing A/B returned +0.7–1.4% on treatment rows while the UNTOUCHED control (Sieve) moved +5.0% and the identical-source Go binaries moved up to +17% between legs — host drift several times the effect size. Read the control row first; when it moves more than the treatment, the timing gate has said nothing. Standing rule for allocation-class golib work from tranche 2 on: gate with AllocationCounter COUNTS (deterministic, host-independent, the ByteSeqAllocationTests pattern) — C3’’s guard already asserts exactly-one-charged-allocation, C1/C5 delete charged allocations outright — and reserve timing claims for a benchmark that actually walks the changed path (a strings.NewReader read-loop perf row is MINTED as a future perf-suite candidate, not commissioned). Sweep note standing: C1/C5 may re-baseline a signature-pinned alloc-count disclosure FAVORABLY at the next sweep — expected, not drift.

2026-08-23 · FINDING (R): (Ꮡ<array<T>>)(uintptr) is MEMORY-UNSAFE by construction — 61 sites, 35 in runtime; DESIGN commissioned, fix is a representation decision

The mechanism, measured in GolibTests with no kernel/socket/async anywhere: array<T> is a managed struct whose first field is a T[] reference; a native-backed materializes with Unsafe.AsRef at the address — so the cast REINTERPRETS whatever bytes live there as a managed array reference and dereferences it. Two regimes: zeroed memory → the reference reads null and ?? [] answers Length=0 — a SILENT wrong answer (this is the netpoll recv panic’’s true mechanism, three layers from where it presented); memory filled with 0xAB → Length=0xABABABABa fabricated managed reference handed to the GC, returning a number by luck rather than safety. The source shape ((*[2]byte)(unsafe.Pointer(&x))) carries its length IN THE TYPE; the emission erases it and array<T> has nowhere to hold an address — so no spelling of the current types can be correct: the representation must change.

Census: 61 sites — runtime 35 (all flavors), syscall 14, internal/poll/windows 4, reflect 2, six elsewhere. Liveness UNAUDITED (stated, not glossed): ManagedPointerTokens.Resolve rescues reflect-originated pointers, and the roster’’s 18,569 green verdicts empirically bound what is reached today — the audit is part of the design, not a prior claim. Release impact: NONE — the class exists at every validated SHA; same accounting as the nested-field finding.

DESIGN COMMISSIONED (R — designs-first; a 61-site runtime-touching change gets no direct fix): weigh remedy (1), a native-backed mode on array<T> mirroring the RATIFIED native-backed slice<T> dual-mode (symmetry: one mental model, and that arc already measured the hot-path branch cost this remedy re-raises), against remedy (2), a distinct NativeArray<T> view implementing IArray<T> (visible/greppable, keeps array<T> untouched, costs an emission change plus two types agreeing wherever IArray<T> is consumed — R’’s recommendation, by the WSABUF-mirror reasoning). Remedy (3), pointer arithmetic, is recorded REJECTED as primary (partial coverage — several sites pass the view onward) but available as a peephole for constant-index sites under either. The design owes: the 61-site liveness audit, the slice-arc’’s measured costs pulled in as evidence (G cc’‘d as that design’’s owner), and the §4.8-fate measurement plan (with the byte-view fixed, whether recv needs the staging seam is a one-run answer). Implementation parks post-release regardless of route.

2026-08-23 · Release-machinery hardening item (user-directed, from the 1.23.1.7 release run) — the flavor loop trusted a build output nothing verified

The finding, from the first genuinely-clean-tree release run ever taken: on R with SDK 9.0.316, dotnet build go2cs-stdlib.slnx completes successfully WITHOUT producing gen/go2cs-gen/bin/Release/netstandard2.0 — a silent solution-member skip — and the subsequent dotnet pack --no-build dies with “could not find a part of the path”. Every prior box passed this loop only because LEFTOVER gen output from earlier builds masked the gap (measured: the coordinator’’s same-day dry run wrote gen output at 6:37 AM on 9.0.317 — correct behavior; R’’s two clean runs on 9.0.316 produced none — convicted twice). Flavor-independent (linux merely bats first in the reversed order); SDK-dependent. The F15 recipe’’s exact 9.0.317 pin was already right.

Work item (post-release, small): push-nuget.ps1 gains POST-BUILD validation in the flavor loop — after each build pass and before its pack, assert the load-bearing outputs exist (minimally go2cs-gen.dll at its expected path, golib’’s assembly) and fail LOUDLY naming the likely cause (“build completed without producing X — solution-member skip; check the SDK against the F15 pin, 9.0.317”) instead of letting pack report a path error three steps later. Stronger option to weigh in the same item: a repository global.json pinning the SDK band (with an explicit rollForward policy) would turn the whole class into a fast, named SDK-not-found failure — but it binds every developer build on every box, so it is a decision with a blast radius, not a default; the item prices both and picks one. —

Windows UDP: the send seam LANDED, and the read is the sixth struct-passing sighting (R, 2026-08-23)

internal/syscall/windows’ two WSASendtoInet4/6 linkname declarations were PartialStubGenerator stubs, so every datagram send on Windows threw. They are hand-owned now (windows/net_windows_impl.cs), submitting through the golib seam ratified as netpoll design §4.7 — so syscall and internal/syscall/windows share ONE operation record without syscall growing a Go-shaped public seam. Windows sends datagrams.

The read does not, and the measurement is the point. With the send fixed, UdpLoopbackRoundTrip reaches ReadFromInet4 and panics index out of range [0] with length 0 in rawToSockaddrInet4. Attribution, because it is easy to get wrong: the SAME (ж<array<byte>>)(uintptr)(new @unsafe.Pointer(...)) round-trip runs in the WRITE direction at fd_windows.cs:1277 and works (TCP exercises it), so the round-trip is innocent. The difference is the box — @new<syscall.RawSockaddrAny>(), managed arrays inside, handed to the kernel by address. Per the AV-vs-panic rule, a clean bounds panic means the array is EMPTY, i.e. nothing was materialised there. Sixth confirmed instance of the struct-passing class, on the DECODE side. Pre-existing and previously unreachable (the send stub threw first) — the LocalTimeZone pattern again: a real implementation exposes what the stub hid.

Its remedy is NOT this change’s shape. The received address arrives asynchronously, so a native staging buffer must be decoded at HARVEST — §4.3’s decode-side problem, shared with AcceptEx’s output buffer. UdpLoopbackRoundTrip therefore stays OUT of tree (registered it fails Windows, unregistered it fails solution-integrity) and registers with the recv increment; it is Linux-proven and parked.

Process-global registration and fake-based tests are in tension — resolve it INTERNALLY (R, 2026-08-23)

syscall registered its async operation factory lazily, from a TCP-shaped code path. A datagram-only program touches none of it, so the first UDP send died with no operation factory registered. Registering in a [ModuleInitializer] fixes it (the corpus already uses module initializers for Go’s init()), but it means syscall claims the process-global factory in any process that LOADS it — including GolibTests, which references core/syscall.

Signature to recognise: tests that pass filtered and fail in the full suite. Five of six new primitive tests did exactly that; they could not win a registration race against a module initializer no matter how they were ordered. The wrong fix is relaxing the public one-factory rule so the tests pass — that rule is the property the seam exists to enforce (two owners for in-flight operations is the failure it refuses). The right fix is an internal swap granted to GolibTests through the InternalsVisibleTo golib already carries. Generalises beyond this seam.



2026-08-23 · LANDED — the second darwin wall falls: four emission classes closed, and a fold narrowed six times by the corpus itself (lane G, claude/darwin-cgo-flavor-emission)

Wall #2’s 10 errors are gone; both cgo-flavor leaves build 0 errors at -p:GoTargetOS=darwin. Four converter/generator classes, each rooted rather than pattern-matched:

  1. CS0266 longuint (2). The constant fold’s unsigned arm declined every target narrower than uint64, so Go’s own negative-test constant 1<<32 - 2 fell to the literal path, widened to long, and met a uint32 slot (_C_uid_t). Fold-then-cast — with five conditions and a story of its own, below.
  2. CS1729 phantom hosts (4). NOT “ImplicitConvGenerator assumes a 1-arg ctor”, as the census read it: these records should never have been EMITTED. os/user and net declare their C-type mirrors as Go ALIASES TO PRIMITIVES (type _C_gid_t = uint32global using _C_gid_t = uint), and the locality predicate counted an alias as a local declaration — so the generator hosted partial struct UInt32 on a BCL primitive. That is the phantom the predicate’s own comment exists to prevent, arriving through the alias door; the name RESOLVES (to the CLR spelling), so it fails on a missing constructor rather than a missing type.
  3. CS0246 primitive sources (2). The same door from the other side: one genuinely-local operand admitted a record whose other side was _C_int = int, and the generated operator dereferenced .Value on a primitive. One shared predicate (typeIsPrimitiveAlias) closes both.
  4. CS0246 unbound T (2). A local named type inside a generic function closes over the function’s type parameters (doBlockingWithCtx[T any]’s type result struct{ res T; err error }), but the lift to package scope dropped them. The fix threads only the parameters the type ACTUALLY references — per the coordinator’s scoping directive — so a lift using none stays byte-identical; the declaration carries the binding, and liftedTypeMap carries the constructed spelling so every use site agrees.

The fold’s six narrowings, because the method is the finding. The fix was correct at its motivating site from the first attempt and wrong everywhere else, and the local darwin build was GREEN at every step — only three-target corpus regenerations exposed it. Sites touched: 754 across 157 files → 46 → 12 → 10 → 2. What each pass taught is recorded in ConversionStrategies-Reference.md (“A NARROW-UNSIGNED target folds a constant only when nothing else can make it compile”): named-type identity loss, underlying-vs-named targets, named-constant readability, untyped-vs-typed operands, and target-width vs uint32 thresholds. The generalizable rule: a converter change is measured against the corpus, not against the file that motivated it.

Gates. os/user + net at darwin 0 errors · go2cs-stdlib.slnx windows 0 / linux 0 · converter go test ./... green (after every narrowing) · CNR byte-identical ×633 after one intended golden re-baseline (ConstSubexprOverflow, the test written for this exact construct; Output phase passed unchanged) · behavioral suite PASS 606 · marker census unchanged. Corpus staged narrowly: the darwin per-GOOS folders plus the two image/* files where the fold legitimately applies; an unrelated linux-flavor regen delta (a rename, a StructLayout import, an array-dims attribute) was left for whoever regenerates next rather than smuggled in.

Proof gate outstanding: the mac legs. My local darwin build succeeding is corroboration, not substitution — if CI disagrees, believe CI.


2026-08-23 · LANDED — the third darwin wall falls: a case-sensitive package class, and an L3 invariant the merge never asserted (lane G, claude/darwin-cgo-flavor-emission)

Wall #3’s 9 errors are gone, and both dispatch roots were one layer off. The fixes matter less than that, so they are recorded together.

Leaf 2 — crypto/x509 CS0234 on macos_package — is a NAMING defect, not an empty assembly. The dispatch read it as a missing $(GoTargetOS) selector block leaving zero sources compiled. Measured instead: the block is present, the Compile item set at darwin is all three darwin/*.cs, and the package builds 0 errors standalone. The real root is that crypto/x509/internal/macos is package macOS. The declaration side already names its class for the Go package (macOS_package); convertImportPathToNamespace composed every importer’s spelling from the import-path tail (macos_package). C# is case-sensitive, so the two never met — and the assembly genuinely contains no macos_package, which is exactly how a true observation (“the symbol isn’t there”) acquires the wrong mechanism.

The path-tail substitution existed but excluded the standard library, premised on “a stdlib package is named for its directory”. Censused across all three targets, that premise fails for exactly four paths: crypto/x509/internal/macos (darwin only), math/rand/v2 (already correct through the /vN branch), internal/trace/internal/testgen/go122 and runtime/internal/wasitest (nothing in the corpus imports either). Trusting the import graph everywhere therefore keeps the byte-identity the exclusion was asserting — with CNR as the proof rather than the comment as the premise. The fix restructures instead of special-casing: when the graph knows a package’s name, that name is the class segment; the /vN convention stays as the fallback. A narrower “substitute only when they differ” test would have looked equivalent and quietly broken the exotic case the convention exists for — a package literally named vN.

Leaf 1 — the duplicate package_info.cs — and two corrections against my own first measurement. The dispatch guessed the flat copy retires and the per-GOOS copies are routed. I diffed flat against darwin/, found them byte-identical, and concluded the opposite: shared, so flat is correct. Wrong. windows and darwin are identical for this package and linux is the one that differs (two extra position-map records for its own sources). The flat copy was the windows flavor all along. Comparing two copies of three proves nothing, and the dispatch’s guess was right.

The second correction is about credit. The live instance was healed by the merge’s existing plan loop — Stale copies removed: 1 is its variant branch retiring the flat copy once all three targets emitted the artifact. My reconciliation pass removed 0. The gap it closes is real and now guarded: the merge only reconciles logical paths some target re-emitted, and “emitted” means the bytes changed (needToWriteFile skips an identical write, which is what keeps a reconvert’s timestamps meaningful and what the seed sentinel rests on). A file every target reproduces exactly appears in no plan, so a corpus that already holds it flat and per-GOOS keeps both forever — invisibly, because the emitted csproj carries <Compile Include="*.cs" /> and <Compile Include="$(GoTargetOS)/*.cs" />, so the duplicate joins its own compilation (CS0579, CS1537 ×6). That state needs no mistake: an earlier regen routes a file per-GOOS, a later restore ritual recreates the flat copy, and both persist. HEAD genuinely held one. But it is a safety net that did not fire here, and it is not what fixed the wall.

Identical duplicates now retire; differing ones are deliberately not guessed at — with no emission data for a path no target rewrote, the merge cannot tell which platform owns the flat copy, and either choice silently breaks one, so it names both paths and fails the run. The scan tests isPlatformSourceFolder, not the directory name, because internal/syscall/windows is a real package whose directory is named for a GOOS.

Ride-along (coordinator-sanctioned): the staging seeder stops copying build outputs. Measured during this regen — the seed reported 60,931 files against a tracked corpus of roughly 4,000: ~57k build artifacts, copied once per target and three times per multi-platform run, the single largest cost in it. Nothing downstream reads them (the emitted-vs-seeded classification only considers files a target wrote, and a staging root is never built in), so the copy bought nothing.

Gates. crypto/x509 at darwin 0 errors · vendor/…/x/sys/cpu at darwin 0 errors · go2cs-stdlib.slnx windows 0 (cold, 395 s) / linux 0 (cold, 404 s), each after purging 916 output folders because a GoTargetOS switch changes the item set without changing a timestamp · converter go test ./... green with seven new guards, the naming one neuter-tested against the unfixed condition (it reports exactly the shipped macos_package spelling) · CNR byte-identical across all 633 behavioral packages · behavioral suite PASS 606 (Output 580 pass / 26 skip, 1,982 s) · go generate . no metadata drift · marker census re-measured at 70 marked files / 56 *_impl.cs companions, 0 clobbered.

Corpus staged narrowly, per wall #2’s precedent on this branch: the two crypto/x509/darwin files, and cpu’s package_info.cswindows/ (git records a pure rename) plus a new linux/package_info.cs. The regen also carried ~24 linux/package_info.cs files gaining a <GoSourcePositionMaps> block, a runtime/windows implicit-conv record delta, a Δp.Value != v.ValueΔp != v pointer-comparison change, five .cs.auto refreshes and 52 CRLF phantoms — all other lanes’ arcs that landed without regens, restored rather than smuggled in.

Method note, the mirror of wall #2’s. Wall #2 taught that a converter change is measured against the corpus, not the file that motivated it. Wall #3 teaches: a pre-rooted dispatch is a hypothesis with evidence attached, not a finding — and so is your own first measurement. Both briefs described real observations and named the wrong mechanism; my own correction of one of them was itself wrong, from measuring two of three copies. The cheap checks that separated all three — read the csproj, diff every flavor, ask which code path actually removed the file — cost minutes each.

Proof gate outstanding: the mac legs, now against the wall-#3 tip rather than d550cdc08. One run, both walls.

F1 fixed: &recv.f1.f2 copy-boxed, and the two halves of an address-of fix must move together (R, 2026-08-23)

The converter recognised &recv.field but not &recv.f1.f2. The deeper chain fell through to the Ꮡ(value) copy-box, so every write through the pointer went into a temporary – compiled clean, ran clean, printed wrong numbers. Corpus census: 4 write-context sites, all in dnsmessage’s incrementSectionCount, where it left the DNS header’s QDCOUNT at 0 and surfaced three levels away as an unexplained resolver timeout. Fixed by walking the chain in BOTH the emission (convUnaryExpr) and the scan that marks a method direct-ж (bodyTakesReceiverFieldAddress); one-hop emission is byte-identical, so nothing else moved.

The lesson that generalises, and it cost a full-corpus build to learn: an address-of fix has TWO halves – where the address is EMITTED and where the box is MARKED – and shipping one without the other is a compile error, not a no-op. Marking is driven by scanning for an explicit &recv.f1.f2. An implicit address is invisible to that scan because there is no ast.UnaryExpr in the tree at all: h.mac.Sum(&mac) in poly1305, where Sum is promoted from an embedded field, takes &h.mac.macGeneric implicitly. The first cut of the fix emitted the box form there and produced CS0103: The name 'Ꮡh' does not exist – in a method whose receiver is this ref MAC h. A deep chain now additionally requires the enclosing method to actually BE direct-ж; implicit sites decline and keep their value-chain form, which is already correct for them (a ref receiver reaches real storage).

CNR would NOT have caught it – no behavioral test has poly1305’s shape. It was caught by the seeded reconvert-and-BUILD that CLAUDE.md already requires for Ꮡ-machinery changes, which is the second time that rule has paid for itself. Corpus footprint after the fix: 6 files (dnsmessage

Two process notes worth banking. (1) A per-package go2cs <pkg> reconvert into src/core is NOT equivalent to a -stdlib run and must never be used to apply a regen: the closures differ, and mine emitted csproj/.cs.auto/extra-file drift across runtime, net and poly1305 that took a git checkout -- src/core to undo. Apply regens from ONE seeded full reconvert. (2) On this machine a 2.5 GB seed copy plus a full -stdlib run both exceed the 10-minute foreground cap – seed in one call, launch the converter DETACHED in the next, and poll; a foreground converter killed at 300/304 leaves a root the ritual says to throw away.



2026-08-23 · LANDED — F2: a promoted interface method was not in the type’s GO METHOD SET, and the root was two layers below where it was reported (lane G, claude/implgen-embedded-witness)

The defect. A type satisfying an interface via an embedded interface plus directly-added methods was not recognized by a converted type assertion — a user type embedding net.Conn with ReadFrom/WriteTo added failed c.(net.PacketConn), so Go took the UDP arm and the conversion took TCP framing. Reported as ImplementGenerator witness territory.

Four controls, each of which killed a candidate root. Reproduced from scratch without net, in a two-package module:

Variant Result What it kills
Same-package interfaces works “embedding is the defect” — the axis is that the interface is FOREIGN
plain, both methods declared directly, no record works “the GoImplement record is the mechanism”
Direct call w.Read() works, emits w.Reader.Read() “the promotion is missing” — the converter resolves the hop at call sites
holder, ordinary field whose name equals its type’s simple name Go says no, converter emits no record “a name heuristic can drive the fix” — it cannot; the record can

The root, two layers below the report. ImplementGenerator is exonerated: it already emits the promoted method as a real MEMBER, from the Promoted = true record it already writes. golib builds a type’s Go method set exclusively from EXTENSION methods (GetGoMethodSetCandidates), and a promoted method is the one kind of Go method that never becomes one. builtin.Implements<T> answers a DIRECT assert with C# is T — which is why asserting to the embedded interface itself always worked — while any other interface falls to the structural probe, which saw only the directly-declared half.

Fixed where the method is created, not where it is missed: the promoted method is now emitted as an extension method too, so it is an ordinary Go method — probe finds it, AdapterBinder binds it through the same candidate source, reflect.NumMethod counts it, as Go does.

Why not golib, having been sanctioned to fix it there — the invariant was working. Two refusals, both in golib’s own words: every method-set candidate is assumed to be an extension method whose FIRST PARAMETER is the receiver (PrefersBindableShape indexes GetParameters()[0]), so a no-argument member throws; and GetGoMethodSetEntries’ header states that probe, count and binder resolve through ONE source precisely so they cannot disagree — teaching only the probe would make Implements answer yes for a shell that then fails to bind. A design that refuses you for a stated reason is evidence, not an obstacle.

Method note — the root moved three times, and each move was a measurement, not a guess. Reported at ImplementGenerator; I placed it at “the members are never realized” (wrong — they are); then at “the record is the mechanism” (wrong — plain works without one); then at golib’s method set (right); then the remedy moved to the generator when golib refused it. Every step was one cheap control away, and the pattern that made it converge is the same one wall #3 produced: a pre-rooted report is a hypothesis with evidence attached, and so is your own last answer. Corrected before spending, twice.

Gates. Behavioral suite PASS 607 (Transpile/Compile/Target 607 each; Output 581 pass / 26 skip; 0 fail) · Target byte-identical and a full 607-project re-transpile left the tree clean, so no converter output moved · CNR byte-identical · guard neuter-tested (with the emission disabled its Output phase fails). Accessibility was measured before emitting: ExtensionMethodRegistry discovers with BindingFlags.NonPublic, so an unexported type’s twin is still found by a FOREIGN assembly’s assert — exactly where F2 bites.

The guard’s third row is the one to keep. EmbeddedInterfaceWitness prints NumMethod per row (2/2/1) so the method set cannot gain an entry it should not, and holder — whose C# field is identical to wrapper’s — is what stops any future fix from resting on the name heuristic that once made dwarf forward Common() through a named field and return a silently wrong answer.

(ж<array<T>>)(uintptr) is MEMORY-UNSAFE by construction — 61 sites, 35 in runtime (R, 2026-08-23)

array<T> is a MANAGED struct whose first field is a T[] reference (array.cs:47, Backing => m_array ?? []), and a native-backed ж<T> materialises its value with Unsafe.AsRef<T>((void*)m_nativeAddr) (ж.cs:250). Together those mean (ж<array<byte>>)(uintptr)(…) reinterprets whatever bytes live at that address as a managed array reference and then dereferences it.

Measured in GolibTests against golib directly — no kernel, no socket, no async:

memory at the address result
zeroed Length=0 (the reference reads null, ?? [] gives the empty array) — a SILENT WRONG ANSWER
filled 0xAB Length=-1414812757, i.e. 0xABABABAB — it fabricated a managed reference out of the filler bytes and dereferenced it, returning a number instead of faulting BY LUCK

The second row is the finding: this is a type-safety hole, not a wrong result. Any real data — a filled sockaddr, a siginfo, a register block — takes that path.

Census: 61 sites. runtime (+ per-GOOS) 35, syscall darwin/linux 14, internal/poll/windows 4, reflect 2, and 6 across net/darwin, the registry wrapper, vendor/…/route, vendor/…/sha3. Not yet audited for liveness — the ManagedPointerTokens.Resolve arm rescues reflect-originated pointers, and many sites are simply unreached on the current roster.

The source shape is unrepresentable, not mis-emitted. Go’s p := (*[2]byte)(unsafe.Pointer(&pp.Port)) carries the length IN THE TYPE; the emission erases it, and array<T> has nowhere to put an address anyway (it holds a T[], not pointer+length). No converter-side spelling of the current types can be correct — the representation has to change. Three remedies priced on the mailbox (native-backed array<T>; a distinct NativeArray<T> view; pointer arithmetic per site); placement is the coordinator’s.

⚠ CORRECTION to the three entries above it: the Windows UDP read is NOT the struct-passing class (R, 2026-08-23)

I banked the recv panic as “the sixth struct-passing sighting, decode side” in the §4.7 board entry, in netpoll §4.7.6 and §4.8.1, and in ConversionStrategies-Reference. That attribution is wrong and it was mine. The panic is the byte-view defect above: a golib representation hole with no kernel anywhere near it.

The argument I used was itself false, and the way it was false is the lesson. I wrote that the identical round-trip “runs in the WRITE direction at fd_windows.cs:1277 and works today — TCP exercises it”, and concluded the round-trip was innocent and the managed box guilty. TCP does not exercise it: sockaddrInet4ToRaw has exactly three callers — WriteMsg, WriteMsgInet4, WriteMsgInet6 (:1456/1490/1520), the WSASendMsg path — and nothing on the roster reaches them. The write direction was unexercised, not working, and it carries the same expression writing p.Value[0] = …, so it fails identically the moment anything calls it.

“A passes, therefore the code A shares with B is sound” requires checking that A actually runs B’s code. I inferred it from a suite being green and did not check the call graph — the same species of unmeasured inference the AV-vs-panic rule exists to replace, applied to my own claim instead of someone else’s. The probe that settled it took two minutes and should have come first.

2026-08-23 · Release-machinery item #2 (user-directed): the NEWS lands BEFORE the tag mints, so the tag’’s tree carries the announcement

From 1.23.1.7’’s ritual: the nuget-<version> tag deliberately mints at Phase 1’’s pre-build point (provenance — the READMEs frozen inside the published packages link tree/nuget-<ver>), but the NEWS/milestone text was applied AFTER the release — so a visitor browsing the repo AT the tag sees a NEWS block that predates the announcement. The tag cannot move (it anchors the shipped binaries’’ exact tree; the post-release master contains merged work the binaries do not).

Rule for the next release: the §4-style announcement text — NEWS block, NEWS.md entry, milestone table rows — is applied to master BEFORE release-nuget.bat runs, so Phase 1’’s tag contains it. The version placeholder is deterministic pre-bump (build+1, the would-be version the dry run already computes), and links into docs/validation/<version>/ resolve on the live site regardless of which tree the visitor browses. Add the step to the release checklist (MILESTONE-prep §3-style docs) alongside the existing hardening item #1 (the post-build gen-output assertion, landed) and the pack-race root-cause (in forensics).

2026-08-24 · THE COMPLETION LEDGER CLOSES — JOB-013 green at 8922d453a; the .NET 10 HOP ERA OPENS

The user’’s sequencing rule — complete the arc, then the hop — is satisfied by measurement. JOB-013: sweep 162/162 (18,569 = 18,569, verdict arithmetic closed by hand), behavioral 609/609, drift matching the documented shapes. Every ledger item closed on its own evidence: the three JOB-010 regression shapes (each a SUBTRACTION — the twin is always internal, the twins leave bare-name lookup, and the promoted pair registers only when types.Implements holds), the leveling bank (which proved itself complete by regenerating to nothing), the provenance increment end to end (mechanism → closed-form audit → consumer, with the suite that killed the safety floor passing the mechanism 609/609), and the pack-race forensics (measured-and-hardened: node reuse defused, the repair kept as an alternation instrument). H1.4 rode alongside and closed the false-green catalogue’’s last route — all five are shut, so the corpus hop cannot be lied to by its own gates.

One rider, non-gating: JOB-013 reported 8 files of unclassified drift, all pure deletions of erroneous Promoted = true records — verified against master: Shape C’’s fix is correct and the COMMITTED corpus test-info for five packages simply predates it. That is intended emission drift, banked by the proper instrument (a per-package -tests regen, never a hand edit), and it gates nothing: the sweep that measured it was itself 162/162 green.

Era transition. The initial era closed with a signed, published, validated anchor (1.23.1.7, over 75% of the testable standard library, on Windows and Linux). The new era opens on the ladder’’s next rung: .NET 10 first (mandatory before .NET 9’’s 2026-11-10 EOL; DotNetMigration.md executed AS WRITTEN, its first run doubling as its shakedown, deviations fixing the runbook in-stage), then Go 1.23.12 (recon banked, the W=4 shard map computed from real per-row wall times). Seats: G owns the .NET 10 before/after performance report — fresh baselines with the bflat exploration’’s lessons folded in and prediction N5 closed on the AOT leg; R owns Stage-0 fleet provisioning; the i9 owns the stage gates it has owned all along. —

2026-08-25 · THE DOC HIERARCHY INVERTS — the runbooks lead, and nothing was deleted to do it

The era directive above says the runbooks are executed AS WRITTEN, deviations fixing the runbook in-stage. The tree did not say that. Exactly one sentence contradicted it — GoCorpusMigration.md’s header, “where they disagree about what to do, the plan governs” — and everything else pointed runbook-ward already. A five-commit docs train on doc-consolidation makes the tree state what it practices. Its internal amendments are stamped 2026-08-24, the day the design was ruled.

The ordering was load-bearing, not stylistic. The plans carried four statements the runbooks and the tree had already falsified, so those were corrected FIRST (commit 1) and authority moved SECOND (commit 3). Inverting first would have crowned a falsehood in the new direction. The falsified four: H1 step 1 asking for GOROOT/VERSION (a pin redirects it silently); 1.4.2’s “proposed remedy” for a hole closed at H1.4; “the Linux corpus does not yet build”, overtaken when the wall fell 2026-08-14 at 307/307; and OQ-14’s “every hop publishes”, which was framed over Go-version hops and needed OQ-H3’s scoping.

The runbooks caught up to their records (commits 2a/2b) — nine lessons absorbed into GoCorpusMigration.md, four into DotNetMigration.md, each a rule that generalizes, each with its destination in the SAME commit as its removal so the diff proves nothing was lost. The sharpest: DERIVE the reserved set at generation time (the copied list drifted twice); a CRASH is not a divergence and no disclosure absorbs one; fragility has TWO axes and a signature-oriented triage looks at only one; a row with NO manifest compares strictly, so strict-compare rows with upstream-changed production code outrank big manifests; and bank a migration’s INPUTS in the commit that claims them, because the report is not the artifact. DotNetMigration.md also stopped prescribing a defect: it told a hop to hoist the TFM, and the hoist was tried in that stage and falsified. A hoist still needs an editor; a derivation needs nobody.

Nothing was deleted. PLAN-corpus-upgrade.md sections 2, 3 and 4 became anchor-preserving pointer shells: every H-heading survives because they are a citation namespace — migrate-gorelease.ps1 cites H2 and ConverterBuildInputs.cs cites H1.4 by name — and all nineteen OQ-n marks survive because section 8’s rulings are what a runbook “(ruled)” resolves against. Two readings with no other home were retained rather than shelled. The counterweight that makes the inversion safe is stated in both runbook headers: a runbook edit never reopens a ruling.

Records got their state lines (commit 4). Five documents still said DRAFT while the work they scouted had shipped. The 1.23.12 rehearsal’s eight findings each carry a disposition now: six closed against named commits, one (the Linux-vs-Windows t_r question) resolved by standing rule — LANES.md already rules that cost inputs come from fresh calibration at campaign recon, so the leg is measured with k and s_w rather than ruled separately — and one carried as evidence. Finding H’s last third closed too: the rehearsal’s own three raws are banked verbatim in hopA-inputs/, so no hop input is living in a session scratchpad any more.

Hop A’s one open blocker is now visible from the campaign plan, not only from an orphan record: the asynctimerchan=2 AV, which is bounded runtime work owed BEFORE H10.

Doc types are defined in Glossary.md — runbook, plan, hop, record, board, mailbox — and CLAUDE.md carries the one-paragraph ladder. Routing rule, in one line: procedure to the runbook in-stage, harness and gate doctrine to CLAUDE.md, findings and measurements here. The mailbox header said “doctrine lands on the board” and predated the runbooks; it now routes by kind. That header also lives on claude/mailbox and the coordinator mirrors it there.

Docs-only: 21 files edited plus 4 created, all under docs/ and CLAUDE.md. Zero source, zero corpus, zero goldens — so CNR is not owed, and the gate each commit DID carry is link integrity over its touched files. One tier of hygiene is deliberately deferred past the hop’s start: DotNetMigration.md’s Stage-0 incident narratives, whose home is STAGE0-provisioning.md.

2026-08-25 · MEASURED at the new pins — math/big does NOT bank, and the TestMulUnbalanced ratio moved the WRONG way: 51x -> 59x across the hop (lane R, claude/laneR-nethttp-mathbig)

The coordinator’s addendum commissioned a re-measurement of math/big at net10.0 + Go 1.23.12 with an explicit either-way question: if .NET 10 brought the converted nat.mul inside Go’s 10x bound the row banks at one disclosure; if not, the row stays with the zh-box/B’ arc and the fresh reading is that arc’s exhibit. It is not inside the bound, and it is further out than it was.

measurement allocSize ratio (Go’s assert) pins
board, r58b / r59 20,499,128 B 51 Go 1.23.1 · .NET 9
this lane, run 1 23,767,976 B 59 Go 1.23.12 · .NET 10
this lane, run 2 23,750,664 B 59 Go 1.23.12 · .NET 10
Go itself, same assert passes (bound is 10) Go 1.23.12

inputSize is fixed by the test at (50000+40) x 8 = 400,320 B, and the assert is ratio := allocSize / inputSize; ratio > 10 — so the second %d in the failure text IS the computed ratio, not the bound. Read it that way; it is easy to misread as “59 times” being a budget. The two runs sit 0.07% apart — the same noise scale as the board’s own 20,499,128 against r58b’s 20,487,208 — so 59 is a stable reading, not a sample. Across the two hops the converted path allocates +3,268,848 B more than it did: +15.9%.

Verdicts otherwise unmoved: 224 of 226, the same two rows as at 1.23.1 (TestMulUnbalanced, TestNewIntAllocs). Unlike net/http/internal, whose count moved 9+1 -> 14+1 on the release’s bare-LF hardening, math/big’s suite did not grow at 1.23.12 — 226 on both sides. TestNewIntAllocs re-measures at exactly 1 obj/run on all seven NewInt shapes (want 0), verbatim the prior reading — class 3b, the escape-analysis elision.

Consequences.

  1. math/big stays OFF the near-term reserves, where the 2026-08-21 ruling placed it. Nothing about the hop rehabilitates it, and the row’s price went UP rather than down.
  2. The +15.9% is not attributed here, deliberately. This was a measurement lane; attributing it would need the decomposition probe the arc owns, and a fix inside a measurement lane un-controls the measurement. It is filed as a question the zh-box/B’ arc should carry: whichever of net10.0 or go1.23.12 moved it, the arc’s target just got ~16% bigger, and an arc sized against 51x is sized against a stale number.
  3. The row is not disclosable — the 2026-08-21 ruling stands, and this measurement reinforces it rather than reopening it. A disclosure names what is provably unsatisfiable; a quantitative excess an arc can reduce is not that. (Contrast the same day’s net/http/internal call, where the excess is one object and structurally inherent, and the disclosure IS correct — the two rows together are a usable worked example of where the line sits.)

2026-08-25 · POST-HOP FRONTIER FOLD — the frontier re-derives twice and agrees to the digit, row 163 banks, tier-1 re-prices at the new pins, and three rulings land (coordinator)

The march to 100% resumes on net10.0 + go1.23.12. The frontier was derived twice independently — a coordinator-side Opus lane and i9’s JOB-019 step 1, different scripts, both under parse-count and subset controls — and agrees exactly: 215 testable − 162 banked = 53 at derivation time. Six of the 53 are ruled out on windows/amd64 (internal/runtime/syscall, internal/syscall/unix, net/internal/socktest, log/syslog, runtime/race — no eligible Test declarations on this target; os/user — Go’s own oracle fails TestGroupIds, no clean baseline exists), so the honest Windows frontier was 47, and with this fold’s bank it is 46 of 52. Four frontier rows are already Linux-proven at zero divergences (net/smtp 19, net/http/httptest 55, net/http/httputil 53, net/rpc 15 — the 2026-08-22 S3 ledger) and Windows-socket-walled; the per-OS roster-shape question they pose is HELD at the coordinator, unruled here.

Row 163 banks: net/http/internal, 14 matching + 1 disclosed at Go 1.23.12 (lane R, claude/laneR-nethttp-mathbig). The count MOVED from the board’s 1.23.1 pricing of 9+1 — the release’s bare-LF request-smuggling hardening added TestChunkInvalidInputs and its four subtests — which is the hop doctrine (measure, never assume) earning its keep on the first post-hop bank. Post-merge filtered sweep at the MERGE RESULT: PASS 14, exit 0; roster header recomputed from the table under a parse-count control: 163 rows / 18,612 matching / 86 disclosed, delta exactly this row.

RULING — the alloc-assert triage recipe is doctrine, and the row’s disclosure class was corrected by it

R’s reclassification of TestChunkReaderAllocs from the dispatch’s alloc-count-semantics to alloc-profile is RATIFIED, with its rooting: the converted NewChunkedReader allocates the box (Go’s &chunkedReader{} analogue) PLUS a chunkedReader->io.Reader interface shell, and DESIGN-iface-shell-caching.md §2 rules the shell structural — C# has no two-word interface value; nothing removes it short of IDynamicInterfaceCastable; the measured loop mints a fresh reader per iteration so no cache amortizes it. The triage recipe generalizes and is now the standard (open the emission for the function under the assert; count the allocations on the measured path):

  1. the converted path allocates an object Go’s does not (interface shell, ж box, captured-closure frame) → alloc-profile — disclosable only when the extra object is structurally inherent;
  2. comparable counts, merely MORE of the same → a quantitative excess an arc can reduce — never disclosed; route it (math/big is the worked example);
  3. the COUNTER itself is incomparable (context’s byte-derived shim) → alloc-count-semantics, the RARE class (6 rows corpus-wide), never the default.

RULING — TestGCMAsm closes as a source-defined platform skip; the manifest class platform-skip is minted

Held since the 2026-08-20 measurement pass; i9’s tier-1 re-measured the identical shape at the new pins (12/13, C# skips "no assembly implementation of GCM", gcm_test.go’s own skip). The ruling: the C# side executes the skip branch Go’s own test source defines for a platform without a distinct assembly GCM implementation, and the managed corpus IS such a platform — by design and permanently (no .s codepaths). The divergence is between two PLATFORMS’ verdicts on one test, not between Go and the conversion on one platform. runtime-capability’s refusal stands (a truthful second GCM exists at a cost); this class exists because that cost buys nothing Go’s own source does not already define away — building a second GCM whose only consumer is a differential test manufactures a platform property instead of measuring one.

Admission test, binding: the skip taken must be the UPSTREAM test’s own skip statement, conditioned on a platform property the deployment genuinely and by-design holds — never a host-limit workaround, never a skip the harness injects, never a skip added by conversion. Anti-laundering: the signature pins Go’s own skip message from the test source, and the row records the verdict pair (Go pass / C# skip) openly. crypto/cipher banks at 12 matching + 1 disclosed (platform-skip); the banking lane implements; the class joins the roster preamble WITH the banking commit, per the chan-direction/runtime-capability precedent.

RULING — the ж-box arc re-sizes against 59x, not 51x

R re-measured math/big’s TestMulUnbalanced at the new pins twice (0.07% apart): allocSize 23.77 MB / ratio 59 vs the banked 51 — +15.9% allocated bytes across the two hops, unattributed by design (a measurement lane does not decompose; the arc owns the probe). The row stays with the ж-box/B’ constituency per the 2026-08-21 ruling; an arc sized against 51x is sized against a stale target. TestNewIntAllocs re-measures verbatim (1 obj/run, all seven shapes); 224 of 226, suite unchanged at 1.23.12.

Tier-1 shard, folded (i9 JOB-019 — measurement only, artifacts retained per row)

FINDING ROOTED — log/slog’s “harness termination” is not a harness defect: the host DIED, and it said so

Lane claude/slog-harness-halt reproduced i9’s tier-1 shape exactly at cc90c2bf1 (Windows, go1.23.12, net10.0, -test-timeout 15m, 209 s): 181 terminal verdicts, the last of them TestSetDefaultfail, and then nothing for the ten ordinally-later top-level tests. The host did not stop advancing. It exited on an unrecovered goroutine panic, which is deliberate Go fidelity (TestRunner.cs:158-170), and it recorded exactly that before dying — the final event in go2cs_test_results.json is a package-level fail whose output reads “test binary died on an unrecovered panic in a goroutine”, and the host’s stderr additionally prints Go’s own crash-report form naming log/slog.(*defaultHandler).Handle() at handler.go:117. The diagnosis was already in the artifacts; it was read as emptiness. No host change is owed for the termination itself.

Root chain, mechanical. Go guarantees an imported package is fully initialized before the importing package’s own initialization. go2cs maps a Go init to [GoInit] = .NET [ModuleInitializer], whose guarantee is weaker — a module constructor runs at first access to that module — and golib’s builtin.initPackage (src/core/golib/builtin.cs:213-236, whose doc-comment states Go’s rule verbatim) exists precisely to close that gap. But the converter emits that forcing only for BLANK imports (src/go2cs/visitImportSpec.go:370, into v.blankImportInits). For a NAMED import the ordering is therefore absent, and log/slog is a package where the difference is observable:

  1. src/core/log/log.cs:268-269log’s init assigns Δinternal.DefaultOutput.
  2. src/core/log/slog/logger.cs:57-59log/slog’s init READS loginternal.DefaultOutput. The test host touches slog first, so slog’s module ctor runs while log’s has not: the read yields nil.
  3. src/core/log/slog/handler.cs:92-97newDefaultHandler(output) stores that nil in defaultHandler.output. The value is CAPTURED, never re-read, so log’s later initialization (any test calling log.SetOutput) cannot repair it.
  4. src/core/log/slog/handler.cs:120return h.output(r.PC, buf.ValueSlot); → NullReferenceException → rendered as Go’s “invalid memory address or nil pointer dereference”.

The consequence then splits by THREAD, which is the whole reason this looked like two unrelated findings:

A/B proof. One line at the top of slog’s initbuiltin.initPackage(typeof(log_package)), i.e. the forcing the converter already knows how to emit — moves TestSetDefault fail → pass and TestLogLoggerLevelForDefaultHandler fail → pass, and the whole tail executes: 214 terminal verdicts, 190 pass / 24 fail, against 181 terminal with the run cut short — +33, i.e. the ten unreported top-level tests and their subtests. (Both figures derive from the strict 1:1 run/terminal event pairing the host emits: 362 events baseline, 428 with the probe.) The 24 remaining failures are the alloc-assert rows plus the Source/CallDepth/RecordSource naming divergences already priced above — untouched by this, and unrelated to it. The probe was reverted; nothing is banked from it.

Classification: converter defect (Go cross-package init ORDER not reproduced for named imports), surfacing as a converted-code nil-func panic. Not a test-host defect, not a golib defect, not a measurement artifact.

Priced remedy — NOT small, deliberately not started. The machinery is all built and proven (hook emission, per-(assembly, package) dedup via packageBlankImportForces, the noInitPseudoPackages fence, the hand-own .cs.auto fence, the marker splice); what changes is only the TRIGGER. Two honest trigger sets:

A read-set heuristic cannot substitute. The tempting narrow rule — “force only imports whose symbols the importer’s init references” — MISSES this exact case: slog’s init reads log/internal.DefaultOutput, but the package whose init WRITES it is log. The dependency that must be forced is not the one the init statement names. Only the unconditional (or transitive-has-init) rule catches it.

Cost to land: converter change + the recorded-init marker + go generate . for stdlib-metadata.txt; a full-corpus regen and rebank (the emission changes nearly everywhere); a roster re-sweep, because eager module-ctor execution can surface latent order dependence in any banked row (that is the point of the fix, and it is also its risk); and a behavioral guard in the class of the existing blank-import guard, exercising a NAMED import whose init side effect the importer’s init reads. log/slog’s row is gated on it: with the fix the package reaches 190/214 in one run, without it the suite cannot finish at all.

Cheap optional follow-up, independent of the above: the comparison artifact (go2cs_test_comparison.json) does not lift the host’s “test binary died” package event, so a consumer reading only that file sees an unexplained empty tail. The signal exists in go2cs_test_results.json and on stderr; surfacing it in the comparison would have made this a five-minute read instead of a lane.

2026-08-25 · reflect.ArrayOf LANDS as a standalone hand-own — the commission’s one-liner held, and the guard found an EMISSION gap next door (lane claude/reflect-arrayof, coordinator i7)

Queue item 2 from the map-key-elem-cargo lane’s closing list, taken on its own merits. The prediction that arc made — reflect.ArrayOf(n, elem) is now nearly free … the hand-own is synthType(typeof(array<>).MakeGenericType(elem), [n, …elemDims]) — is confirmed: the implementation is 14 lines in reflect/value_impl.cs beside its sibling constructor PointerTo, plus one manualConversionFuncs["reflect"]["ArrayOf"] registry entry and the reflect regen that turns the auto body into its placeholder. No typelinks, no linker table, no new machinery in golib or the descriptor layer — the dims cargo was already carrying everything the length needed.

What the auto form was doing, stated once more because it is the reusable shape: it died in typesByStringtypelinks() not because the caller asked for anything exotic but because it was reconstructing Go’s linker-allocated arrayType record. Every accessor that record exists to feed — Len/Elem/Size/Align/String — the managed bridge already answers from (System.Type, dims). The class generalizes: a Go body that fails inside a LINKER-table lookup is usually not a hard wall, it is a body reconstructing something the managed side never lost.

Identity, not resemblance. canonType keys on the managed type PLUS the dims rendering, so ArrayOf(3, TypeOf(byte)) and TypeOf([3]byte{}) intern to the SAME reflect.Type. Every row of the new ReflectArrayOf behavioral guard is that identity claim — scalar, [2]Celsius (defined element), [2]*uint8, [2]pair (struct element, size from Go’s own field layout), [0]uint8, nested [2][3]uint8, and a five-deep composition — plus the value side (New/Index/SetUint/ Zero/DeepEqual) and the negative-length panic. Failing-first proven: at master the guard reports exit code mismatch: C# 2 vs Go 0 — C# stderr: "System.NotImplementedException: typelinks: external (assembly or cgo) function is not implemented"; with the change, PASS on all four phases.

This does NOT flip encoding/gob’s row and no claim is made that it doesTestIgnoreDepthLimit wraps its 101-deep array in a reflect.StructOf, still the feature arc (System.Reflection.Emit, and an AOT question) the previous lane named. gob stays 105 of 106.

Recorded limitation, deliberately not worked around: an array descriptor has no slot to hand a channel’s DIRECTION or a map KEY’s dims down — abi.Type.Elem descends those through a POINTER only — so ArrayOf(n, chan<- T) describes [n]chan T. A DECLARED [n]chan<- T reads back exactly the same way today, so this is the cargo model’s shape rather than the constructor’s (the r39d rule).

The find next door: an empty NESTED composite literal drops its inner dimension (NOT this lane’s, not fixed)

The guard’s nested rows failed on their first run, and the cause is not ArrayOf. The converter emits the two forms differently:

Go emitted C# TypeOf(x).Elem().Len()
var x [2][3]uint8 new(2, () => new(3)) 3 — correct
x := [2][3]uint8{} new array<uint8>[]{}.array(2) 0 — Go says 3

The empty literal’s two elements are default(array<uint8>), i.e. length ZERO, so the inner dimension is gone before reflection is involved at all — ArrayDimsOfValue then measures the first element honestly and reports [2, 0]. Confirmed by reading both emissions from a two-line probe (go run says 3 3); the consequence was measured through the guard, where the constructed [2][3]uint8 compared unequal to the literal-built one while every accessor on the constructed side answered correctly. Reachable from any [N][M]T{} literal with no reflection in sight, and it is an emission fix (converter + a corpus regen + CNR), so it is recorded here and left for whoever takes it. The guard compares against the declared var form, which is what “the type a declaration produces” honestly means on this bridge, and its source says so at the site so nobody switches it back.

Gates

Converter go test -count=1 ./... ok, 207.1 s, exit 0TestStdLibMetadataInSync included, and go generate . reproduced stdlib-metadata.txt byte-identical, so no metadata was owed and that is asserted rather than inferred. GolibTests 299 / 299, 0 failed (112.9 s). Full go2cs-stdlib.slnx Debug (windows target) 0 errors, 315 s. Filtered ReflectArrayOf all four phases PASS. The reflect regen ran seeded per the ritual, with a control first: the same seeded reconvert at master reproduced all 15 committed reflect files byte-identical, so the two files that moved afterwards are the change and nothing else.

Provisioning note for anyone gating on the coordinator i7: the box’s default dotnet is SDK 9.0.317, which cannot target net10.0 at all (NETSDK1045); the 10.0.400 SDK is a side-by-side install at C:\Users\ritchie\dotnet10. The default go is 1.23.1 out of C:\Program Files\Go and a machine-level GOROOT names that same 1.23.1 tree, while the corpus and version.props are 1.23.12 (at %USERPROFILE%\sdk\go1.23.12) — the exact both-pins-needed shape GoCorpusMigration.md §1 records. Both pins must be set in the SAME invocation as every gate.

RULING (owner, 2026-08-25) — the campaign’s terminal denominator is the IMPLEMENTABLE test set, with the excluded packages fully disclosed, each with its why

The 100% march’s target is ruled: 100% of the packages whose test suites a faithful managed conversion can honestly validate, with every exclusion carried as a first-class, visitor-visible ledger — package, verdict count, exclusion class, one-line mechanism, link to the board rooting. Nothing disappears silently: BOTH numbers are always reported (the naive testable count and the honest denominator), exactly as the H10 gate already requires absolute-and-percentage together.

The admission bar for exclusion is the disclosure bar’s sibling, and it is strict: a package is excluded only when validation is PROVABLY meaningless or impossible — never because it is hard, unimplemented, or expensive. Three classes are in evidence so far; each future exclusion is ruled individually, on measurement, like every disclosure:

Mechanics (lands as one docs change when JOB-019 tier-2 completes, so the first ledger is complete rather than dribbled): docs/ValidatedTestPackages.md gains an “Excluded packages” section in the ledger shape above; the progress header reports banked / honest-denominator beside the naive count. No harness change — excluded packages simply are not rows, and the sweep already reads only rows. The anti-laundering clause carries over verbatim: an exclusion whose mechanism is later implemented (or whose oracle is fixed upstream) REJOINS the denominator the day the evidence changes, exactly as chan-direction retired the other way.

RULING (owner, 2026-08-26) — the campaign’s horizon extends to THREE-PLATFORM PARITY: 100% test validation on Windows, Linux and Darwin before leaving Go 1.24

Ruled the night 1.23.12.1 published (175/208 = 84.1% honest on Windows). The goal: every platform reaches 100% of its own implementable denominator, with its own disclosure ledger — disclosures and ruled impossibilities withstanding, exactly as the Windows march defines them.

Sequencing is conditional on measured arc speed, not preference:

What this queues immediately:

  1. A Linux seam-ledger re-measure at current master before pricing the parity arc — the ledger (R1 Stat_t by-address, R2 exec wall, R4 rawSyscallNoError, R5 sockaddr, W1b mmap, W6 cpu caps) predates weeks of arcs, and the Windows socket wall’s silent collapse is the standing precedent for why prices are re-measured, never carried.
  2. The Darwin run layer (FINDING-darwin-run-layer; linux’s syscall keystone is the template) is the LONG POLE — it gates every Darwin row and should start early, in parallel with the Windows tail, regardless of which side of the hop Darwin parity lands on.
  3. The per-OS roster mechanics ruling (held since the four Linux-proven rows) becomes part of this goal’s design: per-OS honest denominators, per-OS disclosure ledgers, and the roster shape that reports all three without averaging — building on the existing linux-annotation convention.

2026-08-26 · JOB-024 FOLD + TWO RULINGS — the Linux bill lands (honest 146/175 = 83.4%, 0.7pt from Windows); R2 is the parity arc; the ж-box sequence goes B1-design-first

The complete Linux re-measure at 59af260e0 (R, all 175 rows, per-row logs, 4.3 h): 144 PASS + 2 DISC-validating = 146/175 honest (83.4%) vs Windows 84.1% on the same roster. Baseline chain 92/159 → 122/161 → 129/161 → 146/175. Three seams COLLAPSED silently this season — R5 sockaddr (json full 491; tls 0/3646 → 396 agree), R3 self-binary (the Linux apphost is genuine ELF), W1b mmap — while R1/R4/W1 hold closed at floor scale. R2 (exec) absorbs 21 of 29 FAILs with two shape regressions inside it (os/exec 16/72→1/88, gcimporter 281→15 — same attribution, bigger crater, diff these first) and ONE new real test (tls.TestCertCache, unrooted). Standing: W2 ×2 converter fixes, W3 plugin crash, W6 caps, W7 (ruled, honest FAIL, backoff changes nothing — re-confirmed), R6 zoneinfo (1 test), and a NEW host-limit family: runtime/debug TestPanicOnFault — the Linux CLR cannot recover hardware faults (no SEH), a structurally per-OS disclosure candidate. R’’s shard-2 ed25519 disclosure-capture finding is RETRACTED by its own re-read; disclosure machinery is per-OS-identical on the evidence (Windows A/B 8/8 ran anyway). Full ledger: R’’s JOB-024 entries, 2026-08-26.

RULING — the Linux parity arc is R2, and it is ONE design item. 21 rows hang on the exec/process-launch surface; the arc’’s docket: the two shape regressions first, then the design note over FINDING-linux-run-layer §5’’s remaining surface (the landed exec-wall implementation is the foundation; checkClonePidfd and the go-build/testenv re-exec family are the new members), with tls.TestCertCache riding as its one unrooted test. The seven small items land as: two bare-annotation fixes (immediate), W2/W3 converter fixes (lane-sized), W6/R6 singletons, and the per-OS roster mechanics as their own DESIGN (annotation grammar, platform-exclusive rows — internal/syscall/windows/registry is the clean case — per-OS counts and floors; a design note is commissioned).

RULING — the ж-box sequence: B1’’s design increment goes FIRST; B′-S1 is DEFERRED. Grounds from S0b’’s measurement: the receiver half was already near-optimal (emission delta 5 boxes vs the census’’s 560 — census local-counts are NOT emission counts), and the arc’’s measured allocation mass (WriteString’’s 17 direct-ж, math/big’’s 59x, netip’’s gradient, nistec’’s four curves) lives in mechanism-B/C territory, not the parameter half. S1 re-opens only if B1’’s emission-priced numbers say the parameter half still matters. B1’’s design must resolve P-F5 (unsafe.Pointer-subclassing vs kind-as-type) and carry the P-F2 three-variant microbench precondition; the Reinterpret source-retention shape (NetShareAdd) is a named input. G’’s two S0b riders bind: the slog-class A/Bs carry byte-identical controls, and census-grade figures are never quoted as emission-grade.


2026-08-26 — The fold: stop-the-line resolved as three mechanisms; two corruption classes named; the giants re-censused; host-identity minted; row 176

Context. JOB-023 (the 175-row confirmation sweep at the rebank tip) went red on three rows and stop-the-line held all banking. Resolution: THREE distinct mechanisms, one real. Master was never regressed — the union train’s one latent defect was invisible to every merge gate because gates run BANKED (pre-union) test sources; only re-derivation exposes that class. All figures below banked at master 57339895d.

1. The one real defect — self-shadowing forcing hooks (FIXED, merged). The init-order arc’s typeof(<pkg>.<pkg>_package) forcing references are captured by a package-level type named for the target’s leading segment emitted into the same class (Go’s own image_test.go declares type image interface). Fix: collision-gated global:: root-qualification — footprint provably zero AND complete (the corpus compiles ⟹ no production instance can exist); CNR 646/646 byte-identical; image re-derives 8/8 = banked. Guard: ImportSegmentTypeShadow (both production- and test-scope shadowing).

2. gcimporter — COUNT-BINDING RULING (no defect). 583 = 303 (TestImportStdLib) + 266 (TestImportTypeparamTests) + 14 standalone, confirmed to the digit. A GOTOOLCHAIN-resolved module-cache GOROOT ships no test/ tree → 318 = 303+1+14, the typeparam parent running with zero subtests, silently. Ruling: banked counts bind to a full-SDK GOROOT; a sweep host resolving a toolchain-download GOROOT is a SWEEP-ENV defect, never row variance.

3. srcimporter — sweep-host capability gap (no defect). Go’s own oracle needs a C toolchain (go tool cgo walk); on a cgo-less host the GO side fails while the converted side passes. Routes to a cgo-capable host. Sweep-host doctrine (new): a sweeper needs (a) full-SDK GOROOT with test/, (b) GOTOOLCHAIN=local beside the explicit GOROOT (else silent redirect), (c) per-box profile paths — never another box’s home path (silent fallback), (d) a C toolchain for cgo-oracle rows.

Row 176 — log/slog banked (213 terminal / 194 matching / 19 disclosed) behind the caller-info fix: -tests emits internal-test files as <pkg>_internal_test_package and the frame-name derivation kept the suffix where Go strips it — the external half had worked by accident. host-identity minted (seventh disclosure class): an assert satisfiable only by the hand-owned test host claiming testing.tRunner/testing.go identity — F15b makes the host a structural replacement and the position-map ruling forbids the fabrication; truth IS the divergence. Bar: host frames only — a missing converted-code frame is a DEFECT (the same test carried both sides: four look-alike rows fixed, one disclosed). Permanent by design, like codegen-liveness.

Two corruption classes, now distinct (both with the moving-ExecutionEngineException signature):

The giants (CENSUS-giants-2026-08-26, merged): 3,105 verdicts, zero capability walls. net RUNS (120 matching; ~85% reach behind the overlay fix). net/http = 1,352 verdicts behind ONE capture-prologue defect (staffed). reflect = one constant.ToInt construct at the surface (unstaffed). runtime = closure-dependent linkname lowering closing a 2-node csproj cycle — design question, do not staff.

slices arc (branch green, next window): conversion-blocked → 114/122; the “one gap” wore four faces (all fact-gated fixes) + a fifth variadic-method-value defect. NEW GOLIB ITEMS (5 verdicts, one mini-arc): zero-size-element slice allocation ([]struct{} of MaxInt must not allocate); subslice −1 sentinel colliding with a real negative index (Go’s s[-1:] panic never fires); operator-bound min/max not propagating NaN. Plus 3 AllocsPerRun rows for the manifest at bank time. mvreturn arc (in flight, banks x509): defect A = return-operand evaluation order (return o, o.mutate() — gc evaluates the call first, C# tuple literals left-to-right; NOVEL); defect B = multi-value return ~-unwrapping a box into an interface result.

Findings collection: (a) FOURTH all-empty cause — a hard test-host build failure (the tell is error CS… in converter stderr; by shape indistinguishable from the file lock). (b) CNR is silent during transpile BY DESIGN — liveness = process churn, never log growth. (c) CNR’s git status is scoped to the behavioral tree; concurrent -tests corpus writes cannot pollute it. (d) PS 5.1 hashtables are case-INsensitive by default — a name-collision census reported Image/image phantoms; censuses need [StringComparer]::Ordinal (the false-POSITIVE sibling of the grep -P false-empty). (e) Mailbox appends via Get-Content/Out-File flip every line’s EOL — the ReadAllText/WriteAllText UTF8-no-BOM pattern is mandatory.

Rebank state: JOB-023 closed 172/175 with exactly the three explained reds; the leveling rebank re-derives at 57339895d. Stale-source members enumerated: corpus-wide init hooks + flag, log, runtime/debug, sync, syscall, log/slog/internal/benchmarks.


2026-08-26 (evening) — Fold #2: four windows in one day; the giants’ walls fall; the corruption hunt reaches a single hypothesis; the arc queue for the reset

Day’s arithmetic at window four (0a03b7ac7): 175 → 177 rows (log/slog 194+19 with the host-identity class minted; crypto/x509 341/341, zero disclosed), 18,979 → 19,514+ matching, 85.1% honest of 208. Four union-gated windows, zero rollbacks, every merge preflighted from its base and duplication-audited.

The giants, end-of-day state:

os/exec corruption hunt (R, 12+ reproductions): every native corridor cleared by three landed soundness fixes (wait buffers; keystone tether; array-provenance registration — case 1 of PinnableStorage’s taxonomy had never joined the provenance record). Fingerprint: a byte-block smash into managed heap (verifyheap: 4 contiguous errors, unaligned member as SIDE EFFECT). Bisect session 1 cleared hashtriemap (VICTIM, not writer), ReinterpretRef (size guard forbids widening), and golib has NO unaligned-write primitive — the single surviving hypothesis is a BLOCK COPY into managed heap with a native-slice-derived WRONG COUNT (copy/ToSpan paths), one count-vs-extent assert from conviction. B2 clause: conviction in kind-split files routes the fix to that arc with an interim guard.

Rulings this fold: (1) count-binding — banked counts bind to a full-SDK GOROOT; a toolchain-download GOROOT on a sweep host is a sweep-env defect, never row variance (proven: 583 = 303+266+14; the 265 is GOROOT/test/typeparam). (2) The initᴛᴛtests hook-banking amendment (now in CLAUDE.md, dated). (3) The slice-shaped-spread arc (Span int32 length ceiling under append spreads; 679 sites/225 files priced) is APPROVED, sequenced POST-B2, before the full leveling. (4) Full-roster test-source leveling: POST-B2, one deliberate pass (r40 precedent); the floor (8 noisy rows) is merged. (5) slices banks at 119+3 when the spread arc lands (alloc rows pre-classified: Concat/Grow = alloc-count-semantics, Insert = alloc-profile).

Findings collection (each paid for today): git add -A after a sweep stages the hook shape silently (staging corollary, now in CLAUDE.md); tr -d '\r\ufeff' has no \u escape — it strips u/f/e from PATHS and fakes a death (false-red sibling of route #6; use sed or no-BOM writes); go test’s 10m default panic-timeout fires under CNR-concurrent load with zero FAIL lines — pass explicit -timeout on loaded runs; ALL-EMPTY has a documented SECOND cause (test assembly never built — check converter/build stderr for CS lines before the file-lock diagnosis); the production recover path UNWINDS PAST THE EMIT — a panicking file is silently missing at exit 0 and surfaces as consumer CS0103/CS0246 pointing away from the cause; GCStress is impractical at corpus scale (starves startup JIT, stresses the build) — HeapVerify is the corpus-scale instrument; CoreCLR’s crash-handler fork/wait choreography defeats naive live-attach and masquerades as a corpus wait.

Sweep-host doctrine (from fold #1, now proven end to end on the sweeper): full-SDK GOROOT with test/, explicit GOTOOLCHAIN=local, per-box profile paths, C toolchain for cgo-oracle rows (the fleet currently has NO cgo-capable host — G’s box untested, owner-batched otherwise).

Arc queue at the reset (post-budget, ranked): local-type-lift (reflect’s first-order residual); C1 mixed-embed per-member design + C2 forwarder marshal; slice-shaped-spread (post-B2); http.Header modeling design; the sendfile-family deterministic stop; netpoll-deadline family; Func-arity generated delegate; writev capability decision; unique weak-ref; runtime linkname design; production-recover loud-fail honesty fix; full leveling (post-B2).


2026-08-29 · Fold #3: net’s empty class goes extinct; the fixture saga ends on the BUNDLER; a silent SUBTRACTION reds the linux corpus at master; pprof reclassifies to a frontier; the defer/go capture defect is narrowed to synthesized lambdas

Source: the fleet mailbox, 2026-08-28 and 2026-08-29 entries (R/R-LAPTOP, G/laptop, i9/sweeper, the local lanes, and COORD rulings). Folded here per the doc-authority ladder — the mailbox is transport, this is the record. Where an item exists only as a coordinator relay of a LOCAL lane’s report, it is attributed as such.

Window ledger for the two days: master 1d01200a9 (ten) → 9f2973b96 (eleven — the Uname root and the five proactive struct-passing mirrors) → 45e0a97a8 (thirteen — smalls-sweep, local-type-lift, net-residuals, net/http’s first run) → 5ff05d8b6 (fourteen — the Uname restore

1. The TransmitFile netpoll seam — net 291 → 440, the EMPTY class extinct; and the two-phase ordering rule

The net-residuals lane (local; reported through COORD’s 2026-08-29 entry) landed the TransmitFile seam hand-own, and the arithmetic closed exactly: net 291 → 440 matching, with the empty class now EXTINCT (the crash class had gone extinct at 291 in fold #2). The 155 unreported verdicts were ONE deadlock seen from two phases — the serial tail plus the parked parallel batch — not 155 divergences.

The bridging arithmetic (COORD reconciliation — derived from the lane’s recorded figures; each equation closes exactly). Three of this story’s figures sit at different scales — 155 unreported, 43 deadline-named, 149 gained — so the bridge is stated as a ledger rather than left to inference. net’s suite is 475. Pre-fix the run held 291 matching + 155 unreported + 29 mismatched, the 155 being the serial tail past the deadlock point plus the parked parallel batch — and the 43-name deadline family is the deadline-named SUBSET of that parked batch, not a third population. The TransmitFile fix un-parked all 155, of which 149 became matching (291 → 440) and 6 newly mismatched (29 + 6 = 35, the residual inventory the lane named). Provenance, so the ledger is auditable rather than believed: 291, 440, 155, 35 and 43 are recorded figures; 475 is forced by two of them (440 + 35), and 29 (475 − 291 − 155), 149 (440 − 291) and 6 (35 − 29) are each forced by two more. Post-fix the ledger closes at 440 + 35 = 475 with zero unreported — the empty class’s extinction, stated as arithmetic.

The ordering-rule clause this mints, fold-bound by COORD and now in CLAUDE.md’s -test-timeout taxonomy: the converted host reports in two phases — serial tests first, then the parallel batch. A single serial-phase death therefore leaves a contiguous tail in the serial phase AND parks the whole parallel batch unreported; the union reads as SCATTERED because the parallel names interleave alphabetically with the serial ones. So the existing tell (“contiguous alphabetical tail = died partway; scattered = genuine divergence”) has exactly one exception: a scattered empty set that EXACTLY EQUALS the package’s t.Parallel() set is one serial-phase death, not divergence. Compare the empty set against the parallel set — one grep — before believing “scattered”. net’s “deadline family” (43 names in fold #2’s own residual inventory) was precisely this shape.

Fold-bound alongside it, flagged and deliberately NOT chased: a load-induced NativeOverlapped lifetime death, nondeterministic, named by the net lane. Recorded so the next sighting is a second sighting rather than a discovery.

Three rulings from the same report:

2. The publish-fixture saga: the deleter is the single-file BUNDLER

The longest thread of the two days, and the one where both lanes’ proposed fixes were the same non-fix. Reconstructed in order because the ORDER is the lesson.

  1. R’s report (2026-08-29): the time row’s TestLoadLocationFromTZDataSlim — banked pass/pass in docs/validation/current/time.md, and time carries no disclosures manifest so it compares strictly — was FAILING, taking the banked linux: 167 to 166. Root as read: the fixtures exist in GOROOT, in the converted package, and in the RID build output, but bin/tests/publish/ held an EMPTY testdata/, and the pipeline runs the host from publish/. CopyFixtures then deliberately skips what is absent (if (!File.Exists(source)) continue;) — correct for a relocated lone-exe copy, and here it converted “the publish step dropped every fixture” into a quiet per-test ENOENT. Proposed fix: emit CopyToPublishDirectory="PreserveNewest" in the -tests csproj template.
  2. G measured Windows and found publish CORRECTcrypto/tls 122/122 fixtures in publish, time 4/4 from a fresh convert-then-publish — and reported “blast radius on this platform is ZERO rows”, hypothesising a Linux AppContext.BaseDirectory self-extraction difference.
  3. R withdrew his own report: a FRESH Linux convert+publish carries all 4. He had read an existing publish tree and reported it as what publish produces. (His stated rule: an artifact’s PRESENCE says nothing about what the current inputs produce — wipe and regenerate before reading a build tree as evidence. He had caught the identical shape an hour earlier on stale PartialStubGenerator stubs and still did not apply it one directory over.) G then dropped his re-added CopyToPublishDirectory half as unmeasured emission churn.
  4. R isolated the real mechanism with three plain dotnet publish commands, no pipeline, no test host: publish #1 → 4 files, publish #2 → 0, publish #3 → 0, exit 0 every time, monotonic not alternating. The act of measuring removed the fixturescompareGoAndConverted Tests calls publishTestHost itself, so his “count taken just before the compare” was taken before the publish that destroyed it. R then ran the Windows arm: 4 → 0 → 0, identical. The mechanism is the SDK’s, not a platform’s; no contradiction between the two lanes’ results had ever existed.
  5. The exposure rule, from source: publishTestHost runs `dotnet publish -o /bin/tests/publish` — a **deterministic path that is never cleaned** (`testConversion.go`). So `-test-action build` publishes and `-test-action compare` publishes **again** (a build-then-compare sequence IS the 4→0 repro by construction); `-test-action all` (the sweep's invocation) publishes exactly once per row per invocation; `bin/` is gitignored, so a row never published **on that box** gets publish #1 and its fixtures. **A fixture-declaring row is therefore correct on its FIRST run on a given box and silently fixture-less on every subsequent one.** No banked verdict is retroactively wrong — the rows banked on the runs that HAD their fixtures. What is wrong is every RE-measurement of such a row since the single-file arc, **including any future banked-row protection sweep**, which is exactly the gate that would have caught a real regression.
  6. G’s three-arm isolation named the deleter and killed both proposals:

    arm publish #1 #2 #3
    as emitted today (single-file) 4 0 0
    CopyToPublishDirectory="PreserveNewest" added 4 0 0
    -p:PublishSingleFile=false 4 4 4
    ExcludeFromSingleFile="true" (single-file kept) 4 4 4

    The deleter is the single-file BUNDLER, which on a republish reclaims the output directory for the files it owns and takes the loose content with it. The item was reaching publish the whole time — the bundler removed it afterwards, which is why CopyToPublishDirectory changes nothing. ExcludeFromSingleFile is the right SHAPE rather than a workaround: a fixture MUST be a loose file because the test opens it by relative path (os.Open("testdata/x")), and the attribute states exactly that property, so the bundler stops claiming a file it should never have claimed. Landed in the converter (testConversion.go, BOTH emission sites — the plain fixture and the shared up<N> link form) with the three-arm table in the comment, verified end to end (rebuild → reconvert time → attribute present → three publishes → 4/4/4). It also satisfies R’s design constraint on its own terms: nothing is staged BEFORE publish (a pre-publish copy step would simply be undone) — the file is removed from the bundler’s claim.

  7. The loud-skip companion, and why it is not merely a guard rail. The silent skip is G’s own from the single-file arc and had a real reason (a relocated lone-exe copy legitimately has no fixtures beside it and must still start). The discriminator landed with the fix: fail the run when a suite declares fixtures and none resolve WHILE the host is not a lone relocated copy (a published host’s directory holds many files; a relocated copy holds one) — which keeps os/exec’s re-exec path working. Under the bundler mechanism it fires on exactly the runs that would otherwise report a silent false failure. Both lanes converged on it: the diagnosis was partly wrong for an hour precisely because the harness said nothing either way.

The publish-#1 doctrine, ratified by COORD and fold-bound: a blast-radius-zero claim must state what observation would have made it non-zero. Every count in G’s Windows negative was a publish #1, which is structurally blind to a republish-only bug — so the negative was unsupported even though every measurement inside it was sound. (G’s own correction on the record: “sound as measurements, wrong as a conclusion”.) The companion methodological line from the same thread: naming a mechanism justifies re-landing A fix, not THAT fix — G nearly re-landed CopyToPublishDirectory as “measurement-justified” on the strength of the mechanism being named rather than on its own measurement; the three-arm table is what stopped it.

3. The Uname silent SUBTRACTION — the linux corpus RED at master with a clean git status

The defect. The converter registration that DISPLACES the generated syscall.Uname wrapper merged; the hand-own *_impl.cs BODY it displaces to did not. Master’s zsyscall_linux_amd64.cs:979 held a placeholder comment pointing at a body that does not exist, and both _impl.cs files DISCUSS Uname — G’s header even promises “Uname is below” — while nothing in syscall/linux defined it. Clean merge, no conflict, no warning, clean working tree; the whole linux stdlib failed -p:GoTargetOS=linux on kernel_version_linux.cs(21,27): error CS0117: 'syscall_package' does not contain a definition for 'Uname'. Found by the local linkname-seam lane against master’s own blobs, days after the seam.

The fix (R, dcac8df44 on claude/laneR-uname): 48abfbc54’s body transplanted verbatim onto current master — 76 insertions, zero deletions, uniformly CRLF. R deliberately did NOT re-apply his old header prose, since G’s 8ac224105 rewrite supersedes it and the narrative needs no second telling.

Measured A/B on internal/syscall/unix (the consumer holding the call site), at -p:GoTargetOS=linux --no-incremental:

  Uname definitions verdict
RED (master) 0 Build FAILED, exit 1 — the CS0117 by name
GREEN (dcac8df44) 1 Build succeeded, exit 0

Full linux corpus gate (go2cs-stdlib.slnx -c Debug -p:GoTargetOS=linux --no-incremental -p:UseSharedCompilation=false): 0 errors, 153 warnings, 7m40s — 307 projects. Uname was the whole of it; closing the leaf-most CS0117 un-skipped no tail of latent dependents. R stated the discriminators rather than trusting “Build succeeded” on an 84-byte -clp:ErrorsOnly log: the 7m40s elapsed (squarely on CLAUDE.md’s linux-corpus figure, impossible for a no-op) and freshly written assemblies across the tree inside the build window (syscall 31, internal/syscall/unix 32, os 48, fmt 54, runtime 20, net 61; timestamps 09:32–09:34).

One process disclosure R volunteered because it would have FLATTERED the fix: his first reproduction attempt is discarded, not reported — he launched it and then edited the tree while that build was still reading it. It returned “Build succeeded, 0 errors”, the answer that would have looked like evidence. A build whose inputs changed under it measures nothing.

The preflight adopted (now in CLAUDE.md’s concurrent-lanes section). This is the mirror of the silent DUPLICATION the integration rules already catch: a silent SUBTRACTION, where one lane removes a definition because another lane’s branch supplies it and the merge drops the supplier. Both diffs are pure additions/removals, git merges them without complaint, and the result compiles nowhere. Mechanical remedy: if git diff --name-only <base>..<branch> shows a manualConversionFuncs registration or a generated-body deletion, assert the matching *_impl.cs body is present in the MERGE RESULT — the same shape as the existing package_info.csstdlib-metadata.txt preflight.

G’s two-sided guard, and the one-sided-property lesson (also now in CLAUDE.md). G named his own check’s blind spot after the field beat it. The ten-names/zero-bodies property he offered the fleet as the merge-seam instrument — every linux-scoped struct-passing name has ZERO generated bodies and EXACTLY ONE placeholder, run twice over all ten names — verifies the generated wrapper was DISPLACED and never that the displacement has a DESTINATION. A placeholder pointing at a non-existent hand-own passes it cleanly, which is exactly what master held; verified rather than assumed, g-seam-verify carried the same gap (registration present, placeholder present, body absent) and the window-twelve unit would have shipped it had R’s fix not landed first. The guard G built in response is a converter-suite test (the go test ./... tier, beside projitemsIntegrity_test): for every manualConversionFuncs entry the corpus must contain a DEFINITION for that name under the scoped platform — registration ⇒ displaced wrapper ⇒ body, all three, or the test names the missing leg; plus the reverse direction (a dead hand-own nothing displaces) where the corpus shape allows it cheaply. That turns the class into a red converter suite at the merge instead of a red linux corpus days later. COORD’s fold-bound statement: a displacement property must assert the destination exists — every seam check carries both sides of the ledger, or it passes the exact failure it was written for in mirror form. Merge order was adjusted for it: thirteen → claude/laneR-uname → G’s twelve re-signal, so the guard would land on a base where the body exists.

4. runtime/pprof reclassified: a CAPABILITY FRONTIER, not a row waiting on one stub (R)

R measured the wall ladder to the bottom with three runs on the same published host, each excluding what the previous proved fatal. Four rungs, not one, and the hopeful “183 rows behind the stub” figure does not survive measurement — COORD retracted it on the record (the “183 rows enumerate” phrasing in window thirteen’s merge message stands as a BUILD statement only).

  1. nanotime1 — CLEARED. claude/laneR-nanotime @ b15d01641: MonotonicClock.Nanoseconds() in golib (Stopwatch-derived, seconds/remainder split so the ns multiply cannot overflow) behind per-GOOS runtime/{windows,linux}/nanotime_impl.cs — per-GOOS because darwin already carries a real body. Failing-first proven as a PROPERTY: two throwing nanotime1 stubs before, 0 after a clean rebuild, with the build succeeding (a stub plus a body would be CS0111). GolibTests 389/389 (386→389, three clock guards each pinned to a distinct failure mode); windows runtime --no-incremental exit 0; and the linux leg verified DISCRIMINATINGLY — no nanotime1 stub generated against the bodyless partial at linux/stubs3.cs:10, because a green build alone would not prove the file was in the compile set. (The branch is gated on both targets but was never queued into a window — window FIFTEEN material.)
  2. getg / the CPU-profile family — NON-fatal (per-test infrastructure-error, the run continues) and NOT a bounded single. R scouted it because the queue would otherwise have paid for the framing, and found three frames, not one: setcpuprofilerate (windows/proc.cs:5556) uses getg() for gp.m.locks++/-- ONLY — a preemption-disable pair that is a no-op in the managed model, and this frame alone WOULD be a narrow hand-own; setThreadCPUProfiler (windows/os_windows.cs:1238) calls getg() again and stores into m.profilehz, a real m field; setProcessCPUProfiler (:1225) calls newm(profileLoop, nil, -1) — it spawns an M running Go’s profile loop, and sample delivery then walks goroutine stacks from another thread, i.e. the same cross-thread stack-walk boundary the SIGQUIT arc measured. There are 560 getg() call sites corpus-wide reading stack.lo/hi, m, p, goid, preempt, gsignal; stubs_impl.cs left getg throwing on purpose, with the rationale written down (any path reaching it “surfaces as a loud, locatable failure rather than quietly operating on a fabricated goroutine descriptor”). A managed getg can return an identity; it cannot make those fields true — implementing it converts one loud failure into 559 quiet ones, the exact trade the repo’s doctrine refuses. Recommendation: classify the CPU-profile family runtime-capability. The cheap honest lever, if the row is ever worth carrying, is the narrow one — hand-own setcpuprofilerate to drop the managed no-op m.locks pair (the systemstack “that branch is the only branch” argument) — clearing frame 1 without minting a fabricated g. NOT cut.
  3. awaitBlockedGoroutine — the host-killer, and it is a SHARED TEST HELPER, not a test. Excluding TestBlockMutexProfileInlineExpansion moved the identical death to TestBlockProfile — same helper, same panic, same masking. It spins until a stack dump shows a goroutine in a NAMED wait state ([chan receive], [sync.Mutex.Lock], [select], [sync.Cond.Wait]); on timeout it panics ON A GOROUTINE, which kills the binary — and that is faithful: Go does the same. 13 call sites. This is what produced the first run’s contiguous alphabetical tail (2 reported / 181 empty — read correctly by the existing CLAUDE.md tell as a run that died partway, not 181 divergences). R corrected himself twice here: he first blamed a single test (wrong — it is the helper) and called it a hang (wrong — the deadline fired correctly both times; it panics). So the row needs goroutine wait-state names in stack dumps before the block/mutex family can even report.
  4. SIGSEGV. With the whole block/mutex family excluded the host reached 15 verdicts and died with exit 139 inside TestGenericsHashKeyInPprofBuilder (protomem_test.go:121) — not a throw, not a deadline, a hard access violation.

The measured tally behind the stub: 37 tests requested, 15 reached, then the crash — 1 pass (TestContextLabels), 10 fail, 3 infrastructure-error, 1 skip. TestFakeMapping’s infrastructure-error comes up through WriteTo → writeHeap → writeHeapInternal, so the heap-profile writer is stubbed too.

Disposition. The board takes runtime/pprof off the near-bankable shelf: it is a capability frontier (goroutine wait-state reporting, an M-spawning profile loop, a cross-thread stack walk, a stubbed heap-profile writer) — sequenced LAST in the Windows chase queue, possibly the release’s honest disclosure story. The Windows chase to 100% now reads: net residual-35 (five named arcs), net/http (AV root, then buckets), internal/trace (staging fix rides window twelve), reflect (4 diagnostics — the Δ-marker arc plus the arity single), pprof (frontier, last).

Rung-4 disposition, and it is a clean acquittal. R flagged the caveat against his own finding: all four rungs ran on a throwaway composition — his clock plus origin/claude/smalls-sweep, whose fixes live in constraintOperations.go, i.e. GENERICS CONSTRAINT EMISSION — and rung 4’s segfault is in a generics test. Plausible causal link, so not attributable to master. Routed to i9 (serial, bounded, Windows) once smalls-sweep was genuinely on master. i9’s methodology note is worth keeping: claude/laneR-nanotime is NOT an ancestor of 45e0a97a8, so testing bare master would have answered nothing (every pprof test throws at rung 1 first) — so i9 recreated R’s effective composition on top of real master (window-thirteen worktree + claude/laneR-nanotime merged locally, never pushed). Result: NO SEGFAULT on the real merged tree. TestGenericsHashKeyInPprofBuilder in isolation gives a clean managed INFRASTRUCTURE-ERROR — System.NotImplementedException: pprof_memProfileInternal: external (assembly or cgo) function is not implemented, at pprof_package.pprof_memProfileInternal.3.stub.g.cs:27 via writeHeapInternal → writeHeap → WriteHeapProfile; process exit 1, a normal exception caught and reported by the framework — no access violation, no unhandled native fault, nothing resembling exit 139. i9 stated the limit rather than the flattering read: this cannot distinguish “purely a composition artifact” from “platform-specific to begin with” (R’s report did not name the box). The Linux-at-real-master leg stays deliberately UNMEASURED and parked — it gates nothing until the pprof frontier work begins, and nobody spends a hard window on it. The stub it now surfaces (pprof_memProfileInternal feeding WriteHeapProfile) is not a new finding — it is rung 3’s heap-profile-writer gap, confirmed still present.

5. The defer/go capture defect, as R narrowed it: the transform misses SYNTHESIZED lambdas

Commissioned as the correctness arc off the net root-cause (witness: TestConcurrentSetDeadline). R rooted it against real emission before touching anything — branch claude/laneR-defer-loop, base 45e0a97a8, probe src/tests/Behavioral/DeferLoopCapture, every shape measured Go-vs-C# rather than reasoned about — and the arc is both NARROWER and WIDER than briefed:

shape Go C# verdict
receiver reassigned after defer, NO loop close first close first ALREADY CORRECT
defer c[i].Close() in a 3-clause loop (the witness) conn2/1/0 PANIC: index out of range [3] with length 3 BROKEN
closure capture, 3-clause loop 2,1,0 2,1,0 ALREADY CORRECT
closure capture, range loop z,y,x z,y,x ALREADY CORRECT
deferred plain ARGUMENTS 2,1,0 2,1,0 ALREADY CORRECT
go c[i].Close() in a 3-clause loop g2/g1/g0 same defect (emission confirmed) BROKEN

Both halves of the brief were wrong as stated, and COORD owned the overstatement. (a) For a source-level func literal the converter ALREADY implements Go ≥1.22 per-iteration semantics — it renames the loop variable and emits the per-iteration copy, for (nint iᴛ1 = 0; …) { var i = iᴛ1; … }; that path is correct today and needs no work. (b) The deferred receiver is ALREADY hoisted for simple receivers by the ʗ capture-snapshot machinery — var xʗ1 = x; defer(() => xʗ1.Close(), …) — proven by the no-loop probe behaving exactly like Go.

The actual defect, stated precisely. The per-iteration transform is triggered by the variable-analysis pass finding a func literal in the Go SOURCE. defer c[i].Close() and go c[i].Close() contain no func literalvisitDeferStmt.go:188 / visitGoStmt.go:202 SYNTHESIZE one (callExpr = "() => " + callExpr) long AFTER that analysis has run. So the synthesized lambda captures variables the capture analysis never knew about, and consequently: the loop variable gets no per-iteration copy (for (nint i …), not iᴛ1), and the ʗ hoist is SHALLOW — it snapshots the receiver’s ROOT (var cʗ1 = c;) but leaves the SUBSCRIPT to be read at run time. Emission proof, verbatim, at both sites:

for (nint i = 0; i < 3; i++) {
    c[i] = new connжcloser();
    var cʗ1 = c;
    defer(() => cʗ1[i].Close(), ref );   // and: goǃ(() => cʗ1[i].Close());
}

with i == 3 when it fires. Why the witness reaches the lambda arm at all: net.Conn.Close() RETURNS error, so hasResults == true forces the "() => " + callExpr fallback instead of the method-group form (which binds its receiver eagerly and is correct). Same for namedFuncType, variadicCallee, variadicLit. The file’s own variadic comment reasons about exactly this hazard — “there are no arguments whose defer-time evaluation the wrap could disturb” — but the reasoning was applied to ARGUMENTS only; the RECEIVER was never in scope.

Consequences for the arc: (1) go statements are in scope and were not named in the brief — same mechanism, same line, different file; (2) the guard’s briefed “three shapes each proven red with its own fix removed” re-bases — three of the briefed shapes are green at master, so they are green CONTROLS that must stay green, and the genuinely red shapes are the two synthesized-lambda ones; (3) corpus reach is narrower than “every 3-clause loop” — it is deferred/go calls whose callee forces the lambda arm AND whose receiver expression is not a plain identifier.

Fix in flight, direction ratified: hoist the RESOLVED receiver expression (not its root) into a defer-statement-time temp in both synthesized-lambda arms. It also yields the Go-correct per-iteration value without touching the source-literal transform, and preserves Go’s statement-time evaluation of side-effecting subscripts (c[f()] runs f once, at the right time) for free. Deliverables still owed: the behavioral guard with its controls, CNR, and — because the reach is corpus-wide — a seeded reconvert-and-BUILD census of how many emission sites move.

6. Named, priced, and deliberately NOT chased

7. Instrument-discipline collection (each paid for in these two days)

The two days produced one family repeatedly, and both lanes named it the same way: the absence of a failure signal is not the presence of correctness. Members, in the order they were paid for:

8. Ledger of what else landed in these two days (context for the rows, not the fold’s subject)

Arc queue after this fold (Windows chase to 100%, ranked): net residual-35 (five named arcs); net/http (AV root, then buckets); internal/trace (staging fix rides window twelve); reflect (the Δ-collision-marker accessibility leveling + the Func 17–24 arity single); the defer/go synthesized-lambda capture arc (in flight); runtime/pprof (capability frontier — last, possibly the release’s honest disclosure story). Also queued: the TZ unix half (R’s family, on its own evidence); the netip deref-only box-chain arc (retitled on the record — NOT B′-S1 re-opened; the 50/33/17 decomposition has the receiver third smallest, and halves() censused 1-of-1 corpus-wide with the Ꮡ(value).of(…) chain at 5 sites, so it is not converter machinery); writev; the CS1113 value-receiver method-group arc.


2026-08-29 (overnight) · Fold #4: six windows in one night; Linux runs 27 → 177 of 183 applicable; reflect executes for the first time; one blocked test yields a census pattern, a golib class, and a CoreCLR liveness proof; the 345x handshake resolves host-conditional; the coordinator’s own blind window joins the ledger

Source: the fleet mailbox, the 2026-08-29 overnight range — mailbox commits 95345838b (window twenty-seven announced, 00:54) through a1e42cae3 (G’s exit-code convergence note, 05:41), extended through 59e9c6f88 (the /h2 settlement, ~05:45) when four posts landed that completed §5’s measurement mid-draft. Voices: R/R-LAPTOP, G/G-LAPTOP, i9/sweeper, three local converter/golib lanes, and COORD rulings. Folded per the doc-authority ladder — the mailbox is transport, this is the record. Every point-in-time figure below is as of 2026-08-29 and anchored to the window that produced it; none is durable.

Window ledger for the night: master 6f529e29f (twenty-seven — the roster’s last red clears: reflect.Set assignability takes testing/quick 7/8 → 8/8, AF_UNIX’s subset-constructor fix lands, reflect’s host stands at 8 build errors) → 1a416ba08 (twenty-eight — the triple fold: G’s double bank ce82439b1 takes the roster to 184 rows, i9’s gosym rebank 2ce5fa643, R’s annotation batches 1–3) → dde1233aa (twenty-nine — R batches 4–6 + BOTH of the night’s first doctrine commits, f68e1a0e5 and dde1233aa itself) → 78fc9544c (thirty — reflect 8 → 0 build errors and FIRST EXECUTION, the seam error-kind fix, R batches 7–9) → f17693d38 (the linux: n/a convention) → 2e3f6b265 (thirty-one — the continue-wrapper converter fix via 450ea4a60/aa206f02b, plus the tail rule’s freshness caveat) → 9e6eb5dc0 (thirty-two — -test-filter + the SetFinalizerBridge guard) → f3e8a1979 (thirty-three — R batches 10–12, Linux 176 of 183 applicable). Between windows, two standalone masters: 1dc689fcc (the sweep’s comparison reader made case-safe) and ab3cbd921 (its parameter-comment residual).

1. The night’s arc, summarized

Six windows (twenty-eight through thirty-three) merged in one overnight shift, zero rollbacks. What moved (all figures as of 2026-08-29):

2. Instrument doctrine — the night’s dominant theme, stated as classes

The night’s densest yield is not a row count; it is a convergent family of instrument-discipline findings, several arriving independently in two or three lanes within hours. Each entry below is a CLASS with its instances cited. Where the lesson is already landed doctrine, the master commit is cited rather than the text restated.

(a) The results-file TAIL rule, and its same-night freshness caveat. A deadline kill states itself as an explicit {"action":"timeout"} final event; the tail is read FIRST, before any empty-set shape analysis, and quoted in any census that reports empty verdicts (doctrine: master f68e1a0e5). Three deadline-kill instances in one night: G’s net/http 25m run, whose kill she published as “213 divergences across 87 parents” before the tail showed her own -test-timeout had killed the host mid-run — withdrawn within the hour with the arithmetic that proves the kill, 1,147 run vs 1,134 terminal events, 13 in flight (mailbox 856ed906d); R’s sync/atomic 30m timeout, initially sitting in a red list beside genuine divergences until he drew the split himself (mailbox a1fcd3d06); and G’s 90m re-run, where the rule was OBEYED — the tail gate she had positive-controlled against the known-bad 25m file fired, she quoted no census, and the identical-runs comparison became the block proof of §3 instead of a third misread (mailbox 727e6e206). crypto/dsa’s 65-minute floor run (§1) is the same kill wearing a “genuine red” costume — the row any sub-hour budget false-reds. The caveat arrived the same night the rule landed (doctrine: master 2e3f6b265): a --run-filtered host does not rewrite go2cs_test_results.json/.xml — G’s four-way A/B, order-independent and exit-code-independent, obvious candidates eliminated, mechanism deliberately unasserted — so a stale results file beside a fresh comparison is NOT a deadline kill, a gated census gates on the CAPTURED STREAM (the artifact the comparison is actually built from), and the check is one timestamp compare (mailbox 0b1c64fb5, banked defa27f62). The --run/results-rewrite defect itself is a routed chip (hand-owned testing host, WriteResults path), mechanism still owed.

(b) One class, two statements: “no negative believed without exit code and raw tail” (R) and “read the OUTPUT, not the status” (G). Two lanes hit the identical instrument-level trap independently in one session, which makes it systemic — a property of the tooling, not a personal tally (G’s own framing, mailbox a1e42cae3). R’s instances: every GUARD EXIT: 0 he quoted this session measured tail’s exit, not the guard’s (check-roster-format.ps1 | tail -3; echo $? — hollow for exactly as long as the gate kept passing, exposed only when a genuine failure made the real exit 1; mailbox e7f62a4e8); a wrong-path probe (directory probed, file existed) that declared an artifact destroyed and got a fix approved on the false premise — one bad probe, two wrong conclusions (mailbox b83784ddc, corrected 32ebce5cb); go: command not found → exit 127 → empty output reading as “no skips.” His full five-defect tally — the |-blind header parser (caught by the guard, not by him — the re-derive-never-trust design doing its one job, with batches 1–8 provably unaffected because the guard AGREED with his header on each); the piped exit codes; the wrong-path probe; the five-commit-stale lane (§2j); and a lapse leaving the session on a single watcher leg — closes on the shared shape: a broken probe returning a confident negative is more dangerous than one returning nothing, because emptiness invites suspicion and a plausible answer does not (mailbox 1214021e2). G’s five, in her order (mailbox a1e42cae3): the Store’s python stub prints “Python was not found” and exits 0; powershell … | head -3; echo rc=$? reports head’s status (“confirmed” a script had run when it had not); a || echo "(none)" fallback hung off a pipeline ending in sed that can never fire; the exit $true-is-exit-1 liveness loop (already in CLAUDE.md — she walked into its shape anyway); and the non-exit-code sibling, a host A/B run without GOROOT returning exit=2 with no files, indistinguishable from the defect under investigation until the TEXT named fixture staging. The one habit that caught all five: read the output, not the status — every one announced itself in words while the exit code lied or belonged to something else. R’s guard catching his own header parser is the same lesson with a gate instead of eyes, which is strictly better. Concrete and cheap: ${PIPESTATUS[0]} in bash, $LASTEXITCODE captured IMMEDIATELY in PowerShell, never a || fallback off a pipeline whose last stage is a formatter. R’s rule was fold-bound at COORD d53816a8a; G’s convergence note closed the range.

(c) “No all-clear wider than the census that produced it.” G found net/http’s case-only-differing test names (…/GZIP vs …/gzip) break PS 5.1’s ConvertFrom-Json, checked the Go WRITER (case-sensitive by construction, all eleven case-folds on file paths), and wrote “the product is sound… nothing to route” — a claim covering the repository from a check covering one component. COORD then found the sweep’s own comparison READER folding the very pair she had published, fixed at master 1dc689fcc (ordinal dictionaries, a fold-detector fixture whose two case-varying keys carry two DIFFERENT values — a positive control must be able to fail — guard 369, smoke-proven). The instructive half, in her words: she warned everyone about “our own downstream tooling” and never asked whether the repo WAS downstream toolingvalidationProofPages.go was already in her own grep output and she read past it because she went looking for a writer and stopped at one. She then ran the census she owed — all SEVEN consumers, with symbols.json cleared for a STRUCTURAL reason (an array of records cannot fold, which stays true as symbols are added) rather than an empirical one (mailbox dcb799b81, 34c836a54, 10cbd86a4; residual comment fixed master ab3cbd921; fold-bound COORD 432f8da2b: a claim’s scope is the list of consumers actually checked, and the warning you issue outward applies to your own repo first). The same family arrived in miniature an hour later: her first continue-wrapper census used a [^{}]-bounded regex that cannot span nested braces and reported ONE file; the nesting-aware scanner found 4 sites across 3 files among the corpus’s 116 wrapper sites — the instrument’s blind spot reported a smaller world (mailbox 485e81337, fold-bound beside the scope lesson at 1f8593dc9).

(d) The symmetric form, minted at the range’s very end: “a red generalizes no better than a green.” G’s “no knob — the /h2 rows cannot be bought with patience” (§5) was a negative all-clear — a claim of universal impossibility from a single host — and i9’s desktop then cleared Go’s real 500ms ceiling 3/3. Her measurement was right for her box; the generalization was not, and she named it as the exact MIRROR of (c): she had applied the scope discipline to positive findings and not to this one, so stating the rule for greens evidently does not immunize against reds (mailbox 883a1c856, ratified 59e9c6f88).

(e) “The control must be checked as hard as the experiment” — and its sharpest phrasing yet: “it survived, rather than being known to hold, and those are different states.” G stress-tested her own published parked-receiver class claim unprompted. It HOLDS — 8/8 vs 8/8, both sides deterministic — but the attempt to narrow the holder’s shape produced a probe that differed from the reproducer only by an os.Args read and an UNEXECUTED if-block, and that was enough to flip Go’s side (the second closure’s presence changes what Go’s stack maps report live; Go makes no promptness guarantee, which is precisely what one leans on at this granularity). So the slot-shape question is UNANSWERED and that probe cannot answer it, since its own control moves; any finalizer-timing probe owes a stability run on BOTH sides before its result is quoted (eight runs cost seconds); and the reproducer is safe to build on because it was measured this way, not because it was written carefully (mailbox c12a4af9a — her third arrival of the same lesson, after the GZIP/gzip trap and the GOROOT-less probe). Absorbed into the golib fix lane’s protocol as three BINDING rules: 8-per-side stability tally first; the probe stays BYTE-IDENTICAL across the pre/post-fix A/B (no prints, no args, not even an unexecuted branch); the guard’s golden banks only after its own stability pair (COORD fd7727697). She then applied it to her own new artifact: SetFinalizerBridge’s stability was measured 8/8 per side before the test was written, not after it passed (mailbox 67020dc82).

(f) The byte-identical-probe corollary, self-caught, and the operating-instruction defect it did not cover. G argued her probes were scaffolding and the numbers the durable artifact — a rebuild from description would do — then overturned herself one post later: that holds for a REPRODUCTION and fails for an A/B, because a cross-host comparison whose probe was independently rebuilt cannot tell a host difference from a probe difference. Same rule the golib lane already carried, out of her own protocol: stated for others, reasoned past for herself, one post after writing it down (mailbox 4c7c4b9b3; probes pushed as bytes, g-probes @ f54087b3b, sha256-pinned, dirs -text so “the same bytes” stays true rather than nearly true). Then the range’s last correction found the half the hashes did not cover: her prediction post named the WRONG COMMAND against her own source — the probe’s argument is the BUDGET and it sets WriteTimeout = timeout/2, so “the deciding run is 500” gave a 250ms WriteTimeout, half the ceiling; the deciding run was 1000. i9 ran 500 first, got FAIL ×3, and caught the mislabel by re-reading her main.go rather than trusting her prose — had they trusted it, a correctly-executed byte-identical run would have delivered a confidently mislabeled result. Her ruling on herself: that is a defect in the PROBE, not just the post — an argument that is not the quantity of interest invites exactly this — so fix the interface, not the sentence (the probe takes the WriteTimeout directly or names units in its output; the recorded hashes change and are re-posted, since a hash that no longer matches what is described is worse than none) (mailbox 883a1c856, 5e5138dd6). And the pair of experiments is now doctrine, ratified from i9’s accidental both-runs design: byte-identical isolates the HOST; an independent rebuild tests the FINDING — different questions, both worth having, named as such (mailbox 3d2488a1b, 3895fdd0a, ratified 59e9c6f88).

(g) The coordinator’s own-push blind window — the coordinator’s failure, with the same prominence as the lanes’. The verified-push pattern fetches and resets to the remote tip before appending, silently ABSORBING posts that landed since the last read; the monitor’s next fire was dismissed as “self-detection” without diffing the range, and the read anchor then advanced past the absorbed posts. Seven posts were skipped in one night — including i9’s full-roster RESULT (mailbox 771821894, posted on time; the coordinator’s “result still owed” nudge was WRONG, apology on the record) and R’s cgo-crash routing request (mailbox e8deb73e7, which then waited three hours) — and seven was itself an undercount: G’s probe-push announcement was among the absorbed, which is why window thirty-three issued a stale rebuild-from-specs instruction against bytes already two hours on the remote (caught by G re-raising with the remote verified via ls-remote, not her local branch; mailbox 3d2488a1b, owned at 59e9c6f88). The amended rule, now in the coordinator’s own doctrine: a monitor fire is NEVER pure self-detection; the diff runs from the last hash actually READ, every time, own-post tip or not (mailbox c8fc7dc5c). R’s reconciliation-counter lesson and G’s scope lesson were the same shape in other hands; the coordinator’s is now on the same page.

(h) The adjacent-INSERT merge hazard. Master inserted go/build immediately above go/build/constraint, a row R had annotated; git folded both into ONE conflict hunk where either single-side resolution silently loses a row (--ours drops G’s new row entirely, --theirs drops R’s annotation) and nothing in the markers says so. R resolved keeping both, asserted the row count across the merge, and validated his re-derived header by positive control first — the same parser against master’s own blob had to reproduce master’s banked header exactly, and did (mailbox a1fcd3d06). Doctrine landed the same window: master dde1233aa (cited, not restated).

(i) “The capability exists and one path fails to reach it” — the earned diagnostic prior. G’s count on the night: the fifteenth time this session the answer took that form — the load-bearing instance being the SetFinalizer bridge itself, expected to be a stub, found to be a real hand-owned ConditionalWeakTable bridge keyed on the referent, and then PROVEN firing by a minimal probe, which forced the retention conclusion in §4 (her post, mailbox 1b0ad1978; fold-bound as the prior at COORD 5bcadf49d). The prior’s proper use is search-ordering, not conclusion-jumping: it says check the reach path before re-litigating the capability, and it earned that status one elimination at a time.

(j) The staleness family, both directions. R’s reconciliation counter refused to read “15 of 16 reported” as complete and thereby caught his lane FIVE COMMITS STALE at its provisioning commit — among the missing was 635b27c7c (reflect.Set), the reflect-bridge class whose canary doctrine his banked counts implicated; the control run then validated reflectlite/sort/fmt against his own banked annotations (the (linux) suffixes), retiring the canary question for all 102 prior rows on a corpus that includes the fix (mailbox 32ebce5cb, COORD d53816a8a). G ran the implied check on her own lane unprompted and pre-registered the result BEFORE her census landed: 7 commits behind, but git diff --name-only HEAD..origin/master -- src/ = zero src/ files, so her in-flight run measured master’s code exactly — “identical where it counts” being actionable where raw commit-distance (“7 behind”) invites an unneeded rebase (mailbox 5c4d14b7e). Same shape as R’s syscall finding: two true statements, only one answering the question.

3. The block-gated census pattern — named, constrained, instrumented, and paid for in one run

The pattern (COORD ruling, mailbox b42c13e16): when a suite is held hostage by a PROVEN deterministic block, gate the block and census what is behind it — gate-then-census, never root-first-while-the-suite-waits. Four constraints keep it honest:

  1. Symmetric — the named test is excluded on BOTH sides (Go reference run and converted host, same filter), so the comparison stays apples-to-apples.
  2. Visible — the census output names its exclusion list in the header; a gated census that reads like a full one is the false-green shape.
  3. Runner-level only — no skip added to converted sources, no harness-injected skip dressed as the suite’s own; the anti-laundering clause applies to diagnostics too.
  4. Diagnostic, never banked — the ROW banks only from an ungated run, after the block is rooted or disclosed under a legitimate class; stated in the census, every time.

The block-proof standard is two-budgets-zero-delta. G’s 25m and 90m net/http runs were IDENTICAL in every particular — 2,283 events both, 1,146 started / 1,134 terminal / 12 in flight both, same last event (run TestTransportGCRequest/h1/Body), same verdict pairs — so 3.6x the budget bought literally zero progress: not slowness, a BLOCK, structurally provable besides (the test ends in an unbounded for/select with no deadline of its own, exiting only when a finalizer fires; §4) (mailbox 727e6e206).

The mechanism did not exist, and G reported the gap before assuming past it (mailbox 2719e9df4): compareGoAndConvertedTests hardcoded both arg lists; the converted host has run but not skip; and capabilityGatedDeclaration gates at CONVERSION, failing constraints 1 and 3 at once. The smallest satisfying mechanism — -test-filter, ONE flag threaded VERBATIM to both invocations as -run — was built, gated, and merged the same night (mailbox 9e195c5ec; branch g-test-filter @ 687b0f922, guard 33a2ca242, master 9e6eb5dc0; +34/−4 across three files). Its two load-bearing design points, on the record because neither is stylistic: the flag never composes the string, it forwards it — the two logged command lines carry the identical filter, auditable by eye, which is what constraint 1 means operationally (a cleverer flag building each side’s filter would be the very thing constraint 1 prevents); and validation compiles the pattern with Go’s RE2, the stricter of the two engines it feeds, so a pattern only .NET would accept dies at the flag naming itself. -run beat -skip because run exists on BOTH sides today; exclusion is an anchored include-list — RE2 has no lookahead, and an explicit include-list IS the exclusion list inverted and visible (~12.6KB for net/http’s ~450 parents, inside Windows’ 32KB). Constraint 4 rides in the help text, because the flag is the last thing read before running one. Collision handling was the concurrent-lanes discipline working UPSTREAM of a merge for once: G flagged the possible reflect-lane collision before cutting; answer, no collision — the reflect TestOffsetLock follow-up was sequencing, not staffing, and will CONSUME her mechanism (mailbox ed55cfaf5).

The result paid for the whole pattern in one run (mailbox 0b1c64fb5, accepted defa27f62): excluding TestTransportGCRequest ONLY (569 of 570 parents kept, the exclusion named in the header), net/http’s apparent 228 divergences across 92 parents collapsed to 18 across 10 — and the whole suite ran in 342 seconds against two runs that burned 25 and 90 minutes finishing nothing. Constraint-1 symmetry was proven arithmetically, not asserted: Go 1,352 → 1,345 names (exactly minus the excluded parent and its six subtests), C# 1,135 → 1,345 — both sides enumerating the IDENTICAL 1,345; pass/pass 1,111 → 1,309, absent 213 → 1, and the arithmetic closes at 1,346 = the union. The 213 “divergences” of the blocked runs were tests that never got to RUN. G’s by-parent rooting then collapsed further: 10 parents → 6 roots (mailbox c83808de7) — the continue-wrapper cookie pair (FIXED on master at w31; a re-census at ≥ 2e3f6b265 expects 16/8); anonymous-function naming (3 parents / 7 rows, one root absorbing TestTimeoutHandlerSuperfluousLogs — we number func literals from 0 and unrepresent nesting; remedy is DESIGN-position-map §8/§11.1’s converter-records-the-ordinal, with ordinal+1 REJECTED as binding since Roslyn’s b__X_Y closure-group index does not correspond to Go’s per-function counter — silently-wrong beats visibly-wrong nowhere; dispatched to a local lane, COORD 8b67f0fb8); the h2 write-deadline pair (§5); TestRegisterErr (3 rows, ONE defect — adapter shell type leaking into a rendered name plus a typed nil printing as a moving address; the standing golib chip, and the survival ruling is its own small lesson: the rendered shell-type string PROVES the subtest ran, since a timeout victim produces no name at all — COORD d8c7f194b); TestReadRequest’s formatter hiding a real channel inequality; and TestServeMuxHandleFuncWithNilHandler throwing at delegate construction BEFORE Go’s panic site. The frontier is countable on one hand.

4. The parked-receiver residual: from a 25-minute timeout misread to a CoreCLR liveness proof

The night’s deepest chain, and the fold records the ELIMINATIONS with the finding, so nobody re-walks them.

The elimination chain (G, black-box, each step measured):

  1. Closure display-class merging — refuted by the emitted code: req is a plain local of the IIFE lambda, the finalizer lambda captures only the channel; Roslyn hoists nothing here (mailbox 1b0ad1978).
  2. reqCanceler unfaithfully converted — refuted: byte-faithful to transport.go:869-878, and Go passes with the identical map in place (same post).
  3. The SetFinalizer bridge itself — refuted by a working minimal probe: converted finalizers FIRE (same post; the probe became the SetFinalizerBridge guard, master 33a2ca242 — zero prior coverage of a hand-owned, subtle, hard-won bridge).
  4. channel<T> retaining its last-received value generally — refuted; a main-frame receive releases. (Footnote preserved per her discipline: on the buffered variant Go itself reported RETAINED, so that variant tested nothing she designed and she drew nothing from it — a control that fails its own premise gets discarded, not explained.)
  5. Debug-JIT local liveness — refuted; Release behaves identically (mailbox 6a344b170).

Along the way the instrument collapsed from 342s to two seconds (a standalone 45-line bisector), and the bisection localized the holder: construction clean, the ROUND TRIP retains, not the Transport object, and CloseIdleConnections() releases — an idle persistConn retains the request it last served, rooted independently by its live readLoop/writeLoop goroutines (mailbox 6a344b170). The class then confirmed HTTP-free in 40 lines: a value delivered to a PARKED goroutine receiver is retained after receipt — Go releases while the goroutine parks, C# retains, Debug and Release alike, with the parks-again ingredient isolated against a main-frame-receive control; WaiterQueue exonerated (Remove fully unlinks), the registry exonerated, and Waiter.Elem named a SUSPECT, explicitly unproven (mailbox 9dce27557). COORD’s declaration-hoisting candidate died cleanly — the reproducer has no inner lambda to carry it (COORD 78dc3cd19, killed 980d617e4).

The golib lane’s resolution (gcroot, not plausibility; COORD relay 636b7da5c): the payload is held by the receiver goroutine’s OWN loop-frame stack slots — heap dumps mid-park after forced compacting gen2 GCs show exactly 3 stack-slot roots (the v local plus Roslyn’s tuple-deconstruction temps) and ZERO heap paths; with tiering off, still 8/8 RETAINED via ONE untracked struct-temp slot. G’s suspect died twice: source shows a fresh Waiter per park (no pooling), and the dump shows the dequeued first-park waiter COLLECTED outright. Three sealing A/Bs: clearing Elem changes nothing (8/8 retained); moving the receive into a NAMED FUNCTION releases 8/8 with zero golib change — frame residency is the whole mechanism; and two sequential payloads show a SINGLE OVERWRITTEN SLOT — first released, second retained. Bounded at one last-delivered value per parked receiver goroutine. Never accumulates.

Mechanism, named: Go’s per-PC stack maps kill the local at the loop back-edge, so Go’s GC frees it while parked; CoreCLR reports untracked frame slots live for the standing frame — a goroutine body called once never tiers up, and even full-opt keeps the unpromoted tuple temp. golib cannot reach a caller’s frame; a converter dead-store cannot reach the Roslyn temp, and full-opt would elide it anyway.

Rulings (COORD 636b7da5c): (1) recorded here as a platform-liveness fidelity residual — bounded (frame-variant and single-slot bounds above), NOT a golib defect, NOT a converter defect; the same artifact family as Go’s own finalizer non-promptness, which G herself showed flips on an unexecuted branch (§2e). (2) TestTransportGCRequest routes to the EXISTING codegen-liveness disclosure class when net/http banks — the class TestCertCache already grounds; the test asserts on collection that CoreCLR’s model cannot promise for a parked frame, and the 40-line reproducer + gcroot transcripts are the proof page’s evidence when that row lands. It stays gated in any diagnostic census until then. (3) A converter-territory narrowing (helper-frame receives) is board-parked, explicitly NOT scheduled — it would reshape every channel receive for a memory-promptness nicety, and nothing correctness-bearing needs it before 1.24. (4) No commits banked, and no guard is constructible — no red-to-green exists for a platform property; the lane’s zero-change stop-and-report was correct. COORD’s summary line stands as the arc’s epitaph: one night, one test — a 25-minute timeout misread, a proven block, a census pattern, a mechanism gap, a runtime-class hypothesis, and a CoreCLR liveness proof, each step measured.

5. The crypto/tls handshake: two-instrument closure, then the no-knob sting dissolves on the fast host

The measurement (G, mailbox d8d1d1e64): converted-C# TLS handshake with NO WriteTimeout set and keep-alives disabled, three runs per side — Go mean 2ms / worst 3ms; C# mean ~691–705ms / worst ~1,078–1,130ms on G-LAPTOP. ~345x. It settles net/http’s h2 write-deadline pair (§3’s Root B): the test’s effective deadlines are 125/250/500ms and the handshake exceeds every one — the deadline is applied CORRECTLY and cannot be met, so the “apply-the-deadline-where-Go-does-not” semantic alternative died unfixed, to its author’s stated relief.

Two instruments, one crossover — the closing form. The direct measurement predicts a pass/fail crossover between 500ms and 2s of WriteTimeout; COORD’s discriminator (a budget sweep at deadlines the real test cannot reach), run by G exactly as specified, landed it exactly there — 250ms FAIL, 1s FAIL, 4s PASS, 16s PASS, three byte-identical runs (mailbox 33d765f76). Neither alone sufficed: the measurement could not rule out a semantic fault surfacing only once timing is met. Two independent lines agreeing is the standard closing form now (COORD 0e6753732).

The sting, precisely drawn and then precisely dissolved. G’s distinction vs the maphash precedent: maphash is slow but PAYABLE — a bigger -test-timeout buys it; here the budget is tryTimeouts (serve_test.go:980), hardcoded {250ms, 500ms, 1s} in Go’s own test source, so the largest WriteTimeout the real test ever sets is 500ms — no knob (mailbox 33d765f76). Then the fast-host leg ran: i9, on G’s sha256-verified probe bytes (g-probes @ f54087b3b), measured its handshake at 368–389ms mean / 601–624ms worst (~1.8x faster than G-LAPTOP, still ~190x Go) and PASSED Go’s real 500ms ceiling 3/3 both sides — i9’s own crossover sits between 250 and 500ms of WriteTimeout, tighter than G-LAPTOP’s, which failed even at 500ms (mailbox 5e5138dd6). RULED (mailbox 59e9c6f88): the /h2 rows are a host-speed-conditional performance gap — they bank the bogo way (the capable-host leg), no disclosure and no new class needed; G’s “cannot be bought with patience” stands correctly scoped to her host class (§2d carries the generalization lesson). The disclosure wording had been deferred DELIBERATELY through the whole thread — a disclosure written now would describe a number we intend to change (COORD 9904336ef) — and the deferral was vindicated by dissolving.

Banked forward: the handshake gap itself — G-LAPTOP ~700ms / i9 ~380ms / Go ~2ms, a two-host baseline — rides to the post-1.24 performance queue with profiling as its first step; 345x in a crypto path smells pathological, which is the good kind of gap (a cliff in the profile). crypto/tls is a BANKED row (402 verdicts as of 2026-08-29, a reflect-bridge canary), so this is a real-world cost in a package nobody thinks of as unfinished — G flagged it as a board candidate with the measurement attached and declined to chase it, which was the right shape (COORD 9904336ef, a0c257759).

6. Two named product items out of the Linux endgame

The cgo Syntax-pairing converter crash — go2cs -tests cannot convert ANY cgo package on Linux (R, mailbox e8deb73e7). conversionDriver.go:228 does pkg.GoFiles[i] inside for i := range pkg.Syntax — but go/packages fills Syntax in parallel with CompiledGoFiles, not GoFiles, and for a cgo package the two differ (plugin on linux/amd64 is GoFiles=1 PLUS CgoFiles=1), so the loop walks off the end. Measured as a CLASS with controls on both sides: plugin panics index-out-of-range [2]len2, os/user [6]len6, internal/testpty [2]len2 — all at that identical line — and hash/adler32 (non-cgo) exits 0 with zero panics as the negative control. Census on linux/amd64 is FIVE: internal/testpty, net, os/user, plugin, runtime/cgo — a standing blocker on a substantial future target, not a one-row curiosity. Windows never meets it (no cgo files there, equal lengths), which is why the row banks on Windows and is unmeasurable on Linux. DISPATCHED as the night’s third converter lane (branch claude/cgo-syntax-pairing): the pairing fixed as a CLASS with a full pairing-site census ordered (not just line 228), a platform-neutral red-first unit guard, CNR to prove non-cgo byte-identity; Linux-side confirmation (plugin/os/user converting clean) is the Linux lane’s post-merge follow-up — unblocking net’s future Linux leg (COORD c8fc7dc5c).

The runtime/debug fault-to-panic bridge (R, same post; carried in the w33 record, master f3e8a1979). The row’s apparent nineteen divergences are ONE host crash: the empties are a contiguous alphabetical tail from the crash point (the documented died-partway signature — NOT MEASURED), TestFreeOSMemory already binds its pinned Windows codegen-liveness disclosure signature exactly, and the whole red is TestPanicOnFault taking a fatal AccessViolationException where Go recovers a panic — the test deliberately faults to prove Go turns a bad dereference into a recoverable panic, and the managed host dies instead. The fault-to-panic bridge in the signal path is a named golib/runtime queue item; cost today is eight unrelated verdicts, and absent the crash the row plausibly banks at its Windows shape (4 + 5).

7. Rulings ledger (one line each, with citations)

Arc queue after this fold (as sequenced at the range’s close): the reflect divergence arc (OffsetLock block + 11 non-reporters + named panic roots — the block-gated census pattern’s second customer); the func-literal ordinals lane (in flight, local); the cgo Syntax-pairing lane (in flight, local); the net/http residue (TestReadRequest + nil-handler chips, the TestRegisterErr golib pair, then banking against the codegen-liveness disclosure for TestTransportGCRequest); the Linux tail — os/exec (parked genuine divergence), sync/atomic (re-run with a floor), syscall roots 2+3 then bank, gosym 9+1 bank (i9’s confirming re-run at leisure), the fault-to-panic bridge; the post-1.24 perf queue gains the two-host TLS-handshake baseline with profiling first; and the board-parked, explicitly unscheduled converter narrowing for helper-frame receives stays parked.

2026-08-29 · internal/godebug BANKS at 5 | 0 by completing its own hand-own — and the bisect arm is the first measured proof the managed traceback surface is hash-stable across processes; internal/concurrent re-measured at 1.23.12: the structural non-bank stands, with a second, LIVE whitebox surface the 2026-08-19 entry did not name (lane claude/local-internal-banks, i7-5820K)

internal/godebug 5|0 (was 3 of 5). The first pipeline pass reproduced the prior census exactly — TestGet pass, TestBisectTestCase pass, TestPanicNilRace skip=skip, TestCmdBisect bisect: fatal error: target failed without printing any matches, TestMetrics NonDefault value = 0, want 3. Both failures were the hand-own’s own DOCUMENTED shortcuts (“IncNonDefault is inert”, “the value#pattern suffix is stripped, enabling the setting unconditionally”), and both were rooted rather than disclosed — runtime-capability’s admission test answers YES for each (a truthful managed implementation exists), so no class could take them.

Gates: pipeline validated 5/5 (1 skipped identically on both sides), proof page emitted at converter 773afa2c2; post-bank filtered sweep PASS 1 at 5 [85s], exit 0; check-roster-format 479 checks at 190 rows; zero compiler warnings in the changed files; package_info.cs untouched (no stdlib-metadata regen owed); full go2cs.slnx built once after the runtime-API addition per doctrine. Roster: 190/215 = 88.4% · 26,048 matching · 148 disclosed · honest 190/208 = 91.3%; the new Windows-only row moves the Linux applicable denominator 188 → 189 (178 validated there, unchanged).

internal/concurrent — re-measured at Go 1.23.12: closed-by-design STANDS, and gets stronger. Conversion succeeds (the 2026-08-19 emitAutoConversionSiblings crash fix holds), the tests build fails on exactly the banked single diagnostic — hashtriemap_test.cs(418,145): error CS0426: The type name 'node<,>' does not exist in the type 'concurrent_package' (the line moved 406→418 with the 1.23.12 emission; a direct rebuild of the tests project confirms it is the ONLY diagnostic) — so the comparison never runs and the 20 verdicts stay unmeasured. One NEW fact the 2026-08-19 entry did not name: the whitebox surface is not just the dead dumpMap/dumpNode scaffolding. TestHashTrieMapBadHash LIVE-assigns the private keyHash field (m.keyHash = func(_ unsafe.Pointer, _ uintptr) uintptr { return 0 }, hashtriemap_test.go:29) to force total collisions — a live test writing the replaced implementation’s private hash hook. A comparer-backed pluggable hash could honor that one truthfully, but dumpNode’s node/indirect/entry types still have no truthful managed referent, so the compile wall stands either way and the forbidden move (dead scaffolding for dead code) buys nothing. The row stays inside the naive denominator awaiting its individual ruling, exactly as the exclusion ledger’s candidates paragraph records. No artifacts banked.


unique classified at master: an HONEST NON-BANK — 7 of 20 measured, and a naming blocker that no disclosure can express (2026-09-01, coordinator lane)

Commissioned to classify unique’s residual toward a bank on the tracker’s reading (“16/20, residual isolated pure, disclosure-shaped — GC-liveness territory”). The classification came back non-bankable, on a blocker that is not a liveness question at all. Same outcome shape as the same day’s os attempt, and for the same doctrinal reason: a disclosure names a structural property of the deployment shape, never an unimplemented-but-fixable defect.

Full pipeline at master 6aa91b0f6, Go 1.23.12, explicit -test-timeout 10m: go2cs -tests -test-action all <GOROOT>\src\unique <worktree>\src\core\unique, exit 1. The results-file tail carries NO action:"timeout" event — the run completed; the mass-empty family is not in play and no shape inference was needed.

The arithmetic — and a correction to the tracker’s row

The C# host produced 20 terminal rows: 8 pass, 12 fail. Against Go’s 20, matching by name and verdict, 7 match:

Set Rows State
TestMakeCloneSeq + 6 of its 7 subtests 7 match (pass/pass) — the r41c makeCloneSeq root stays closed
TestHandle 8 value subtests 8 Go pass / C# failcheckMapsFor
TestHandle parent 1 Go pass / C# fail (propagated)
TestMakeClonesStrings 1 Go pass / C# fail
testEface naming pairs 3 logical (6 comparison rows) one side has NO matching row

The tracker’s “16/20” is contradicted, and the coincidence is diagnostic: 16 is exactly the MISMATCH-entry count in the comparison’s own error string. The reading appears to be the mismatch count transcribed as the match count. The last board-recorded figure before this was 4 of 19 (r43e), and that entry already named “the TypeFor/Name subtest-naming rows” as an unresolved root — so the optimistic row was never backed by a measurement here. Corrected in docs/phase4/TRACKER-100-percent.md with this run as the evidence.

Blocker A — the testEface naming divergence. NOT disclosable, on two independent grounds

type testEface any is emitted as global using testEface = object; — a C# using alias, not a type. The Go name is ERASED at conversion, so reflect.TypeFor[T]().Name() answers the empty string where Go answers testEface, and t.Run then names the subtest from that empty string. Measured, both directions:

This is systemic, not a unique quirk: a census of the committed corpus finds 167 global using <Name> = object; sitescrypto.PublicKey, crypto.PrivateKey, crypto.DecrypterOpts and their re-exports among them. Every named type whose underlying type is the empty interface loses its name to reflect.

It cannot be disclosed:

  1. Doctrine. The CLR is perfectly capable of carrying a named type here; the erasure is an EMISSION-MODEL choice (it is what makes Go’s universal assignability to any fall out of C# assignment for free). That is an unimplemented-but-fixable property of the converter, not a structural property of the deployment shape — the exact bar today’s os ruling set.
  2. Mechanism, and this one is decisive on its own. A disclosure pins a signature on a FAILING NAMED ROW. Here the two sides do not share a row to pin: Go’s .../testEface/<nil> has no C# counterpart and C#’s ...//<nil> has no Go counterpart. The oracle matches by test name, so a name mismatch is unpinnable — the same mechanism gap that DESIGN-object-lifetime-disclosure.md §3c records for the hang shape, met from a new direction.

Named remedy (owner of the row, not this lane): carry the Go name for a named-empty-interface type into the emission so reflect can recover it, without losing implicit assignability. Blast radius is the 167 alias sites; crypto’s key types make it a corpus-wide reflect-fidelity question, not a test-naming nicety.

Blocker B — the 10 GC rows ARE codegen-liveness, measured, and disclosure-shaped once A is gone

The eight checkMapsFor failures all report Go’s own message value <v> still referenced a handle (or tiny block?), and TestMakeClonesStrings reports string was improperly retained. Both ask the collector to take an object while the asserting frame is still running.

The converted test frame is faithful and the shape is the banked one: v0 and v1 are plain locals of the subtest lambda; Handle<T> is a struct holding ж<T>; Value() is an extension method taking that struct by value; and drainMaps + checkMapsFor run in the SAME frame. By-value struct consumption materializes a caller temp that CoreCLR’s GC info reports live for the whole method — verbatim the TestOnceXGC mechanism already banked in src/core/sync/go2cs_test_disclosures.json.

Measured, not asserted — frame-residency A/B over the real internal/weak machinery, ONE ARM PER PROCESS, four fresh processes per arm:

Arm What it does Result
static (positive control) box rooted by a static field RETAINED 4/4
callee (positive control) box built in a NoInlining callee; only the weak Pointer returned RELEASED 4/4
frame (the measurement) box is a frame LOCAL, last use is a by-value consumption, runtime.GC() and the check in that same frame RETAINED 4/4

Both controls fire in their own direction, so the probe can observe retention AND release — it is not a gate that cannot go red. The conclusion is the platform-liveness lane’s, reached independently: frame residency is the whole mechanism.

This also answers the commissioning question about the weak/CWT machinery directly: there is NO leak and NO named fixable remedy there. The callee arm releases 4/4 through internal/weak’s WeakReference + ConditionalWeakTable canonical index and runtime.GC()’s unique-cleanup arm — the machinery releases exactly when nothing roots the box. unique’s intern map, weak.Pointer’s handle indirection and the cleanup wiring in runtime/managed_impl.cs are all exonerated.

So the 10 rows are codegen-liveness (structural — the object CANNOT be collected while the frame runs), not object-lifetime (temporal): they fail DESIGN-object-lifetime-disclosure.md §2’s “genuinely unreachable at that point” clause, which is precisely the sibling boundary that document’s ⟨OQ-L1⟩ ratified.

Disposition

unique does not bank. Its test artifacts were restored, not committed — the standing outcome for this package, now for a rooted reason rather than a wall. All 11 dirt items classified as known non-drift before restoring: one CRLF phantom (doc.cs, empty numstat), one .cs.auto sibling, the init-forcing hook RELOCATING from handle.cs into package_info.cs’s <ImportInitializers> block, the position-map funcLit argument (the 5th closure shape), and the go2cs.SynthesizedStructs InternalsVisibleTo grant. No real drift.

The row is bankable the day Blocker A is fixed — at that point all 10 remaining rows are codegen-liveness disclosures whose A/B evidence is the table above, and the arithmetic closes at 20/20 with 10 disclosed. Nothing else stands in the way.


OPEN (2026-09-02, found by lane G-LAPTOP while building the receiver-snapshot family’s untested variants) — a VALUE-receiver method value over a POINTER-typed receiver expression emits the box where the value is wanted: CS1929, pre-existing, unreached in production

The shape. Go’s implicit deref through a field path: rh.p.label where p *frame and label has a VALUE receiver is (*rh.p).label — what Go saves at method-value evaluation is the POINTEE’s copy. The converter renders the wrapper over the pointer expression, so the generated extension method (which takes the value) is offered a ж<frame>: error CS1929. Two errors on a three-arm probe.

Attribution, by the five-minute control (CLAUDE.md’s “revert the fix, build past the blocker”). The error first appeared in a file commit 3 of the receiver-snapshot family had just rewritten, and was about to be reported as that commit’s regression; the family-tip converter (commits 1+2 only) reproduces the IDENTICAL two errors on the same program. The family neither caused nor cures it. Commit 3 (claude/g-receiver-eval-once) carries a guard that SKIPS the hoist for this shape so it does not rewrite emission it cannot render correctly (“do not hoist what you cannot render”).

Reach. Unreached in the production corpus (307/0 compiles at master); a converter GAP, not a live break. Any corpus site the commit-3 census instrument finds in this shape is counted here, never under the family’s diff.

Remedy (unassigned; sized cut wanted). The correct emission hoists the pointee COPY at evaluation (var recvʗ1 = rh.p.Value;-shaped, i.e. the selector’s own auto-deref reproduced faithfully) and binds the wrapper to it. Getting the auto-deref subtly wrong is how a receiver ends up aliasing the wrong storage, which is why it was named rather than guessed at. Guard: a behavioral position with a pointer-typed field receiver and a value-receiver method, mutation between creation and call, output-compared against go run. Candidate owner: a coordinator sub-agent after the family closes.


2026-09-02 · Two Linux-axis findings from the never-measured sixteen: netip’s disclosure set is the managed allocation regime rather than the Windows host, and cgo state is a per-package build variable with a measured seven-member class (lane C1, cloud Linux)

1. net/netip’s 57 disclosures reproduce leaf-for-leaf on Linux — the zh-box arc owes ONE pass, not two. The row validated at linux: 210 + 57 against a Windows 210 + 57: identical on both numbers. Those 57 are the three AllocsPerRun families — TestNoAllocs, TestAddrStringAllocs, TestParsePrefixAllocs — want-0/want-1 asserts Go satisfies by stack allocation and inlining where the converted path measurably heap-allocates, each leaf pinned on its own counter line. Their reproducing identically on a second OS says the set is a property of the managed allocation regime, not of the Windows host or its toolchain.

What that changes for the arc’s owner: the netip harvest retires the same 57 leaves on both platforms when the reduction lands, so the arc owes one measurement pass and one bank, not a Windows pass plus a Linux re-derivation. If a future run finds the two platforms’ disclosed sets diverging, that is a real change in the allocation regime and not noise — the invariance is now a recorded baseline it can be measured against.

2. cgo state is a per-package variable that decides which PRODUCTION files exist, and the class has exactly seven members at Go 1.23.12. The corpus’s emission state is CGO_ENABLED=0. A sweep converting under CGO_ENABLED=1 therefore compiles a different source set than the committed tree holds for any package whose file selection is cgo-conditional: declarations migrate between files, the stale other-selection file remains, and the build dies on the duplicates — zero verdicts, and it reads exactly like a converter regression.

Established by a one-variable A/B on os/user, same host, same tree, same row:

arm result
CGO_ENABLED=1 FAIL in 12 s, zero verdicts, closure build dying; leaves cgo_unix_test.cs / cgo_user_test.cs behind, artifacts with no Windows counterpart
CGO_ENABLED=0 validated at 12 — all agreeing, 0 disclosed, 0 withdrawn, a strict superset of the 5 banked Windows names, nothing absent

The census, so the class is met once rather than one package at a time. Grepping the roster’s 199 applicable rows for //go:build lines mentioning cgo, split by production vs test:

package prod test consequence
net 16 1 build failurecgo_stub.go (unix && !cgo && !darwin) vs cgo_unix.go ((cgo && unix) \|\| darwin) are mutually exclusive; the corpus holds net/linux/cgo_stub.cs, so it is the cgo-OFF side. Measured FAIL at 183 s with cgo_stub.cs absent from the run’s own drift list — i.e. not re-emitted, because it was not selected. Needs the pin.
os/user 7 4 build failure — pinned; validates at 12
plugin 2 0 converter crashplugin_dlopen.go ((linux && cgo) \|\| …) is literal C (import "C", #include <dlfcn.h>); plugin_stubs.go (… \|\| !cgo) is pure Go, and the corpus holds plugin_stubs.cs. So the crash at conversionDriver.go:228 is a cgo-state artifact, not a converter defect — a candidate for the pin, and the reading of that row’s root should be corrected. UNMEASURED as of this entry; C1 runs the arm.
crypto/internal/boring 1 0 inertnotboring.go’s constraint is a negated conjunction containing boringcrypto, already true when that tag is off, so cgo does not move its selection. Banked linux: 3, no pin.
debug/pe 0 1 count onlyfile_cgo_test.go //go:build cgo holds TestDefaultLinkerDWARF / TestInternalLinkerDWARF / TestExternalLinkerDWARF, which ARE the row’s Linux surplus. linux: 13 vs Windows 10 is cgo-ON’s three extra tests, and they pass because on Linux both runtimes skip them in agreement (testDWARF skips when GOOS != "windows").
os/exec 0 1 count onlyexec_linux_test.go linux && cgo; unmeasured (R2 docket)
os/signal 0 1 count onlysignal_cgo_test.go; banked linux: 29 + 2

The rule the class yields, and the trap in it: the count moves in BOTH directions, so neither cgo state is the safe default. Pinning off fixes net and os/user and would reduce debug/pe from 13 to 10; a session-wide zero brings the three cgo-ON-derived rows (debug/buildinfo 204, go/internal/gcimporter 582, go/internal/srcimporter 7) back short. The remedy is therefore a per-package table ($cgoOffPackages beside $longTimeouts in run-validated-sweep.ps1, applied around the converter invocation and restored in a finally), pinning what the corpus’s emission state requires and leaving every other row alone. A test-only-conditional row’s annotation is only meaningful beside the cgo state it was taken in, which is worth stating on the row rather than assuming.

Carried into the runbook (docs/GoCorpusMigration.md §3.3) rather than left here, because a version hop re-derives every row and would meet this on its first cgo-conditional package: the existing precondition covers the Go side (a count discrepancy); this is the converted side (a build failure), and the census is the cheap way to meet the class once.


TryMarshalAssignable’s named/unnamed unwrap arms are CORRECT — the “narrow the wrong-admit class” arc is refuted by a 70,071-admit census, and the whole wrong class is ONE known row (2026-09-02, lane R-LAPTOP, reflect tail)

The reflection bridge’s GoReflect.TryMarshalAssignable (golib GoReflect.ValueMarshalling.cs) has two arms that admit a value across a Go named↔unnamed boundary: A-wrap constructs the value into a named wrapper through its generated single-argument constructor, B-unwrap unwraps a named wrapper’s m_value into its underlying-typed slot. Go’s ASSIGNMENT rule requires identical underlying types with at least one side UNNAMED, and a standing question was whether these arms admit the both-NAMED case they should refuse (a wrong admit would let type S string fill a string slot, which Go’s assignment forbids — reflect.Value.MapIndex’s TestMap row asserts exactly that panic and the bridge answered a miss instead).

The arc as framed — instrument the arms, classify every admit, narrow the wrong class in the shared helper — is REFUTED, because the shared helper is also the CONVERSION path, and Go’s Convert legally admits both-named (type A inttype B int is a valid conversion; the same pair is not a valid assignment). An env-gated stderr trace on both arms, tagged with the arm and the calling method two frames up, over a full reflect -tests run:

total admits captured                                    70,071
  conversion path (TryConvertTo / TryByteSliceAs)        70,065   <- both-named is GO-LEGAL here
  assignment path, correct-Go (one side unnamed)              5
  assignment path, BOTH-NAMED and WRONG                        1   <- the entire wrong class

The 70,065 conversion admits are dominated by six shapes at 10–20k each — the TestArrayOfGC/ChanOfGC/MapOfGC… GC tests round-tripping through reflect.Value.Convert. Narrowing the arm to Go’s assignment rule, as the arc directed, would refuse all 70,065 legal conversions to fix one row. So the shared helper must NOT be touched.

The one wrong admit is B-unwrap from reflect.Value.MapIndex: TestMap_S → string — the same row already on the reflect tail, left honestly red by the MapIndex key-order commit (e9cea1e3b, 2026-09-01), whose in-code note already records that the naive tightening is measured-wrong.

Disposition — the remedy is a per-ASSIGNMENT-caller gate, worth exactly one row

The fix belongs at the handful of ASSIGNMENT callers (MapIndex, and its Set/SetMapIndex siblings — the sites that apply Go’s assignment relation, distinct from the Set{Int,…}/Convert callers that apply conversion), NOT in the shared helper. Two constraints, both measured:

The residual arc is therefore a CORRECT named/unnamed assignability predicate — one side genuinely unnamed, where “unnamed” is the raw golib container / ж<T> box / delegate / array-of-CLR and a predeclared type like string is NAMED (Go spec: “Predeclared types, defined types, and type parameters are called named types”) — gated at the ~5 assignment callers. Worth exactly the one TestMap row. Approved and owned by lane R (sizing-first); alternatively that row may be absorbed by the typed-nil-func neighborhood if it reaches the same site first, in which case it is one row reported superseded rather than cut twice.

Instrument note for the next reader: the trace lines concatenate under the test host’s stderr capture, so a per-LINE parse reports ~60 where the ASCII-grep count is 70,071 — parse with a whole-file findall, not per line, and the two reconcile. The instrument was reverted after the run (golib byte-identical to master); this record is the deliverable.

FINDINGS (2026-09-02) — two shapes worth meeting before the next bridge or method-value arc

One rule, TWO minters — a bridge change can be green on the probe and wrong on the row. reflect.rtype.Field(i) mints its struct-field descriptor through its own structFieldDescriptor, beside abi.synthesizeStructType, whose header states the one-rule invariant both are supposed to honor. A change that substitutes at one minter and not the other passes a probe aimed at the other and fails the verdict — so a descriptor/bridge change enumerates the minters before it measures, and either substitutes at both or states why one is out of scope.

The method-value family’s fourth face has TWO mechanisms, not one (4 of 17 sites red-first). M1 — the receiver EXPRESSION is deferred into the wrapper lambda, so any non-trivial expression at a lambda site re-executes per call; this is kind-independent. M2 — the root-ident snapshot aliases through a REFERENCE-semantics base (the value-receiver lambda path), which is a third axis the M1 predicate never reads. The pairing is the lesson: one commit needed two axes in its control where it varied one, and the next needed two mechanisms where it saw one — the same error in both directions. Both are covered by the evaluate-once ruling; recorded here so a future census names both before it counts.



2026-09-02 · net/http’s h2 write-deadline rows are a HOST-CAPACITY margin, not a Linux property — the managed TLS handshake bracket, and a falsifiable prediction for the Linux row (lane C1, cloud Linux, 4 cores)

The row’s own committed disclosure predicted this by name. net/http carries one manifest entry, TestWriteDeadlineExtendedOnNewRequest/h2, class performance-margin, signature TLS handshake error from. Its text brackets the managed handshake using the sibling test as the instrument: TestWriteDeadlineEnforcedPerStream/h2 runs Go’s own tryTimeouts ladder, setting Server.WriteTimeout to 125 ms, then 250 ms, then 500 ms on successive attempts, and on the reference box 125 ms and 250 ms fail with that exact signature while 500 ms passes — bracketing the handshake to (250 ms, 500 ms].

On a 4-core cloud container under the full 1,345-test suite, all three rungs fail — including the 500 ms one — so the handshake there exceeds 500 ms. The mechanism is Go’s own: conn.serve() arms the server write deadline before calling Handshake, so WriteTimeout bounds the TLS handshake itself rather than only post-handshake response writes. The client therefore loses Get #1, and the write-deadline semantics these tests were written to check are never reached at all.

Three observations isolate it, and each had an available opposite reading:

Arithmetic, corrected. The undisclosed shortfall is 2 verdicts, not 4: go=1345, disclosed=2 (the Extended pair), leaving TestWriteDeadlineEnforcedPerStream and its /h2 as the only undisclosed disagreements. An earlier posting of 4 counted the already-disclosed pair and is withdrawn.

No disclosure was minted, deliberately, and this is the case where minting one would be wrong rather than merely out of a lane’s authority. The reference bracket says 500 ms passes, so EnforcedPerStream/h2 is not a cannot — it is a row this host is too small to run. Disclosing it would convert a host-capacity limit into a permanent corpus claim, and it would destroy the instrument the existing disclosure depends on, since that entry’s bracket is this test’s ladder.

PREDICTION, stated so a later run can falsify it: on any Linux host that keeps the managed h2 handshake inside 500 ms under full-suite load, net/http validates at linux: 1343 + 2 — identical to its Windows columns, with no new disclosure and no change to the committed manifest. If a larger-host run reports anything else, the difference is real and this entry is wrong. Until such a run exists the row stays unannotated for Linux; a 4-core container is not evidence about the corpus. Note for whoever takes it: the GitHub OS-matrix ubuntu runners are the same shape as the container that produced this, so they cannot answer it.

The general form, which outlives this row: a timing-shaped divergence measured on a host smaller than the reference is a statement about the host until a control separates the two. The cheap control is the one used here — re-run the same tests filtered, on an idle box, and see whether they pass. Where the row’s own disclosure already carries a measured bracket, the bracket is the yardstick and the only question is which side of it the host sits on.

Amendment, 2026-09-02 (same lane, same day) — the prediction above is FALSIFIED, and the rung values it quotes do not exist

The entry states: “on any Linux host that keeps the managed h2 handshake inside 500 ms under full-suite load, net/http validates at linux: 1343 + 2… If a larger-host run reports anything else, the difference is real and this entry is wrong.” A larger-host run reported otherwise, so the entry is wrong by its own terms. Measured on a 10-thread Linux host, solo, 572 s wall against a 40 m budget with zero "action":"timeout" events in the record: 1341 matching + 2 disclosed + 2 UNDISCLOSEDTestWriteDeadlineEnforcedPerStream and its /h2, the same two — with Go passing on that same host in the same run as the native control. The row does not bank there either.

Second error, and it originates upstream of me but I propagated it. This entry quotes the retry ladder as 125 ms, then 250 ms, then 500 ms, taken from the row’s committed performance-margin disclosure. Go 1.23.12’s actual ladder is {250 * time.Millisecond, 500 * time.Millisecond, 1 * time.Second} (net/http/serve_test.go:980). No rung of the quoted description exists in this release. I took the prose as the record instead of re-deriving from source — precisely the failure CLAUDE.md names when it says a finding’s description is not its record — and I did so while building a prediction on top of it. The reference bracket (250 ms, 500 ms] appears to survive the correction (two failures then a pass, with the rung values mis-transcribed one position down), but that is a reading of someone else’s measurement, not a re-measurement, and it is flagged as such.

What the corrected ladder does to this entry’s own numbers: “all three rungs fail on the container, so the handshake exceeds 500 ms” should read exceeds 1 s. My conclusion was understated, not overstated.

Where the load hypothesis actually lands, stated more carefully than I stated it. It is not dead and it is not what I claimed. Two controls now exist and they do not point at host size:

condition host result
full 1,345-test suite 4 cores all rungs fail
full 1,345-test suite 10 cores all rungs fail (250 / 500 / 1000)
the two tests FILTERED, idle box 4 cores passes

So the divergence is concurrency-sensitive but not core-count-sensitive in the 4→10 range: under full-suite load the managed h2 handshake exceeds 1 s on both hosts measured, and adding cores did not buy a rung. My error was inferring “this host is too small” from “load makes the difference” — a real observation and the wrong conclusion drawn from it, since the filtered control only ever showed that load matters, never that capacity was the binding constraint.

The general form in this entry needs the same correction. It says a timing-shaped divergence measured on a host smaller than the reference “is a statement about the host until a control separates the two”, and offers the filtered re-run as the cheap control. The filtered control separates load from no load; it does not separate this host from a bigger host, and I used it as though it did. The control that answers host size is a bigger host, which is what falsified this. Keep the cheap control — it is still worth running first — but do not let a filtered pass license a prediction about hardware.

Still not minting a disclosure, and now for a firmer reason than the entry gave: two hosts and a native Go control on the larger of them say the converted h2 handshake does not clear 1 s under suite concurrency, which makes this a real converted-side property rather than a host limit — but establishing that is a root-cause question about the managed TLS handshake under load, not a disclosure to mint from this lane. The row stays unannotated for Linux.


2026-09-02 · net’s 73 unreported Linux verdicts have ONE root, and it is not the deadline: TestUnixgramServer/0 blocks the converted host indefinitely — proven by a 40 m/60 m A/B whose event streams are identical (lane C1, cloud Linux, 4 cores)

The measurement that settles it. The same net suite was run twice on one host, cgo-OFF, differing only in -TestTimeout (40 m, then 60 m). The two event streams are identical:

  40 m run 60 m run
tests started 528 528
terminal verdicts 501 (383 pass / 63 skip / 55 fail) 501 (383 pass / 63 skip / 55 fail)
orphaned (run, never terminal) 27 27
last event in the stream TestUnixgramServer/0 run TestUnixgramServer/0 run
tail event elapsed 2400, package timeout after 00:40:00 elapsed 3600, package timeout after 01:00:00

Terminal-set symmetric difference: empty. Orphan-set symmetric difference: empty. Twenty extra minutes bought zero additional verdicts. A suite that is merely slow makes progress; this one does not, so the deadline is not the constraint and raising it further cannot help.

The block is a single test, and the stream says so positionally. TestUnixgramServer run is event 1029 of 1031 and TestUnixgramServer/0 run is event 1030 — nothing follows. The other 26 orphans are not independent hangs: 24 carry t.Parallel() and are the parked parallel batch (the documented two-phase shape — a serial-phase death leaves the whole parallel batch unreported), and the remaining two, TestAllocs and TestUDPIPVersionReadMsg, each emit an explicit infrastructure-error and the phase moves on past them. Go’s own side passes TestUnixgramServer and all three subtests.

It sits in a coherent unixgram cluster, which is what makes it a root rather than an oddity:

TestReadUnixgramWithUnnamedSocket    go=pass  C#=fail   connection refused | read unixgram /tmp/.../sock: i/o timeout
TestUnixgramLinuxAbstractLongName    go=pass  C#=fail   connection refused | read unixgram @abstract_test: i/o timeout
TestUnixgramConnLocalAndRemoteNames  go=pass  C#=fail   got 0x7ef6ac05ed18; want 0x7ef6ac05f368
TestUnixgramServer/0                 go=pass  C#=(hang)
TestUnixgramAutobind                 go=pass  C#=pass

Two of the three failures share one shape — a datagram send refused, then the peer’s read reaching its deadline — and TestUnixgramServer/0 performs that same round trip. What it does NOT share is the outcome, and that is the part worth stating precisely: packetTransponder (mockserver_test.go:440) sets SetDeadline, SetReadDeadline and SetWriteDeadline to someTimeout before its ReadFrom, so a conn honouring its deadline cannot block there indefinitely — it would error, send on the channel, and close(ch) would end the test’s select loop. The hang therefore localises to either a deadline not being honoured on that conn or a block earlier in the subtest (ListenPacket, newLocalServer, buildup). I have not distinguished those, and this entry does not claim to.

Correction to my own earlier posting. I recorded net’s 40 m result as “needs more than 40 m on this host class — a deadline datapoint for the ledger, not a divergence.” Both halves are wrong: it does not need more time, and it is a divergence. The 60 m run is what falsified it.

Refinement to R’s Linux-frontier map, not a contradiction of it. R recorded 73 unreported; this host reproduced 73 exactly (go 576 / C# 503), along with Buffers_WriteTo’s writev-9 to the digit. What is new is that the 73 are not 73 problems. They are one hang plus its consequences: the serial tail alphabetically at-or-after TestUnixgramServer (TestUnixgramWrite, TestUnixgramZeroByteBuffer, TestUnixgramZeroBytePayload, TestZeroByteRead, …) and the 24 parked parallel parents with their subtests. Closing one test therefore unblocks the whole tail, which changes what fixing it is worth by an order of magnitude — the same re-pricing runtime/debug’s TestPanicOnFault needed.

FALSIFIABLE PREDICTION, and the run that tests it is already tasked. G is standing up WSL2 as the fleet’s big Linux host and is to run net at 60 m. If the hang is a deadlock, that run stops at TestUnixgramServer/0 with the same 501 terminal verdicts on a much larger box. If instead it runs further, the block is contention on a 4-core container and this entry is wrong. Either way the answer is one run away and needs no new instrument.

The general form. Two runs of one suite at different deadlines are a cheap and decisive instrument: if the verdict sets are equal, the deadline is not the constraint and the tail is a block to be located, not time to be bought. A deadline-killed run should be A/B’d against a longer one before its shortfall is priced, because “needs a bigger timeout” and “hangs on one test” produce the identical tail event and the identical NOT MEASURED verdict, and only the A/B separates them.


2026-09-02 · net/http’s h2 write-deadline rows are NOT a host-capacity margin — BOTH predictions falsified on a 10-core host, and the residual is managed-vs-native TLS handshake latency that scales with SINGLE-THREAD speed (lane G, G-LAPTOP WSL2, 10 cores, solo)

The entry above staffed this row on core count: the theory was that a 4-vCPU container under 1,345-test load could not hold the h2 write-deadline pair, and that a wider Linux host would clear it at linux: 1343 + 2 with no new disclosure. That prediction is falsified, and so is the theory behind it. Both are recorded here by name so the row is not re-staffed on either.

Measured

   
host G-LAPTOP WSL2, nproc 10, 16 GB, solo, Ubuntu 22.04.5, kernel 6.18.33.2
toolchain go 1.23.12 (GOROOT=/usr/local/go1.23.12, bare go version verified), .NET SDK 10.0.400, GOTOOLCHAIN=local, clone at master 3bbb04ca4
wall 572 s against a 40 m budget
"action":"timeout" events 0 — read FIRST; this is a real failure, not a deadline kill
go 1345 entries — 1329 pass, 16 skip
C# 1345 entries — 1325 pass, 16 skip, 4 fail
result 1341 matching + 2 disclosed + 2 UNDISCLOSED (predicted: 1343 + 2)

The 2 undisclosed are TestWriteDeadlineEnforcedPerStream and /h2 — the pair the entry above predicted would PASS here. TestWriteDeadlineExtendedOnNewRequest + /h2 absorbed as performance-margin exactly as expected.

The failure text, and the h1/h2 split

TestWriteDeadlineEnforcedPerStream/h2   fail  elapsed=1.5619664
  server log: http: TLS handshake error from 127.0.0.1:42840:
              write tcp 127.0.0.1:43587->127.0.0.1:42840: i/o timeout
  failed at 250000000  →  retrying at 500000000
  failed at 500000000  →  retrying at 1000000000
  failed at 1000000000 →  all attempts failed

/h1 passes on BOTH tests (0.51 s, 0.38 s); only the TLS-bearing /h2 fails. That split is the row’s own confirmation that the handshake — not the WriteTimeout mechanism generally — is what the deadline cannot cover.

The control that makes this clean: GO PASSED ON THE SAME HOST, SAME RUN

The obvious confound on a WSL2 box is virtualized loopback inflating a 127.0.0.1 TLS handshake. It is exonerated without a new experiment, because the native control was already in the data: Go’s side passed every one of these tests on this host, this loopback, this run, completing inside the 250 ms rung. A network-stack explanation would have failed both sides. Parallelism is exonerated the same way — 10 cores, solo, a wider miss than the 4-vCPU container.

What remains is managed-vs-native TLS handshake latency, serial: (250 ms, 500 ms] on the i9 reference, > 1 s here. The consistent reading is single-thread speed — a mobile part versus a desktop — and TLS handshake work does not parallelise.

Correction to the committed disclosure’s ladder

The manifest entry describes tryTimeouts as 125 ms / 250 ms / 500 ms. Go 1.23.12’s actual ladder (net/http/serve_test.go:980) is {250 ms, 500 ms, 1 s} — no rung of the stated description exists in this release. The bracket conclusion survives (the author almost certainly mis-transcribed the rung values one position down: two failures then a pass IS (250 ms, 500 ms]), and the correction makes the host comparison rung-for-rung rather than an inference across two differently-described ladders:

host 250 ms 500 ms 1 s managed handshake
i9 (reference) fail pass (250 ms, 500 ms]
G-LAPTOP WSL2, 10 cores, solo fail fail fail > 1 s

Standing

No disclosure minted — the reference bracket still says 500 ms passes on a fast desktop, so this is a slow host, not a cannot. The Linux row stays UNANNOTATED. The host’s role is recorded honestly: right for wide rows and for rows a container deadline-kills, wrong for near-threshold serial-latency rows — do not route a timing row here on core count.

The instrument this owes: the gap is a number nobody has measured directly. A PerfTlsHandshake row in src/tests/Performance (N loopback TLS 1.3 handshakes, in-memory cert, self-timed, compared across Go / C# JIT / C# AOT) turns “the ladder fails” into a ratio measurable on every host in the fleet, with the i9 — the fastest single thread available — as the natural reference. Sized before cut, per the standing rule.


Amendment, 2026-09-02 (same lane, same day) — the dichotomy above is FALSE, and the root is simpler: someTimeout is ONE HOUR

The entry above says packetTransponder sets three deadlines before its ReadFrom, “so a conn honouring its deadline cannot block there — it would error, send, and close(ch) would release the test’s select loop”, and concludes the hang must be either a deadline not honoured or a block earlier in the subtest. net’s own test constant is someTimeout = 1 * time.Hour (conn_test.go:18). A conn honouring that deadline blocks for an hour, which is longer than any package deadline the pipeline has ever given this package — so the hang needs no deadline defect to explain it, and the dichotomy the entry poses is not a real fork. I read the helper and not the constant, and the correction is mine.

What a filtered diagnostic then established, with the test running ALONE on an idle container — which also removes contention as an explanation, the reading a 4-core box most needed to rule out:

So the root is the cluster’s root, not a separate one: a unixgram WriteTo reports success while the peer’s ReadFrom never receives. TestReadUnixgramWithUnnamedSocket and TestUnixgramLinuxAbstractLongName show the same failure with a short deadline and therefore FAIL (connection refused, then read i/o timeout); TestUnixgramServer/0 shows it with a one-hour deadline and therefore HANGS. One defect, two presentations, and the deadline value is the only thing that decides which.

That also re-prices the fix the right way round: this is not “a hang to unblock” plus “two failures to root” — it is one unixgram delivery defect worth three failing tests and 73 unreported verdicts.

Unproven and left unproven deliberately: whether the datagram reaches the kernel at all. ss is not installed on this container and /proc/net/unix carries no queue depths, so I could not read the peer socket’s receive queue while it hung. The idle netpoller is suggestive — a queued datagram should have raised readiness — but that inference assumes those fds are registered with that epoll, which I did not verify. A host with ss can settle it in one command during the hang, and that is the next measurement rather than a conclusion drawn here.

Filtered control, run after the above and decisive on the contention question. TestUnixgramServer alone, -test-filter, 6 m package deadline, nothing else on the box. The host’s entire record is four events:

(pkg)                 run
TestUnixgramServer    run
TestUnixgramServer/0  run
(pkg)                 timeout 360   "package timeout after 00:06:00"

Six minutes, zero progress past the run event, with the machine otherwise idle — while Go passes the parent and all three subtests. Contention is eliminated as an explanation, which was the reading a 4-core container most needed ruled out, and the block reproduces identically at a third deadline (6 m alongside 40 m and 60 m). Three deadlines, one result.

(Diagnostic only, per the standing rule — a -test-filter record never banks a row, and this one was deleted rather than left for a later run to read as its own.)

Amendment, 2026-09-02 — the prediction is CONFIRMED on a 10-thread host, independently, and the 73 rows are verified as one root rather than argued into one

The entry above predicted: on a >4-core Linux host at 60 m, net stops at TestUnixgramServer/0 with 501 terminal verdicts and the same 27 orphans; if it instead completes, the block is contention on a small container and this entry is wrong. A different lane ran it on a 10-thread WSL2 host, solo, cgo-OFF, and the run deadline-killed at 60 m with the final run pair TestUnixgramServerTestUnixgramServer/0 — the predicted test, named in advance.

  this container (4 cores) the big host (10 threads)
terminal verdicts 501 506
orphaned 27 28
last run event TestUnixgramServer/0 TestUnixgramServer/0
tail timeout after 01:00:00 timeout after 01:00:00

Within one on each count, on two hosts differing by 2.5x in thread count. Contention is dead as an explanation — it was already ruled out here by the filtered single-test control, and a bigger box reproducing the same block at the same test settles it from the other direction.

And the one-root claim was VERIFIED there rather than inherited from this entry. The absent set on that host is alphabetically EARLY (TestAcceptTimeout, TestCloseRead, TestConnClose…), which is not a deadline tail’s shape — the shape heuristics alone would have read it as scattered and published dozens of findings. Parsing GOROOT’s net/*_test.go gave 304 test functions of which 28 carry t.Parallel(); of the 41 absent top-level names, 28 are exactly that parallel set with zero of them reporting, and the remaining 13 are a contiguous U→W alphabetical tail beginning exactly at the block. Serial phase dies at TestUnixgramServer/0, takes the 13 serial tests after it, and the parallel batch never starts. 41 names / 73 verdict rows / ONE root, derived independently.

That is the difference between one finding and seventy-three, and it is the two-phase rule doing precisely what it was written for.

What the confirmation does NOT settle, and is worth keeping separate: the unixgram delivery defect itself is still un-rooted below the observation that a WriteTo reports success while the peer’s ReadFrom never receives. The queue-depth question this entry left open (ss -xa during the hang) remains the next cheap measurement, and that host has ss.

Amendment, 2026-09-02 — WriteTo DOES error. A 60-line reproducer falsifies this entry’s “reports success” claim, and the corrected reading points AT the sockaddr seam where the old one pointed away from it

The two amendments above rest on a sentence this one retracts: “a unixgram WriteTo reports success while the peer’s ReadFrom never receives”. It does not report success. Reduced to two sockets in a temp directory, one WriteTo, one ReadFrom under a 1-second deadline — short on purpose, since someTimeout = 1 * time.Hour is the only reason the upstream failure presents as a hang rather than a verdict:

GO   write: n=19 err=<nil>
     read:  n=19 payload="UNIXGRAM ROUND TRIP" peer_nil=false

C#   write: n=0  err=write unixgram …/cli.sock->…/srv.sock: sendto: connection refused
     read:  FAILED err=read unixgram …/srv.sock: i/o timeout

Three runs each, byte-identical every time; the converted build is clean with zero strict errors. No test framework, no sweep, no deadline arithmetic.

How the wrong claim got published, because the mechanism is the reusable part. It was an INFERENCE dressed as an observation: packetTransceiver reaches its ReadFrom, therefore its WriteTo must have returned nil. The host’s event stream carries no event for a write’s return value, so I never saw it — and the entry should have said “not observed” instead of asserting the opposite. A /proc inspection and a filtered control both agreed with everything around that sentence, which is exactly why it survived: surrounding measurements corroborate the frame, not the unmeasured claim inside it.

The hang survives the correction and is now mechanical rather than inferred, which is why this sharpens the entry instead of overturning it. The write fails, so packetTransceiver sends on trch and returns, closing it. packetTransponder is parked in ReadFrom for an hour because nothing arrives, so tpch never closes — and the test’s loop is for trch != nil || tpch != nil (server_test.go:308), waiting for BOTH. One channel closes, the other cannot, and the host sits until the package deadline. The 73 rows follow from that with no lost datagram required.

Two independent measurements now converge on the same boundary. A sibling lane’s ss -xa during the hang reports both sockets at Recv-Q 0 / Send-Q 0 — the datagram reaches no queue; this reproducer reports ECONNREFUSED from sendto, n=0 — it is never accepted. The send does not leave, and the kernel rejects it rather than dropping it.

That relocates the suspect. ECONNREFUSED on sendto to a bound, existing unixgram socket is the kernel objecting to the DESTINATION — i.e. the sockaddr encoder. net/linux/fd_unix.cs:21 pins writeToSyscallName = "sendto", and Sendto sits in the Linux sockaddr seam’s own deliberately-not-covered list beside Sendmsg, behind an evidence gate whose stated rule is “fix a censused wrapper when a suite REACHES it”. The “silently lost” reading pointed away from that seam; the measured one points into it.

Still labelled a hypothesis: that this and syscall’s TestPassFD / TestSCMCredentials EISCONN pair share one root. Both are unix-domain sends whose destination is handed to an uncovered wrapper and both fail at the kernel boundary — but ECONNREFUSED and EISCONN are different rejections, and no one has yet read what sendto writes for a SockaddrUnix. That read is one function and it is the next step.

The reproducer is not committed. It would be a known-failing behavioral test today, and the suite’s value is that it is green; it lands as a guard when the seam closes.


2026-09-02 · reflect crash-residue item 2 BANKED, and the MakeFunc/Variadic family SPLIT so nobody re-derives it (lane R-LAPTOP, reflect tail)

Two rows off the crash residue, banked on claude/reflect-tail-r-lite (rebased onto master 21f7c9677):

THE SPLIT — do not re-derive it. The other five MakeFunc/Variadic reds sit on DISTINCT roots and are NOT moved by the two above (crash frames measured 2026-09-02, e0dcdb4f5, train-4-stable):

Reports received to this ledger (recorded, NOT re-derived):


The 53× RSA gap is NOT arithmetic — math/bits intrinsics measured, TWO NULLS (G, 2026-09-02)

The finding outranks the cut that produced it. Reached from net/http’s h2 write-deadline pair (itself a Debug-build margin, see the pipeline-configuration ruling); this is the residual underneath.

What was measured (Release + DOTNET_TieredCompilation=0, one host, sequential, distinct records)

measurement Go converted ratio
TLS 1.3 handshake, steady-state median 2.59 ms 57.87 ms 22×
TLS 1.3 handshake, first in process 3.73 ms 1.1530 s
RSA-2048 PSS signature 0.834 ms 44.5 ms (WSL) / 64.6 ms (Windows) 53× / 77×

The signature is 79% of the handshake residual — direct attribution, since a TLS 1.3 handshake performs exactly ONE server CertificateVerify signature.

The hypothesis, and why it was WRONG

Go intrinsifies the whole math/bits family (Mul64MULQ, OnesCount64POPCNT, LeadingZeros64LZCNT, RotateLeft64ROL, ReverseBytes64BSWAP) and additionally aliases math/big’s own mulWW to the Mul64 intrinsic (ssa.go:5113:5022), while declaring addMulVVW bodyless in arith_decl.go (hand-written assembly). go2cs necessarily emits the portable fallbacks. The natural conclusion — that this emulation is the 53× — is false.

Cut and measured (claude/g-mathbits-intrinsics 1de4445dc, kept as a negative-result branch): sixteen functions hand-owned onto Math.BigMul / BitOperations / BinaryPrimitives / UInt128.

primitive Go before after speedup after ÷ Go
Mul64 0.474 ns 5.759 ns 3.025 ns 1.90× 6.4×
OnesCount64 0.230 ns 5.177 ns 2.908 ns 1.78× 12.6×
RotateLeft64 0.740 ns 4.179 ns 2.618 ns 1.60× 3.5×
Add64 0.506 ns 4.883 ns 4.814 ns 1.01× TIED 9.5×

An RSA-2048 CRT modexp performs ~5×10⁵ word multiplies; at 5.76→3.03 ns that is ~1.4 ms saved against a 64.6 ms signature — ~2%, inside the arms’ own spread. That is the whole explanation of the null.

What it means for the seam hunt — do not re-walk these

Correctness note (the cut is right, just not useful)

math/bits 26/26 and math/big 224/224 both unmoved with the sixteen replacements live — real evidence the mappings are semantically correct at Go’s edges (LeadingZeros64(0)==64, Len64(0)==0, RotateLeft64 with negative k).

Eliminated by measurement, so nobody re-walks them

Platform (Windows ≡ WSL to within ~1 ms on the same silicon), certificate/key type (httptest and PerfTlsHandshake embed the SAME RSA-2048 testcert), session resumption (PerfTlsHandshake sets no ClientSessionCache; Go’s default is nil), .NET ThreadPool starvation (golib/builtin.cs:79 — goroutines get dedicated threads). ⚠ And the ~21% non-signature remainder is not nistec: TLS 1.3’s default key share is X25519 (handshake_client.go:153), so that segment is crypto/ecdhcrypto/internal/edwards25519/field plus the record layer and parsing.


addMulVVW as emitted is 13.1× a raw loop — the seam is CALL GRANULARITY, apportioned (G, 2026-09-02)

Follows the math/bits two-null block above. That cut proved the arithmetic is not the 53× RSA gap; this measures what is. math/big’s addMulVVW inner loop — the innermost loop of Montgomery multiplication, i.e. of every RSA private-key operation — 32 words × 200 k reps, Release + DOTNET_TieredCompilation=0, same data and an observed sink for every variant so none is dead code. Two runs, spread under 2%.

Variants — the table names what each one REMOVES, and the attributions are DERIVED from it

variant container multiply assembly attr ns/word vs B  
F FULL emitted slice<Word> bits.Mul cross 24.15 – 24.35 13.1×  
A emitted slice<nuint> bits.Mul cross 22.31 – 22.38 12.1×  
C Span<ulong> bits.Mul cross 14.23 – 14.34 7.7×  
E-CROSS slice<nuint> bits.Mul cross yes 10.96 – 11.24 6.0×  
G slice<nuint> local copy same 5.36 – 5.38 2.9×  
H slice<nuint> local copy same yes 10.95 – 11.06 5.9× (UntypedInt branch)
E slice<nuint> local copy same yes 4.01 – 4.05 2.2×  
D slice<nuint> Math.BigMul 2.72 – 2.77 1.5×  
B raw Span<ulong> Math.BigMul 1.85 – 1.86  

Word is math/big’s own generated [GoType("num:nuint")] struct, copied verbatim from its Generated/ output — not a reproduction.

Apportionment — every pair below differs in EXACTLY ONE thing

UntypedInt `UintSize` branch   H / E       2.72 - 2.73x   <- DOMINANT
AggressiveInlining, same-asm   G / E       1.32 - 1.42x
golib slice vs Span            D / B       1.47 - 1.50x
Word generated-struct wrapper  F / A       1.08 - 1.09x   <- nearly free
assembly boundary              H vs E-CROSS  WITHIN NOISE -> ~1.0x

The seam is the EMITTED BODY, and the assembly boundary is not part of it. H (same assembly) and E-CROSS (cross assembly) carry the identical body and the identical attribute and read 10.95–11.06 against 10.96–12.31 — the same number. A JIT that inlines identical IL emits identical machine code whichever assembly it came from, and that is what the measurement shows.

Two mechanisms, both in the body:

  1. UintSize == 32 is a struct comparison, evaluated per call — 2.72×. bits.cs:21 emits public static UntypedInt UintSize => 64; — a property returning the generated UntypedInt struct, whose operator == is left.Equals(right) over a private Compare the JIT compiles standalone at IL 141 and never inlines. Go folds this branch at compile time; the emission evaluates it on every bits.Mul and every bits.Add. This is a converter-level property of untyped constants, not of math/bits — any emitted UntypedInt compared against a literal in a hot path pays it.
  2. IL size over the inlining budget — 1.32–1.42×. bits_package:Mul and Add are 83 and 87 IL bytes from the two-level chain, tuples and conversions, so the JIT declines by default; AggressiveInlining overrides it.

~1.5× of A remains unapportioned and is named rather than absorbed: 4.02 × 2.72 × 1.38 = 15.1 against a measured A of 22.4. The likely reading is compounding — an un-inlined call whose body itself contains a non-inlined UntypedInt.Compare pays both, and the two do not multiply cleanly. It is not claimed as boundary cost.

⚠ CORRECTED BEFORE MERGE — what this block said first, and why it was wrong

This block originally read “cross-assembly boundary A/G 4.16–4.17× ← DOMINANT” and concluded “the assembly boundary is the seam.” That was wrong, and the number was an artifact of the instrument.

The tell was raised by the coordinator: E (4.0 ns/word) and E-CROSS (11.1) are the same code with the same attribute on the same four methods, and a JIT cannot produce 2.75× from provenance alone. Two falsifiers settled it — the assemblies were all optimized (IsJITOptimizerDisabled=False read in-process, so not a Debug-callee artifact), and the JIT summary showed the cross-assembly chain was inlined under the attribute (bits_package absent from the arm’s compile list entirely). The gap was my hand-written G/E copies using const int UintSizeLocal = 64 where the emission uses the UntypedInt property. Variant H restores that one difference and reproduces E-CROSS exactly.

The failure is the same class the block already warned about — A/G differed on TWO axes, assembly AND body — committed by the person who wrote the removes-column rule one section earlier. Kept visible rather than quietly replaced, because a retracted number with its cause is worth more than a corrected one without. A same-assembly copy with no attribute at all is already 4.2× faster than the emitted form.

E’s prediction, written BEFORE the run and scored

“E improves substantially but does NOT reach B: I predict 2–6× of B (3.7–11.4 ns/word).”

Both E arms landed inside it — same-assembly 2.2×, cross-assembly 6.0×. The stated mechanism also held: inlining removed call overhead but the tuple materialisation and nuintuint64 conversions at two levels survive, which is the 2.2× floor.

What each remedy candidate is now worth| candidate | measured | converter change? ||—|–:|—|| one-level word-size hand-own (Mul/Add/Sub → a single BCL call each) | ~3.7× (removes the 2.72× branch AND the 1.38× decline together) | no — a hand-own || AggressiveInlining alone, body unchanged | 1.32 – 1.42× | no || converter intrinsic table, emit at the site | not needed for most of the above | yes || additionally fix slice + tuples (raw ceiling) | 12.1× | golib + emission |⚠ The one-level hand-own is the level the WITHDRAWN math/bits cut did NOT register — it registeredMul64/Add64/Sub64 and not the word-size Mul/Add/Sub that math/big actually calls. Sothat cut left the two-level chain and the UintSize branch standing, which explains its 0.0% farbetter than “intrinsics do not help”.

⚠ Two labelling corrections, recorded because they are the failure mode here

  1. The first run printed the slice and call attributions swapped (variant C removes the slice and keeps the call; D does the opposite). Numbers right, labels backwards — it would have pointed the design item at golib’s slice instead of the call boundary.
  2. C/G was then quoted as “cross-assembly boundary” while differing in container AND assembly. The clean pair is A/G, and the true factor is 4.17×, not 2.7×.

Both survive review by looking self-consistent, which is why the table above names what each variant REMOVES and the attribution lines are derived from that column rather than typed beside it.

Method

Instrument is a scratch console (CConv, plus CConvE against a scratch core/math/bits copy whose Mul/Mul64/Add/Add64 carry the attribute — never the corpus). Call depth was checked first and is two (bits.MulMul64, bits.AddAdd64, each materialising and destructuring a ValueTuple), so the attribute went on all four levels; on the outer pair alone it would have measured the wrong thing. Both consoles reference golib and math/bits by ABSOLUTE path, so the per-configuration $(go2csPath) Release trap cannot reach them. Neither is proposed for banking.

2026-09-02 · EnvironBlockWalk’s golden is per-GOOS and only Windows can satisfy it — a behavioural-corpus instance of the L3 alias question, stated as a finding with no fix (lane C1, cloud Linux)

The observation, from a Linux CNR at master. EnvironBlockWalk/package_info.cs is one of twelve files a Linux CNR reports as CHANGED, and it is the only one of the twelve that is informative: the other eleven belong to the six platform-exclusive packages whose byte-identical check CNR itself declares vacuous. This one type-checks, converts fully, and still differs — so it is a real, standing, per-GOOS golden difference rather than an artifact of an unmeasurable package.

What differs, and why. The committed golden carries three imported aliases:

18: global using syscallꓸHandle   = go.syscall_package.ΔHandle;
19: global using syscallꓸSignal   = go.syscall_package.ΔSignal;
20: global using syscallꓸSockaddr = go.syscall_package.ΔSockaddr;

and syscall’s own per-GOOS declarations are split:

GoTypeAlias          windows   linux   darwin
Handle                     1       0        0
Signal                     1       1        1
Sockaddr                   1       0        0

A Linux conversion re-derives the imported-alias section from the linux flavour and therefore emits only syscallꓸSignal, dropping the two that flavour never declares. The golden was minted on Windows, where all three exist. Neither emission is wrong for its own flavour; the golden simply encodes one of them.

Why it is NOT platform-exclusive, which is the load-bearing distinction. The program uses syscall.Environ and syscall.Getenv — both platform-neutral. It compiles and runs on every flavour, so it is correctly absent from F8’s gating set (six windows-native packages), and marking it platform-exclusive to silence the diff would be false: it would suppress a package that genuinely works everywhere.

Same shape as the withdrawn L3 alias arc, arriving through the behavioural corpus instead of the stdlib. That arc was withdrawn when a differential showed the Linux emission re-derives the section wholesale rather than merging a windows-seeded alias forward — measured on syscall’s flat package_test_info.cs. This is that same mechanism seen in a behavioural golden, and it is why the census the arc produced was kept even though its code was not: the shape is real, it simply is not the defect that arc’s predicate targeted.

NO FIX PROPOSED, deliberately. Three shapes exist and each has a real cost: mint the golden per-GOOS (a golden set per flavour, and every future behavioural golden inherits the question); make the goldens alias-insensitive (weakens a byte-exact comparison to silence one line); or accept that a Linux CNR reports one CHANGED file and diff it against this named baseline (free, and what the lane does today). Choosing between them is a goldens-strategy decision, not a lane call, and nothing is blocked meanwhile — the diff is one file, named here, and reproducible from any Linux CNR at master.

The general form. A per-GOOS golden difference in a package that type-checks everywhere is NOT the same class as a platform-exclusive package, and the two must not be conflated: the first is a golden that encodes one flavour’s emission, the second is code one flavour cannot compile. F8’s marker answers the second and must not be reached for to silence the first.


2026-09-02 · The Linux-parity residual is FIVE rows and FOUR unrelated roots — the state of each, so the next reader starts from it rather than re-deriving (lane C1, cloud Linux)

Master 64a064098 carries Linux: 194 of 199 applicable. The five unannotated rows are not a backlog of one kind; they are four unrelated roots plus one row waiting on another lane’s arc. Stated per-row because the whole cost of this block is that the next lane does not re-derive it.

row state root owner
runtime/debug CUT, banks 4 + 6 TestPanicOnFault takes the process down (mmaps PROT_READ, writes, expects SIGSEGV→panic; no SEH equivalent on the CLR) C1 — rebases after train 11
syscall 13 mints banked, 14th approved the posix_spawn seam, five refused SysProcAttr fields C1
net ROOTED, not open RawSockaddrUnix 24 managed bytes vs the kernel’s 110 C2 (sockaddr seam)
net/http measured; needs a bigger host managed h2 TLS handshake vs WriteTimeout under load bank on G’s WSL
internal/poll open, blocked waits on the typed-nil widening arm whichever lane takes that root

runtime/debug — cut, not pending. TestPanicOnFault is worth NINE verdicts rather than one: the host reports a single verdict (TestFreeOSMemory, first alphabetically) and tests 2..10 are a contiguous alphabetical tail of absences, because the process dies. Excluded through the host-fatal class — withdrawn from BOTH sides by name and COUNTED in disclosed — the row derives 4 + 6 from its own comparison record, matching its Windows columns plus the new entry.

syscall — the residue is three roots, not one row. The thirteen platform-skip mints and the approved fourteenth (TestExecPtrace, host-limit) all discharge ONE property: the posix_spawn seam refusing Credential, Cloneflags, Unshareflags, Chroot, Ptrace — the same property os/exec’s TestCredentialNoSetGroups discloses, reached from a second package. What remains after them is four rows across three unrelated roots: runtime_BeforeExec unimplemented (TestExec), the EISCONN pair (TestPassFD, TestSCMCredentials — the sockaddr seam, C2’s), and Setegid/Seteuid answering “operation not supported” (TestSetuidEtc).

net — rooted, and it is one defect, not 73. The 73 unreported verdicts collapse to a single unixgram delivery failure: sendto returns ECONNREFUSED because the encoder hands the kernel a managed RawSockaddrUnix whose Path is a 16-byte reference where sun_path[108] is expected. The apparent “hang” is the same defect with a one-hour deadline (someTimeout) instead of a short one. Two independent measurements agree: zero queue depth on both sockets during the block, and ECONNREFUSED with n=0 from a 60-line reproducer.

net/http — a host limit, not a Linux property. The row’s own committed performance-margin disclosure brackets the managed TLS handshake to (250 ms, 500 ms] on the reference box using the sibling test’s tryTimeouts ladder as the instrument. A 4-core container misses the 500 ms rung under the full 1,345-test suite and PASSES the same tests filtered on an idle box — so the shortfall is load. No disclosure was minted, deliberately: doing so would convert a host-capacity limit into a permanent corpus claim AND destroy the instrument the existing entry depends on.

The general form, which is why this is a block rather than a list. “Unannotated” is a property of the ROSTER, not a diagnosis: of five such rows here, one is cut and waiting on a train, one is rooted and reassigned by root, one is a host limit needing no code at all, one is blocked on another arc, and only the residue of the fifth is genuinely open work. A residual counted by rows over-states the work remaining by a factor of several, and counting it by ROOTS is what made the C1/C2 split resolvable by evidence rather than by territory.

2026-09-02 · The struct-passing seam restated at its TRUE size: the CLR AUTO-LAYOUTS any struct holding a reference, so the kernel reads the WRONG FIELD — not a wrong-width one (lane C2, cloud Linux; offsets measured, not reasoned)

The class was under-described for as long as it has been known, and the under-description sends people to the wrong fix. Every prior statement of it — including the one in this lane’s own train-11 commit — said some version of “one word where four bytes belong”: a ж<T> or array<T> field is 8 or 16 bytes of object reference where the kernel wants inline storage, so the fields after it are pushed along. That is a WIDTH story, and a width story invites a width remedy: widen the field, mirror the declared order, count the bytes again. It is wrong. A C# struct containing an object reference does not get sequential layout at all — the CLR is free to reorder its fields, and it does. The kernel does not read a too-narrow version of the field the code assigned; it reads a different field.

Measured with Unsafe.ByteOffset against the converted types themselves, GoTargetOS=linux, x64:

Msghdr             SizeOf=80   native=56
  Name         managed=  0   native=  0
  Namelen      managed= 40   native=  8     <-- MOVED
  Pad_cgo_0    managed= 48   native= 12     <-- MOVED
  Iov          managed=  8   native= 16     <-- MOVED
  Iovlen       managed= 24   native= 24
  Control      managed= 16   native= 32     <-- MOVED
  Controllen   managed= 32   native= 40     <-- MOVED
  Flags        managed= 44   native= 48     <-- MOVED
  Pad_cgo_1    managed= 64   native= 52     <-- MOVED

RawSockaddrUnix    SizeOf=24   native=110
  Family       managed=  0   native=  0
  Path         managed=  8   native=  2     <-- MOVED

RawSockaddrInet4   SizeOf=40   native=16
  Family       managed=  0   native=  0
  Port         managed=  2   native=  2
  Addr         managed=  8   native=  4     <-- MOVED
  Zero         managed= 24   native=  8     <-- MOVED

Iovec              SizeOf=16   native=16
  Base         managed=  0   native=  0
  Len          managed=  8   native=  8

Msghdr is the worked example, and it explains two errnos that had no explanation. Of nine fields, only Name (0) and Iovlen (24) land where the kernel expects them. At native offset 8 — where the kernel reads msg_namelen — sits Iov, an object reference: always non-zero on a live message, and huge. A non-zero msg_namelen on an established unix STREAM socket is EISCONN by definition (unix_stream_sendmsg); on the DATAGRAM path the same non-zero namelen sends the kernel to validate an “address” at msg_name — the NativeBox<byte>(0) heap object a nil to produces — and that is EINVAL. Both were observed at master, three runs each, on a reduction of Go’s own TestSCMCredentials; both were previously attributed to the nil-name conversion alone, which cannot be the whole story because the C# code does assign Namelen = 0 — at managed offset 40, which the kernel reads as part of msg_controllen.

Iovec is the converse and is the sharper half of the finding. Every offset correct, SizeOf correct — and still unusable, because Base is a managed reference: the kernel reads a heap address where a data address belongs. Right place, wrong value. So “the offsets check out” is not a clearance, and neither is “the size matches”.

RawSockaddrInet4 is the confirmation, derived twice, months apart, by different instruments. The train-8 Sendto arc instrumented the generated body and dumped the sixteen bytes it hands the kernel:

02 00   AE 54   00 00 00 00   30 04 4A 68 ED 7F 00 00
family  port    ADDRESS       the reference sitting where Zero belongs

Family and port right, four ZERO bytes at native offset 4, a heap pointer at native offset 8. That is exactly what Addr managed=8, Zero managed=24 predicts, and the byte dump was taken before this probe existed. Two independent derivations agreeing is what makes the offsets safe to build on.

Why RawSockaddrUnix announced itself where RawSockaddrInet4 hid. Both are equally broken. Inet4’s damage was invisible on loopback because a destination of 0.0.0.0 means “this host” to Linux, so a loopback datagram arrived anyway (every non-loopback destination went silently elsewhere — the silent variant). Unix’s sun_path starts at native offset 2, where the managed struct has the low half of Family plus padding, and the path bytes are 6 bytes further on behind a heap pointer; the path names nothing that exists, so sendto answers ECONNREFUSED immediately. The loud one was found first, which is an accident of address family, not a difference in severity.

DOCTRINE (accumulator 179). The remedy for this class is encode into a native buffer (what writeNativeSockaddr and NativeMsghdr do) or an explicit-layout blittable mirror — never a managed struct handed to the kernel by address, and never a width adjustment to the declared struct. Two corollaries the measurements force:

Reproducing it. A console project referencing core/golib and the package under test, built with -p:GoTargetOS=linux -p:go2csPath=<repo>/src/; for each field, Unsafe.ByteOffset(ref Unsafe.As<TStruct,byte>(ref s), ref Unsafe.As<TField,byte>(ref s.Field)), printed against the native offsets from the platform headers. Nothing is mutated and nothing is written to the corpus, so it is safe to run against any tree.

Where it was found: attributing C1’s two Linux roots of the sockaddr seam (RawSockaddrUnix ECONNREFUSED — closed at master by train 8’s Sendto, attributed by a three-arm A/B; the EISCONN/EINVAL pair — closed by train 11’s SendmsgN). C1’s handover reported RawSockaddrUnix as “24 managed bytes where the kernel wants 110, Path a 16-byte reference instead of 108 inline bytes”: the byte counts were right, the mechanism was the width story, and C1 has recorded the correction from their side. The lane’s own train-11 commit message carried the same error and is corrected in cc38f0082e.


2026-09-02 — C1 — syscall Linux residue: the two roots assigned to C1, rooted end to end (one is a two-line no-op; the other is not a defect at all but a branch choice, and the choice is measurably wrong)

Root 1 was cut, measured, and WITHDRAWN — the empty bodies FORK BOMB the row (96 processes in ~7 minutes). Root 2 lands on cgocaller, which the sibling hand-own’s own header parks as “the cgo boundary, a separate question”, so it is posted for a ruling rather than taken. Branch claude/c1-syscall-exec-hooks is at c2d140db5 and is comment-only against master — 47 added lines, zero behavior, diff-verified. be61a6fa1 (the code half) must not be seated.

Root 1 — runtime_BeforeExec / runtime_AfterExec (TestExec): scheduler bookkeeping with no managed counterpart, exactly the banked entersyscall/exitsyscall judgment

Symptom. System.NotImplementedException: runtime_BeforeExec: external (assembly or cgo) function is not implementedPartialStubGenerator.cs:111’s verbatim text, thrown on the first syscall.Exec.

Chain, each link read rather than assumed.

link evidence
declaration src/core/syscall/linux/exec_unix.cs:247,249 — bodyless internal static partial void, from exec_unix.go’s two //go:linkname pulls
callers exec_unix.cs:275 (before execve) and :302 (after a failed execve) — the ONLY two, both inside Exec
no body ⟹ stub PartialStubGenerator emits the throwing stub for a bodyless partial
Go’s body $GOROOT/src/runtime/proc.go:4992 = execLock.lock() + a darwin/ios-only pendingPreemptSignals drain; :5008 = execLock.unlock()

Why nothing is the right body — and it is stronger than “unnecessary”. execLock has exactly two readers in the whole runtime, both read out of the 1.23.12 source:

  1. proc.go:2839/2844newm1/newosproc, i.e. Go’s own OS-thread creation (// Prevent process clone, issue #19546).
  2. signal_unix.go:372/389preemptM, and that reader is if GOOS == "darwin" || GOOS == "ios", i.e. unreachable on linux and part of async-preemption machinery the managed model never runs.

So on linux the write-lock’s sole purpose is to serialize against newosproc. A converted program has no newosproc: threads are created by the CLR, which does not consult runtime_package. The lock is therefore not merely unneeded here — it could not serialize anything even if it were taken, which is the honest reason rather than a convenience. The darwin drain reads pendingPreemptSignals, a counter only preemptM increments, so it is unreachable twice over.

The post-condition is the same: Exec replaces the process image via SYS_EXECVE, so no runtime bookkeeping survives to be left inconsistent; and on the failure path AfterExec releases a lock that was never taken.

Both are func(). Nothing is computed, so there is no plausible answer to fabricate — the same argument syscall_linux_impl.cs’s header already records for runtime_entersyscall / runtime_exitsyscall, and for the same class of obligation.

Placement is forced by the facts, not chosen. exec_unix.cs exists per-GOOS in linux/ and darwin/. The linux copy is ALREADY a whole-file hand-own ([module: go.GoManualConversion], line 34 — the posix_spawn seam), so the bodies belong in it, 28 lines above their caller. The darwin copy carries zero markers — it is converted output a -stdlib reconvert would clobber — so darwin keeps its throwing stub and is unchanged by this cut. (Darwin has no run layer; FINDING-darwin-run-layer.md owns that.) A flat file is impossible: the declaration does not exist on Windows, so a flat implementing part is CS0759.

“Why not forward?” — asked, because runtime DOES carry a real converted body, and answered. runtime/linux/proc.cs:4983 holds syscall_runtime_BeforeExec/AfterExec fully converted (execLock.@lock()/.unlock(), darwin drain and all), pushed here by runtime’s //go:linkname syscall_runtime_BeforeExec syscall.runtime_BeforeExec. Two things stand between that and a forwarder, and only the second is a judgment:

Recorded in the source comment too, so it is not rediscovered as an oversight. If the coordinator prefers the forward on faithfulness grounds, the registry row is the whole change and this cut retires cleanly.

Size: two bodies plus the rationale, one already-hand-owned file, no converter change, no registration, no other platform touched.

…and then the measurement said no, so it is withdrawn

The argument above is sound and I still hold it. The row still did not improve — it fork bombed: 96 syscall.tests processes in ~7 minutes, ~1 per 3 s, each a CHILD of the last. That chain is itself the proof execve did not replace the image, since execve keeps the pid. Three sampled generations carried garbage /proc/<pid>/cmdline and empty /proc/<pid>/environ; a fourth was an ordinary spawn from TestDeathSignal, so UNFILTERED suites were running. Positive control exonerating the run filter: the host honors -test.run=^TestZeroSysProcAttr$ and runs it alone.

The reading, labelled as the INFERENCE it is: Exec hands execve MANAGED memory — argv0p/argvp/envvp from BytePtrFromString/SlicePtrFromStrings, passed as (uintptr)@unsafe.Pointer.FromRef(ref (Ꮡ(argvp, 0)).Value), a **byte into the managed heap. The exec’d image comes up with corrupted argv and environ, loses -test.run and GO_WANT_HELPER_PROCESS, runs the whole suite including TestExec, and spawns the next generation. That is the open “wrapper passes managed memory by address” class this board already tracks, reached through a new door — and this one is on Linux.

So the throwing stub is, accidentally, the recursion brake, and one honest infrastructure-error beats a fork bomb on every host that sweeps the row. Next step recorded at the site rather than in a plan: marshal Exec’s argv/envp into UNMANAGED memory BEFORE re-attempting the bodies — the same file already does exactly that for the posix_spawn seam (MarshalStringZ/MarshalStringVector/FreeStringVector), under its header’s rule that every buffer handed to a native call lives in unmanaged memory for the duration and is freed in a finally.

Root 2 — Setegid/Seteuid “operation not supported”: the banked ENOTSUP is correct for AllThreadsSyscall and wrong for these callers, because Go offers them a second implementation the managed host is a better fit for

Symptom. TestSetuidEtc: [0] "Setegid(1)" failed: operation not supported.

Chain. Setegid(1)cgo_libc_setegid == nil (no cgo layer in a converted binary) → AllThreadsSyscall(SYS_SETRESGID, minus1, 1, minus1) (syscall_linux.cs:1103) → runtime_doAllThreadsSyscallthe banked hand-own’s deliberate ENOTSUP (syscall_linux_impl.cs:96) → errnoErr(95)zerrors_linux_amd64.cs:1611 [95] = "operation not supported"t.Errorf("[%d] %q failed: %v"). Every link read; the string is the errno’s, not a paraphrase.

This is not a stub and not a bug. runtime_doAllThreadsSyscall’s ENOTSUP is banked with its rationale, and the rationale is right about its own function: a managed host owns threads Go’s runtime never sees, which is precisely the condition under which Go itself answers ENOTSUP, and that answer is what turned TestAllThreadsSyscallSignals from an infrastructure-error into the same skip Go takes.

What the rooting adds is that these nine callers never had to reach it. Read from the 1.23.12 source: all nine linux credential setters — Setegid, Seteuid, Setgid, Setuid, Setgroups, Setregid, Setreuid, Setresgid, Setresuid — carry TWO implementations and pick by whether cgo is linked:

if cgo_libc_setegid == nil {
    if _, _, e1 := AllThreadsSyscall(SYS_SETRESGID, minus1, uintptr(egid), minus1); e1 != 0 {  }
} else if ret := cgocaller(cgo_libc_setegid, uintptr(egid)); ret != 0 {  }

A cgo Go binary passes TestSetuidEtc — it never sees ENOTSUP, because Go deliberately routes to libc’s setegid, which glibc implements with the nptl setxid broadcast that reaches every thread, foreign ones included. The ENOTSUP branch is Go’s answer for the non-cgo runtime whose stop-the-world replay is the only broadcast it has.

So the converted program takes the ENOTSUP branch for one reason only: cgo_libc_setegid is nil. And the argument the hand-own makes — the managed host has foreign threads the runtime cannot broadcast over — is the argument for it being the cgo case, which is the branch that WORKS. The corpus is one branch away from the right answer and is taking the other one.

The recommendation’s own measurement — libc’s broadcast is real, and it lands on Go’s expected string

The case for (a) rests on a claim it would be cheap to assert and wrong to: libc’s setegid reaches threads the caller did not create. Measured on this host rather than believed (a lane-local SetxidProbe — a thread parks, main calls setegid(1), both read their own /proc/self/task/<tid>/status):

BEFORE (uid=0):
  main     tid=…766   Gid:	0	0	0	0
  thread   tid=…767   Gid:	0	0	0	0
AFTER  setegid(1) returned 0:
  main     tid=…766   Gid:	0	1	0	1
  thread   tid=…767   Gid:	0	1	0	1

The parked thread moved. And 0 1 0 1 is byte-for-byte what TestSetuidEtc compares against for that row ({call: "Setegid(1)", filter: "Gid:", expect: "\t0\t1\t0\t1"}), so this is not “a different error” — it is the passing value.

Scope, stated because it is the one place this could fail: glibc’s setxid broadcast walks its OWN thread list, i.e. every pthread_create thread. .NET’s Linux threads are pthreads, so CLR threads are on that list; a thread made by a bare clone(2) behind glibc’s back would not be, and nothing in the converted corpus makes one. The probe’s parked thread is exactly the shape in question: foreign to Go’s runtime, ordinary to glibc.

Why this is a ruling and not a cut. Reaching that branch means cgocaller and the nine cgo_libc_* pointers, and syscall_linux_impl.cs’s header explicitly parks cgocaller as a separate question. Two shapes, and the existing ruling already prefers one:

I have not built either. What I can say from measurement is that the branch is the defect, not the ENOTSUP, and that the fix’s blast radius is nine functions and one test.

Go’s own test guards make the split explicit, and this is the strongest evidence in the block. All three tests that call AllThreadsSyscall directly open by skipping on ENOTSUP:

if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
    t.Skip("AllThreadsSyscall disabled with cgo")
}

TestAllThreadsSyscall :304, TestAllThreadsSyscallError :593, TestAllThreadsSyscallBlockedSyscall :606 — and those three skip correctly on both runtimes today, which is exactly what the banked hand-own bought.

TestSetuidEtc has no ENOTSUP guard, and its only skips are non-root, the swarming builder, and alpine (:524-535). Go did not forget it: in Go, ENOTSUP is a legitimate answer for the raw API and an impossible one for the nine wrappers, because whichever branch a build takes the wrappers WORK — cgo routes them to libc, non-cgo to a runtime broadcast that really happens. The converted corpus returns it for both because they share a bottom the nine were never meant to reach on a host with foreign threads.

None of the three guards is disturbed by fixing the branch: they call the raw API, which keeps answering ENOTSUP.

Host state, checked so the row is not read as a skip: id -u = 0, /etc/alpine-release absent, USER unset — none of TestSetuidEtc’s three skip guards fires, so the full 20-entry table runs on both sides.

The measurement — the rooting’s own control, stated before the run and then run

Prediction, written before the sweep: if AllThreadsSyscall→ENOTSUP is the root, then every TestSetuidEtc entry fails with the identical string, not the two named, because all of them call one of those nine. If only some fail, this rooting is wrong.

Run: run-validated-sweep.ps1 -Filter syscall -Exact -TestTimeout 30m under the pinned wrapper (gate printed go version go1.23.12 linux/amd64, PIN VERIFIED), at master 64a064098, 287 s. Record preserved before the restore.

Confirmed, and I was off by one in the count — the table is 21 entries, not 20 (indices [0] through [20]), and all 21 fail:

[0] "Setegid(1)" failed: operation not supported
[1] "Setegid(0)" failed: operation not supported
[2] "Seteuid(1)" failed: operation not supported
[3] "Setuid(0)" failed: operation not supported
[4] "Setgid(1)" failed: operation not supported
…
[18] "Setresuid(1,0,2)" failed: operation not supported
[19] "Setresuid(0,2,1)" failed: operation not supported
[20] "Setresuid(0,0,0)" failed: operation not supported

Nine distinct functions, twenty-one calls, one string. Go: pass.

And the contrast is in the SAME record, which is the part worth keeping. The three tests that call the raw API report go=skip cs=skip — agreeing — on the C# side via "AllThreadsSyscall disabled with cgo", reached through the hand-own’s ENOTSUP:

test go c#
TestAllThreadsSyscall skip skip
TestAllThreadsSyscallError skip skip
TestAllThreadsSyscallBlockedSyscall skip skip
TestSetuidEtc pass fail

One ENOTSUP, one record: it makes three tests agree and the fourth diverge. That is the whole finding in four rows — the answer is right for the API and wrong for the wrappers, because Go routes the wrappers around it precisely when the host is the shape this one is.

Root 1’s verdict came back with its own stack, and it exonerates everything around it:

INFRASTRUCTURE-ERROR TestExecHelper — System.NotImplementedException:
    runtime_BeforeExec: external (assembly or cgo) function is not implemented
  at go.syscall_package.runtime_BeforeExec()  … PartialStubGenerator/…runtime_BeforeExec.3.stub.g.cs:18
  at go.syscall_package.Exec(…)               … src/core/syscall/linux/exec_unix.cs:275
  at go.syscall_test_package.TestExecHelper(…) … src/core/syscall/exec_unix_test.cs:374

Read the frames: the PARENT spawned the child successfully (the posix_spawn seam works), the child entered TestExecHelper, and the throw is on the first line of Exec that isn’t argument marshalling. The stub is the only thing between this test and a pass — nothing else in the exec path is implicated.

Residue arithmetic, and what it means for the row

55 rows both sides. 37 agreeing + 13 disclosed (the banked posix_spawn platform-skip set) + 5 errors = 55 — it closes, so nothing is unaccounted for.

remaining error owner
TestExec root 1, C1 — cut, measured, WITHDRAWN (see below); the stub is the recursion brake
TestSetuidEtc root 2, C1 — posted for a ruling, not cut
TestExecPtrace the approved 14th mint (host-limit), staged
TestPassFD C2 (the SCM_RIGHTS seam)
TestSCMCredentials C2 (the SCM_RIGHTS seam)

The 5 → 4 fall the 14th mint was expected to produce is confirmed as available: the residue is exactly the five named, and TestExecPtrace is one of them.

– C1

2026-09-02 · HOST QUALIFICATION — i9’s Go-side bogo runner FLAKES: seven crypto/tls cases failed on the ORACLE with the converted side clean, and the ruled reading is “not a row finding” (lane i9, coordinator ruling same day)

Recorded here because the run it came from is otherwise indistinguishable from a converted-code regression: the sweep prints FAIL crypto/tls, the row’s status is "failing" and matched is false, and none of that is about the conversion.

The run. Run 3 of the three-run standard applied to the row’s earlier host death — quiet box (nothing else running), Release + DOTNET_TieredCompilation=0, 2026-09-02 20:48:04–20:54:59Z, 414 s.

go entries 3644  /  csharp entries 3644          identical to the passing run 2
environment { configuration: Release, tiered: false,
              oracleGoVersion: go version go1.23.12 windows/amd64 }
status "failing"   matched false

errors:  ORACLE-side   (Go=fail C#=pass)   7
         CONVERTED-side (Go=pass C#=fail)   0
         process-level                      2
disclosed: TestCertCache (codegen-liveness) -- absorbed correctly

The seven, named, so a later sighting can be compared rather than re-derived:

TestBogoSuite                                           Go=fail  C#=pass
TestBogoSuite/Downgrade-TLS10-Client                    Go=fail  C#=pass
TestBogoSuite/Downgrade-TLS12-Client                    Go=fail  C#=pass
TestBogoSuite/MinimumVersion-Client-TLS11-TLS1-TLS      Go=fail  C#=pass
TestBogoSuite/MinimumVersion-Client-TLS13-TLS12-TLS     Go=fail  C#=pass
TestBogoSuite/MinimumVersion-Client2-TLS13-TLS11-TLS    Go=fail  C#=pass
TestBogoSuite/WrongMessageType-TLS13-ServerHello-TLS    Go=fail  C#=pass

Six version-negotiation / downgrade cases plus the parent. Every one failed on Go’s own bogo runner while the converted side passed, which by construction cannot be converted-code drift: the converted code produced the right answer and the oracle did not.

The ruling (COORD, 2026-09-02), because the shape invites the wrong three reactions. A run whose failure set is entirely Go=fail / C#=pass with zero converted-side failures is a run in which the ORACLE failed to produce a reference — the os/user oracle-side shape arriving as a FLAKE rather than a deterministic host limit. Therefore:

The near-miss that makes it worth a board entry. The sweep line reads FAIL crypto/tls [414s] and the child reports exit status 1; that was read as the earlier access violation recurring and almost written up as a second crash. It is not — exit status 1 is go test’s ordinary “tests failed”, a crash is 0xc0000005 / 3221225477, and this record carries zero crash signatures and no deadline event in either spelling. Read the failure MODE before writing the word “crash”: the crash-signature grep and the results tail are one command each and they decide it.

2026-09-02 · OPEN, and NOT a row property — a net/http A/B carried 390 unreported verdicts that the census’s own clean run of the same row, same host, same configuration did NOT (lane i9)

Banked so the next lane meeting this does not spend the run I spent proving it is not what it looks like. It looks like a Release regression in net/http. It is not.

The pair. Both at Release, same host, same converter, corpus restored from HEAD before each:

run context unreported (Go="pass" C#="") errors
census net/http in-shard, behind other packages (WARM), 174 s 0 2 — both TestRegisterErr
A/B arm A row alone, go2cs_test_manifest.json deleted first (COLD), 352 s 390 398
A/B arm B as arm A but -TestTiered, 358 s 390 396

The two A/B arms’ unreported NAME SETS are byte-identical (0 differing lines), and neither record carries a timeout event in either spelling; both hosts exited at ~355 s against a 600 s wall. So it is not a deadline kill, not slowness, and not tiering — it is deterministic and identical across a varied axis.

The census’s record settles what it is not. i9-shard1-moved-rows/net.http.comparison.json is the same row at the same configuration: Release, tiered: false on the same host, and it reports all 1,343 verdicts with exactly two errors. A row that reports everything in one run and parks 390 in the next is not exhibiting a property of the row, the configuration, or the conversion.

What differs, and what is NOT asserted. The census run was warm and in-shard; the A/B arms each deleted the manifest and paid a full rebuild (the 174 s → 352 s doubling). That is the only difference identified, and the mechanism is unrooted — a cold rebuild has no obvious path to 390 unreported verdicts, and the standing shape heuristics do not settle it either (no deadline event to read; the set is neither a clean alphabetical tail nor plainly the t.Parallel() set). It is recorded as an open observation, not a diagnosis.

Two rules it re-earns. State cold-vs-warm when comparing two runs — it is the only variable that separated these three. And preserve a failed row’s comparison record before any restore: the census record is the entire reason this could be classified at all, and it was nearly missed because a find … | head truncated it out of view and the absence was read as a fact. A filtered view answers a different question than the one asked.


2026-09-02 — coordinator sub-agent — the 215 re-derived from go list std: net/http/pprof was in NO accounting, one ledger row is OUTSIDE the denominator it is subtracted from, and the recorded 216 − testing derivation disagrees with today’s by exactly that pair

A read-only census over the worktree found four record contradictions in the roster’s own arithmetic. Every number below was re-derived from primary sources for this entry rather than taken from the census: go list std with GOROOT pinned explicitly to the go1.23.12 root (go version reports the binary’s build stamp, not the root it resolves — an ambient GOROOT pointing at a go1.23.1 installation resolved package directories under that root while go version still printed go1.23.12, so the pin is stated, not assumed), a func Test regex over every *_test.go in each package’s GOROOT directory, and a .csproj probe under src/core.

The six numbers

# Measure Value How
1 go list std, windows/amd64, go1.23.12 306 GOROOT pinned; go env GOROOT echoed back before the run
2 …carrying ≥1 func Test in GOROOT sources 219 tag-INDEPENDENT glob over *_test.go; the regex admits a bare func Test(t *testing.T), which internal/diff — a banked row — declares, so the stricter ^func Test[A-Z] form would manufacture a banked-not-testable contradiction
3 and converted (a production .csproj under src/core) 215 reproduces the roster header’s denominator exactly
4 banked roster rows 201 parsed with the roster’s own committed row regex; Tests column sums to 27,734 and Disclosed to 154, both matching the header to the digit
5 remaining 14 215 − 201
6 14 = 3 lane-owned + 5 ledger-inside-215 + 6 unowned closes see below

The four packages separating 219 from 215 — embed/internal/embedtest, internal/coverage/test, net/internal/cgotest, runtime/internal/wasitest — are GOROOT directories with zero non-test .go files and no .csproj: there is no production package for a host to reference. Only embedtest carries a ruling (2026-08-11). The other three have no board row of any kind.

Positive controls on the func Test count: strings 68, unicode/utf8 14, sort 29, cmp 4, internal/diff 1 — each a banked row whose count is consistent with its record. Negatives: unsafe 0, internal/goarch 0.

The 14, by disposition

Package func Test Last recorded state (source)
os 225 682 / 686. Two implementable blockers, neither disclosable: the NetShareAdd byte-buffer fork (“queued, next free full lane”) and the WriteStringAlloc machinery-alloc arc (ElemRefBox (T[],nint) increment queued). — tracker, re-derived 2026-09-02
testing 59 Option 1 ruled, sequenced post-wave (see below). No verdict count has ever been recorded on this board.
runtime/pprof 45 37 requested, 15 reached, then the crash — 1 pass, 10 fail, 3 infrastructure-error, 1 skip. Reclassified a capability frontier, “sequenced LAST in the Windows chase queue, possibly the release’s honest disclosure story”. — Fold #3, 2026-08-29 (R)
net/http/pprof 4 5 of 15.TestHandlers fails and seven of its subtests infrastructure-error … TestDeltaProfile skips where Go passes. Profile collection has no managed body — sibling of runtime/pprof’s and runtime/trace’s stubs” — Scout batch 2, 2026-08-14 (lane B), under “Four packages measured for the FIRST time”
runtime/trace 2 0 of 2.NotImplementedException: getg … Both tests enter the tracer through getg; no managed body exists.” — 2026-08-14; restated the same day in the “no re-measure owed until a relevant capability lands” list. ⚠ That condition has arguably fired: the getg module-init no-op equivalence landed at 65b6dd5ba. Whether it reaches this path is unmeasured — 2 verdicts, the cheapest probe on the list.
crypto/internal/boring/bcache 1 0 of 1.NotImplementedException: registerCache … a PartialStubGenerator stub reached from Register[K,V]” — Scout batch 2, 2026-08-14. Still unruled, so the roster keeps it inside the naive denominator.

Finding 1 — net/http/pprof appeared in NO accounting

It is a converted package (src/core/net/http/pprof/net.http.pprof.csproj, pprof.cs, package_info.cs), it is inside the 215, it declares four func Test, and it was measured 5 of 15 on this board. It appears in no roster row, no exclusion-ledger row, and not in the tracker’s list of remaining rows, which names eight. Before the correction below, grep 'http/pprof' docs/ValidatedTestPackages.md returned nothing — positive control on the grep: net/http returned 10 hits in the same file, so the empty was absence, not a broken probe.

The implementable remainder is therefore nine, not eight: the tracker’s eight plus this row. 201 + 9 + 5 = 215. The roster’s Excluded packages section now derives the 215 from go list std and names all fourteen, this row among them.

Finding 2 — one ledger row is OUTSIDE the naive denominator, so 215 − 6 subtracts a non-member

internal/runtime/syscall is not in go list std on windows/amd64 at all — build constraints exclude every file, which is exactly what its own E1 mechanism says (“there is not even a package to convert”). It therefore cannot be a member of a set derived from that listing. Five of the six exclusions are inside the 215; strictly, the Windows-axis implementable set is 210 and the ratio is 201 / 210 — 95.7%, where the header states 209 and 96.2%.

This board asserted the opposite on 2026-08-09 (the r56a breadth harvest, “the twelve rooted non-validators”): internal/runtime/syscall … Joins net/internal/socktest, internal/syscall/unix, log/syslog and runtime/race: in the naive 215 denominator, cannot bank on this target.” The first four of that group are in the 215 (their sources define tests that Go’s constraints then deselect — the count above is tag-independent, which is precisely why they are inside it and then subtracted). The fifth is not, because there is no package on this target for the glob to reach. The grouping was right about four rows and wrong about the one it was written for.

Nothing published was changed for this. src/check-roster-format.ps1 computes implementable = testable − ledger.Count, i.e. it assumes every ledger row is inside the naive denominator, and it has no check that can see otherwise — so the two corrections available are (a) strike the row from the Windows ledger and re-derive the header, or (b) keep it and teach the guard to subtract only in-denominator rows. Both move a published headline; both are rulings, not docs fixes. Owed to the owner. Note the row is a genuine Linux-axis testable package — src/core/internal/runtime/syscall is converted with an L3 linux/ folder and GOROOT carries syscall_linux_test.go with one func Test — so option (a) must not lose the measurement.

Finding 3 — the recorded derivation of 215 and today’s disagree by exactly one swap

Both derivations land on 215, by different routes and with different memberships. Quoted in full, both sides:

2026-08-17, HARVEST — the pure-compute tail re-measured, “The enumeration, and what it excludes”: 305 converted package directories under src/core carry a production .csproj (306 counting the hand-written golib); 216 have a func Test in their Go 1.23.1 GOROOT sources; minus hand-owned testing that is the roster header’s 215, and 215 − 150 banked = 65 unbanked.”

2026-09-02, this entry: go list std on windows/amd64 = 306 → with a func Test in GOROOT sources = 219 → converted = 215. testing is an ordinary member (59 func Test, src/core/testing/testing.csproj); internal/runtime/syscall is absent from the listing.

The reconciliation is exact and is the same pair as Finding 2. The 2026-08-17 route enumerates src/core directories, which counts internal/runtime/syscall IN (it is converted) and then hand-subtracts testing OUT (it is hand-owned). Today’s route enumerates go list std on this target, which counts testing IN and internal/runtime/syscall OUT. One swap, both totals 215, and the difference is invisible in the total — which is how the phantom survived three weeks of arithmetic that “came out right”.

The roster now follows the second derivation, and says so. Two reasons, both on the record: today’s live arithmetic requires it (201 banked + 14 remaining = 215 only if testing is inside, and the tracker’s remaining list has named testing throughout), and the owner ruling below puts testing on the road to a validating row — a row that can bank must be inside the denominator it banks against. The 2026-08-17 derivation stands as the record of its own date; it is not rewritten.

Finding 4 — testing’s “Option 1 ruled” — the ruling IS located, and it is owed a durable home

The tracker’s ”| testing | meta | Option 1 ruled; post-wave. |” cites no ruling, and nothing on this board states what Option 1 is. Both halves were found:

The ruling has never landed outside the mailbox, whose own protocol says “campaign rulings → the BOARD … The mailbox is transport, not record; an answer given here that changes doctrine is a defect until it lands in its durable home.” Master’s docs/phase4/MAILBOX.md is the 36-line protocol stub — the entry lives only on the mailbox branch, so a reader at master could not reach it from either the tracker or this board. This entry is that landing. Its consequence for the arithmetic is Finding 3’s: under Option 1 testing becomes a partially-validating row, so it is inside the denominator, not subtracted from it.

Finding 5 — a guard-shape defect on an unmerged branch (not present at master; recorded so the merge can carry the fix)

Commit 1fb37f437 on origin/claude/c1-board-syscall-roots appends C1’s syscall Linux-residue section by splitting this file’s final guard line in two: it replaces the one-line <!-- (endraw) … --> guard with the bare opener <!-- , writes the section, and re-adds the tail half (endraw) … --> as the new last line. The guard is a single HTML comment whose purpose is to hide a Liquid tag; split that way, the whole appended section (284 lines) sits inside an HTML comment and renders nowhere on the published page, and the file carries two guard fragments where doctrine requires exactly one, final. It is greppable in source, which is why it survives review.

At master 62c63b572 the file is well-formed — a comment-balance pass finds exactly two self-closing HTML comments, the guard at line 1 and the guard at the final line, and exactly one literal Liquid raw open and one endraw close, in that order, with no conflict markers. The four interior --> hits are not comment closers: three are .NET exception text (---> go.PanicException) quoted in prose, and the fourth is this board’s own note about this exact hazard (2026-08-23), which spells the tag without its brace syntax on purpose because quoting it inside the raw guard would terminate the guard. So there was nothing to repair here and this entry does not touch that region. The one-line remedy at the merge: delete the orphan <!-- line and place the appended section above the intact guard line, which is what every other append on this board does.

Instruments: read-only. No build, test, converter or sweep was run. check-roster-format.ps1 was run read-only before and after the roster edit — identical both times (544 of 546 checks pass; the two failures are pre-existing at master 62c63b572, both execution args: release-tc0 fixture assertions about the retired -test-release-tc0 converter flag, fixed on origin/claude/i9-roster-guard-testconfig and not touched here). Positive control on the guard’s ledger arithmetic: injecting one fake ledger row moved it to 6 of 548, naming all four arithmetic assertions (excluded count 7 vs 6, difference 208 vs 209, denominator 208 vs 209, percentage 96.6 vs 96.2); the file was restored byte-identical (SHA-256 verified) and the guard returned to 2 of 546 — so the prose added to the roster is invisible to both the ledger and roster parsers, and their green is a measurement rather than a vacuum.

– coordinator sub-agent


2026-09-02 — coordinator sub-agent — OWNER RULING: the implementable denominator is the STRICT set, 210 — the phantom ledger row struck, net/http/pprof named, and the published 209 reconciled as one swap

The contradiction the previous entry recorded as owed to the owner is ruled. The roster’s implementable denominator is 210, not 209, and the honest ratio is 201 / 210 — 95.7%. Nothing about a banked row moves; what moves is one subtraction that was never legitimate.

THE ARITHMETIC, END TO END. The naive denominator is unchanged at 215 — every converted package whose Go 1.23.12 sources define a Test function, derived in the roster from go list std (306) → carries a func Test (219) → exists in the corpus as a converted package (215). Of those 215, five are legitimately excluded, not six:

So 215 − 5 = 210, and 201 / 210 = 95.7% (the format guard rounds it, away from zero, from the two counts it derives itself).

THE OLD 209 RECONCILED — one swap, not one error. The 215 was reachable by two live memberships that differ by exactly one package each way, and both land on 215, which is how the phantom survived weeks of arithmetic that “came out right”. The board’s 2026-08-17 derivation counted src/core directories whose GOROOT sources define a Test: internal/runtime/syscall in (it is converted, with an L3 linux/ folder, and GOROOT carries syscall_linux_test.go), hand-owned testing subtracted. The 2026-09-02 derivation counts go list std on this target: testing in (an ordinary member, 59 func Test, a .csproj), internal/runtime/syscall out. Under the first membership 215 − 6 = 209 was exact; under the second it was one too many. The campaign follows the second — the owner ruling of 2026-08-30 puts testing on the road to a validating row (Option 1, bucket D banks), and a row that can bank must be inside the denominator it banks against — so the ledger, not the derivation, is what had to move.

THE SECOND CORRECTION. net/http/pprof is converted (src/core/net/http/pprof, with its own .csproj and pprof.cs), declares four func Test in GOROOT, and was measured 5 of 15 on 2026-08-14 (this board, Scout batch 2) — and appeared in no roster row, no ledger row and no tracker list. With the ledger corrected the remainder closes from both sides: 210 − 201 = 9, and the nine are reflect, runtime, unique (lane-owned) plus os, testing, runtime/pprof, net/http/pprof, runtime/trace, crypto/internal/boring/bcache. The implementable remainder is nine, and every surface that names it now names the same nine.

WHAT THE STRIKE DOES NOT DO: lose the measurement. internal/runtime/syscall is a genuine Linux-axis testable package — converted with an L3 linux/ folder, and GOROOT carries syscall_linux_test.go. What the strike removes is a Windows exclusion that was excluding nothing. Its E1 reading is kept verbatim in the note that now sits under the ledger table, explicitly as the Windows half of its story, and the row belongs to Linux’s own denominator when the per-OS denominators land (DESIGN-peros-roster.md already computes windows as 215 − 5 = 210 on its own scoping, which is the same number from the other direction).

SURFACES. docs/ValidatedTestPackages.md — header line (215 − 5 excluded = 210, 201 / 210, 95.7%, “The five”, plus one sentence of prose in the header itself naming why the denominator moved); the exclusion section’s opening count (six → five); the ledger table (row struck) with a new two-paragraph note under it carrying the strike’s reason and the Linux-axis measurement; os/user’s rejoin arithmetic (208 → 209 becomes 209 → 210, with the as-published figures kept in a parenthesis); and the derivation subsection updated from owed to the owner to ruled, its E1 bullet, its disposition bullet and its remainder line now closing against the header (210 − 201 = 9). docs/phase4/TRACKER-100-percent.md — the on-master ratio (201 / 210 = 95.7% honest), the implementable-remainder count (8 → 9) and its named list (net/http/pprof added, bcache re-labelled the ninth, “implementable 209” → 210); that file declares the roster its authority and states that it is stale when the two disagree, so leaving it at 96.2% was not an option. Deliberately NOT touched: docs/NEWS.md and docs/news/* (dated history, true when written); docs/phase4/SESSION-ROLL-* (dated session records); docs/phase4/DESIGN-roster-per-os-bubbles.md and docs/phase4/DESIGN-peros-roster.md (design proposals quoting a point-in-time header — the former’s quoted Linux line is already stale by 17 rows, which is what makes it a snapshot rather than a live surface); every earlier board entry (append-only). docs/README.md quotes the naive 215, which this ruling does not move, and src/go2cs/validationProofPages.go with its testdata/validationproof/expected.md carry no campaign figure at all — both verified by grep, both correctly untouched.

THE GUARD IS THE CALCULATOR, AND IT NEEDED NO CHANGE. check-roster-format.ps1 computes implementable = testable − ledger.Count and recomputes the difference, the denominator and the percentage from the table — so striking the row is the whole edit and the header follows. Before: roster format guard: 550 checks pass (201 rows, 195 with a linux annotation, 4 with an execution config, 6 excluded), exit 0. After: roster format guard: 548 checks pass (201 rows, 195 with a linux annotation, 4 with an execution config, 5 excluded), exit 0 — two fewer checks because the two per-ledger-row assertions (ruled class, five-column render) go with the struck row.

Positive control, because a green that cannot go red is not a measurement: the header was reverted to (215 − 6 excluded = 209): 201 / 209 — 96.2% against the corrected ledger, and the guard went RED at exactly the four arithmetic assertions — excluded count expected 5 got 6, stated difference expected 210 got 209, denominator expected 210 got 209, percentage expected 95.7 got 96.2 — exit 1. The file was restored and verified byte-identical by SHA-256 (99cfc67667dfa3ae452c4bfe3d6046b1e2fc9879c88eb172c6f44514a5acb2fc), and the guard returned to 548 of 548, exit 0.

Instruments: read-only apart from the three documents edited. No build, converter run, test or sweep — a train battery was running on the host. The go list std verification is a toolchain query against a pinned GOROOT and touches no repository file.

– coordinator sub-agent


2026-09-02 – net/http’s h2 deadline pair survives a clean golib A/B, and the row needs MORE than 30m on the i7 at Debug (lane claude/sub-os-row)

Two measurements, taken as canary work for the NetShareAdd retention and worth keeping on their own account.

1. The h2 pair is not moved by a golib pointer change – measured, not assumed. net/http is the second-largest reflect importer on the derived canary set, and it FAILED on the change’s arm with exactly four divergent rows: TestWriteDeadlineEnforcedPerStream, its /h2, TestWriteDeadlineExtendedOnNewRequest, its /h2. The A/B was run rather than the failure excused by the known-artifact note: with the change REVERTED to the parent commit, the SAME four rows fail, identically. Two legs of the three-run standard (fail-with, fail-without); the re-restored third leg was not run, and this entry does not claim it. This is an independent confirmation of the build-CONFIGURATION reading CLAUDE.md records for that pair (Debug publish + default tiering) from a direction that lane did not take – a corpus-wide golib change moves it zero rows.

2. net/http is UNDER-BUDGETED at 30m on this machine class, and the two arms bracket it. The change’s arm completed in 1,836 s; the clean arm took 2,171 s and its results tail states package timeout after 00:30:00 outright. The deadline kill’s signature is the documented one and is worth restating because it nearly reads as a regression: the killed arm showed 18 extra empty verdicts on top of the four real failures – a contiguous alphabetical tail (TestWriteHeader*, TestWriteResponse, TestWriteSetCookies, TestZeroLengthPostAndResponse*) plus the parked parallel batch interleaved among it, which reads scattered and is not. Two arms of the same package, 335 s apart, one side of the budget each: that is the whole margin. net/http is not in run-validated-sweep.ps1’s $longTimeouts, and on this host at Debug it wants a floor the way net (40m) and crypto/tls (30m) already have one. Not added here – a floor is a change to the shared sweep and belongs to whoever owns the Release-config flip that is about to re-time every row anyway – but the number is recorded so the next lane meets the measurement instead of the surprise.

2026-09-03 — internal/poll validates on Linux — TestSplicePipePool disclosed codegen-liveness, with the heap root path verbatim

internal/poll is a Windows-banked row (19 · 0). On Linux Go compiles a different test set: the COM-port and WSASocket rows disappear and splice_linux_test.go appears, so the Linux arithmetic is its own. Every Linux verdict matches go test except one, and that one is disclosed rather than tolerated.

The divergence

TestSplicePipePool allocates 64 splice pipes, returns them all to sync.Pool, drops the slice that held them, then GCs in a loop and asserts every write descriptor has been closed by the pipes’ finalizers. Under the CLR none ever is: the loop spins until the test’s own deadline and reports all 64 descriptors leaked.

The root, measured rather than argued

A heap dump taken while the loop was spinning, gcroot on a leaked splicePipe box (Release publish, dotnet-dump) — verbatim:

Thread 6e5c:
    7eef53ffe440 55bf47f64a84 go.internal.poll_test_package.TestSplicePipePool(go.`1<T>)
        rbp-270: 00007eef53ffe5a0
          -> 7eef8e89ba48     go.<go.internal.poll_package+splicePipe>[]
          -> 7eef8e8974d0     go.StandardBox<go.internal.poll_package+splicePipe>

        rbp-138: 00007eef53ffe6d8
          -> 7eef8e89ba48     go.<go.internal.poll_package+splicePipe>[]
          -> 7eef8e8974d0     go.StandardBox<go.internal.poll_package+splicePipe>

        rbp-b0: 00007eef53ffe760
          -> 7eef8e89ba48     go.<go.internal.poll_package+splicePipe>[]
          -> 7eef8e8974d0     go.StandardBox<go.internal.poll_package+splicePipe>

Found 3 unique roots.

Every root is a frame slot of the test’s own method — three of them, all pointing at the one splicePipe[] backing array, which holds all 64 boxes. The named local ps is assigned default! at its source line, so none of the three is ps; they are slice-header copies — the append result and JIT spill temps — with no source-level name to null. Go’s precise stack maps report those dead at that point; the CLR reports them live. The pipes therefore stay reachable, their finalizers never become due, and destroyPipe never runs.

Three controls, so the attribution is a measurement

  1. sync.Pool is not the retainer. Its chains dumped emptypoolCleanup had already released everything it cached. Pool retention stays separately observable, which matters for the scope note below.
  2. The JIT’s optimization level does not reach it. Debug vs Release+TC0: zero verdicts moved, identical descriptor set.
  3. The one emission-level copy that could be nulled is not a root. The converter’s range enumeration over ps (foreach (var (_, vᴛ1) in ps)) was replaced with an index loop — one axis, everything else untouched — and the live count was unchanged to the object: 64 boxes, 67 sentinels, identical to baseline. The null was controlled by confirming the built assembly postdated the patched source, so it is a measurement and not an unapplied patch.

Control 3 falsifies “the converter’s enumeration keeps it alive” to the object and leaves the managed frame itself — the same conservatism sync’s TestOnceXGC and TestPoolGC already disclose, and the same class string: codegen-liveness (not gc-liveness; the corpus class is codegen-liveness and that is what the sweep matches).

The disclosure’s scope note, stated rather than buried

The pinned signature is leaked descriptors: , the failure’s message prefix. The descriptor numbers vary per run, so — unlike TestPoolGC, which pins an exact count — a partial leak would match this entry too. A real retention regression in the pipe pool could hide behind it. The compensating observation is control 1: the pool’s chains were measured empty, so pool retention is separately observable and a regression there would show as something other than this signature. The note is carried inside the manifest entry’s own reason, where the next reader stands.

A measured property worth carrying: this row’s wall is a function of the BUDGET, not of the work

TestSplicePipePool derives its own timeout from t.Deadline()timeout = deadline.Sub(now) less 10% — so when it cannot succeed it consumes 90% of whatever package deadline it is given and nothing runs after it (it is alphabetically last in this suite). Two points, measured:

-test-timeout TestSplicePipePool elapsed package total the other 12 verdicts
60m 3234.5 s 3240.6 s 6.1 s
15m 804.6 s 810.6 s 6.1 s

(The other twelve verdicts run before it and cost 6.1 s on both points; nothing measurable runs after it. The column is labelled for what was measured — the suite minus this one test — rather than for where it sits in the ordering.)

Total ≈ 0.9 × T + 6 s, on both points. The consequence is the opposite of the usual one: this row needs no $longTimeouts floor at all. A floor would not protect it — it cannot time out, because it always finishes inside 90% of its budget — it would only make the row cost that much wall on every sweep. At the sweep’s default 10m the row costs ~9 min; at a 60m floor it would cost ~54 min for exactly the same verdicts. The general shape is worth naming: a test that sizes itself from t.Deadline() converts a deadline into a duration, so the budget-vs-wall reasoning the timeout table encodes runs backwards for it — bigger budget, longer row, same result.

The arithmetic, and a correction to the figure it was commissioned at

Two bare sweeps — no -TestConfig, no -TestTimeout, so the row publishes under the configuration of record with per-row annotations respected, which is what makes a sweep bank-eligible:

run tree result wall
control f3a6e6ac8 (pre-train-14 base + this disclosure) CVAC internal/poll 12 (validated) 726 s
bank 7abd9e442 (rebased on landed master 8c15217c8) CVAC internal/poll 12 (validated) 717 s

Identical. Train 14 carried converter, golib, go2cs-gen and run-validated-sweep.ps1 changes, so the bank was re-taken at the merge result rather than inherited from the base — and it moved nothing for this row. Both comparison records read matched: true, status: validated, configuration: Release, tiered: false, and oracleGoVersion: "go version go1.23.12 linux/amd64".

Thirteen Linux verdicts: twelve pass, one disclosed — so the annotation is linux: 12 + 1. The bank was commissioned at “13 with 1 disclosed”; that counts the disclosed verdict into the matched half. The converter’s own README badge arithmetic agrees independently — it rewrote itself to 12/13 during the sweep. The roster header moves 195 → 196 of 199 applicable rows, 22,583 → 22,595 matching verdicts, 159 → 160 disclosed; the format guard computed all three independently (551 checks pass), and the prediction recorded before running it held on all three.

Three artifacts this bank does NOT produce, each by derivation rather than by omission

A Linux-axis bank is not a Windows bank with a different number, and three of the usual artifacts are answered by the corpus rather than by doing them:

  1. Committed test sources. Corpus-wide: 22 committed *_windows_test.cs, 0 *_linux_test.cs or *_unix_test.cs. The committed emission is the Windows record exactly as the Tests and Disclosed columns are. internal/poll’s test set genuinely differs by GOOS — its error_test.cs alone is 30 lines shorter under Linux — so committing the Linux flavour would overwrite the Windows row’s own reproducible sources.
  2. The proof page. 0 of the pages under docs/validation/current/ carry a Linux section; the only two files mentioning “linux” do so in Windows-run test names. The Linux arithmetic’s home is the roster annotation, which is where this bank put it.
  3. A $longTimeouts floor. Measured above: the row cannot time out, so a floor cannot protect it and would only make it cost ~54 min a sweep instead of ~9.

A fourth artifact belongs with (2) and is easy to bank by accident: the package README badge. A Linux sweep rewrote src/core/internal/poll/README.md from the Windows record 19/19 to the Linux 12/13. It composes from the committed proof page’s counts, so it is a Windows artifact too and is restored, not banked, from a Linux host.

One correction to the doctrine’s own wording while it is in view: the pipeline’s comparison record for this row is a FILE, go2cs_test_comparison.json, not the go2cs_test_comparison/results.json directory the tail-reading rules name. The rules still apply; the path does not.

– C1

2026-09-02 · crypto/internal/boring/bcache BANKS at 1 of 1 — the board’s registerCache root was two days stale, and the flake that withheld the bank did not reproduce in 30 runs at the Release + tiering-off default (coordinator sub-agent; coordinator ruling same day)

The row banked. The defect behind its flake did NOT, and is recorded here as open rather than closed.

What this board said, and why it was wrong

The row’s standing entry — Scout batch 2, 2026-08-14, restated in the 2026-09-02 remainder table — read: “0 of 1. NotImplementedException: registerCache … a PartialStubGenerator stub reached from Register[K,V], and “Still unruled, so the roster keeps it inside the naive denominator.”

That root was closed on 2026-08-30 by 378951155“bcache: wire registerCache truthfully — a clear DELEGATE, not an address” — and no board row records it. Go’s Register hands the runtime the ADDRESS of the cache’s ptable word for clearpools to nil with atomicstorep; that word is an atomic.Pointer[cacheTable[K,V]] whose managed slot holds a ж<T> REFERENCE, so the storage is not pinnable, the provenance record can never satisfy IsPinnedAt, and ManagedPointerTokens answers MISS for the number by design. The commit replaced the address with a clear DELEGATE — the currency clearpools’ other two arms already use — bound to the package’s own Clear, which Go’s doc comment names as exactly what the collector performs here.

That commit measured 1/1 and deliberately did not bank, in its own words: “NOT BANKED, and the roster row + proof page are deliberately not carried: the suite is FLAKY at ~70% under the default config, from a defect this change neither introduces nor touches.”

The re-measure, 2026-09-02, at master 01a7fdefe

Pipeline, windows/amd64, -tests -test-action all -test-timeout 10m with -go2cspath pinned to the worktree; converter built from the same tree, go version <exe> = go1.23.12. The comparison record reads back configuration: "Release", tiered: false, oracleGoVersion: "go version go1.23.12 windows/amd64" — the configuration of RECORD since the 2026-09-02 default flip, and the axis the prior measurement did not have.

Test go test go2cs
TestCache pass pass

status: validated, matched: true, disclosed: [], errors: []. The results-file TAIL was read FIRST and carries no timeout event in either the plain or the escaped form; it ends on the package’s own "action":"pass".

The flake, sampled rather than assumed — 30 consecutive runs, zero failures

378951155 measured the failure rate under the then-default Debug configuration: 7/10 at the default, 4/10 with DOTNET_TieredCompilation=0, 3/10 with the sentinel disarmed, and 10/10 under DOTNET_GCgen0size=1GB — GC-correlated, not a tiering artifact and not the sentinel’s.

At the new default this lane ran 30 consecutive comparisons: 30 validated, 0 failures (runs 1–9 records preserved individually; runs 10–30 sampled with failure-record preservation armed, which never fired). Repeat runs used -test-action compare against the existing digest-validated artifacts, so each is a fresh publish-and-run of the same emission rather than a re-conversion.

The reading, stated as a bound and not as a cure. Against the prior 30% failure rate, P(30 consecutive passes) ≈ 2.3 × 10⁻⁵; the 95% one-sided upper bound on the failure rate implied by 30/30 is ≈9.5%. So the Release configuration has moved this row’s behaviour by a wide margin — most plausibly because Release’s allocation and optimization profile closes the window the race needs — but 9.5% is not zero, and nothing in this lane fixed anything. No golib, converter or corpus line changed; the only variable is the published configuration.

OPEN — the element-aliasing lost write under concurrent CAS plus collection

378951155 rooted the flake and the root is untouched: every failure it saw was confined to the test’s final section, which uses an UNREGISTERED cache and loses entries under concurrent Put, while the registered-cache/runtime.GC() assertion that the commit owns passed in every one of 40+ runs. Its own placement: “the single-threaded 10,000-entry section never failed, which places it in golib’s element-aliasing machinery under concurrent CAS plus collection, not in bcache.”

That is where it stays. Banking this row does not close it, and the row is not evidence that it is closed — it is evidence that the configuration of record does not currently reach it at a rate 30 runs can see. If a future sweep reds this row, this entry is the first thing to read: the suspect is the aliasing defect, not whatever change is in flight.

What banked

Four test artifacts (cache_test.cs, package_test_info.cs, go2cs_test_host.cs, crypto.internal.boring.bcache.tests.csproj), the generated proof page, its docs/validation/index.md row, and the roster row. Two properties worth recording because the package is one of the four hand-owned-by-consequence members (every non-test Go file hand-owned, so the driver continues before writeProjectFile and its .csproj/package_info.cs/README.md are never re-emitted):

The roster row carries no execution: segment (it validated at the default) and no linux: segment (measured on windows/amd64 only). The absent Linux annotation is load-bearing rather than an omission: an unannotated row makes no Linux claim and reports comparison-validated-at-count there, so it grows the Linux applicable denominator 199 → 200 without touching the numerator. bcache’s Go sources carry no build constraint, so it is genuinely applicable on Linux and genuinely unmeasured — the honest direction, and the header records it.

Instruments: the -tests pipeline (1 conversion + 30 comparisons) and check-roster-format.ps1. The roster guard is the calculator and was run before and after: before, 550 checks pass (201 rows, 195 with a linux annotation, 4 with an execution config, 6 excluded); with the row inserted and the header untouched it FAILED 6 of 551, naming every derived number it disagreed with (package count 202 vs 201, verdicts 27735 vs 27734, 94.0 vs 93.5, honest numerator 202 vs 201, 96.7 vs 96.2, linux applicable denominator 200 vs 199) — so the header was recomputed FROM the guard rather than incremented by hand, and after, 551 checks pass (202 rows, 195 with a linux annotation, 4 with an execution config, 6 excluded), exit 0. That mid-state failure is this entry’s positive control on the guard: it can go red, and it named the right six assertions. Both comparison records and both results files were preserved to distinct paths before the tree was restored.

– coordinator sub-agent

2026-09-03 — net validates on Linux — 577 + 2 at 6fa031d08, the DNS/socket stack’s Linux axis closed by four seats from three lanes

The campaign’s largest single row banks on the Linux axis: 577 matched · 2 disclosed, with 18 of the matched being identical skips on both sides, 58 declarations excluded (benchmarks deferred to 4D + unsupported), and 0 errors. status: validated, measured on the WSL bank host (G-LAPTOP) at train-16 master 6fa031d08 under the sweep’s own invocation for the row — go2cs -tests -test-action all -go2cspath <clone>/src <GOROOT>/src/net <clone>/src/core/net, output into the corpus dir so the dnsclient_impl.cs hand-own survives. configuration: Release, tiered: false, oracleGoVersion: "go version go1.23.12 linux/amd64", CGO_ENABLED=0, GoTargetOS=linux; BANK_EXIT=0, zero empty verdicts, no timeout event. The comparison record holds 579 tests each side; the two distributions — go {560 pass · 18 skip · 1 fail} and C# {558 pass · 18 skip · 3 fail} — agree on 577.

Why the Linux count (577) exceeds the Windows count (472)

A verdict count is a fact about a package and an OS: Go runs a different test set per GOOS — build-tagged tests, capability gates, GOOS-keyed skips — and the Linux socket surface exercises paths the hand-owned Windows WSA mirrors do not. The 577 is read from the run’s OWN comparison record, never blended with the Windows 472; the row keeps the Windows record in its Tests/Disclosed columns and carries the Linux arithmetic in its linux: 577 + 2 annotation, as the axis convention requires.

First contact closed by four seats from three lanes

This row is where the fleet’s Linux axis has been converging since first contact. Four roots, each measured and named by its lane, are all landed at this master: the dual-embed interface-witness fix (35 DNS verdicts — a fakeDNSPacketConn that dual-embeds an interface and a struct got no witness, so c.(PacketConn) read false and a UDP conn took the stream arm, TCP-prefixing the query; train 13), C1’s Iovec layout seat (the TestBuffers writev path; train 15), C2’s abstract-unix-name seat (train 15), and C2’s multicast mreq seat (train 16). With all four in, net reaches 577.

The two alloc-profile disclosures

Both are zero-alloc asserts guarded by SkipIfOptimizationOff — Go reaches zero only through escape analysis, and the converted path cannot, at three structural sites: golib’s slice<T> is a struct over a heap T[], every []byte crossing the Conn/PacketConn interface surface is a managed object, and per-call syscall marshalling allocates its own buffers. The CLR publishes no in-process malloc counter, so go2cs’s own runtime counter (golib’s allocation sites, the structural mirror of runtime.MemStats.Mallocs) is the reported pin — the golib-site lower-bound mechanism the class already carries, the same shape as bytes/strings.

TestLookupCNAME on the host-qualification ledger — universal drift, not a row disclosure

Go’s own net suite (cgo off) fails exactly one test on this host — TestLookupCNAME — because a live public CDN CNAME record drifted from Go’s expected value. It is universal drift: three independent resolvers agree the record moved, so it is not any one host’s failure. In the bank it is go=fail / cs=fail — both sides observe the same world and agree — a MATCHED verdict, folded into the 577, and NOT a per-row disclosure. It is recorded here, on the host-qualification ledger, per the standing rule; if the record ever returns to Go’s expectation the row reads pass/pass and nothing here changes. The 18 TestLookupNoSuchHost leaves pass, confirming the host’s re-qualified state (the fec0:0:0:ffff::* placeholder-resolver class that gated net’s Windows bank stays closed here).

Four artifacts this bank does NOT produce, each by derivation rather than by omission

  1. No committed Linux test sources. The corpus’s committed emission is the Windows record and this row’s test set differs by GOOS; committing the Linux flavour would overwrite the Windows row’s own sources. (Emitted into the bank clone for the measurement, then cleaned.)
  2. No proof page. None of the validation pages carries a Linux section; the linux: 577 + 2 annotation is the home.
  3. No $longTimeouts entry owed. net already carries its 40m floor; the run finished in ~4 min (warm dependency closure), never approaching it.
  4. No README badge rewrite. The badge composes from the Windows proof page; a Linux sweep did not touch it here, and it is left as it stands.

– G